国立研究開発法人 科学技術振興機構

【Python3版】ワードクラウドで「トーゴーの日シンポジウム」のキーワードを可視化する方法

2018年12月20日
建石 由佳(NBDC)

10/3の記事で、ウェブツール+Excelを使ってトーゴーの日シンポジウムのポスター要旨からワードクラウドを作る方法(以下、Excel版と呼びます)をご紹介しました。今回はPython3のライブラリを使ったワードクラウド作成方法をご紹介します。
(なお、11/21に今年のシンポジウムのポスター発表データを公開しましたので、よろしければそちらもご覧ください)

前回の記事では「2011年と2018年のワードクラウドを比較することで、シンポジウムにおけるキーワードの変遷を捉える」という話がありました。Excel版ではランダムな色付けしかできませんでしたが、wordcloudライブラリには「カラーマップからランダムに選ぶ」以外に「ユーザーが色付け関数を定義してその関数に基づいて色を付ける」という機能が用意されているので、 各単語を、2011年要旨集に出てくる頻度と2018年要旨集に出てくる頻度に基づいた色付けで表示して、視覚的に比較しやすくしてみました。

また、研究動向にはあまり関係のない一般名詞を除くため、科学技術用語形態素解析辞書を使って専門用語と固有名詞のみを抽出しています。

今回の記事はPythonとJupyter Notebookの使用経験のある方を対象にしています。プログラムと注釈部分をどちらも読みやすく、コピーしやすく表示するため、Jupyter Notebookで作成した原稿をhtmlファイルに変換して掲載します。

1. 準備

ワードクラウド作成にはwordcloudライブラリを使います。
他に使用するライブラリは下のセルを参照してください。numpyは科学技術計算、matplotlibはグラフ描画用のライブラリです。またpyplotは、matplotlibの中に入っているプロットを実行するためのモジュールです。

Anacondaという開発環境のパッケージをインストールすると、Jupyter Notebookとnumpy、matplotlibがインストールされた環境を作ることができますが、wordcloudは別にcondaやpipを使ってインストールする必要があります。

# import 
import wordcloud as wc 
 
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt

Jupyter Notebookを使ってmatplotlibの描画結果を表示するには下のコマンドが必要です。

# Jupyter Notebook描画用
%matplotlib inline

2. 日本語テキストのための準備

wordcloudライブラリはもともと英語テキストでの使用を前提として作られているため、 日本語テキストを文字化けせずに表示させるためには、日本語を表示できるフォントが必要になります。

以下ではIPAフォントをローカルにダウンロードして利用していますが、(Windowsなどの)システムフォントのあるパスを指定しても大丈夫なはずです。

#IPAフォント
FONT="./fonts/IPAexfont00301/ipaexg.ttf"

3. テキストを読み込む

また、英語のテキストであれば何もしなくてもスペースを単語区切りとみなして文を区切ってくれますが、日本語のテキストは形態素解析などをしてあらかじめ分かち書きをしてやる必要があります。

ここでは、形態素解析エンジンMeCab科学技術用語形態素解析辞書を使って要旨のテキストを解析し、専門用語と固有名詞のみをCSVファイルに格納しておいたものを利用します(この作成過程は今回は省略します)。

たとえば、こんな感じです。

生命科学,データベース,検索,インターネット,生命科学,データベース,キーワード,検索,システム,月,日本語,総説,蛋白質,核酸,酵素,出版,研究,報告書,特許,学会,中心,データベース,検索,検索,データベース,利用者,検索,用語,遺伝子,蛋白質,農学,カテゴリー,検索,ノイズ,低減,情報,迅速,取得,バイオサイエンス,データベース,センター,基盤,技術開発,プログラム,統合化,推進,プログラム,統合,データベース,プロジェクト,開発,データベース,利用,将来,日本,生命科学,データベース,検索,システム
ライフサイエンス,研究,遺伝子,タンパク質,解析,生命,システム,理解,シフト,生物学,データ,科学,研究者,情報,データ,散逸,情報源,効率,取捨選択,利用,背景,利用者,情報,データ,情報源,効率,統合,参照,システム,統合,検索,開発,プロテオーム,質量分析,データ,ライフサイエンス,統合,データベース,センター,データベース,統合,基盤,技術,連携,システム,開発,技術,調査,データ,収集,整備,プロトタイピング

