23
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

世界のラーメンスタイルをNetworkXで可視化する

Last updated at Posted at 2018-03-18

共起ネットワークを描きたくてやってみました。
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()

#結果

共起ネット.png

ノードの大きさ:出現回数(レビューされた回数)
エッジの太さ:共起性の強さ

世界的にはパックが主流なんですね。
確かに、輸入食品店に行くとほとんどパックですね。
日本はボウルが中心なのがわかります。
おなじみのアレですね。

日本がラーメン大国かと思いきや、意外とアメリカもラーメン大国だということがわかります。
スタイルでいえば、アメリカの方が豊かなのかもしれません。

日本と韓国は似てますね。
(と思ったけど、この図からそれを判断するのは微妙だということが後でわかった)

##単純にクロス集計を図にした場合

クロス集計と共起性の違うところは、クロス集計は単純に出現頻度が多いものが強調され、必ずしも関係が強いものが強調されるわけではない、というところです。
量的な規模を中心に見たいときはクロス集計、量的な規模の影響を消したいときは共起、という感じでしょうか。

クロス集計.png

(形状など、ちょっと違う描き方をしています)

パックとボウルのエッジの差が顕著になりました。
・・・とはいえ大勢は思ったほど変わらないかも。
そもそもこういうデータを読むときに、共起性を計算するのが妥当なのか、という話があります。
(冒頭にも描きましたが、今回は共起ネットワークを作図したくてやってみた、という感じです。共起ネットワークはアンケートの選択回答+自由記述分析などで威力を発揮します。)

#ついでにISOMAP

isomap.png

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でタッチすると強調表示される系のギミックをつけてあげると見やすくていい感じになりそうです。

23
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?