
【人文情報学散歩(8)】 ネットワーク分析: 「コミュニティ検出」と「ノード属性分析」‐ 第3回
1.はじめに
こんにちは、前回はネットワーク分析における中心性と重みについて勉強しました。ネットワークの可視化がとても面白くなったせいか、ビュー数とスキの数が多く、反応がかなり良かったです。 (かなり嬉しいです!、これからもスキとフォローをたくさん押してください!)
特にYasunoroiさんが、「ソシオグラムの入り口?」というコメントと一緒にリンクを一つ残してくださいました。 はい、そうです、事実、今やっている作業は、ソシオグラムを描くための基礎勉強のようなものです。

なぜ私が今この勉強をするかというと、私が進めている歴史資料分析にソシオグラムを導入しようと思っているからです。 近年、IT情報技術の発達とともに、人文情報学(Digital Humanities)や歴史情報学(Digital History)という新しい学問分野が徐々に拡大しています。
韓国では本当に人気があり、早稲田、慶応と似たようなトップ大学が軒並みデジタル人文学、デジタル歴史学関連学科を新設しているそうです。

韓国の高麗大学の人文情報学センター
他にはドイツ・アメリカなど全世界的に人文情報学ブームが起きています。
私は一度実験的に、来年3月ぐらいに日本のある大学でその名簿を分析した内容を発表しようと思っています。 これが私の最初のデジタル歴史学関連の研究成果になることを期待しています。

(無料で提供されているので、興味のある方はご覧ください)_
韓国の歴史学界で長い間広く活用されましたが、断片的にしか利用されておらず、この資料自体に対する全体的な分析と図式は提示されていません。