まず、CSVファイルから単語を読み込んで単語と出現回数の対応をpythonのdict型({単語:出現回数 } の辞書)で作る関数を用意します。

def nouns2wdict(nfile):
    """
    CSVを入力として単語の出現回数を数え、Dict形式にする
    Normalizationは行っていない
    
    Parameters
    -------------
    nfile: str
        UTF-8でエンコードされたCSVファイルの名前
    
    Returns
    ---------
    wdict: dict(str->int)
        {単語:出現回数}
    
    """
    wdict={}
    with open(nfile,encoding="utf-8") as f: #comma-separated
        for line in f:
            x= line.strip().split(",")
            for surface in x:
                wdict[surface]=wdict.get(surface,0)+1
    return wdict

今年(2018年)の要旨集から専門用語と固有名詞を抽出したものが2018.nouns.txt、7年前の2011年の要旨集から専門用語と固有名詞を抽出したものが2011.nouns.txt、にそれぞれ用意されているとして、その2つからそれぞれの年に対応する辞書を作ります。

#2018 トーゴーの日アブストラクト
wdict2018=nouns2wdict("2018.nouns.txt")
#2011 トーゴーの日アブストラクト
wdict2011=nouns2wdict("2011.nouns.txt")

4. ライブラリの基本的な使い方

この辞書からワードクラウドを作るために、wordcloudではまず次のようなワードクラウドのオブジェクトを作ります。

# 2018要旨集ワードクラウド
 
wc2018=wc.WordCloud(
        font_path=FONT
    ).generate_from_frequencies(wdict2018)

これだけでは何も表示されませんので、あらためて描画してやる必要があります。一般的なグラフ描画と同じくmatplotlibを使えば描画できますが、繰り返し使うので関数化しておきます。

def plot_wordcloud(wordcloud, title="WordCloud", pngfile=None):
    """
    WordCloud描画
    
    parameters
    -------------
    wordcloud: wordcloud object
        描画対象
    tilte: str
        タイトル
    pngfile: str (or None)
        ファイル名
        None出なければここで指定した名のpngファイルに出力
    """
    # 描画できるような形に変換
    awc = wordcloud.to_array()
 
    #描画 (画像を作る)
    fig=plt.figure()   
    left, width = 0.01, 0.98
    bottom, height = 0.01, 0.90
    ax=  plt.axes([left, bottom, width, height])
    
    #plot wordclouds
    ax.imshow(awc, interpolation="bilinear")
    ax.axis("off")
    ax.set_title(title,fontsize=18)
 
 
    # 表示
    plt.show()
    # png
    if pngfile:
        fig.savefig(pngfile)
    
plot_wordcloud(wc2018, title="2018")

Wordcloud for 2018 abstracts in default setting

パラメータを指定しないデフォルトでは上のような画像になりますが

  • 印刷することを考えると白背景にしたい
  • 単語を詰め込みすぎていて見にくい
  • 小さい文字は小さすぎ、大きい文字は大きすぎるように見える

という点を改良したいです。そこで、wordcloudのパラメータで調整します。

wordcloudでは、背景色を background_color、単語の最大数をmax_words、最小の文字サイズをmin_font_size、最大の文字サイズをmax_font_sizeで指定することができます。背景色はHTMLの色名で指定できるので"white"とし、単語数をExcel版にあわせて50、最小文字サイズを30ピクセル、最大文字サイズを96ピクセルにします。

ついでに、描画領域の大きさは800×600(width=800, height=600)にしておきます。 以下では指定していませんが、prefer_horizontal=1を指定すると縦書きの語をなくすこともできます。

wc2018=wc.WordCloud(
    background_color="white",
    font_path=FONT,
    max_words=50,
    min_font_size=30,
    max_font_size=96,
    width=800,
    height=600
).generate_from_frequencies(wdict2018)
 
plot_wordcloud(wc2018, title="2018")

Wordcloud for 2018 abstracts in white background

文字色は、デフォルトではmatplotlibライブラリの"viridis"(matplotlibのユーザーガイドの「Perceptually Uniform Sequential Colormaps」図を参照してください)というカラーマップからランダムに割り当てた色です。viridisは「色相の変化と明度の変化が同じになるように色を並べているため、色の変化に関する情報が白黒印刷してもそのまま反映される」というありがたい性質を持ったカラーマップで、よくある虹色配色よりもカラーユニバーサルの趣旨にもあっています。
matplotlibでもデフォルトの配色に使われています。

