乃木撮を使ってnetworkXについて勉強する。Vol.04

# はじめに
前回は「[乃木撮を使ってnetworkXについて勉強する。Vol.03](https://qiita.com/Hakoshi/items/e4e312ba801c9e730b9e
 "Qiita Home")」という記事で、写真集から作成したリストデータを使ってグラフを描画してみました。

以下のコードは前回の復習です。
```python
# ライブラリのインポート
import numpy as np
import networkx as nx
import itertools
from collections import defaultdict

# 写真一枚に写っている人を１つのタプルにまとめる
# 最初は撮影者、以後、被写体を入れる。
# 被写体は複数人いる可能性もある。自撮りのこともある
photos_01 = [
    ('新内眞衣', '齋藤飛鳥'),  # 01-001
    ('若月佑美', '白石麻衣', '衛藤美彩'),  # 01-002
    ('斉藤優里', '西野七瀬'),  # 01-003
    ('堀未央奈', '衛藤美彩', '秋元真夏'),  # 01-004
    ('秋元真夏', '生田絵梨花'),  # 01-005
    ('秋元真夏', '白石麻衣'),  # 01-006
    ('伊藤万理華', '星野みなみ', '西野七瀬', '若月佑美', '松村沙友理'),  # 01-007
    ('伊藤万理華', '松村沙友理'),  # 01-008
    ('斉藤優里', '生田絵梨花'),  # 01-009
    ('伊藤万理華', '西野七瀬', '衛藤美彩', '高山一実', '秋元真夏'),  # 01-010
         .
         .
         .
     （中略）
    ]

member_data = {
    '秋元真夏': (1, '1993/08/20', 'B', '獅子座', 154),
    '生田絵梨花': (1, '1997/01/22', 'A', '水瓶座', 160),
    '伊藤かりん': (2, '1993/05/26', 'O', '双子座', 153),
    '伊藤純奈': (2, '1998/11/30', 'A', '射手座', 166),
    '伊藤理々杏': (3, '2002/10/08', 'B', '天秤座', 154),
         .
         .
         .
     （中略）
    }

G = nx.Graph()
G.add_nodes_from(member_data.keys())

# 撮影者、つまりphotos[i][0]を取り出す。
photographer = {person: 0 for person in G.nodes}  # 各人にvalue=0を入れた辞書データを作成する。defaultdictを使用すると、写真撮影していない人が出たときにエラーとなる。
for photo in photos_01:
    photographer[photo[0]] += 1

# 被写体の回数
subject = {person: 0 for person in G.nodes}
for photo in photos_01:
    for person in photo[1:]:
        subject[person] += 1

# ノードに撮影回数、何期生かなどの情報を入れる
for person in G.nodes:
    G.nodes[person]['generation'] = member_data[person][0]
    G.nodes[person]['dateofbirth'] = member_data[person][1]
    G.nodes[person]['bloodtype'] = member_data[person][2]
    G.nodes[person]['constellations'] = member_data[person][3]
    G.nodes[person]['height'] = member_data[person][4]
    G.nodes[person]['photographer'] = photographer[person]
    G.nodes[person]['subject'] = subject[person]

# 何期生かによって色を変えるためのカラーリストを用意する
node_colors = {
    1: 'red',
    2: 'skyblue',
    3: 'lime',
    4: 'lightyellow',
    5: 'violet'
}

# 写真撮影回数、同じ写真に写った回数を数えて関係性の強さとしてエッジの太さで表現する。
connection = defaultdict(int)
for photo in photos_01:
    connects = list(itertools.combinations(set(photo), 2)) # 自撮りに備えてset化する
    for connect in connects:
        connection[connect[0], connect[1]] += 1
        connection[connect[1], connect[0]] += 1

G.add_edges_from(connection.keys())
nx.set_edge_attributes(G, connection, 'weight')

# グラフ表示
pos=nx.spring_layout(G, seed=5)

nx.draw_networkx_nodes(G,
                       pos=pos,
                       node_color=[node_colors[G.nodes[node]['generation']] for node in G.nodes],
                       node_size=[G.nodes[node]['subject'] * 100 for node in G.nodes],
                       alpha=0.5,
                       )
nx.draw_networkx_labels(G,
                        pos=pos,
                        font_family='IPAexGothic'
                        )
nx.draw_networkx_edges(G,
                       pos=pos,
                       width=[G.edges[edge]['weight'] * 0.1 for edge in G.edges],
                       alpha=0.5)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3999109/956db307-d865-46c5-ba21-ac5f6abae493.png)
以上、前回までの復習でした。

# クラスター分析
データの中に存在する似たもの同士をグループ分けする手法です。誰と誰の仲が良さそうなのかは、グラフからも読み取れそうですが、客観的に分析したいと思いました。
今回はLouvain法を使ってメンバー間の関係性に基づいてグループ分けします。他にも階層的クラスタリングやk-means法というのもあるようですが割愛します。

```python
import community.community_louvain as community_louvain

