🎨 直感 📐 力学モデル 📐 中心性 🔬 数式を言葉で読み解く 🧮 手計算 🐍 networkx 作図 🐍 中心性 🐍 force-directed 🐍 PageRank ⚠️ 落とし穴 🌐 関連手法 🔗 関連用語 📚 教材
あなたが今見ているもの: 用語集 / 可視化カテゴリ / ネットワーク可視化 (Network Visualization)
前提: グラフ理論 / 隣接行列 / 中心性(あれば)
応用: SNS 分析 / 推薦システム / 知識グラフ
「人と人の友達関係」を紙に書くとき、 友達同士を線で結ぶ。 これがネットワーク可視化の全て。 ただし「どの位置に描くか」と「どの線を強調するか」で印象がガラリと変わるのが難しさ。
レイアウトには 4 大流派がある。 力学モデル (force-directed) は「エッジをばね、 ノードを反発電荷」と見なし、 物理シミュレーションでバランスする位置を探す。 階層 (hierarchical) は親子関係を縦方向に並べる。 円環 (circular) は全ノードを円周上に置く。 スペクトル (spectral) はラプラシアン行列の固有ベクトルで座標を決める。
| レイアウト | 向くケース | 弱点 |
|---|---|---|
| force-directed (Fruchterman-Reingold, ForceAtlas2) | 汎用、 クラスタが自然に分離 | 毎回位置が変わる、 1000 ノード超で重い |
| hierarchical (Sugiyama) | DAG、 系統樹、 組織図 | サイクルがあると破綻 |
| circular | 全関係を「弦」で表現したい | ノード数 50 超で混雑 |
| spectral | クラスタ構造の数学的可視化 | 直感解釈しにくい |
グラフ $G = (V, E)$ は頂点集合 $V$ と辺集合 $E \subseteq V \times V$ で定義される。 隣接行列 $A \in \{0,1\}^{n \times n}$ は $A_{ij}=1$ iff $(i,j) \in E$。
Fruchterman-Reingold 力学モデル: ノード間の力を反発と引力の和で計算する。
$$F_{\text{rep}}(i,j) = -\frac{k^2}{\|x_i - x_j\|}, \quad F_{\text{att}}(i,j) = \frac{\|x_i - x_j\|^2}{k} \cdot \mathbb{1}_{(i,j) \in E}$$
ここで $k = \sqrt{\text{area}/|V|}$ は最適距離。 反復で位置 $x_i$ を更新し、 全体エネルギーを最小化する。
中心性 4 種:
$$C_D(v) = \frac{\deg(v)}{n-1}, \quad C_C(v) = \frac{n-1}{\sum_{u \neq v} d(v,u)}$$ $$C_B(v) = \sum_{s \neq v \neq t} \frac{\sigma_{st}(v)}{\sigma_{st}}, \quad \text{PR}(v) = \frac{1-d}{n} + d \sum_{u \in N^{-}(v)} \frac{\text{PR}(u)}{|N^{+}(u)|}$$
| 記号 | 意味 | 具体例(47 都道府県ネットワーク) |
|---|---|---|
| $V$ | 頂点集合 (ノード) | $|V|=47$ (47 都道府県) |
| $E$ | 辺集合 (エッジ) | 5 指標距離で「近い 5 県」を結ぶ → $|E|=153$ |
| $A_{ij}$ | 隣接行列要素 | 東京-神奈川: 1、 北海道-沖縄: 0 |
| $\deg(v)$ | ノードの次数 | 長野県=9 (最多、 ハブ)、 北海道=5 (最少) |
| $C_B(v)$ | 媒介中心性 | 静岡県 0.498 → 大都市圏県群と中規模県群を繋ぐ「橋」 |
| $\text{PR}(v)$ | PageRank | 長野県 0.053 (1位)、 関東圏 4 県が上位 |
| $k$ | 最適距離 (FR モデル) | 描画領域 100x100, ノード 47 → $k=\sqrt{10000/47}\approx14.6$ |
| $d$ | PageRank ダンピング係数 | 通常 0.85 (15% は全ノードへランダムジャンプ) |
SSDSE-B-2026 の 47 都道府県 × 5 指標(総人口 A1101 / 死亡数 A4200 / 出生数 A4101 / 65 歳以上人口 A1303 / 婚姻件数 A9101)を標準化し、 各県の「近い 5 県」をエッジで結んだネットワークの実値です。
💬 読み解き: 「次数」と「PageRank」は似た上位を出すが、 「媒介」は別。 静岡県の媒介 0.498 はネットワーク全体の媒介中心性のほぼ半分を独占しており、 「ここを切ると大都市圏 9 県のグループと中規模県グループが断絶する」要衝(実際に静岡県を除去すると最大成分が 2 つに割れる)。
🎯 このコードでやること: SSDSE-B-2026 の 47 都道府県を 5 指標で類似度ネットワーク化し、 force-directed レイアウトで描く。
📥 入力データ (SSDSE-B-2026.csv 抜粋):
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 | import pandas as pd import numpy as np import networkx as nx import matplotlib.pyplot as plt from scipy.spatial.distance import squareform, pdist df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df.iloc[:,0]==2023].reset_index(drop=True) # 5 指標を標準化 feats = ['A1101','A4200','A4101','A1303','A9101'] X = df[feats].astype(float).values Z = (X - X.mean(0)) / X.std(0) D = squareform(pdist(Z, 'euclidean')) # 各県の「近い 5 県」をエッジ化 G = nx.Graph() for i in range(47): G.add_node(i, name=df['Prefecture'][i]) for i in range(47): for j in np.argsort(D[i])[1:6]: G.add_edge(int(i), int(j)) print(f'ノード数 {G.number_of_nodes()} エッジ数 {G.number_of_edges()}') pos = nx.spring_layout(G, seed=42, k=0.5) plt.figure(figsize=(10,8)) nx.draw_networkx_nodes(G, pos, node_size=300, node_color='#4DB6AC') nx.draw_networkx_edges(G, pos, alpha=0.4) nx.draw_networkx_labels(G, pos, labels={i:df['Prefecture'][i] for i in range(47)}, font_size=8, font_family='Hiragino Sans') plt.axis('off'); plt.tight_layout(); plt.savefig('prefnet.png', dpi=120) |
📤 実行結果:
💬 読み解き: spring_layout はばねモデル。 関東圏 (東京・神奈川・千葉・埼玉) が中央に固まり、 北海道・沖縄が端に弾き飛ばされる。 47 ノードは「ちょうど読める」サイズ。
🎯 このコードでやること: ① で作ったネットワーク G に対し、 4 種類の中心性を計算して比較する。
📥 入力データ: ① で生成した G (47 ノード、 153 エッジの無向グラフ)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import networkx as nx # (G, df は ① の続き) # 連結成分が複数ある時は最大成分を抽出 largest = max(nx.connected_components(G), key=len) G2 = G.subgraph(largest).copy() deg = nx.degree_centrality(G2) bet = nx.betweenness_centrality(G2) clo = nx.closeness_centrality(G2) pr = nx.pagerank(G2, alpha=0.85) def top3(c, label): items = sorted(c.items(), key=lambda x:-x[1])[:3] print(f'\n[{label}] top3:') for i, v in items: print(f' {df["Prefecture"][i]:6s} {v:.4f}') for c, lbl in [(deg,'degree'), (bet,'betweenness'), (clo,'closeness'), (pr,'PageRank')]: top3(c, lbl) print(f'\n直径={nx.diameter(G2)}, 平均最短経路={nx.average_shortest_path_length(G2):.3f}') |
📤 実行結果:
💬 読み解き: 4 種の中心性で「1 位」が違うことに注意。 静岡県は媒介と近接で 1 位だが、 PageRank では 5 位以下。 「目的に応じた中心性の使い分け」がネットワーク可視化の核心。
🎯 このコードでやること: 同じネットワーク G を 4 種のレイアウト (spring / circular / kamada_kawai / spectral) で並べて描き、 印象差を確認する。
📥 入力データ: ① の G。 レイアウト関数の入出力は {node_id: (x, y)} の辞書。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import networkx as nx, matplotlib.pyplot as plt layouts = { 'spring (force-directed)': nx.spring_layout(G, seed=42), 'circular': nx.circular_layout(G), 'kamada_kawai': nx.kamada_kawai_layout(G), 'spectral': nx.spectral_layout(G), } fig, axes = plt.subplots(2, 2, figsize=(14, 12)) for ax, (name, pos) in zip(axes.flat, layouts.items()): nx.draw_networkx_nodes(G, pos, node_size=120, node_color='#4DB6AC', ax=ax) nx.draw_networkx_edges(G, pos, alpha=0.3, ax=ax) ax.set_title(name, fontsize=14); ax.axis('off') plt.tight_layout(); plt.savefig('layouts.png', dpi=110) print('保存: layouts.png') |
📤 実行結果:
💬 読み解き: 「同じグラフでも見え方は無数」。 デフォルトの spring で違和感があったら kamada_kawai を試す。 報告書では 1 種類に決め打ちし、 「なぜそのレイアウトか」を 1 文で必ず添える。
🎯 このコードでやること: PageRank をノードサイズに反映させて「重要度の見える化」をする。
📥 入力データ: ① の G と df['Prefecture']。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import networkx as nx, matplotlib.pyplot as plt pr = nx.pagerank(G, alpha=0.85, max_iter=200) sizes = [pr[i] * 12000 for i in G.nodes()] colors = [pr[i] for i in G.nodes()] pos = nx.spring_layout(G, seed=42, k=0.6) plt.figure(figsize=(11, 9)) nodes = nx.draw_networkx_nodes(G, pos, node_size=sizes, node_color=colors, cmap=plt.cm.viridis, alpha=0.9) nx.draw_networkx_edges(G, pos, alpha=0.25) labels = {i: f'{df["Prefecture"][i]}\n{pr[i]:.3f}' for i in G.nodes() if pr[i] > 0.030} nx.draw_networkx_labels(G, pos, labels=labels, font_size=8, font_family='Hiragino Sans') plt.colorbar(nodes, label='PageRank') plt.axis('off'); plt.tight_layout(); plt.savefig('pagerank.png', dpi=120) # 上位 10 件を表で表示 ranking = sorted(pr.items(), key=lambda x:-x[1])[:10] for rank, (i, v) in enumerate(ranking, 1): print(f'{rank:2d}. {df["Prefecture"][i]:6s} PR={v:.4f}') |
📤 実行結果:
💬 読み解き: PageRank は「重要な県とつながっている県も重要」とする再帰的指標。 長野県は北陸・中部・関東の橋渡しになるため上位。 東京は意外に低い (人口最大なのに) — 「似た県が少ない」ためエッジが少なく PageRank が下がる。
seed= を固定しないと毎回違う図が出る。 報告書では必ず seed を記録する。並列: 散布図 / ヒートマップ / ツリーマップ / 弦図 / サンキー図
発展: コミュニティ検出 / グラフ埋め込み / GNN / PageRank / 推薦システム / SNS 分析
| 中心性 | 定義 | 向く解釈 | 弱点 |
|---|---|---|---|
| 次数 (degree) | $\deg(v)/(n-1)$ | 「友達が多い」 | 局所的、 ネットワーク全体は見ない |
| 近接 (closeness) | $\frac{n-1}{\sum d(v,u)}$ | 「みんなに近い」 | 非連結だと無限大、 サイズ依存 |
| 媒介 (betweenness) | $\sum \sigma_{st}(v)/\sigma_{st}$ | 「橋・ボトルネック」 | 計算コスト $O(nm)$ で大規模に弱い |
| 固有ベクトル (eigenvector) | $Av = \lambda v$ の主固有ベクトル | 「重要な人とつながる人」 | 無向、 連結グラフでないと不安定 |
| PageRank | ダンピング付き固有ベクトル | 「ハイパーリンク的重要度」 | ダンピング係数の選択に依存 |
| ツール | 向く用途 | 最大ノード目安 | 学習コスト |
|---|---|---|---|
| networkx | Python パイプライン、 教育 | ~5,000 | 低 (matplotlib 連携) |
| igraph (Python/R) | 大規模解析、 C 実装で高速 | ~100,000 | 中 |
| Gephi | GUI、 ForceAtlas2、 論文図用 | ~50,000 | 低 (Java GUI) |
| Cytoscape | 生物ネットワーク (PPI, パスウェイ) | ~30,000 | 中 |
| D3.js | Web 上のインタラクティブ可視化 | ~3,000 | 高 (JavaScript SVG) |
| deck.gl / Sigma.js | WebGL ベース、 超大規模 | ~1,000,000 | 高 |
先ほどの 47 都道府県ネットワーク (153 エッジ) に Louvain 法を適用すると、 5 個のコミュニティに自然分割される (random_state=42 で Q = 0.679)。 各コミュニティが「人口・死亡・出生・高齢人口・婚姻の規模パターンが似た県群」を表現する。
🎯 このコードでやること: Louvain 法で modularity 最大化のコミュニティを検出し、 色分けして描画する。
📥 入力データ: ① の G。
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 | import community as community_louvain # pip install python-louvain import networkx as nx, matplotlib.pyplot as plt partition = community_louvain.best_partition(G, random_state=42) modularity = community_louvain.modularity(partition, G) print(f'コミュニティ数 = {len(set(partition.values()))}, modularity Q = {modularity:.4f}') # 各コミュニティのメンバーを表示 from collections import defaultdict groups = defaultdict(list) for node, comm in partition.items(): groups[comm].append(df['Prefecture'][node]) for c, members in sorted(groups.items()): print(f' C{c}: {members}') # 描画 pos = nx.spring_layout(G, seed=42) plt.figure(figsize=(11, 9)) cmap = plt.cm.tab20 node_colors = [cmap(partition[i]) for i in G.nodes()] nx.draw_networkx_nodes(G, pos, node_size=350, node_color=node_colors, alpha=0.9) nx.draw_networkx_edges(G, pos, alpha=0.3) labels = {i: df['Prefecture'][i] for i in G.nodes()} nx.draw_networkx_labels(G, pos, labels=labels, font_size=8, font_family='Hiragino Sans') plt.title(f'Louvain communities (Q={modularity:.3f})') plt.axis('off'); plt.tight_layout(); plt.savefig('communities.png', dpi=120) |
📤 実行結果:
💬 読み解き: modularity Q=0.68 は強いコミュニティ構造を示す (0.3 以上で意味あり、 0.6 以上で明瞭)。 5 コミュニティは地理的区分ではなく人口規模の階層にほぼ一致する — C0 は人口 500 万超の大都市圏 9 道府県、 C2 と C4 は中規模県、 C1 と C3 は小規模県。 5 指標 (人口・出生・死亡・高齢人口・婚姻) がいずれも人口規模に比例する「実数系」のため、 規模の近い県同士が結ばれるのは当然の帰結。 地理的なまとまりを見たいなら高齢化率・合計特殊出生率のような「比率系」指標で構築し直す必要がある。
weight='distance' 引数で挙動切替。隣接行列 $A$ の固有値分解は、 ネットワーク全体の性質を一気に語る強力な道具。 SSDSE-B-2026 ネットワーク (47×47 隣接行列) で具体的に確認できる。
| 量 | 意味 | SSDSE-B ネットワーク値 |
|---|---|---|
| $\lambda_1$ (最大固有値) | スペクトル半径 = ハブの強さ | 約 7.0 (平均次数 6.5 とほぼ一致) |
| $v_1$ (主固有ベクトル) | 固有ベクトル中心性 | 長野・宮城など高次数県が大値 |
| $\lambda_2$ (Fiedler 値、 ラプラシアン) | 代数的連結性 | 2 連結成分なので $\lambda_2 = 0$ (要 union) |
| $v_2$ (Fiedler ベクトル) | スペクトラルクラスタリングの分割軸 | 符号で「人口規模 大/小」の 2 クラスタに分かれる |
| トレース $\text{tr}(A^k)$ | 長さ k の閉路数 | $\text{tr}(A^3)/6$ = 三角形数 |
nx.spring_layout(G, iterations=10) で反復数を絞る、 もしくは ForceAtlas2 (Gephi または fa2 Python パッケージ) を使う。 5000 ノード 5 秒程度で収束する。plt.loglog() でべき乗則 ($P(k) \propto k^{-\gamma}$) を確認する。 SNS は通常 $\gamma \approx 2{-}3$。seed=42 を指定、 ファイル名に _seed42 等を付記、 .gephi/.graphml ファイルも添付する。FR レイアウトの内部動作を 1 反復ずつ追うと、 なぜ「自然な」配置が得られるかが分かる。
| ステップ | 操作 | 物理イメージ |
|---|---|---|
| 1 | 全ノードをランダム配置 $x_i^{(0)} \in [0,W]^2$ | 空間にバラバラに撒く |
| 2 | 最適距離 $k = C \sqrt{\text{area}/|V|}$ を決める | 「これくらい離れていたい」の目安 |
| 3 | 全ペアで反発力 $F_r = -k^2 / \|d\|$ を計算 | 電荷同士の反発 |
| 4 | エッジ両端で引力 $F_a = \|d\|^2 / k$ を計算 | ばねの引っ張り |
| 5 | 合力で位置を更新 $x_i \leftarrow x_i + (F_r + F_a) \cdot t$ | temperature t で動きを制御 |
| 6 | temperature を $t \leftarrow 0.95 t$ で減衰 (simulated annealing) | 徐々に動きを小さく |
| 7 | 3-6 を 50-100 反復 | エネルギー最小に収束 |
| 分野 | ノード | エッジ | 主な解析 |
|---|---|---|---|
| SNS | ユーザー | フォロー / 友達 | インフルエンサー検出 (PageRank)、 コミュニティ検出 |
| 交通 | 駅 / 空港 | 路線 | 最短経路 (Dijkstra)、 ハブ空港の特定 |
| 生物 (PPI) | タンパク質 | 相互作用 | 疾患関連タンパク質予測、 モジュール解析 |
| 電力網 | 変電所 | 送電線 | 故障伝搬解析、 ロバストネス評価 |
| 論文引用 | 論文 | 引用関係 (有向) | 研究分野特定、 古典論文の同定 |
| 金融 | 銀行 | 貸借関係 | システミック・リスク (too big to fail) 検出 |
| 推薦システム | ユーザー / 商品 | 購入履歴 (二部グラフ) | 協調フィルタリング、 ランダムウォーク |
| 疫学 | 個人 | 接触履歴 | 感染シミュレーション (SIR モデル)、 隔離戦略 |
| 操作 | 計算量 | 1000 ノード時の目安 |
|---|---|---|
| 次数中心性 | $O(n + m)$ | 10 ms 未満 |
| 媒介中心性 (Brandes) | $O(nm)$ for unweighted | 1-5 秒 |
| 近接中心性 | $O(n(n+m))$ | 数秒 |
| PageRank (冪乗法 50 反復) | $O(50 m)$ | 100 ms |
| Louvain コミュニティ検出 | $O(n \log n)$ | 500 ms |
| spring_layout 50 反復 | $O(50 n^2)$ | 5-30 秒 |
| kamada_kawai_layout | $O(n^2)$ | 10-60 秒 |
| ForceAtlas2 (fa2) | $O(n \log n)$ (Barnes-Hut) | 1-3 秒 |
これまでは「近い 5 県を 0/1 で結ぶ」二値エッジだった。 ここでは距離 $d_{ij}$ から類似度 $s_{ij} = \exp(-d_{ij}^2 / 2\sigma^2)$ をエッジ重みに使う Gaussian kernel グラフを構築する。
🎯 このコードでやること: 47x47 の距離行列から Gaussian kernel で類似度行列を作り、 ヒートマップとして可視化する。
📥 入力データ: ① で計算した D (47x47 ユークリッド距離行列)。
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 | import numpy as np, matplotlib.pyplot as plt # D は ① で作った距離行列 (47x47) sigma = D[D>0].mean() # 平均距離をスケールに S = np.exp(-D**2 / (2 * sigma**2)) np.fill_diagonal(S, 0) # 自己ループ削除 print(f'類似度行列 形状: {S.shape}, 最大値: {S.max():.3f}, 平均: {S[S>0].mean():.3f}') # 県名でラベル付け prefs = df['Prefecture'].values order = np.argsort(df['A1101'].values)[::-1] # 人口で降順並べ替え S2 = S[order][:, order] prefs2 = [prefs[i] for i in order] # ヒートマップ plt.figure(figsize=(10, 9)) plt.imshow(S2, cmap='YlOrRd', aspect='auto') plt.xticks(range(47), prefs2, rotation=90, fontsize=7, fontfamily='Hiragino Sans') plt.yticks(range(47), prefs2, fontsize=7, fontfamily='Hiragino Sans') plt.colorbar(label='類似度 exp(-d²/2σ²)') plt.title('47 都道府県類似度ヒートマップ (人口降順)') plt.tight_layout(); plt.savefig('simheatmap.png', dpi=120) # 類似度上位 5 ペアを表示 import itertools pairs = [] for i, j in itertools.combinations(range(47), 2): pairs.append((S[i,j], prefs[i], prefs[j])) pairs.sort(reverse=True) print('\n類似度 top5 ペア:') for s, a, b in pairs[:5]: print(f' {a} - {b}: {s:.4f}') |
📤 実行結果:
💬 読み解き: 類似度が高いペアは「人口・出生・高齢化の規模が似た小県」同士。 徳島-高知のような隣県だけでなく、 山梨-佐賀のように地理的に遠いペアも並ぶ — 結ばれているのは距離ではなく規模の近さだ。 ヒートマップの右下 (小県群) が高類似度ブロックを形成し、 左上 (大都市) は互いに似ていない (東京は唯一無二)。 これが先ほどの「東京の PageRank が低い」現象の正体。 なお σ を平均距離に取ると近いペアの類似度が 1 近くに飽和するため、 重みの分解能が欲しいときは σ を小さく (例: 距離の中央値の半分) 調整する。
| 表現したい属性 | 推奨エンコーディング | 具体例 |
|---|---|---|
| ノードの重要度 (連続値) | サイズ + 色 (順序尺度) | PageRank → 半径と viridis 色 |
| ノードのカテゴリ | 色 (名義尺度、 tab10 等) | コミュニティ ID |
| ノードの 2 次元属性 | 形状 + 色 | 性別 (○/□) × 年代 (色) |
| エッジの強さ | 線の太さ + 透明度 | 通話回数 → 太さ、 alpha |
| エッジの方向 | 矢印 + 曲線 | フォロー関係 |
| エッジのカテゴリ | 色 (3 種類まで) | 「友達」「家族」「同僚」 |
| 時系列 | アニメーション or small multiples | 月ごとの SNS スナップショット |
🎯 このコードでやること: holoviews + datashader でエッジを束ね、 47 都道府県の関係を「川の流れ」のように描く。
📥 入力データ: ① の G を holoviews 形式のノード/エッジ DataFrame に変換。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import pandas as pd, networkx as nx import holoviews as hv from holoviews.operation.datashader import bundle_graph hv.extension('matplotlib') # G, df, pos は ① の続き nodes_df = pd.DataFrame({ 'id': list(G.nodes()), 'x': [pos[i][0] for i in G.nodes()], 'y': [pos[i][1] for i in G.nodes()], 'name': [df['Prefecture'][i] for i in G.nodes()], }) edges_df = pd.DataFrame([(u, v) for u, v in G.edges()], columns=['source','target']) graph = hv.Graph((edges_df, hv.Nodes(nodes_df, kdims=['x','y','id']))) bundled = bundle_graph(graph) hv.save(bundled.opts(node_size=10, edge_alpha=0.5), 'bundled.png', fmt='png', size=400) print('保存: bundled.png (エッジが滑らかな曲線で束ねられた図)') |
📤 実行結果:
💬 読み解き: 通常の描画では「直線エッジの重なり」で読めなくなる 100+ エッジも、 edge bundling で 10-15 本の太い流線に集約できる。 飛行機の航空路図、 SNS の情報伝播ヒート図に有効。
🎯 このコードでやること: 1 枚の図に (ノード色=コミュニティ ID、 ノードサイズ=PageRank、 エッジ太さ=類似度) の 3 次元情報を載せる。
📥 入力データ: ① の G、 ② の pr、 ⑤ の partition、 ⑥ の S。
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 | # ── この抜粋で使うグラフを用意します ── # 47 都道府県を「指標が似ている県どうしを結んだ網」として作る。 import numpy as np, pandas as pd import matplotlib.pyplot as plt, networkx as nx from sklearn.preprocessing import StandardScaler from sklearn.metrics.pairwise import cosine_similarity df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] # 2023 年の 47 都道府県 df = df.reset_index(drop=True) _X = StandardScaler().fit_transform(df[['A1101', 'A1303', 'A4101', 'L3221']].astype(float)) S = cosine_similarity(_X) np.fill_diagonal(S, 0) G = nx.Graph() G.add_nodes_from(range(len(df))) for _i in range(len(df)): for _j in range(_i + 1, len(df)): if S[_i, _j] > 0.8: G.add_edge(_i, _j, weight=float(S[_i, _j])) pr = nx.pagerank(G, alpha=0.85, max_iter=200) # community_louvain(python-louvain)はブラウザに無いので networkx の実装を使う _comms = nx.community.greedy_modularity_communities(G) partition = {n: c for c, grp in enumerate(_comms) for n in grp} pos = nx.spring_layout(G, seed=42, k=0.6) plt.figure(figsize=(13, 10)) # ノード: 色=コミュニティ、 サイズ=PageRank cmap = plt.cm.tab10 node_colors = [cmap(partition[i] % 10) for i in G.nodes()] node_sizes = [pr.get(i, 0.01) * 14000 for i in G.nodes()] # 第 2 成分は最小サイズ # エッジ: 太さ=類似度 S edge_widths = [S[u, v] * 3 for u, v in G.edges()] nx.draw_networkx_edges(G, pos, width=edge_widths, alpha=0.35) nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color=node_colors, alpha=0.9) labels = {i: df['Prefecture'][i] for i in G.nodes() if pr.get(i, 0) > 0.025} # pr は最大成分のみ nx.draw_networkx_labels(G, pos, labels=labels, font_size=8, font_family='Hiragino Sans') # 凡例 for c in sorted(set(partition.values())): plt.scatter([], [], c=[cmap(c % 10)], label=f'コミュニティ {c}', s=100) plt.legend(loc='upper left', fontsize=9) plt.title('47 都道府県ネットワーク (色=コミュニティ、 サイズ=PageRank、 太さ=類似度)') plt.axis('off'); plt.tight_layout(); plt.savefig('hybrid.png', dpi=130) print('保存: hybrid.png (3 次元情報を 1 枚に圧縮)') |
📤 実行結果:
💬 読み解き: 3 つの情報を 1 図に重ねるのは Tufte の「データ密度」哲学。 色とサイズで主要情報を、 エッジ太さで補助情報を伝える。 ただし4 次元以上は読めないので、 形状/輪郭/透明度などをさらに追加するのは NG。
SSDSE-B-2026 のネットワーク化を学んだら、 次は自分の手元データで試したい。 以下のアイデアが入門に最適。
| 症状 | 原因 | 解決 |
|---|---|---|
| 日本語ラベルが □□□ | matplotlib のデフォルトフォントが日本語非対応 | font_family='Hiragino Sans' (Mac) / 'Yu Gothic' (Win) を指定 |
| spring_layout が収束しない | エッジが少なすぎる、 連結成分が孤立 | iterations=500 に増やす、 もしくは kamada_kawai に切替 |
| PageRank が NaN | 孤立ノードあり、 ダンピング 1.0 を指定 | alpha=0.85 を明示、 孤立ノードを除外 |
| betweenness 計算が遅い | $O(nm)$ が大規模で重い | k=100 でサンプル近似: nx.betweenness_centrality(G, k=100) |
| ノードが重なって読めない | spring_layout の k パラメータが小さい | k=1.5 など大きく、 figsize も拡大 |
| 隣接行列が巨大すぎる | 密行列で持っている (n=10000 で 800MB) | scipy.sparse.csr_matrix に変換 (エッジ密度 5% 未満なら必須) |
| エッジに方向が表示されない | nx.Graph (無向) で構築 | nx.DiGraph に変更、 描画時 arrows=True |
| draw_networkx_labels で文字が小さい | font_size デフォルト 12 が PDF で小さい | font_size=14 以上、 図全体を拡大 |
| 手法 | 向くケース | 向かないケース |
|---|---|---|
| ネットワーク図 | 関係性、 経路、 中心性が主役 | 数値の比較 (棒グラフが優位) |
| ヒートマップ | 隣接行列の全体パターン | 経路の発見 (ネットワーク図が優位) |
| ツリーマップ | 階層 + 数量 | サイクル、 多対多 |
| サンキー図 | フロー、 多段階遷移 (有向、 DAG) | サイクルがあるネットワーク |
| 弦図 (chord diagram) | 少数ノード (20 以下) の相互関係 | 100+ ノード (混み合う) |
| 散布図 + 線 | 2 連続変量 + ペア関係 | 3 次以上の関係性 |
🎯 このコードでやること: ① で作った G を Gephi で開けるファイル形式 (GraphML, GEXF) で保存する。
📥 入力データ: ① の G とノード属性 (Prefecture, 人口)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import networkx as nx # ノード属性を付与 for i in G.nodes(): G.nodes[i]['name'] = df['Prefecture'][i] G.nodes[i]['population'] = int(df['A1101'][i]) G.nodes[i]['birth_rate'] = float(df['A4103'][i]) G.nodes[i]['community'] = int(partition[i]) G.nodes[i]['pagerank'] = float(pr[i]) # GraphML (XML) で保存 — Gephi, yEd, Cytoscape 対応 nx.write_graphml(G, 'prefnet.graphml') # GEXF (Gephi 公式) で保存 nx.write_gexf(G, 'prefnet.gexf') import os print(f'GraphML: {os.path.getsize("prefnet.graphml"):,} bytes') print(f'GEXF: {os.path.getsize("prefnet.gexf"):,} bytes') print('Gephi で開く → File > Open > prefnet.gexf → ForceAtlas2 を実行') |
📤 実行結果:
💬 読み解き: networkx で前処理 → Gephi で美しいレイアウト → PNG/SVG/PDF エクスポートが論文用作図の王道パターン。 ノード属性を全部仕込めば、 Gephi 上で色・サイズ・ラベルを自由に変えられる。
以上で ネットワーク可視化 の用語ページ拡充版は終了。 SSDSE-B-2026 の 47 都道府県を素材に、 基本概念から実装・コミュニティ検出・edge bundling まで一通り扱った。 次は 中心性 や コミュニティ検出 の個別ページへ進み、 さらに GNN でグラフ学習の世界へ。
| 関数 | 用途 |
|---|---|
nx.Graph() | 無向グラフ作成 |
nx.DiGraph() | 有向グラフ作成 |
G.add_node(i, name='x') | ノード追加 (属性付き) |
G.add_edge(u, v, weight=0.5) | エッジ追加 |
G.add_edges_from([(0,1),(1,2)]) | エッジ一括追加 |
nx.degree(G) | 全ノードの次数 |
nx.shortest_path(G, src, tgt) | 最短経路 |
nx.connected_components(G) | 連結成分 |
nx.diameter(G) | 直径 (要連結) |
nx.clustering(G) | クラスタ係数 |
nx.degree_centrality(G) | 次数中心性 |
nx.betweenness_centrality(G) | 媒介中心性 |
nx.closeness_centrality(G) | 近接中心性 |
nx.pagerank(G, alpha=0.85) | PageRank |
nx.spring_layout(G, seed=42) | 力学レイアウト |
nx.draw(G, pos, with_labels=True) | 簡易描画 |
nx.write_gexf(G, 'g.gexf') | Gephi 形式保存 |
nx.adjacency_matrix(G) | 隣接行列 (scipy sparse) |
SSDSE-B-2026 の A1101(総人口)と G7101(延べ宿泊者数)をノード属性に乗せると、 「サイズ=総人口、 色=延べ宿泊者数」の二重符号化ができる。 単なる地図ではなく、 「観光・宿泊需要が集中するハブはどこか」を一目で示せるのがネットワーク可視化固有の価値。
🎯 このコードでやること:SSDSE-B-2026 の 47 都道府県を networkx.Graph のノードに登録し、 総人口と延べ宿泊者数を属性として持たせる。 隣接県を edge として追加し、 ノードサイズ=総人口、 色=延べ宿泊者数で matplotlib 描画する。
📥 入力データ(SSDSE-B-2026 抜粋):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import networkx as nx import matplotlib.pyplot as plt import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101','G7101']].reset_index(drop=True) G = nx.Graph() for _, r in df.iterrows(): G.add_node(r['Prefecture'], pop=r['A1101'], night=r['G7101']) # 隣接県を追加(一部) edges = [('北海道','青森県'),('東京都','神奈川県'),('東京都','埼玉県'), ('東京都','千葉県'),('大阪府','京都府'),('大阪府','兵庫県'), ('大阪府','奈良県'),('福岡県','佐賀県'),('沖縄県','鹿児島県')] G.add_edges_from(edges) pos = nx.spring_layout(G, seed=42, k=0.6) sizes = [G.nodes[n]['pop']/5000 for n in G.nodes()] colors = [G.nodes[n]['night']/1e6 for n in G.nodes()] nx.draw(G, pos, node_size=sizes, node_color=colors, cmap='YlOrRd', with_labels=True, font_size=8) plt.savefig('pref_network.png', dpi=150) |
📤 実行例:
💬 結果の読み方:東京都ノードが最大サイズ(人口)+最濃色(延べ宿泊者数)で「人口・宿泊需要ハブ」が一目瞭然。 spring layout は引力(edge)と斥力(全ノード対)の均衡で配置を決めるため、 「東京・神奈川・埼玉・千葉」が中央に集約され、 沖縄・北海道は外側に分離される。 これは地理的な位置とは異なる「関係性の地図」になっている点が重要。
エッジに weight 属性を持たせると、 「線の太さ=移動量・取引量」のような流量ネットワークが描ける。 47 都道府県の通勤流入量・物流量・電力融通量など、 ペアごとの量的関係はすべてエッジ重みで表現可能。 nx.draw_networkx_edges(G, pos, width=[d['weight']*0.01 for _,_,d in G.edges(data=True)]) のようにリスト指定する。
| レイアウト | 使う場面 | 計算量 |
|---|---|---|
spring_layout | 中規模、 一般用 | O(n²) 反復 |
kamada_kawai_layout | 最短経路ベース、 美しい | O(n³) |
circular_layout | 対称性を見せたい時 | O(n) |
spectral_layout | グラフラプラシアン固有値 | O(n³) |
pyvis interactive | Web 上で zoom/drag | 物理エンジン JS |
「どのレイアウトを選ぶか」自体が分析の主張になる。 spring は「クラスタを見せたい」、 circular は「全ノード対等性」、 spectral は「コミュニティ構造の連続性」を強調する。 47 都道府県で電力融通網を描く場合は kamada_kawai が美しく、 SNS 友達網のように規模が大きい場合は spring + ForceAtlas2 が現実的。
🍰 まずはやさしく
つながりを図にする方法です。
データの関係をパッと見るために使います。
SNSの友達関係のような図です。
この手法のポイントを短くまとめます。
ネットワーク・グラフ・階層構造の可視化
network visualization を 30 秒で把握する重要ポイント:
🍰 まずはやさしく
データのつながりを扱う学習です。
分析のスキルを身につけるために使います。
都道府県のデータを使って練習します。
定義から注意点まで順番に学びます。
本ページでは「network visualization」を扱う。 統計データ分析コンペティション (2026) の教材で、 SSDSE-B-2026 (47 都道府県 × 複数年 × 100 超列) の実データを使った再現可能な学習を目指す。
「network visualization」は統計・データサイエンスの体系における重要概念のひとつ。 本ページは「定義・直感・数式・実装・落とし穴・関連手法」の 6 視点で構成され、 各視点は独立して読めるが順序通り読むと体系的な理解が得られる。
🍰 まずはやさしく
点と線で結ぶ地図のようなものです。
誰が中心にいるかを見つけるために使います。
部活の人間関係を線で結ぶイメージです。
図を描くときの4つのルールを学びます。
ネットワーク可視化は「点 (ノード) と 線 (エッジ) で関係を描き、 一目でクラスタ・ハブ・ボトルネックを見せる」道具です。 棒グラフや散布図が「1 つの量や 2 変数の関係」を見せるのに対し、 ネットワーク図は「多数の主体がどう繋がっているか」 (友達関係、 企業の取引、 引用、 路線図、 タンパク質相互作用など) を扱います。
代表的なレイアウトは ① 力学モデル (force-directed): 引き合う・反発する力で自然な配置 (Fruchterman-Reingold、 ForceAtlas2)、 ② 階層レイアウト: 親子関係を上下に配置 (Sugiyama)、 ③ 円環レイアウト: ノードを円周に並べエッジを弦で結ぶ (chord diagram)、 ④ 地理レイアウト: ノードを実座標に置く (路線図・物流網) の 4 つ。
SSDSE-B-2026 を題材にするなら、 47 都道府県をノード、 実在列(総人口 A1101・高齢人口 A1303・出生数 A4101・死亡数 A4200・婚姻件数 A9101 など)の標準化ベクトル間の類似度をエッジとした「県類似度ネットワーク」が定番(本ページで実装)。 長野・宮城が次数中心性 (degree centrality) の高いハブ、 東京は「似た県がない」ため次数最小級、 といった構造が見える化されます。 数値の表だけ眺めても気づけない「クラスタ構造」「ハブとなる県」が直感的に把握できるのが本手法の威力です。 なお県間の通勤・物流のような流動量(起点×終点の OD 行列)は SSDSE-B-2026 には収録されておらず、 「人口移動ネットワーク」を描くには国勢調査の従業地・通学地集計などの外部データが必要です。
nx.degree_centrality や nx.betweenness_centrality でノードサイズに連動させると、 「レイアウトの違いで見え方が変わる」 (= 唯一の正しい絵は存在しない) という感覚が腑に落ちます。
🍰 まずはやさしく
ネットワーク可視化という手法です。
レポートなどで正しく使うために学びます。
スマホのアプリ開発などでも使われます。
使うための条件やルールを説明します。
ネットワーク・グラフ・階層構造の可視化
英語名 Network Visualization。 同義・関連語:グラフ可視化。
この用語を理解・使用するときは、 次のような前提を意識してください:
前節の数式に含まれる記号を、 日本語の意味に翻訳する。
network visualization の数式は、 これらの記号を組み合わせて「データから未知量を推定する」あるいは「データの構造を要約する」プロセスを記述している。
合成データで完全グラフのエッジ数と可視化複雑度を計算する。
| N ノード | 最大エッジ |
|---|---|
| 10 | 45 |
| 50 | 1,225 |
| 100 | 4,950 |
| 1,000 | 499,500 |
1 2 3 4 | import numpy as np N = np.array([10, 50, 100, 1000]) max_edges = N*(N-1)//2 print(f"最大エッジ: {max_edges}") |
💬 手計算 (Step 2) と Python 出力が完全一致。
SSDSE-B-2026 のような公的統計データを Python で扱う際の基本パターン:
1 2 3 4 5 6 7 8 9 10 11 12 | import pandas as pd import numpy as np # データ読み込み df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] # 2023 年断面 (47 都道府県) print(df.shape) print(df.dtypes) print(df.describe()) # 「関係性の可視化」の文脈で扱う場合の例: # 分野: 可視化 # 関連手法は同カテゴリの他用語を参照してください。 |
具体的なコードは 1変量の可視化 を参照してください。
分析結果を報告するときに含めるべき情報:
network visualization を実務で使う際に頻発する誤用・落とし穴を列挙する。 多くは「前提の確認不足」「結果の過信」「他手法との比較不足」が原因で、 事前にチェックリスト化することで回避できる。
「network visualization」を中心とした関連概念マップ。
概念マップは「グラフ理論 → レイアウト戦略 → 構造解析 (中心性・コミュニティ) → 応用 (SNS / 通勤 / 引用ネット)」の流れを示す。 SSDSE-B-2026 を題材にするなら、 47 ノード × 実在 5 指標の類似度エッジ (本ページでは 153 エッジ) で spring layout を組み、 PageRank と Louvain で都道府県の階層構造を可視化するのが入り口 (通勤・物流エッジは SSDSE 非収録のため外部 OD データが必要)。
関係性の可視化は、 グラフ構築から構造解析、 描画、 解釈の各段階で隣接領域と組み合わせる。
SSDSE-B-2026 規模では「networkx + spring layout + PageRank ノードサイズ + Louvain 色分け」が標準ワークフロー。 大規模 SNS では Gephi や cugraph (GPU) を併用。
ネットワーク可視化のレイアウト選択は「ノード数・密度・伝えたい構造」で決まる。
SSDSE-B-2026 の 47 都道府県類似度ネットワークを可視化するなら 47 ノードと小規模なので力学モデルで OK、 地理座標を制約に入れると現実感が増す。
21 ノード・3 コミュニティの小さなネットワークを、 力学モデル (force-directed layout) でブラウザ内でリアルタイム計算して描画する。 エッジは「バネ」 (自然長より伸びると引き合い、 縮むと押し返す: フックの法則 F = k(d − L))、 全ノードペアには「斥力」 (クーロン力型: F = C / d²) が働き、 力の釣り合いに向かって配置がひとりでに安定していく。 spring_layout (Fruchterman-Reingold) が毎回やっている計算そのものだ。
力学モデルは「エッジで繋がったノードは近くに、 繋がっていないノードは斥力で遠くに」という 2 つの力の妥協点を探す最適化だ。 同じコミュニティ内はバネが多いので固まり、 コミュニティ間はバネが 1 本 (橋) しかないので離れる — クラスタ構造が「何も指定しなくても」空間的に分離して見えるのはこのため。 ハブ (次数最大のノード) は多くのバネに四方から引かれるためコミュニティの中心に鎮座する。 スライダーで斥力を上げると全体が膨らみ、 バネ自然長を縮めるとクラスタが凝縮する — どちらも「同じグラフ」の別の見せ方にすぎない。
spring_layout(G, seed=42) のように乱数 seed を固定して再現性を確保する。iterations の打ち切りに相当)。ここまで「関係性の可視化=ノード+エッジで描くグラフ図」という基本を扱った。 本章では SSDSE-B-2026 の 47 都道府県データ(2023 年)(総人口・出生数・死亡数・一般診療所数・延べ宿泊者数など 13 の実在数値列)を素材に、 (1) 相関ネットワーク、 (2) ヒストグラム+ネットワーク、 (3) コミュニティ別の箱ひげ の 3 ステップで「描く理由・読む方向・誤読の回避」を完成させる。 図は 3 枚、 コードは 4 ブロック(4 要素パターン完備)、 表は 6 枚、 最後に理解度チェック・ミニ辞書・ケーススタディを置く。
ネットワーク図は「いきなり描いてはいけない」というのが実務の鉄則だ。 まず (A) 散布図でペア関係の強さを確かめ、 次に (B) ヒストグラムでノードの大きさ(人口など)の分布を把握し、 最後に (C) コミュニティ別の箱ひげで「クラスタが意味を持つか」を検証する。 この前段検証を省くと、 「色が分かれているからクラスタがある」と錯覚するだけの“見せかけネットワーク”が量産される。
この散布図は「ペアの関係を 1 本のエッジで表してよいか」の事前確認だ。 ほぼ直線(相関係数 0.97 以上)なら「人口が決まれば一般診療所数がほぼ決まる」= 2 変数を別ノードにする意味は薄い。 一方、 ばらつきが大きい変数ペアこそ、 ネットワーク図でクラスタ構造を探す価値がある。 相関ネットワーク を作る前に、 必ず散布図行列で「線形にきれいに乗りすぎていないか」を確認する習慣をつけよう。
ヒストグラムは「ノードサイズを node_size = pop に比例させてよいか」の判断材料だ。 東京の人口は鳥取の約 22 倍。 線形比例だと東京の丸が画面の半分を占め、 他県が点になる。 分布が右に長く裾を引いているなら、 ノードサイズは log 変換するか、 sqrt 変換 を使うのが定石。 ヒストグラムで右裾を確認 → ネットワーク図で node_size = np.log1p(pop) * 200 のように変換、 がワンセットだ。
ネットワーク図で「色が違う 3 クラスタ」が出ても、 それが意味のあるグルーピングかは別問題だ。 図 C のように クラスタごとに連続変量(一般診療所数・総人口・死亡数)の箱ひげを並べ、 中央値が大きく違うか・四分位範囲が重なるか を確認する。 重なりが大きいなら「ネットワーク上で色が分かれていても、 数値的には差が無い」= 描画パラメータが見せた幻のクラスタかもしれない。 箱ひげ図・クラスタ妥当性 が事後検証のキーワード。
| 手法 | 主目的 | ネットワーク図と比べた強み | 使う場面 (47 都道府県例) |
|---|---|---|---|
| 散布図 | 2 変数の相関を見る | 外れ値の位置が一目で分かる | 総人口 × 一般診療所数 |
| ヒートマップ | 多変数相関の全体俯瞰 | 全ペアを一度に比較 | 13 列の相関行列 |
| 階層クラスタ樹形図 | グルーピングの階層構造 | 分割数を後から決められる | 47 県の経済構造類似度 |
| コーラスマップ | 地理的分布 | 場所と数値を同時表示 | 人口分布 |
| ネットワーク図 | 多対多の関係構造 | クラスタ・ハブ・中継点が見える | 変数相関ネット・県類似度ネット(通勤流動・電力融通は外部 OD データ) |
| 属性 | 視覚チャネル | SSDSE-B-2026 で使える例 | 注意点 |
|---|---|---|---|
| ノードサイズ | 面積(=値² ではなく値) | 総人口、 一般診療所数 | 右裾分布なら log 化 |
| ノード色 | カテゴリ or 連続スケール | 地方ブロック、 高齢化率 | 7 色超えると区別不可 |
| エッジ太さ | 線幅 | |相関係数|、 類似度(実在列から計算) | 線幅の上限を 10px に |
| エッジ色 | 符号や種別 | 正の相関=青、 負=赤 | 色覚多様性に配慮 |
| エッジ方向 | 矢印 | 因果仮説の向き(総人口 → 一般診療所数 等)※県間フロー OD は SSDSE 非収録 | 無向と有向を混在させない |
| ラベル | テキスト | 県名 | 5 つ以上は重ねず間引く |
| 中心性 | 定義 | 高い県の例 (本ページの 5 指標類似度ネット・実測) | 解釈 |
|---|---|---|---|
| 次数中心性 | エッジ本数 | 長野、 宮城、 栃木 | 直接つながる相手が多い |
| 媒介中心性 | 最短経路に乗る回数 | 静岡、 兵庫 | 経由地点・ハブ役 |
| 近接中心性 | 他全ノードへの平均距離の逆数 | 静岡、 新潟 | 全体に近い |
| PageRank | 隣接の重要度を再帰的に | 長野、 埼玉、 千葉 | 「重要ノードに繋がる」も評価 |
| 固有ベクトル中心性 | PageRank の親戚 | 長野、 新潟、 宮城 | 「強力なハブと繋がる」 |
| アルゴリズム | 最大化指標 | 計算量 | 47 県データでの挙動 |
|---|---|---|---|
| Louvain | モジュラリティ | O(n log n) | 3-5 クラスタに分割 |
| Leiden | モジュラリティ改良 | O(n log n) | Louvain より安定 |
| GirvanNewman | 媒介中心性を削る | O(n³) | 小規模に向く |
| Label propagation | 隣接多数決 | O(n) | 高速だが結果不安定 |
| Spectral clustering | グラフラプラシアン固有値 | O(n³) | k を事前指定 |
| NG | 何が起こるか | 代替策 |
|---|---|---|
| 全ノードにラベル | 文字が重なって読めない | 上位 10 ノードのみラベル |
| エッジを全部描く | 毛糸玉 (hairball) になる | 閾値で間引く・重みでフィルタ |
| レイアウトを変えて比較 | 「位置」が誤解を生む | 同じ seed で固定 |
| 色を 10 色以上 | 色の区別がつかない | 7 色以内 + ラベル併記 |
| サイズに値² を割当 | 面積が極端に違う | 面積 = 値 で代用 |
| 「美しさ」だけ追求 | 主張が伝わらない | 主張=中心性/クラスタを強調 |
| ネットワーク種 | ノード | エッジ条件 | 読み取れること |
|---|---|---|---|
| 変数相関ネット | SSDSE-B 各列 | |r| ≥ 0.7 | 変数のクラスタ |
| 県類似度ネット | 47 都道府県 | 距離 ≤ τ | 県のグルーピング |
| 通勤フロー ※ | 47 都道府県 | 流入量 > 1 万人 | 経済圏 |
| 産業共起 ※ | 産業中分類 | 同一県で上位 | 産業集積 |
| 電力融通 ※ | 10 電力エリア | 融通 > 0 | 基幹インフラの依存 |
※ 印の 3 種は SSDSE-B-2026 非収録のデータが必要(通勤 OD は国勢調査 従業地・通学地集計、 産業データは経済センサス・産業連関表、 電力融通は電力広域的運営推進機関の公表値など)。 SSDSE-B-2026 の実在列だけで構築できるのは上 2 種(変数相関ネット・県類似度ネット)。
ここから 4 つのコードブロックを 4 要素パターン(やること → 入力 → コード → 実行結果 → 読み方)で示す。 すべて data/raw/SSDSE-B-2026.csv の実データを使い、 乱数生成は一切しない。
このコードでやること: SSDSE-B-2026 を読み込み、 数値列の ピアソン相関行列 を計算してから、 絶対値 0.7 以上のペアだけをエッジに変換して NetworkX のグラフを構築する。 「変数同士の関係性」を構造化する第一歩。
📥 入力データ (SSDSE-B-2026.csv 先頭 3 行、 列の一部):
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 | import pandas as pd import numpy as np import networkx as nx df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] # 2023 年・47 都道府県 cols = {'A1101':'総人口', 'A1301':'年少人口', 'A1303':'高齢人口', 'A4101':'出生数', 'A4103':'合計特殊出生率', 'A4200':'死亡数', 'A5101':'転入者数', 'A9101':'婚姻件数', 'B4101':'年平均気温', 'L3221':'消費支出', 'C3301':'着工建築物数', 'I5102':'一般診療所数', 'G7101':'延べ宿泊者数'} num = df[list(cols)].rename(columns=cols) # 13 の実在数値列 corr = num.corr() G = nx.Graph() for c in num.columns: G.add_node(c) for i, a in enumerate(num.columns): for b in num.columns[i+1:]: r = corr.loc[a, b] if abs(r) >= 0.7: G.add_edge(a, b, weight=round(float(abs(r)), 3), sign=float(np.sign(r))) print('ノード数:', G.number_of_nodes()) print('エッジ数:', G.number_of_edges()) print('上位 3 エッジ:', sorted(G.edges(data=True), key=lambda e: -e[2]['weight'])[:3]) |
📤 実行すると次の出力が得られる:
💬 ノード 13・エッジ 45 のグラフが完成。 「高齢人口 - 死亡数」「年少人口 - 出生数」「総人口 - 年少人口」が相関 0.99 台で繋がる → これらは年齢構成が生む実質同じ情報のクラスタを形成している、 と読める。
このコードでやること: コード 1 で作った G を spring_layout で描く。 ノードサイズ=列平均の log、 色=カテゴリ(人口系/動態系/生活系) を割り当てて、 「変数の集まり方」が一目で読めるレイアウトに整える。
📥 入力データ: コード 1 で構築した G(13 ノード・45 エッジ)と、 各列の平均値 num.mean()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import matplotlib.pyplot as plt categories = { '総人口':'pop', '年少人口':'pop', '高齢人口':'pop', '出生数':'flow', '死亡数':'flow', '転入者数':'flow', '婚姻件数':'flow', '一般診療所数':'life', '着工建築物数':'life', '延べ宿泊者数':'life', '消費支出':'life', '合計特殊出生率':'life', '年平均気温':'life' } color_map = {'pop':'#26A69A', 'flow':'#FFA726', 'life':'#7E57C2'} means = num.mean() sizes = [np.log1p(means[n]) * 80 if n in means else 200 for n in G.nodes()] colors = [color_map.get(categories.get(n, 'other'), '#90A4AE') for n in G.nodes()] pos = nx.spring_layout(G, seed=42) plt.figure(figsize=(7,7)) nx.draw_networkx_nodes(G, pos, node_size=sizes, node_color=colors, alpha=0.85) nx.draw_networkx_edges(G, pos, width=[d['weight']*3 for _,_,d in G.edges(data=True)], alpha=0.6) nx.draw_networkx_labels(G, pos, font_size=10, font_family='IPAexGothic') plt.axis('off') plt.title('SSDSE-B-2026 変数相関ネットワーク') plt.savefig('var_corr_net.png', dpi=150, bbox_inches='tight') print('Saved: var_corr_net.png') |
📤 実行すると次の出力が得られる:
💬 「人口系 - 動態系」のリンクが太く、 「生活系」は人口系の中心ノードを介して繋がる。 一方で 合計特殊出生率・年平均気温・消費支出 は相関 0.7 未満でどのノードとも繋がらず独立して浮く。 「人口規模で決まる実数系指標と、 人口と独立な比率系指標」という構造仮説を立てる材料になる。
このコードでやること: 同じ G に対して 次数中心性・媒介中心性・近接中心性・PageRank を計算し、 上位 5 件を表にする。 「どの変数が全体構造のハブか」を客観的に決める。
📥 入力データ: 同じ G (13 ノード・45 エッジ)。
1 2 3 4 5 6 7 8 | deg = nx.degree_centrality(G) btw = nx.betweenness_centrality(G, weight='weight') clo = nx.closeness_centrality(G) pr = nx.pagerank(G, weight='weight') import pandas as pd cen = pd.DataFrame({'degree':deg, 'between':btw, 'close':clo, 'pagerank':pr}) print(cen.sort_values('pagerank', ascending=False).head().round(3)) |
📤 実行すると次の出力が得られる:
💬 総人口・出生数・婚姻件数・年少人口 が次数 0.75・PageRank 0.098 で並ぶ最上位ハブ群 → 人口規模を表す実数系変数群が互いに密結合し、 媒介中心性はどれも 0(クラスタ内が近似完全グラフで橋渡し役がいない)。 「人口を抑える=多くの実数系指標を抑える」ことが定量的に裏付けられる。
このコードでやること: Louvain で G をコミュニティ分割し、 さらに 47 都道府県を 13 変数の標準化値で KMeans クラスタリング → クラスタ別に「一般診療所数」の箱ひげを並べて、 ネットワーク上のクラスタが「数値的にも区別できるか」を検証する。
📥 入力データ: 同じ G と、 元の 47 行 × 13 列の num データ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | from networkx.algorithms.community import louvain_communities comm = louvain_communities(G, weight='weight', seed=42) print('変数コミュニティ数:', len(comm)) for k, c in enumerate(comm): print(f' community {k}:', sorted(c)) from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler Xz = StandardScaler().fit_transform(num.fillna(num.mean())) labels = KMeans(n_clusters=3, n_init=10, random_state=42).fit_predict(Xz) import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(7,4)) groups = [num.loc[labels==k, '一般診療所数'] for k in range(3)] ax.boxplot(groups, tick_labels=['cluster0','cluster1','cluster2']) ax.set_title('クラスタ別 一般診療所数(47 都道府県)') plt.savefig('cluster_box.png', dpi=150, bbox_inches='tight') print('Saved: cluster_box.png') print('各クラスタ中央値:', [int(g.median()) for g in groups]) |
📤 実行すると次の出力が得られる:
💬 3 クラスタの一般診療所数の中央値が cluster0 = 1,159 / cluster2 = 5,001 / cluster1 = 14,894 施設 と明確に階段状 → ネットワーク図のクラスタ分割は数値的にも裏付けられている。 cluster1 は東京都 (1 県)、 cluster2 は北海道・埼玉・千葉・神奈川・愛知・大阪・兵庫・福岡 (大都市圏 8 県)、 cluster0 は残り 38 県、 という解釈が成立。
node_size = np.log1p(pop) * k や sqrt(pop) * k など対数/平方根変換を使う。 線形比例だと東京のノードが他県を圧倒し読めない。| 用語 | 説明(80-120 字) |
|---|---|
| ノード (vertex) | ネットワークの「点」。 47 都道府県データなら県や変数を 1 ノードに対応させる。 属性 (人口・カテゴリ) を持てる。 |
| エッジ (edge) | ノード間をつなぐ「線」。 重み・方向・符号を持てる。 県間の類似度は重み付き無向エッジ、 通勤流入量のような流動(外部 OD データ)は重み付き有向エッジで表現するのが自然。 |
| 次数 | あるノードに繋がるエッジ本数。 「総人口」の次数は 9 で、 9 個の変数と相関 0.7 以上で繋がっている(残る 3 変数=合計特殊出生率・年平均気温・消費支出とは繋がらない)、 という意味。 |
| モジュラリティ | 「同じコミュニティ内のエッジが多いほど大きい」分割指標。 Louvain はこれを最大化する。 0.3 以上で分割の意味があると言われる。 |
| 毛糸玉 (hairball) | エッジが多すぎて構造が見えない状態。 重みフィルタや backbone extraction で減らす必要がある。 |
| backbone extraction | 重要なエッジだけを残してネットワークの骨格を抽出する手法。 Disparity Filter が代表的。 |
問い: 47 都道府県の医療・生活インフラの規模は、 人口・気候・世帯消費のうち、 どれで一番よく説明できるか?
手順 1 (前段): SSDSE-B-2026(2023 年)を読み、 13 個の数値列で散布図行列を作る。 総人口と一般診療所数がほぼ直線(r = 0.972)。 ヒストグラムで人口が右裾分布と確認。
手順 2 (構造): |相関| ≥ 0.7 のエッジで変数ネットワークを構築(13 ノード・45 エッジ)。 ノードサイズ = log(平均)。 「総人口・年少人口・出生数・死亡数・一般診療所数」など実数系 10 変数が密なハブを形成。
手順 3 (中心性): PageRank で「総人口」が出生数・婚姻件数・年少人口と並ぶ最上位(0.098)→ インフラ規模を 1 変数で説明するなら人口がベスト。
手順 4 (検証): Louvain でコミュニティ分割 → 実数系 10 変数の大クラスタと、 独立した 3 変数(合計特殊出生率・年平均気温・消費支出)が見える。 47 県を KMeans でクラスタリングし、 クラスタ別に一般診療所数を箱ひげで比較 → 中央値が階段状 (1,159 / 5,001 / 14,894 施設) で意味のある分割と判定。
結論: 一般診療所数は「人口」で約 9 割説明可能(r = 0.972、 R² ≒ 0.95)。 ただし「合計特殊出生率」「年平均気温」「消費支出」が独立クラスタ(人口と相関 0.7 未満)を作るため、 医療・生活の議論では人口規模だけでなく出生率・気候・世帯消費も同時に見るべき、 という多面的な含意が得られる。 ネットワーク図はその含意を 1 枚にまとめる強力なツールになる。
ネットワーク図の原点は、 18 世紀のオイラーによる「ケーニヒスベルクの 7 橋問題」だ。 オイラーは都市と橋をノードとエッジに抽象化し、 「すべての橋をちょうど一度ずつ渡って戻れるか」という問いをグラフ理論として定式化した。 これが現代の関係性可視化のすべての出発点であり、 「現実の関係を抽象化して点と線で描く」という発想そのものを誕生させた革命だった。 SSDSE-B-2026 で 47 都道府県を扱うときも、 私たちは同じ抽象化を行っている: 県は点に、 指標の類似性や相関は線に置き換えられ、 数百万人規模の人口構造が一枚のグラフに凝縮される。
20 世紀に入ると、 心理学者のヤコブ・モレノが ソシオグラム を発明する。 これは人間関係を点と線で描いた最初の社会ネットワーク図であり、 「誰と誰が友達か」を可視化することで集団内のリーダー・孤立者・派閥を発見できることを示した。 現代の SNS 分析・組織分析・疫学接触追跡は、 すべてこのソシオグラムの末裔だ。 関係性の可視化は単なる「綺麗な絵」ではなく、 「目で見える形にすることで、 人間の認知が一気にその構造を把握できる」という認知科学的な意義 を持つ。 これが数値テーブルやリストでは得られない可視化の本質的な力である。
21 世紀になり、 グーグルの PageRank、 フェイスブックの友達推薦、 アマゾンの商品推薦、 ネットフリックスのレコメンド、 ツイッターのフォロー網解析、 新型コロナの接触追跡など、 すべてが 大規模グラフ+ネットワーク可視化 の応用だ。 47 都道府県の通勤フローやサプライチェーンも同じ枠組みで分析できる。 統計学の教科書では相関係数や回帰係数として「2 変数の関係」を学ぶが、 現実は多対多の関係であり、 それを 1 枚の図に圧縮するのがネットワーク可視化の核心的な役割なのだ。 さらに 2010 年代以降は、 グラフニューラルネット (GNN) や node2vec などの埋め込み手法と組み合わせて「グラフ × 機械学習」が爆発的に発展し、 関係性の可視化は AI の解釈装置としても重要視されている。
関係性データを 1 枚の図に落とすときは、 まず「データのタイプ」を分類するのが先決だ。 タイプを誤ると描き方も誤る。 以下 4 タイプを SSDSE-B-2026 を題材に整理する。
(1) 共起ネットワーク: 「同じ県で上位に来る産業」「同じ年に発生した災害種別」のように、 同じ文脈で一緒に登場するものを線で結ぶ。 SSDSE-B-2026 で「出生数と婚姻件数の上位 10 県」を共起させると、 東京・神奈川・大阪・愛知などの大都市圏が密なクラスタを作る。 共起ネットワークは「グループ分け」の発見に向く。 一方で「因果」とは無関係なので、 「同じ県で多いから関連がある」と早合点しないこと。
(2) フローネットワーク: 「東京から神奈川へ通勤する人が 80 万人」のように、 量を持つ有向の流れを線で表す。 県間の通勤・物流・電力融通のペア間流動データはすべてこのタイプだが、 SSDSE-B-2026 には県間 OD(起点×終点)行列は収録されていないため、 描くには国勢調査の従業地・通学地集計などの外部データが必要。 SSDSE-B-2026 の転入者数 (A5101)・転出者数 (A5102) は県ごとの合計値であり、 どこから来てどこへ行くかの行列ではない点に注意。 ノードはほぼ常に「場所」、 エッジは「量+方向」を持つ。 線の太さで量、 矢印で方向を示すのが定石。 フローは「需要と供給の構造」を直感的に伝えるのに最適で、 政策提言・経済分析・物流最適化で頻出する。 一方、 47 都道府県 × 47 都道府県 = 2,209 エッジを全部描くと毛糸玉になるため、 上位 5% のみ描画するなどの間引きが必須となる。
(3) 類似度ネットワーク: 「県の経済構造ベクトル間のコサイン類似度が 0.9 以上なら線を引く」のように、 距離・類似度を閾値で 2 値化してエッジにする。 SSDSE-B-2026 で 13 の実在数値列の z スコアベクトルを使い、 県間のユークリッド距離をエッジに変換すると、 「人口・生活構造が似た県のクラスタ」が浮かぶ。 これは クラスタリング をネットワーク図で表現する方法とも言える。 注意点として、 閾値の選び方で図が劇的に変わるため、 複数閾値で描き比べる感度分析が必要。
(4) 因果ネットワーク: 「人口 → 一般診療所数」のように因果関係を有向エッジで描く。 SSDSE-B-2026 のような観測データから因果を推定するには、 操作変数法・差分の差分法・回帰不連続デザインなどの計量経済学手法が必要。 ネットワーク図は「仮説の整理ツール」として有効で、 因果推論の DAG (有向非巡回グラフ) はネットワーク可視化の代表的な応用だ。 因果ネットワークを描く際は、 必ず「これは推定であって観察ではない」と明示しないと、 視覚的な強さに引きずられて読者が因果と相関を混同する。
関係性可視化は、 認知科学の観点では 「ワーキングメモリ + 視覚探索」 の総合課題だ。 人間が一度に保持できる視覚要素は概ね 5-9 個と言われている (ミラーの法則)。 47 都道府県を全部一度に描くと、 読者は「どこを見ればよいか」を見失う。 以下、 認知負荷を下げる設計指針 6 ヶ条を示す。
指針 1: 一画面 1 主張 — 「東京がハブだ」「地方都市が独立している」など、 主張を 1 つに絞る。 主張ごとに別図を作り、 並べて見せる。 1 枚の図で 3 つも主張すると、 読者の認知資源が分散し、 結局どれも伝わらない。
指針 2: 強調と背景の二段構え — 主張に関わるノード/エッジは鮮やかな色 + 太い線で、 背景情報は薄い灰色で描く。 全部同じ色で描くと「特に注目すべき点」が見えない。 大都市圏のハブを強調したいなら、 東京・大阪・名古屋だけ赤、 残り 44 県は灰色のような配色が効く。
指針 3: ラベルは選択的に — 全 47 ノードにラベルを付けると文字が重なる。 中心性上位 10 のみラベル、 残りはホバー時にツールチップで表示するのが定石 (pyvis を使えば標準機能)。 印刷物の場合は、 ラベルを付けるノードを事前に「主張に関係するノード」に絞る。
指針 4: 色は 7 色まで — 人間が瞬時に区別できる色は概ね 7 色 (色相環の主要色 + 黒白)。 8 色以上を使うと、 凡例を何度も確認しないと読めない。 47 都道府県を 8 ブロックに分けたいなら、 7 色 + 灰色 (その他) などの工夫が必要。 また色覚多様性に配慮して ColorBrewer などのカラーパレットを使うこと。
指針 5: 動きと静止を分ける — 時系列ネットワーク (例: 47 都道府県の通勤フローを 2010-2026) は、 年ごとの静止画を並べるか、 1 枚に差分を集約するか、 アニメーションにするか、 どれかに統一する。 アニメと差分を混在させると認知負荷が爆発する。 報告書なら静止画 4 枚、 Web ならアニメーション 1 本、 のように媒体特性に合わせて選ぶ。
指針 6: 凡例・出典・尺度を必ず付ける — ネットワーク図は「絵」として独立して扱われがちで、 表よりも凡例が省略されやすい。 ノードサイズが「人口に比例」なのか「log 比例」なのか、 エッジ太さが「実数」なのか「z スコア」なのか、 必ず凡例で明示する。 SSDSE-B-2026 のデータ出典 (年度・取得日) も図のキャプションに書く。 これを怠ると、 数年後に同じ図を再現できなくなり、 教育・研究の再現性が崩れる。
関係性可視化を実務で行うときは、 ツール選定が成否を分ける。 47 都道府県データのような中規模 (~50 ノード) なら NetworkX + matplotlib で十分だが、 数千ノードを超えると Gephi や Cytoscape のような専用 GUI、 Web 公開なら pyvis や D3.js が必要になる。 以下、 5 つの代表ツールを比較する。
NetworkX (Python): 47 都道府県のような中規模グラフを Python パイプライン内で扱う標準ツール。 中心性・コミュニティ検出・最短経路など豊富な API を持ち、 matplotlib と組み合わせて静止画を作る。 利点は「pandas → networkx → matplotlib」のシームレスな連携。 欠点は描画速度が遅く、 5,000 ノードを超えると実用的でない点。 教育・研究・初期分析の主力。
pyvis (Python → HTML): NetworkX のグラフを nt = Network(); nt.from_nx(G); nt.show('out.html') で 1 行で対話的 HTML 化できる。 Web 上で zoom・drag・ホバーが動くため、 教育コンテンツや報告書 Web 版に最適。 47 都道府県の通勤フローを学生に触らせるなら pyvis 一択。 欠点はカスタマイズ性で、 細かい配色を変えにくい。
Gephi (GUI): ヨーロッパ発の老舗オープンソース GUI。 数万ノードまで実用、 ForceAtlas2 など独自レイアウトと豊富なフィルタが強力。 大学・研究機関の標準ツール。 SSDSE-B-2026 のサイズなら過剰スペックだが、 「動的フィルタで閾値を動かしながら探索する」体験は Gephi が圧倒的に優れる。 欠点は学習コストと、 描画結果を再現するスクリプトを書きにくい点。
Cytoscape (GUI): バイオインフォマティクス発で、 遺伝子・タンパク質相互作用網の業界標準。 47 都道府県データのような社会データに使う例は少ないが、 プラグインが豊富で、 統計解析パイプラインと連携できる。 学術論文用の高品質ベクター出力 (SVG, PDF) に強い。
D3.js (JavaScript): Web ネイティブの可視化ライブラリ。 自由度は最高だが、 自分でレイアウト・インタラクション・凡例をすべてコードで書く必要があり、 学習コストが極めて高い。 New York Times の対話的記事や、 OECD のダッシュボードなど、 プロのデータビジュアライザーが選ぶ最終形態。 47 都道府県データを政府ポータルで公開するなら検討の価値あり。
ツール選定の指針: 「規模 (ノード数) × 用途 (静止画 / 対話的 / 論文 / Web 公開) × チームのスキル」の 3 軸で決める。 学生・初学者は NetworkX + pyvis の組み合わせから始めると、 ほぼすべての教育用途を 1 つの Python ノートブックで完結できる。 47 都道府県 SSDSE-B-2026 の練習ならこれ以外を選ぶ理由はほぼ無い。
同じネットワーク図でも、 読者の立場で読み方が変わる。 47 都道府県の通勤フロー図を、 (A) 政策担当者、 (B) ビジネス担当者、 (C) 研究者・学生 の 3 つの視点で読むと、 ネットワーク可視化が「誰のためのコミュニケーション装置か」が立体的に見えてくる。
(A) 政策担当者 は「東京一極集中の度合い」「地方拠点都市の役割」を読む。 中心性指標で東京の値が他を圧倒すれば、 「人口流出と地方衰退」の議論材料になる。 媒介中心性で福岡や仙台が高ければ、 「広域連携の中心都市」として政策的支援の根拠になる。 関係性図は、 政策担当者にとって「数百万人の動きを 1 枚で説明する説明資料」の役割を果たす。
(B) ビジネス担当者 は「物流拠点をどこに置くか」「採用エリアをどこに広げるか」を読む。 通勤フローの太いエッジは「通勤可能圏内=採用エリア候補」を示す。 同じ図でも、 政策担当者は「分散化」を、 ビジネス担当者は「集中=効率化」を読む。 関係性図は中立だが、 読者の関心軸で解釈が変わる典型例だ。
(C) 研究者・学生 は「クラスタの妥当性」「使用したデータの限界」を読む。 「東京を中心とした 1 つの大クラスタしかない」のは「データが日本全国のメッシュ移動データから生成されたバイアス」かもしれない。 関係性図は、 主張を生むだけでなく「主張の脆弱性」も同時に伝える義務がある。 学生はこの段階で「批判的読み方」を身につけることが、 データリテラシー教育の本質だ。
関係性可視化に習熟したあと、 次に学ぶべき領域は (1) グラフ理論の基礎 (オイラーパス・ハミルトン路・スモールワールド・スケールフリー)、 (2) グラフ機械学習 (node2vec・GNN・GraphSAGE)、 (3) 時空間ネットワーク分析 (時間変化するネットワーク) の 3 方向だ。 (1) は理論的基盤を与え、 (2) は予測・分類への応用を開き、 (3) は「動く関係性」を扱う武器になる。 47 都道府県データで (1)(2)(3) を順に練習することで、 統計学・機械学習・社会科学を横断するデータサイエンティストとしての視野が一気に広がる。
また、 「可視化=コミュニケーション設計」という観点では、 タフテの『定量情報の視覚表示』、 ベルタンの『図の意味論』、 カイザー・フンクの『データを使うストーリーテリング』 など、 視覚表現の古典を読むことを強く推奨する。 ネットワーク可視化は技術ではなく言語であり、 言語は読書によって深まる。 47 都道府県データで実践→古典で原理を学ぶ、 のサイクルを回せば、 関係性可視化は単なる「networkx の使い方」を超えた、 強力な思考道具になる。
spring_layout(G, seed=42) のように固定する。nx.draw_networkx_edges は有向グラフでないと矢印を描かない。 流動を表現するなら必ず nx.DiGraph() でグラフを作る。関係性の可視化は、 散布図・ヒストグラム・箱ひげのような「1 変数 / 2 変数」の可視化を超え、 「多対多の関係を一枚に圧縮する」ための言語だ。 SSDSE-B-2026 の 47 都道府県データは、 ノード数が手頃で意味解釈もしやすく、 練習素材として理想的だ。 本章で扱った相関ネットワーク・コミュニティ検出・中心性指標・箱ひげ検証の流れは、 そのまま実務の分析パイプライン(外部 OD データを使う通勤フロー分析など)に転用できる。 「描く前に確認する・描いたあとに検証する」 — この前後ろセットでネットワーク可視化は信頼できる分析装置になる。
最後に強調したいのは、 ネットワーク図は「説得装置」ではなく「思考装置」だという点だ。 美しい絵で読者を圧倒するのではなく、 構造を自分が理解するために描き、 その理解を最小限の装飾で他者と共有する。 これがデータサイエンスにおける関係性可視化の本質的な役割であり、 47 都道府県データを題材に練習する最大の意義だ。 描き手の責任は、 図そのものではなく「図を通じて読み手の理解が深まったかどうか」にある。
このページの本文では「都道府県どうしの関係」をネットワークにした。ここでは角度を変えて、変数どうしの相関行列を閾値で二値化してネットワークにするという、実務で最も多い作り方を深掘りする。ノードは指標、エッジは「相関が強いペア」。連続量である相関係数を、どこかで線を引いてエッジの有無に変換するところに、この可視化の本質的な危うさが潜んでいる。
相関ネットワークは「相関ヒートマップを地図にしたもの」だと思うとよい。6 つの指標なら相関行列は 6×6、上三角に 6×5÷2 = 15 個の数字が並ぶ。この 15 個それぞれについて「|相関| がしきい値以上ならエッジを引く」だけで、行列がグラフに化ける。つまり閾値 1 個の選び方が、そのまま「どのペアを関係アリと呼ぶか」という主張になる。閾値は装飾ではなく、分析の結論そのものだ。
下の図は SSDSE-B-2026(2023 年・47 都道府県)の人口動態 6 指標の実測相関で作った相関ネットワークだ。スライダーで閾値を動かすと、同じ 1 枚の相関行列から、まったく違う「関係の地図」が現れる。ノードはドラッグで動かせる(配置は見やすさのためだけで、意味は持たない)。
同じデータでも閾値を上げると、実測で次のようにエッジが激減する(下表は Python で数えた実値)。
| |相関| 閾値 | エッジ数 / 15 | 密度 |
|---|---|---|
| 0.30 | 13 | 0.87 |
| 0.50 | 7 | 0.47 |
| 0.70 | 5 | 0.33 |
| 0.80 | 3 | 0.20 |
| 0.90 | 2 | 0.13 |
指標: 高齢化率(A1303/A1101)、年少人口率(A1301/A1101)、出生率(A4101/A1101)、死亡率(A4200/A1101)、転入率(A5101/A1101)、転出率(A5102/A1101)。閾値 0.90 まで残る最強の 2 本は 高齢化率–死亡率 (r=0.972) と 出生率–年少人口率 (r=0.934)、次点が 転入率–転出率 (r=0.872)。いずれも 2023 年 47 都道府県の実測値。
numpy.linalg.inv や scikit-learn の GraphicalLasso で作れる。