5. 2011年要旨集と2018年要旨集の比較

wordcloudライブラリは「カラーマップからランダムに選ぶ」以外に「ユーザーが色付け関数を定義してその関数に基づいて色を付ける」という機能が用意されているので、各単語を、2011年要旨集に出てくる頻度と2018年要旨集に出てくる頻度に基づいた色付けで表示することができます。

まず、

f= (2018での頻度-2011での頻度)/(2018での頻度+2011での頻度)

として{単語:f}の形の辞書(以下、頻度比較辞書と呼ぶことにします)を作ります。
fは-0.5から0.5までの値を取り、f>0ならば2018のほうが高頻度、f<0ならば2011のほうが高頻度、となります。このfの値に応じて色を選ぶわけです。

def make_flags(wdict1,wdict2):
    """
    単語頻度の比較を得るための辞書
    
    Parameters
    -------------
    wdict1, wdict2:dict(str->int)
        {単語:出現回数}からなる辞書
    
    Returns
    ---------
    fdict: dict (str->float)
        {単語w:(wdict2でのwの頻度-wdict1でのwの頻度)/(wdict1でのwの頻度+wdict2でのwの頻度) }
    """
    fdict={}
    allnouns=set(list(wdict1.keys())+list(wdict2.keys()))
    sum1=sum(wdict1.values())
    sum2=sum(wdict2.values())
    for key in allnouns:
        freq1=wdict1.get(key,0)/sum1
        freq2=wdict2.get(key,0)/sum2
        if key!='':
            fdict[key]=(freq2-freq1)/((freq1+freq2)*2)
    return fdict    
fdict=make_flags(wdict2011,wdict2018)

前回の記事の中で、

さて、前章で得られた2011年と2018年のワードクラウドを比較すると、出現頻度が特に高い単語(たとえば、「データ」「データベース」「情報」等)については、多少の順位の変動はあっても、概ね共通しているように見えます。
一方で、出現頻度がやや低い単語について、2011年に見られなかったいくつかのキーワード(たとえば、「疾患」「リポジトリ」「SPARQL」等)が2018年のワードクラウドに現れています。

という記述がありましたが、それぞれの単語に対するfの値を見てみると次のようになります。

print("{0}\t{1: .2f}".format("データ",fdict["データ"]))
print("{0}\t{1: .2f}".format("データベース",fdict["データベース"]))
print("{0}\t{1: .2f}".format("情報",fdict["情報"]))
print()
print("{0}\t{1: .2f}".format("疾患",fdict["疾患"]))
print("{0}\t{1: .2f}".format("リポジトリ",fdict["リポジトリ"]))
print("{0}\t{1: .2f}".format("SPARQL",fdict["SPARQL"]))
データ	 0.02
データベース	-0.12
情報	 0.02
 
疾患	 0.29
リポジトリ	 0.50
SPARQL	 0.50

確かに「データ」「情報」はfの値が0に近く、2011年と2018年でほぼ同頻度と言えるでしょう。「データベース」はどちらかというと2011年に多いようです。一方、「疾患」「リポジトリ」「SPARQL」は2018年に多く、特に「リポジトリ」「SPARQL」はfが最大値の0.5をとるので、2011年の要旨集には一度も出てきていない語、ということになります。

次に、fに基づく色付け関数を作ります。こちらのwordcloudライブラリの使用例を改変したものです。

FreqColorFuncは頻度比較辞書freqとColormapオブジェクトcmapをパラメータとして、「単語のfreq中の頻度からcmapの上でその頻度に応じた色を返す」関数のクラスとなります。

class FreqColorFunc(object):
    """
       単語頻度比較辞書に基づくカラーマップを生成する関数のクラス
       Wordcloudのドキュメントに出てきたデモプログラムを改変
       
       Returns
       ---------
       c: function(str->(float,float,float))
            Wordcloudのcolorfuncで使える形の関数
       
       See Also
       ---------
       make_flags
    """
 
 
    def __init__(self, freq, colormap):
        """
            Parameters
           ----------
           freq : dict(str -> float)
               単語から頻度の比較を示す値f(f ∈[-0.5,0.5])へのマップ
           colormap : matplotlib.colors.Colormap 
               カラーマップオブジェクト 
 
 
        """
        self.m=255
        self.cm=colormap
        self.f=freq
        self.af=lambda x:(x+0.5)  # [-0.5,0.5]から[0,1] への関数ならなんでもよい
    def c(self,x):
            r,g,b,a=self.cm(int(self.cm.N*self.af(x)))
            return(int(r*self.m),int(g*self.m),int(b*self.m))
 
    def __call__(self, word, font_size, position, orientation,random_state, **kwargs):
        return self.c(self.f[word])