# best_partition関数は、グラフ内のノードをコミュニティに分割し、各ノードがどのコミュニティに属するかを辞書データで返してくれます。
partition = community_louvain.best_partition(G)

nx.set_node_attributes(G, partition, 'partition')
partitions = sorted({G.nodes[node]['partition'] for node in G.nodes})
```
partitionsは[0, 1, 2, 3]であり、４つのグループに分かれそうです。それぞれのpartitionに色を設定します。

```python
# コミュニティ別に色分けする。
node_color_cluster = []
for person in G:
    if G.nodes[person]["partition"] == 2: # 期別の色分けとなるべく合わせるため、数字を交換している
        node_color_cluster.append("red")
    elif G.nodes[person]["partition"] == 0:
        node_color_cluster.append("skyblue")
    elif G.nodes[person]["partition"] == 3:
        node_color_cluster.append("lime")
    elif G.nodes[person]["partition"] == 1:
        node_color_cluster.append("yellow")
    elif G.nodes[person]["partition"] == 4:
        node_color_cluster.append("violet")
    else:
        node_color_cluster.append("grey")

plt.figure(figsize=(30, 30))
nx.draw_networkx(G,
                 pos = nx.spring_layout(G, seed=5), # 上の画像とノードの位置を合わせるためseedを固定する
                 node_color=node_color_cluster,
                 alpha=0.5,
                 node_size=node_size,
                 with_labels=True,
                 font_family="IPAexGothic",
                 font_size=20,
                 width=edge_weight,
                 edge_color='black')

plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3999109/10256b60-941d-49a0-8725-4a9ac38a2972.png)

期ごとの関係性が強そうだということはわかります。3期生はそのまま一つのコミュニティを作っています。
1期生と2期生は大まかには分かれているようにも見えますが、一部混在しています。これには、何期生かという情報だけでなく選抜常連かどうかも影響しているようにも思われます。選抜回数をデータに入れて検討する必要がありそうです。後日の課題とします。
特筆すべきは、黄色のグループです。乃木坂の中心メンバーの西野七瀬と、乃木撮で強い印象を残している斉藤優里を中心としたコミュニティの存在が示唆されます。これがスイカのメンバー（伊藤かりん、伊藤純奈、川後陽菜、斉藤優里、西野七瀬）の関係性を表しているものだとすれば、個人的にはとてもよいと思います。乃木撮には仕事場で撮影されたであろう写真が多い中で、スイカグループだけはプライベートの旅行や食事風景を残してくれています。

# ノードの形を変える
常識として乃木坂メンバーの名前を見れば何期生かは反射的にわかるものですが、そうではない人のために、ノードの形は何期生かを反映し、ノードの大きさは撮影回数を反映し、ノードの色はコミュニティを反映するようにして、グラフを表示したいと考えました。

ただし、条件に合わせてノードの形を変更するのは意外にめんどくさいことがわかりました。ノードの色と大きさはリストを用意して割り当てられるのですが、ノードの形を同じように準備しても反映されません。ノードの形ごとにグラフを描写して重ね合わせるというイメージです。