とにかく雑談が長かったです今日は「コミュニティ検出」と「ノード属性分析」を勉強します。
コミュニティ検出は、ネットワーク内で密接な関連性を持つグループを識別する作業です。 これにより、ネットワークの構造とパターンを理解し、類似の関心や接続性を持つグループを見つけることができます。これは、ソーシャルネットワークの友達グループ、組織内の部署、あるいはウェブサイトのテーマグループなど、様々な分野で適用することができます。
歴史学的に活用すると、歴史的なコミュニティやグループを特定することで、特定の時代や場所における社会構造を把握します。例えば、特定の地域の歴史的な町や都市内の様々な集団とそれらの相互作用を理解することができます。
ノード属性分析は、ネットワークのノードが持つ様々な属性を探ります。この属性は、年齢、性別、国、あるいは何らかの特性であり、これらの属性を通じてネットワークの構成員間の違いと関連性を把握することができます。これにより、特定の特性を持つノード間の接続性や影響力を理解することができます。
ノード属性分析を歴史学的に利用してみると、年齢、性別、国籍、職業、その他の属性を考慮して、特定の人物がどのように歴史的な出来事に関与し、その時代の社会構造に与えた影響を理解することができます。
2.ノード・エッジの設定
もう少し複雑なネットワークのために、より多くのノードとエッジを追加しました。
import networkx as nx
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
# Creating a directed graph
DG = nx.DiGraph()
DG.add_nodes_from([
("Lee", {"age": 30, "gender": "Male", "country": "Korea"}),
("Choi", {"age": 28, "gender": "Female", "country": "Korea"}),
("Jeong", {"age": 35, "gender": "Male", "country": "Japan"}),
("Kito", {"age": 32, "gender": "Female", "country": "Japan"}),
("Kim", {"age": 28, "gender": "Male", "country": "Japan"}),
("Yoon", {"age": 29, "gender": "Male", "country": "Japan"}),
("Ahn", {"age": 27, "gender": "Female", "country": "Japan"}),
("Kang", {"age": 31, "gender": "Male", "country": "Korea"}),
("Hwang", {"age": 30, "gender": "Female", "country": "Korea"}),
("Jung", {"age": 32, "gender": "Male", "country": "Korea"}),
("Nam", {"age": 29, "gender": "Female", "country": "Korea"}),
("Lim", {"age": 28, "gender": "Male", "country": "Korea"}),
("Oh", {"age": 34, "gender": "Female", "country": "Korea"}),
("Seo", {"age": 30, "gender": "Male", "country": "Korea"}),
("Kwon", {"age": 28, "gender": "Female", "country": "Korea"}),
("Kang", {"age": 33, "gender": "Male", "country": "Korea"}),
])
new_nodes = [
("Seo2", {"age": 25, "gender": "Male", "country": "Korea"}),
("Chung", {"age": 29, "gender": "Female", "country": "Korea"}),
("Park2", {"age": 27, "gender": "Male", "country": "Korea"}),
("Kim2", {"age": 33, "gender": "Female", "country": "Korea"}),
("Kang2", {"age": 29, "gender": "Male", "country": "Korea"}),
("Han2", {"age": 35, "gender": "Female", "country": "Korea"}),
("Yoo2", {"age": 30, "gender": "Male", "country": "Korea"}),
("Moon2", {"age": 28, "gender": "Female", "country": "Korea"}),
("Shin2", {"age": 32, "gender": "Male", "country": "Korea"}),
("Lee2", {"age": 29, "gender": "Female", "country": "Korea"}),
("Kwon2", {"age": 31, "gender": "Male", "country": "Korea"}),
("Son2", {"age": 30, "gender": "Female", "country": "Korea"}),
("Song2", {"age": 28, "gender": "Male", "country": "Korea"}),
("Baek2", {"age": 34, "gender": "Female", "country": "Korea"}),
("Goo2", {"age": 30, "gender": "Male", "country": "Korea"}),
("Ahn2", {"age": 29, "gender": "Female", "country": "Korea"}),
("Kang3", {"age": 31, "gender": "Male", "country": "Korea"}),
("Park3", {"age": 33, "gender": "Female", "country": "Korea"}),
("Kwon3", {"age": 26, "gender": "Male", "country": "Korea"}),
("Choi2", {"age": 28, "gender": "Female", "country": "Korea"})
]
DG.add_nodes_from(new_nodes)
DG.add_edges_from([
("Lee", "Choi", {"relation_type": "Friend", "weight": 1.2}),
("Choi", "Kito", {"relation_type": "Colleague", "weight": 0.4}),
("Jeong", "Kito", {"relation_type": "Friend", "weight": 0.8}),
("Kito", "Lee", {"relation_type": "Friend", "weight": 1.4}),
("Lee", "Kito", {"relation_type": "Friend", "weight": 1.8}),
("Kim", "Kito", {"relation_type": "Family", "weight": 0.8}),
("Kito", "Jeong", {"relation_type": "Colleague", "weight": 0.2}),
("Kito", "Kim", {"relation_type": "Family", "weight": 0.9}),
("Kito", "Yoon", {"relation_type": "Colleague", "weight": 0.3}),
("Yoon", "Kito", {"relation_type": "Colleague", "weight": 0.2}),
("Yoon", "Choi", {"relation_type": "Friend", "weight": 0.3}),
("Ahn", "Choi", {"relation_type": "Friend", "weight": 0.8}),
("Choi", "Hwang", {"relation_type": "Colleague", "weight": 1.6}),
("Hwang", "Jung", {"relation_type": "Friend", "weight": 3.0}),
("Jung", "Nam", {"relation_type": "Family", "weight": 0.9}),
("Nam", "Lim", {"relation_type": "Friend", "weight": 0.1}),
("Lim", "Oh", {"relation_type": "Colleague", "weight": 0.5}),
("Oh", "Seo", {"relation_type": "Colleague", "weight": 0.5}),
("Seo", "Kwon", {"relation_type": "Friend", "weight": 0.8}),
("Kito", "Ahn", {"relation_type": "Friend", "weight": 0.7}),
("Jeong", "Nam", {"relation_type": "Colleague", "weight": 0.6}),
("Nam", "Kang", {"relation_type": "Colleague", "weight": 0.8}),
("Kang", "Kwon", {"relation_type": "Friend", "weight": 0.7}),
("Kwon", "Ahn", {"relation_type": "Friend", "weight": 0.6}),
("Kito", "Kang", {"relation_type": "Friend", "weight": 0.9}),
("Choi", "Hwang", {"relation_type": "Colleague", "weight": 0.6}),
("Jung", "Nam", {"relation_type": "Family", "weight": 0.8}),
("Nam", "Lim", {"relation_type": "Friend", "weight": 0.6}),
("Lim", "Oh", {"relation_type": "Colleague", "weight": 0.7}),
("Oh", "Seo", {"relation_type": "Colleague", "weight": 0.5}),
("Seo", "Kwon", {"relation_type": "Friend", "weight": 0.6}),
])
new_edges = [
("Lee", "Chung", {"relation_type": "Friend", "weight": 0.9}),
("Choi", "Kim2", {"relation_type": "Colleague", "weight": 0.7}),
("Jeong", "Han2", {"relation_type": "Friend", "weight": 0.8}),
("Kito", "Kang2", {"relation_type": "Family", "weight": 0.6}),
("Kim", "Yoo2", {"relation_type": "Friend", "weight": 0.9}),
("Yoon", "Moon2", {"relation_type": "Colleague", "weight": 0.7}),
("Ahn", "Shin2", {"relation_type": "Friend", "weight": 0.8}),
("Kang", "Lee2", {"relation_type": "Family", "weight": 0.6}),
("Hwang", "Kwon2", {"relation_type": "Friend", "weight": 0.9}),
("Jung", "Son2", {"relation_type": "Colleague", "weight": 0.7}),
("Nam", "Song2", {"relation_type": "Friend", "weight": 0.8}),
("Lim", "Goo2", {"relation_type": "Family", "weight": 0.6}),
("Oh", "Ahn2", {"relation_type": "Friend", "weight": 0.9}),
("Seo", "Kang3", {"relation_type": "Colleague", "weight": 0.7}),
("Kwon", "Park2", {"relation_type": "Friend", "weight": 0.8}),
("Kang", "Choi2", {"relation_type": "Family", "weight": 0.6}),
("Kito", "Kang3", {"relation_type": "Friend", "weight": 0.9}),
("Kito", "Hwang", {"relation_type": "Colleague", "weight": 0.7}),
("Kito", "Jung", {"relation_type": "Friend", "weight": 0.8}),
("Jung", "Kang2", {"relation_type": "Family", "weight": 0.6}),
("Kito", "Moon2", {"relation_type": "Friend", "weight": 0.9}),
("Yoon", "Han2", {"relation_type": "Colleague", "weight": 0.7}),
("Kito", "Lee2", {"relation_type": "Friend", "weight": 0.8}),
("Kito", "Kwon3", {"relation_type": "Colleague", "weight": 0.6}),
("Ahn", "Yoo2", {"relation_type": "Friend", "weight": 0.9}),
("Hwang", "Shin2", {"relation_type": "Family", "weight": 0.6}),
("Jung", "Kwon2", {"relation_type": "Friend", "weight": 0.8}),
("Nam", "Song2", {"relation_type": "Colleague", "weight": 0.7}),
("Lim", "Goo2", {"relation_type": "Friend", "weight": 0.9}),
]
DG.add_edges_from(new_edges)
3.コミュニティ検出結果の出力
# Community detection and printing node attributes for each community
from networkx.algorithms.community import greedy_modularity_communities
communities = list(greedy_modularity_communities(DG))
community_colors = {}
for i, comm in enumerate(communities):
print(f"Community {i + 1}:")
for node in comm:
print(f"- Node: {node}, Age: {DG.nodes[node]['age']}, Gender: {DG.nodes[node]['gender']}, Country: {DG.nodes[node]['country']}")
community_colors[node] = i
print()
# Printing node attributes for each community
for i, community in enumerate(communities):
print(f"Community {i + 1}:")
for node in community:
print(f"- Node: {node}, Age: {DG.nodes[node]['age']}, Gender: {DG.nodes[node]['gender']}, Country: {DG.nodes[node]['country']}")
print()
Community 1:
- Node: Lee, Age: 30, Gender: Male, Country: Korea
- Node: Kim2, Age: 33, Gender: Female, Country: Korea
- Node: Yoon, Age: 29, Gender: Male, Country: Japan
- Node: Chung, Age: 29, Gender: Female, Country: Korea
- Node: Jeong, Age: 35, Gender: Male, Country: Japan
- Node: Kim, Age: 28, Gender: Male, Country: Japan
- Node: Moon2, Age: 28, Gender: Female, Country: Korea
- Node: Choi, Age: 28, Gender: Female, Country: Korea
- Node: Kwon3, Age: 26, Gender: Male, Country: Korea
- Node: Kito, Age: 32, Gender: Female, Country: Japan
- Node: Han2, Age: 35, Gender: Female, Country: Korea
Community 2:
- Node: Kwon, Age: 28, Gender: Female, Country: Korea
- Node: Seo, Age: 30, Gender: Male, Country: Korea
- Node: Goo2, Age: 30, Gender: Male, Country: Korea
- Node: Park2, Age: 27, Gender: Male, Country: Korea
- Node: Lim, Age: 28, Gender: Male, Country: Korea
- Node: Ahn2, Age: 29, Gender: Female, Country: Korea
- Node: Oh, Age: 34, Gender: Female, Country: Korea
- Node: Kang3, Age: 31, Gender: Male, Country: Korea
Community 3:
- Node: Jung, Age: 32, Gender: Male, Country: Korea
- Node: Kang2, Age: 29, Gender: Male, Country: Korea
- Node: Son2, Age: 30, Gender: Female, Country: Korea
- Node: Hwang, Age: 30, Gender: Female, Country: Korea
- Node: Kwon2, Age: 31, Gender: Male, Country: Korea
Community 4:
- Node: Lee2, Age: 29, Gender: Female, Country: Korea
- Node: Choi2, Age: 28, Gender: Female, Country: Korea
- Node: Nam, Age: 29, Gender: Female, Country: Korea
- Node: Song2, Age: 28, Gender: Male, Country: Korea
- Node: Kang, Age: 33, Gender: Male, Country: Korea
Community 5:
- Node: Ahn, Age: 27, Gender: Female, Country: Japan
- Node: Shin2, Age: 32, Gender: Male, Country: Korea
- Node: Yoo2, Age: 30, Gender: Male, Country: Korea
Community 6:
- Node: Seo2, Age: 25, Gender: Male, Country: Korea
Community 7:
- Node: Baek2, Age: 34, Gender: Female, Country: Korea
Community 8:
- Node: Park3, Age: 33, Gender: Female, Country: Korea
どのようにコミュニティ検出をするのでしょうか?様々な方法がありますが、4つほどまとめてみます。
接続強度
最も一般的なコミュニティ検出方法の1つは、ノード間の接続強度です。ノード間の接続重み、接続密度、またはクラスタリング係数などの指標を使用します。
構造的類似性
ノード間の構造的類似性に基づいてグループを形成する方法もあります。この方法は、同様の接続パターンを持つノードを同じコミュニティにまとめます。
モジュール性
モジュール性は、ネットワーク内のノード間の接続パターンがランダム性に対してどれだけ類似しているかを測定する指標です。コミュニティ検出はモジュール性を最大化しようとする傾向があります。モジュール性は、特定のノードセットがネットワーク内でランダムに接続されているノードよりも多く相互接続されているかどうかを示します。
階層構造
ネットワークは多くの場合、複数のレベルの階層構造を持ちます。コミュニティ検出は、この階層構造を考慮し、複数のレベルのグループを形成する場合があります。これにより、より詳細または上位レベルのグループを識別することができます。
4.ネットワークの可視化
gender_colors = {'Male': 'lightblue', 'Female': 'lightpink'}
country_colors = {'Korea': 'lightcoral', 'Japan': 'lightgreen'}
node_colors_gender = [gender_colors[DG.nodes[node]['gender']] for node in DG.nodes]
node_colors_country = [country_colors[DG.nodesnode]['country']] for node in DG.nodes]
# Graph visualization
plt.figure(figsize=(24, 20))
plt.title("Weighted Bidirectional Network with Advanced Analysis", fontsize=16, fontweight='bold')
pos = nx.spring_layout(DG)
edge_labels = {(edge[0], edge[1]): edge[2]["weight"] for edge in DG.edges(data=True)}
# Community visualization
plt.subplot(231)
plt.title("Community Detection")
nx.draw(
DG, pos, with_labels=True, font_size=12, font_weight='bold',
node_color=node_colors_community, cmap=plt.get_cmap('viridis'), node_size=800, width=list(edge_weights), edge_color='gray'
)
# Node attribute analysis visualization by gender
plt.subplot(232)
plt.title("Node Attribute Analysis by Gender")
nx.draw(
DG, pos, with_labels=True, font_size=12, font_weight='bold',
node_color=node_colors_gender, node_size=800, width=list(edge_weights), edge_color='gray'
)
# Node attribute analysis visualization by country
plt.subplot(233)
plt.title("Node Attribute Analysis by Country")
nx.draw(
DG, pos, with_labels=True, font_size=12, font_weight='bold',
node_color=node_colors_country, node_size=800, width=list(edge_weights), edge_color='gray'
)
plt.show()

