共起ネットワークを描きたくてやってみました。
Python、NetworkXを使います。
#KH-Coder
いきなりこの記事を否定するようですが、手作業にこだわらないならKH-Coderを使うことをおすすめします。
テキストや集計データを一瞬で可視化できる素晴らしいツールです。
共起ネットワークもいい感じのやつがあっさり描けます。
コーディングも簡単にできる。
ほんとすごい。
#ラーメンデータセット
インスタント?ラーメンのおいしさレビューのデータです。
世界中の全てのラーメンではなく、レビューサイトの評価を受けたラーメンが母数です。
なので、レビューの人が入手できる範囲かつ、ある程度好みなどの偏りも出ると思います。
が、今回はひとまず、世界のインスタントラーメンの縮図である、ということにして話を進めます。
カラムは、ブランド、スタイル(容器)、商品名、国籍、評価、その年のトップ10、です。
結構遊べそうですが、スタイルと国籍だけ抜き取ります。
#コード
##クロス集計
groupbyを使いましたが、crosstabで表計算スタイルのクロス集計でもいいです。
import pandas as pd
#データ読み込み
df = pd.read_csv("ramen-ratings.csv")
#必要なデータの切り出し
df2 = df.loc[:, ["Country", "Style", "Review #"]].groupby(["Country", "Style"]).count().reset_index()
df2.head(3)
Country | Style | Review # | |
---|---|---|---|
0 | Australia | Cup | 17 |
1 | Australia | Pack | 5 |
2 | Bangladesh | Pack | 7 |
注:Review #は、出現回数に変わっています(手抜き)。
##Jaccard係数を計算
下記のように計算しています。
計算方法が間違っていたら教えていただけるとうれしいです。
AとBの共起性
= AとBが同時出現する回数 / AかBのいずれかが出現する回数
= AとBの積集合 / AとBの和集合
#スタイルデータとカントリーデータを作る
df_Style = pd.DataFrame(df.Style.value_counts())
df_Style.columns = ["n_Style"]
df_Country = pd.DataFrame(df.Country.value_counts())
df_Country.columns = ["n_Country"]
#スタイルデータとカントリーデータを結合
df2 = pd.merge(df2, df_Style, left_on="Style", right_index=True, how="left")
df2 = pd.merge(df2, df_Country, left_on="Country", right_index=True, how="left")
#Jaccard係数の計算
# AとBの同時出現回数 / A出現とB出現の和集合
df2["Jaccard"] = df2["Review #"] / (df2.n_Style + df2.n_Country - df2["Review #"])
df2.head(3)
Country | Style | Review # | n_Style | n_Country | Jaccard | |
---|---|---|---|---|---|---|
0 | Australia | Cup | 17 | 450 | 22 | 0.037363 |
1 | Australia | Pack | 5 | 1531 | 22 | 0.003230 |
2 | Bangladesh | Pack | 7 | 1531 | 7 | 0.004572 |
##NetworkXで共起ネットワークを描く
データ別にノードの形や色を変えたり、関係の強さに応じてエッジの太さを変えたり、結構カスタマイズが利くことがわかりました。
(Rではできそうだけど、Pythonではできないと思っていました。ちなみにPythonで頑張った理由はライセンスです。)
import networkx as nx
G = nx.Graph()
#ノード定義
def apply_add_node(row):
global cnt
G.add_node(row.name)
#好みでパラメータを作る
G.node[row.name]["size"] = row[0] * 10
G.node[row.name]["color"] = plt.cm.Set2(0)
if row.name in df_Style.index:
cnt += 1
G.node[row.name]["color"] = plt.cm.Set2(cnt)
#エッジ定義
def apply_add_edge(row):
global cnt
G.add_edge(row.Style, row.Country)
#好みでパラメータを作る
G.edges[row.Style, row.Country]["weight"] = row.Jaccard * 100
G.edges[row.Style, row.Country]["color"] = G.node[row.Style]["color"] #スタイル側ノードの色に合わせる
#ノード定義とエッジ定義を実行
cnt = 0
df_Style.apply(lambda row: apply_add_node(row), axis=1)
df_Country.apply(lambda row: apply_add_node(row), axis=1)
df2.apply(lambda row: apply_add_edge(row), axis=1)
import matplotlib.pyplot as plt
plt.figure(figsize=(20,20))
#kはノードの中心への集まり具合、大きくすると円の外側にノードが離れる
pos = nx.spring_layout(G, k=3.5)
#スタイルだけ正方形でプロット
nx.draw_networkx_nodes(G, pos, alpha=0.8, node_shape="s", linewidths=5,
nodelist=[i for i, j in G.nodes(data=True) if i in df_Style.index],
node_color=[j["color"] for i, j in G.nodes(data=True) if i in df_Style.index],
node_size=[j["size"] for i, j in G.nodes(data=True) if i in df_Style.index])
#国籍だけ円形でプロット
nx.draw_networkx_nodes(G, pos, alpha=0.6, node_shape="o", linewidths=0,
nodelist=[i for i, j in G.nodes(data=True) if i in df_Country.index],
node_color=[j["color"] for i, j in G.nodes(data=True) if i in df_Country.index],
node_size=[j["size"] for i, j in G.nodes(data=True) if i in df_Country.index])
#ラベルを作図
nx.draw_networkx_labels(G, pos, font_color="b")
#エッジを作図
nx.draw_networkx_edges(G, pos, alpha=0.6,
width=[k["weight"] for i, j, k in G.edges(data=True)],
edge_color=[k["color"] for i, j, k in G.edges(data=True)])
plt.axis('off')
plt.show()
#結果
ノードの大きさ:出現回数(レビューされた回数)
エッジの太さ:共起性の強さ
世界的にはパックが主流なんですね。
確かに、輸入食品店に行くとほとんどパックですね。
日本はボウルが中心なのがわかります。
おなじみのアレですね。
日本がラーメン大国かと思いきや、意外とアメリカもラーメン大国だということがわかります。
スタイルでいえば、アメリカの方が豊かなのかもしれません。
日本と韓国は似てますね。
(と思ったけど、この図からそれを判断するのは微妙だということが後でわかった)
##単純にクロス集計を図にした場合
クロス集計と共起性の違うところは、クロス集計は単純に出現頻度が多いものが強調され、必ずしも関係が強いものが強調されるわけではない、というところです。
量的な規模を中心に見たいときはクロス集計、量的な規模の影響を消したいときは共起、という感じでしょうか。
(形状など、ちょっと違う描き方をしています)
パックとボウルのエッジの差が顕著になりました。
・・・とはいえ大勢は思ったほど変わらないかも。
そもそもこういうデータを読むときに、共起性を計算するのが妥当なのか、という話があります。
(冒頭にも描きましたが、今回は共起ネットワークを作図したくてやってみた、という感じです。共起ネットワークはアンケートの選択回答+自由記述分析などで威力を発揮します。)
#ついでにISOMAP
Jaccard係数ではなく、クロス集計の過程でダミー変数を作って計算しています。
共起ネットワークでは日本と韓国は似てるなーと思ったんですが、実は結構違っていました。
韓国ではボウルよりもパックが強いんですね。
たしかに共起ネットをよーく見ると、そういう太さになっていました。
Style/Country | USA | Japan | South Korea | Taiwan | UK | India | Indonesia |
---|---|---|---|---|---|---|---|
Bar | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
Bowl | 70 | 126 | 68 | 37 | 2 | 0 | 0 |
Box | 1 | 2 | 0 | 0 | 0 | 0 | 1 |
Can | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
Cup | 70 | 49 | 40 | 2 | 32 | 3 | 21 |
Pack | 128 | 155 | 183 | 181 | 35 | 28 | 104 |
Tray | 52 | 20 | 18 | 3 | 0 | 0 | 0 |
ISOMAPはラーメン大国対その他大勢の違いが見えるほか、ラーメン大国間のタイプの違いもはっきり出ているのがすごいなーという感じです。
軸に意味はありませんが、ポジションに分けると、右外周が大国、左が小規模、右下がボール系、右上がパック系、って感じですね。
まあそもそも、ISOMAPはカテゴリ関係なく要素間の関係を見ていて、共起ネットワークはスタイルと国籍のカテゴリ間の関係を計算していて国要素間の関係は計算していません。
だから共起ネットで国間の関係がよくわからないのは当たり前かもしれません。
図の描き方はこちらをどうぞ。
https://qiita.com/nabebugyo/items/643c5d7e324fb50c4205
#ほか
NHKの謎グラフも面白そうですね。ただこれは描いた人にしかわからないやつだと思う。
(特に帯部分の色付けのルールがわからないと意味不明)
コードグラフというそうで、Pythonのイマドキ系の作図ライブラリにも入っていた気がする。
あとは、D3のforceで単に動くやつを描くだけですごい感じに見えそう。
ネットワーク図は足切りしないと複雑になるので、D3でタッチすると強調表示される系のギミックをつけてあげると見やすくていい感じになりそうです。