```python
# コミュニティによってノードの形を決める。
node_shapes = {
    0: 'o', # 円形
    1: 'h', # 六角形
    2: 'p', # 五角形
    3: 's', # 四角形
    }
plt.figure(figsize=(30, 30))

pos=nx.spring_layout(G, seed=5)

for partition in partitions: # ノードの形ごとにグラフを描写する
    # 現在のパーティション番号に該当するノードだけを抽出してリストにまとめる。
    node_list = [node for node in G.nodes if G.nodes[node]['partition'] == partition]
    nx.draw_networkx_nodes(G,
                           pos=pos,
                           node_shape=node_shapes[partition], # パーティションごとに異なる形状を node_shapes 辞書から取得。
                           nodelist=node_list,
                           node_color=[node_colors[G.nodes[node]['generation']] for node in node_list],
                           node_size=[G.nodes[node]['subject'] * 100 for node in node_list],
                           alpha=0.5,
                           )
nx.draw_networkx_labels(G,
                        pos=pos,
                        font_family='IPAexGothic'
                        )
nx.draw_networkx_edges(G,
                       pos=pos,
                       width=[G.edges[edge]['weight'] * 0.1 for edge in G.edges],
                       alpha=0.5)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3999109/6bf0b27e-776e-4110-a87d-c2afa384bb83.png)

苦労した割には、ノードの形は色ほどの視覚的情報を伝えてはくれないようで残念です。例えば、円形と四角形では大きさの比較が困難なように思います。

# 関係性の解析
グラフで表示して喜んでいるだけではなく、数字として客観的に評価してみたいと思います。networkxにはノードの重要性を評価するための様々な指標を簡単に計算できるようになっています。

### 次数中心性（Degree Centrality）
次数中心性は、そのノードに直接接続している他のノードの数（次数）を、グラフ内の可能な最大接続数で割ったものです。
簡単にいうと、ネットワークの中で「どれだけ多くの人と直接つながっているか」を示す指標です。
nx.degree_centrality(G)関数を使用して、グラフGの各ノードの次数中心性を計算します。
```python
degree_centrality = nx.degree_centrality(G)
degree_centrality = dict(sorted(degree_centrality.items(), key=lambda x: x[1], reverse=True))
print(degree_centrality)
# degree centrality順に名前と数字が表示される。
```

|||次数中心性|
|--:|:-------:|---:|
|１位|秋元真夏　|0.760|
|２位|樋口日奈　|0.673|
|３位|斉藤優里　|0.630|
|４位|衛藤美彩　|0.587|
|　　|齋藤飛鳥　|0.587|

### 媒介中心性（Betweenness Centrality）
任意の2点間で最短経路を通る際に、特定のノードがどれくらい通過するかを計算します。
特定のノードがどの程度「橋渡し」の役割を果たしているかを示す指標らしいです。媒介中心性の高い人は異なるグループ間のつながりを作る重要な人物であることが多いようです。

|||媒介中心性|
|--:|:-------:|---:|
|　１位|樋口日奈　|0.090|
|　２位|秋元真夏　|0.074|
|　３位|斉藤優里　|0.061|
|　４位|齋藤飛鳥　|0.034|
|　５位|松村沙友理|0.031|

### 固有ベクトル中心性（Eigenvector Centrality）
接続しているノードの重要度を考慮に入れた中心性指標です。重要なノードと接続しているノードは、そうでないノードよりも高い固有ベクトル中心性を持つとのことです。

|||固有ベクトル中心性|
|--:|:-------:|---:|
|　１位|秋元真夏　|0.259|
|　２位|衛藤美彩　|0.227|
|　３位|斉藤優里　|0.217|
|　４位|齋藤飛鳥　|0.215|
|　５位|生駒里奈　|0.213|

# 感想
指標の違いはよくわからないものの、秋元真夏の圧倒的な強さはわかります。撮影回数1位（123回）、被写体回数4位（61回）と大車輪の活躍でした。
被写体としては圧倒的な強さを誇っていた西野七瀬（81回、1位）と白石麻衣（75回、2位）の名前が目立たないのも、理由はよくわかりませんが面白いです。齋藤飛鳥（70回、3位）はどの指標にもトップに名を連ねているのですが。
また、樋口日奈の健闘も光っているように思います。固有ベクトル中心性がそれほど高くないこと、撮影回数は多いものの（45回で4位）被写体回数はそれほど多くないことからすると、中心メンバーではないメンバーの写真を積極的に撮影してくれているのではないでしょうか。感謝しかありません。

次回は、乃木撮Vol.02、Vol.03のデータを利用して、関係性の経時的変化について検討していく予定です。