コミュニティ検出
コミュニティ検出はネットワーク分析の基本的なタスクであり、ネットワーク内で他のグループのノードよりも密に接続されたノードのグループを識別することを目的とします。ここには貪欲なモジュラリティコミュニティアルゴリズムを使用してコミュニティ検出が行われています。
コードは、有向グラフ内の異なるコミュニティを識別して表示します。各コミュニティは、お互いとの強い接続を示す可能性があり、共有の特性や所属を示しています。コードの可視化部分では、同じコミュニティ内のノードには同じ色が割り当てられ、これらのグループを区別するのが簡単になります。この分析は、ネットワーク内の基本的な構造と関係を明らかにするのに役立ち、ソーシャルネットワーク、協力パターンなどを理解するのに有用です。

性別に基づくノード属性分析
ノード属性の分析は、ネットワークノードの特性を調査する作業を指します。このコードでは、"gender"属性に基づいたノード属性分析が行われています。ノードは "Male" と "Female" の2つのグループに分類されます。コードはノードの性別属性に基づいて異なる色を割り当てることで、この分析を可視化します。
この可視化により、ネットワーク内で性別がネットワークの構造と接続に与える影響を理解するための重要なステップです。これは、ソーシャルネットワーク、組織構造など、さまざまな現実世界のシナリオにおける性別のネットワークへの影響を理解するために重要です。

国に基づくノード属性分析
性別に基づく分析に加えて、コードは "country" 属性に基づいたノード属性分析も実行します。ノードは "Korea" や "Japan" の異なる国に分類されます。性別ベースの分析と同様に、コードは可視化のために国の属性に基づいてノードに特定の色を割り当てます。
この分析は、ネットワークが異なる国や地域でどのように構成されているかを示します。国際的なネットワークを扱う場合、ノードの地理的分布を理解することは非常に重要で、ネットワーク内の地理的な分布の理解は大変重要です。
5.おわりに
これまでノードとエッジの設定を通じて実際のデータをグラフで表現し、コミュニティ検出を通じてネットワーク内のグループを把握し、ノードの属性分析を通じて性別や国による特性を視覚的に確認しました。
早く歴史的な資料からネットワーク分析を通じてデータの隠れた興味深いパターンを発見したいですね。 それが可能になるその日まで、もっともっと頑張ります。
(私のGithubです。ネットワーク分析のコードをアップします!)
デジタル歴史家
ソンさん