FreqColorFuncはクラスなので、実際のカスタム色付け関数を得るには頻度比較辞書とカラーマップを指定してインスタンス(実際に使用する関数)を作ります。

これを使うと頻度の比較(f)の情報が色によって表現されることになるので、「fの値が大きく異なる文字列が同じような色で表示されない」ようなカラーマップを選んでやる必要があります。白黒印刷やカラーユニバーサルの観点からは、「明度がfの値に応じて変わる」カラーマップを選ぶのが安全です。
デフォルトの配色(viridis)はそのような目的に向いています。

上で定義したクラスでは、matplotlibのColormapオブジェクトを引数にとることに注意してください。

# matplotlibのカラーマップ名から対応するColormapオブジェクトを得る
colormap=plt.get_cmap("viridis") 
# cfuncが色付け関数となる
cfunc= FreqColorFunc(fdict, colormap)

このカスタム色付け関数でワードクラウドを作るには、次のようにします。
color_funcというパラメータに色付け関数を指定します。

wordc= wc.WordCloud(
            color_func= cfunc,    #   WordCloud関数にカスタム色関数を渡す
            background_color="white",
            font_path=FONT,
            min_font_size=30,
            max_font_size=96,
            max_words=50,
            width=800,height=600
            ).generate_from_frequencies(wdict2018)
 
plot_wordcloud(wordc, title="2018")

Wordcloud for 2018 abstract with custom color

上の図では2018年に多いものほど明るい黄色に近く、2011年に多いものほど暗い青に近い、という配色になっています。

ソースコードを短くするためにフォント、背景色、図の大きさ、文字の大きさ、単語数のパラメータをすべて固定して関数化してみました。cfuncというパラメータに色付け関数を指定できるようにしてあります。

def make_wordcloud_custom_color_func(topicdict,cfunc):
    """
   カスタム関数でWordcloud
   
        
    parameters
    -------------
    topicdict:dict(str->int) 
        {単語:出現回数}からなる辞書
    cfunc: function 
        色付け関数
        
    returns
    --------
    wordcloud (描画は別に行う) 
    
    
    """
    
    wordc= wc.WordCloud(
            color_func= cfunc,    #   WordCloud関数にカスタム色関数を渡す
            background_color="white",
            font_path=FONT,
            min_font_size=30,
            max_font_size=96,
            max_words=50,
            width=800,height=600
            ).generate_from_frequencies(topicdict)
 
    return wordc

これらを組み合わせて、2つのワードクラウドを比較できるような描画をする関数を作ります。

