Pythonで共起ネットワークを作成するでは、青空文庫にある夏目漱石の『こころ』のテキストデータをもとにPythonライブラリのNetworkXとグラフ描画パッケージのGraphvizとで共起ネットワークを作成した。ここでの共起とは2単語が同じ章に出現することで、この単語ペアをカウントしてJaccard係数(Jaccard係数の計算式と特徴(1)を参照)を計算した後に、その結果をもとにネットワーク図を作成した。今回はグラフライブラリのPlotlyを利用してインタラクティブな共起ネットワーク図を作成する。
※Jupyter Lab拡張機能インストール方法の追加とソースコードコメント修正(2021/3/21)
環境
テキストデータの準備から形態素解析まで
『こころ』のテキストを章ごとに分割して形態素解析し、単語の原型リストを作成する。また、形態素解析したすべての単語を使うのでなく、名詞のうち詳細分類1が「サ変接続」「ナイ形容詞語幹」「形容動詞語幹」「一般」「固有名詞」の単語のみを対象とする。ここまではPythonで共起ネットワークを作成するのコードと同じ。
Jaccard係数の算出
Jaccard係数の算出は以前と別の方法を使う。以前は章ごとに単語ペアの数をカウントするなどしてJaccard係数を求めたが、今回はテキストをベクトル化した上でJaccrad係数を算出する。また、以前は単語ペアの出現数やJaccard係数の閾値でネットワーク図に表示されるedge数を調整するようにしていたが、この方法だと入力するテキストによってedge数が変わって汎用性がないのでJaccard係数の上位~個を対象とするように変更する。また、すべての単語を対象にするとベクトルデータのサイズが大きくなりすぎるので対象単語を文書頻度(Document Frequency)で制限する。
以下がJaccard係数を算出するコード。入力データはPythonで共起ネットワークを作成するのchapter2bformの結果(形態素解析した章ごとの単語リスト)を使う。まずは対象単語を文書頻度上位80語に限定し、章ごとのタブ区切りテキストにする。それをScikit-learnのCountVectorizerを使ってベクトルにする。その結果をone-hotベクトルにして、Scipyのpdistを使ってJaccrad係数を求める。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | def filter_word_by_freqency(texts, freq_top = 80 ): # Document frequency c_word = Counter([word for t in texts for word in set (t)]) top_word = [word for word, cnt in c_word.most_common(freq_top)] texts_fitered = [] for t in texts: filtered = list ( set (top_word).intersection( set (t))) if len (filtered) > 0 : texts_fitered.append(filtered) return texts_fitered def compute_jaccard_coef(texts, edge_top): # 単語リストをタブ区切りテキストに変換 tab_separated = [ '\t' .join(t) for t in texts] # tokenizerでタブで分割するように指定 vectorizer = CountVectorizer(lowercase = False , tokenizer = lambda x: x.split( '\t' )) vec = vectorizer.fit_transform(tab_separated) # 単語リスト words = vectorizer.get_feature_names() # 0/1のベクトルにするためにカウント1以上はすべて1にする vec_one = (vec.toarray() > = 1 ).astype( int ).transpose() # pdistを使うと結果は密行列(condensed matrix)で得られる # pdistで得られるのは距離なので、係数にするために1から引く jaccard_coef = 1 - pdist(vec_one, metric = 'jaccard' ) # 密行列の順番はitertools.combinationsnの結果と一致するのでcombinationsでJaccard係数に対応する単語ペアを作成する # How does condensed distance matrix work? (pdist) w_pair = list (combinations(words, 2 )) # 単語ペアをキーとするJaccard係数のdict dict_jaccard = {pair: value for pair, value in zip (w_pair, jaccard_coef)} # Jaccard係数はネットワーク図のedgeに相当する # その数を一定数に限定する dict_jaccard = dict ( sorted (dict_jaccard.items(), key = lambda x: x[ 1 ], reverse = True )[:edge_top]) return dict_jaccard # ルビなど除去済みのテキストを読み込む with open ( 'kokoro.txt' ) as f: doc = f.read() # 章ごとの単語原型リスト bform_2l = chapter2bform(doc2chapter(doc)) texts_filtered = filter_word_by_freqency(bform_2l, freq_top = 80 ) jaccard_dict = compute_jaccard_coef(texts_filtered, edge_top = 60 ) print (jaccard_dict) |
インタラクティブな共起ネットワークを作成
Jaccard係数が計算できたら、それをもとに共起ネットワークを作成する。NetworkXとGraphvizでネットワーク図のnodeの配置を作成し、それをもとにPlotlyでインタラクティブなネットワーク図を作成する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | def build_interactive_network(G, pos, node_sizes, node_colors): # edgeデータの作成 edge_x = [] edge_y = [] for edge in G.edges(): x0, y0 = pos[edge[ 0 ]] x1, y1 = pos[edge[ 1 ]] edge_x.append(x0) edge_x.append(x1) edge_x.append( None ) edge_y.append(y0) edge_y.append(y1) edge_y.append( None ) edge_trace = go.Scatter( x = edge_x, y = edge_y, line = dict (width = 0.5 , color = '#888' ), hoverinfo = 'none' , mode = 'lines' ) # nodeデータの作成 node_x = [] node_y = [] for node in G.nodes(): x, y = pos[node] node_x.append(x) node_y.append(y) # nodeの色、サイズ、マウスオーバーしたときに表示するテキストの設定 node_trace = go.Scatter( x = node_x, y = node_y, text = list (G.nodes()), hovertext = node_sizes, textposition = 'top center' , mode = 'markers+text' , hoverinfo = 'text' , marker = dict ( showscale = True , colorscale = 'Portland' , reversescale = False , color = node_colors, size = node_sizes, colorbar = dict ( thickness = 15 , title = 'Page Ranking' , ), line_width = 2 )) data = [edge_trace, node_trace] # レイアウトの設定 layout = go.Layout( paper_bgcolor = 'rgba(0,0,0,0)' , plot_bgcolor = 'rgba(0,0,0,0)' , showlegend = False , hovermode = 'closest' , margin = dict (b = 10 , l = 5 , r = 5 , t = 10 ), font = dict (size = 10 ), xaxis = dict (showgrid = False , zeroline = False , showticklabels = False ), yaxis = dict (showgrid = False , zeroline = False , showticklabels = False )) fig = go.Figure(data = data, layout = layout) fig.show() def build_coonw(texts, freq_top = 80 , edge_top = 60 ): # 対象をDocument Frequency上位に限定 texts_filtered = filter_word_by_freqency(texts, freq_top) dict_jaccard = compute_jaccard_coef(texts_filtered, edge_top) print ( 'dict_jaccard=' , dict_jaccard) # Document frequency df = Counter([word for t in texts_filtered for word in set (t)]) # nodeリスト nodes = sorted ( set ([word for pair in dict_jaccard.keys() for word in pair])) # 単語出現数でnodeサイズを変更する c_word = {n: df[n] for n in nodes} G = nx.Graph() # 接点/単語(node)の追加 G.add_nodes_from(nodes) print ( 'Number of nodes: {}' . format (G.number_of_nodes())) # 線(edge)の追加 for pair, coef in dict_jaccard.items(): G.add_edge(pair[ 0 ], pair[ 1 ], weight = coef) print ( 'Number of edges: {}' . format (G.number_of_edges())) # nodeの配置方法の指定 seed = 0 np.random.seed(seed) pos = nx_agraph.graphviz_layout( G, prog = 'neato' , args = '-Goverlap="scalexy" -Gsep="+6" -Gnodesep=0.8 -Gsplines="polyline" -GpackMode="graph" -Gstart={}' . format (seed)) # nodeの色をページランクアルゴリズムによる重要度により変える pr = nx.pagerank(G) # インタラクティブな共起ネットワークの可視化 build_interactive_network(G, pos, list (c_word.values()), list (pr.values())) # ルビなど除去済みのテキストを読み込む with open ( 'kokoro.txt' ) as f: doc = f.read() # 章ごとの単語原型リスト bform_2l = chapter2bform(doc2chapter(doc)) # 共起ネットワークの作成 build_coonw(bform_2l, freq_top = 80 , edge_top = 60 ) |
結果、以下のような共起ネットワークが作成される。マウスオーバーで単語数を表示したり、図のズームなどができる。
![]() |
こんにちは、共起ネットのpythonプログラムありがとうございました。源氏物語を身近に感じるためにも使わせていただきました。Plotlyでのインタラクティブなネットワーク図は目的よりも道具に関してしまいますね。
返信削除https://vanlaborg.hatenablog.com/