def wordcloud_compare(oldwdict,newwdict, colormap="viridis", 
                      title1="Word Cloud 1",title2="Word Cloud 2",label_left="old", label_right="new", label_offset=0,
                      outfile=None):
    """
    2つのWordcloudを並べてその下にカラースケールを表示
    
    parameters
    -------------
    oldwdict, newwdict:dict(str->int)
         {単語:出現回数}からなる
    colormap : matplotlib colormap or str
        色付け関数のベースとなるカラーマップ(名前でも可) (see FreqColorFunc) 
    title1, title2 :  str
        Wordcloudのタイトル(wdict1のほうがtitle1)
    label_left, label_right: str 
        カラースケールの両端に表示するラベル
    label_offset: int
        カラースケールの両端ラベルの場所調整用
    outfile: str or  None
         画像をセーブするファイル名 
    
    output
    -------
    2つのWordcloudとカラースケールを表示した画像がファイルか標準出力に
    
    """
    # generate colorfunc
    if type(colormap)==str:
        colormap=plt.get_cmap(colormap)
    fdict=make_flags(oldwdict,newwdict)
    cfunc= FreqColorFunc(fdict, colormap)
    
    #generate wordclouds
    wc1=make_wordcloud_custom_color_func(oldwdict,cfunc)
    wc2=make_wordcloud_custom_color_func(newwdict,cfunc)
    
    #plot area
    fig=plt.figure(figsize=(12,6))   
    left, width = 0.01, 0.48
    bottom, height = 0.10, 0.90
    left2 = left+width+0.02
    ax1=  plt.axes([left, bottom, width, height])
    ax2 = plt.axes([left2, bottom, width, height])
    ax3 = plt.axes([0.05, 0.05, 0.90, 0.05])
 
    #plot wordclouds
    awc = wc1.to_array()
    ax1.imshow(awc, interpolation="bilinear")
    ax1.axis("off")
    ax1.set_title(title1,fontsize=18)
 
    awc = wc2.to_array()
    ax2.imshow(awc, interpolation="bilinear")
    ax2.axis("off")
    ax2.set_title(title2,fontsize=18)
 
    #plot color scale
    n=colormap.N
    ax3.imshow(np.arange(n).reshape(1,n),
              cmap=colormap,
              interpolation="nearest", aspect="auto")
    ax3.tick_params(axis='both', length=0)
    ax3.set_xticks([label_offset,n/2, n-label_offset])
    ax3.set_yticks([-.5, .5])
    ax3.set_xticklabels([label_left,"*",label_right],
        fontdict={'fontsize': 18,
                 'fontweight': 'light',
                 'verticalalignment': 'top',
                 'horizontalalignment': 'center'})
    ax3.set_yticklabels([])
 
    plt.show()
    #save 
    if outfile:
        fig.savefig(outfile)
  
        

これで描画したのが、下に示す2011年と2018年のワードクラウドです。ワードクラウドの下に示したカラーマップの * より左は2011年のほうに高頻度に出てくる語、右が2018年のほうに高頻度に出てくる語だということを示しています。

wordcloud_compare(wdict2011,wdict2018,       
        title1="2011",title2="2018", 
        label_left="More frequent in 2011",label_right="More frequent in 2018",label_offset=20)

Wordclouds comparing 2011 and 2018 abstracts

対比を強調するような配色にするためには

  • 両端が別の色相で高彩度
  • 真ん中が低彩度

というカラーマップも使えます。matplotlibにもそのようなカラーマップがあります(ユーザーガイドのDiverging colormapsという図を参照してください)が、真ん中が白に近く、ということは白背景では2011年と2018年でほぼ同頻度のものが見えなくなってしまうので、カラーパレットライブラリpalettableから

  • 2018年に多いものは赤
  • 2011年に多いものは青
  • 同頻度のものはグレー

という配色を選んでみました。

from palettable.colorbrewer.diverging import RdBu_4_r
wordcloud_compare(wdict2011,wdict2018,
                  title1="2011",title2="2018",
                  colormap=RdBu_4_r.mpl_colormap,
                  label_left="More frequent in 2011",label_right="More frequent in 2018",label_offset=20)

Wordclouds comparing 2011 and 2018 abstracts

新旧の対比がよりわかりやすくなったように見えます。ただし、このようなカラーマップは両端が同じような明度になるので、白黒印刷する可能性があるなら避けたほうがよいでしょう。

6. 終わりに

Pythonでワードクラウドを作成してみました。
今回は2011年と2018年の頻度の違いに関する情報を図示するために配色をいじってみましたが、他のパラメータも変えることによっていろいろなワードクラウドを作成することが可能です。wordcloudライブラリのさまざまな使用例がGallery of Examplesに用意されていますので参考にしてください。もっと他の配色がいいという場合はmatplotlibやpalettableにある色々なカラーマップを試したり、自分でカラーマップを作ってしまうこともできますが、それはまた別の機会にしたいと思います。

なお、今回のように色に情報を持たせようというときは、ブラウザのアドオンなどで、種々の色覚特性を持っている読者にどう見えるかをシミュレートしておくのが安全です。ここではGoogle ChromeのSpectrumを使って大雑把なチェックをしています。

※記事の内容には十分注意を払っていますが、正確性および安全性を保証するものではありません。

使用したライブラリのライセンス

cc by Licensed under a Creative Commons 表示4.0国際 license
©2018 建石由佳(国立研究開発法人科学技術振興機構バイオサイエンスデータベースセンター)

NBDCメルマガ

NBDCの講習会・研究費公募・成果情報など、ライフサイエンスの研究者・技術者・支援者の皆さまに毎月お届けします。