NetworkX 提供了強大的工具來分析和視覺化網路結構。理解節點和邊的屬性操作是進行網路分析的基礎。透過設定和讀取這些屬性,我們可以深入瞭解網路的特性。文章使用空手道俱樂部網路為例,示範如何設定和使用節點的 club 屬性來區分不同社群,並根據此屬性進行視覺化,不同顏色代表不同的俱樂部。此外,文章也示範瞭如何設定和使用邊的 internal 屬性來區分俱樂部內部和外部的連線,並以實線和虛線分別繪製,清晰地展現網路結構。利用共同鄰居數量計算邊權重,並使用 spring_layout 函式根據權重調整節點位置,使網路圖更具資訊性,強連線的節點會更加靠近。文章也介紹了有向網路的處理方式,使用 DiGraph 類別和荷蘭教室學生友誼網路為例,示範如何讀取、視覺化有向網路,以及如何區分前驅和後繼節點。最後,文章也討論瞭如何將有向網路轉換為無向網路,以及如何將網路資料儲存到檔案和從程式碼建立網路,涵蓋了邊緣列表、加權邊緣列表等格式,並使用 read_edgelist 和 write_edgelist 等函式進行讀寫操作。
使用NetworkX處理網路結構 Chapter 2
節點屬性的操作與視覺化
在NetworkX中,Graph類別允許為每個節點新增任意數量的屬性。以空手道俱樂部網路為例,每個成員最終分裂成兩個不同的俱樂部。我們可以為每個節點新增一個屬性,以描述成員所加入的俱樂部。成員i所加入的俱樂部由以下列表的第i個元素給出:
member_club = [
0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 1, 1, 0, 0, 1, 0,
1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1]
設定節點屬性
我們可以透過迭代所有節點ID,並根據member_club中的值設定節點屬性:
for node_id in G.nodes:
G.nodes[node_id]["club"] = member_club[node_id]
新增節點時自動設定屬性
在新增節點時,也可以透過傳遞關鍵字引數給add_node()方法來設定屬性:
G.add_node(11, club=0)
檢視節點屬性
設定好club屬性後,我們可以檢視個別節點的屬性值:
G.nodes[mr_hi]
{'club': 0}
G.nodes[john_a]
{'club': 1}
節點屬性的視覺化
我們可以根據節點的club屬性為其賦予不同的顏色,並將這些顏色傳遞給draw_networkx()函式進行視覺化:
node_colors = [
'#1f78b4' if G.nodes[v]["club"] == 0
else '#33a02c' for v in G]
nx.draw_networkx(G, karate_pos, label=True, node_color=node_colors)
圖示說明
此圖示展示了空手道俱樂部分裂後的兩個俱樂部。
邊屬性的操作與視覺化
設定邊屬性
與節點屬性類別似,邊屬性儲存在G.edges[v, w]中。由於Graph類別表示無向網路,這些屬性也可以透過G.edges[w, v]存取。我們可以迭代所有邊,檢查邊的端點是否具有相同的club屬性,並設定一個名為internal的屬性來表示該邊是否位於同一個俱樂部內:
for v, w in G.edges:
if G.nodes[v]["club"] == G.nodes[w]["club"]:
G.edges[v, w]["internal"] = True
else:
G.edges[v, w]["internal"] = False
分別繪製內部和外部邊
為了視覺化不同型別的邊,我們可以將內部邊和外部邊分開繪製。首先,我們根據internal屬性找出內部和外部邊:
internal = [e for e in G.edges if G.edges[e]["internal"]]
external = [e for e in G.edges if ~G.edges[e]["internal"]]
然後,分別繪製節點、內部邊和外部邊:
# 繪製節點和節點標籤
nx.draw_networkx_nodes(G, karate_pos, node_color=node_colors)
nx.draw_networkx_labels(G, karate_pos)
# 繪製內部邊(實線)和外部邊(虛線)
nx.draw_networkx_edges(G, karate_pos, edgelist=internal)
nx.draw_networkx_edges(G, karate_pos, edgelist=external, style="dashed")
圖示說明
此圖示展示了空手道俱樂部網路中的內部和外部邊。
新增邊權重
計算連線強度
在某些情況下,網路中的連線具有不同的強度。我們可以計算每條邊的連線強度(tie strength),這裡定義為兩個節點共同鄰居的數量加一:
def tie_strength(G, v, w):
v_neighbors = set(G.neighbors(v))
w_neighbors = set(G.neighbors(w))
return 1 + len(v_neighbors & w_neighbors)
設定邊權重
計算出連線強度後,我們可以將其作為邊權重儲存在網路中:
for v, w in G.edges:
G.edges[v, w]["weight"] = tie_strength(G, v, w)
edge_weights = [G.edges[v, w]["weight"] for v, w in G.edges]
圖示說明
此圖示展示瞭如何計算和儲存邊權重。
使用 NetworkX 處理網路資料:深入探索
在前面的章節中,我們已經探討了使用 NetworkX 處理無向網路的基本方法。然而,在許多實際應用中,關係的方向性是至關重要的。本章節將探討有向網路(Directed Networks)的處理方法,並介紹 NetworkX 中的 DiGraph 類別。
加權網路視覺化
在分析 Zacharay 的空手道俱樂部資料時,我們引入了邊權重的概念來表示成員間關係的強度。透過將邊權重傳遞給 spring_layout() 函式,我們可以使強連線的節點更加緊密地聚集在一起。以下程式碼展示瞭如何視覺化加權網路:
weighted_pos = nx.spring_layout(G, pos=karate_pos, k=0.3, weight="weight")
nx.draw_networkx(
G, weighted_pos, width=8, node_color=node_color,
edge_color=edge_weights, edge_cmap=plt.cm.Blues,
edge_vmin=0, edge_vmax=6)
nx.draw_networkx_edges(
G, weighted_pos, edgelist=internal, edge_color="gray")
nx.draw_networkx_edges(
G, weighted_pos, edgelist=external, edge_color="gray", style="dashed")
內容解密:
spring_layout()函式根據邊權重調整節點位置,使強連線的節點更為接近。draw_networkx()函式根據邊權重為邊著色,使用Blues色彩對映表。- 內部和外部邊分別以實線和虛線繪製,以區分不同型別的關係。
有向網路與 DiGraph 類別
在許多現實世界的網路中,關係的方向性是重要的。例如,員工與老闆之間的關係是單向的。在 NetworkX 中,DiGraph 類別用於處理有向網路。
載入和視覺化有向網路
以下程式碼展示瞭如何載入和視覺化 Andrea Knecht 收集的荷蘭教室中的學生友誼網路:
G = nx.read_gexf("data/knecht2008/klas12b-net-1.gexf")
student_pos = nx.spring_layout(G, k=1.5)
nx.draw_networkx(G, student_pos, arrowsize=20)
內容解密:
read_gexf()函式用於讀取 GEXF 格式的網路資料。spring_layout()函式計算節點的位置。draw_networkx()函式視覺化有向網路,arrowsize引數控制箭頭的大小。
有向網路中的鄰居和前驅/後繼節點
在有向網路中,鄰居的概念被細分為前驅(predecessors)和後繼(successors)節點。以下程式碼展示瞭如何存取這些節點:
list(G.neighbors(0))
list(G.successors(0))
list(G.predecessors(0))
內容解密:
neighbors()方法傳回節點的後繼節點。successors()方法與neighbors()方法功能相同,傳回後繼節點。predecessors()方法傳回節點的前驅節點。
將有向網路轉換為無向網路
DiGraph 類別提供了 to_undirected() 方法,可以將有向網路轉換為無向網路。以下程式碼展示了兩種轉換方式:
G_either = G.to_undirected()
G_both = G.to_undirected(reciprocal=True)
內容解密:
- 當
reciprocal=False時,只要存在單向邊,就會在無向網路中建立一條無向邊。 - 當
reciprocal=True時,只有當雙向都存在邊時,才會在無向網路中建立一條無向邊。
透過本章節的介紹,我們深入瞭解了 NetworkX 中處理有向網路的方法,以及如何視覺化和分析這類別網路。這些技術對於理解複雜系統中的方向性關係具有重要意義。
從資料到網路:以 NetworkX 建立網路模型
在利用 NetworkX 分析系統時,首先必須將系統建模為網路,並將其表示為 NetworkX 中的物件。本章節將闡述建立資料網路表示的基本流程。第一部分涵蓋了在腦海中進行的建模過程:將資料建模為網路。接下來的部分將展示在程式碼中實作的過程:使用兩種不同的方法從資料建立 NetworkX 圖形。
將資料建模為網路
在將資料表示為網路時,需要做出許多決策。不同的網路型別有助於理解不同型別的資料,並提出不同型別的問題。本文將更仔細地探討一些重要的考慮因素。
定義節點和邊緣
在從資料建立網路時,要考慮的最重要問題之一是節點和邊緣究竟應該代表什麼。通常,即使對於相同的資料集,也有很多可能性。任何特定的選擇都會聚焦於資料的某些方面,可能會捨棄其他方面。網路本質上是關於關係和連線的,因此定義節點和邊緣的一種方法是思考您感興趣的關係型別。一些可能性包括:
- 社會關係,例如友誼、戀愛關係,甚至是競爭對手關係
- 流動,例如資訊、人、錢、流體或能量
- 影響力,例如科學參照、軟體依賴關係或蛋白質互動作用
- 連線,例如網路中的電腦、骨骼中的骨頭、鄰國或鐵路線路
- 互動,例如捕食者與獵物的關係或國際條約
- 文字中共現的詞語
一旦您知道要了解哪種型別的關係,節點的選擇就顯而易見:社交網路中的人、蛋白質互動作用中的蛋白質、國際條約中的國家等。同樣,關係的性質通常會建議使用哪種型別的網路。流動具有方向性,建議使用有向網路。鐵路線路允許雙向行駛,建議使用無向網路。如果您對非常具有傳染性的疾病是否可以在兩個人之間傳播感興趣,您可能只需要一個無權重的網路。但是,如果感染通常需要多次接觸,您可能需要一個加權網路來跟蹤兩個人互動的次數。當然,總是可以從相同的資料集建立多個網路,以研究多個問題。
NetworkX 中的圖形類別
NetworkX 提供了多種圖形類別,包括 Graph、DiGraph、MultiGraph 和 MultiDiGraph。這些類別分別用於表示無向網路、有向網路、具有平行邊緣的無向網路和具有平行邊緣的有向網路。
# 建立 Königsberg 網路
G = nx.MultiGraph()
G.add_edges_from([
("North Bank", "Kneiphof", {"bridge": "Krämerbrücke"}),
("North Bank", "Kneiphof", {"bridge": "Schmiedebrücke"}),
("North Bank", "Lomse", {"bridge": "Holzbrücke"}),
("Lomse", "Kneiphof", {"bridge": "Dombrücke"}),
("South Bank", "Kneiphof", {"bridge": "Grüne Brücke"}),
("South Bank", "Kneiphof", {"bridge": "Köttelbrücke"}),
("South Bank", "Lomse", {"bridge": "Hohe Brücke"})
])
內容解密:
上述程式碼建立了一個 MultiGraph 物件 G,用於表示 Königsberg 網路。我們使用 add_edges_from 方法增加了多個邊緣,每個邊緣都具有一個 bridge 屬性,用於表示橋樑的名稱。
將資料儲存到檔案
在建立網路模型後,我們通常需要將其儲存到檔案中,以便於後續的分析和處理。NetworkX 支援多種檔案格式,包括 GraphML、GEXF 和 CSV 等。
使用 GraphML 格式儲存網路
GraphML 是一種根據 XML 的檔案格式,用於表示圖形結構。NetworkX 提供了 write_graphml 函式,用於將網路儲存為 GraphML 檔案。
# 將網路儲存為 GraphML 檔案
nx.write_graphml(G, "konigsberg.graphml")
內容解密:
上述程式碼將 G 網路儲存為 konigsberg.graphml 檔案。我們可以使用 write_graphml 函式將網路儲存為 GraphML 格式,以便於後續的分析和處理。
從程式碼建立網路
除了從檔案中讀取網路外,我們還可以從程式碼中建立網路。NetworkX 提供了多種方法,用於從程式碼中建立網路,包括使用 add_node 和 add_edge 方法。
使用程式碼建立簡單網路
# 建立一個簡單的無向網路
G = nx.Graph()
G.add_node("A")
G.add_node("B")
G.add_node("C")
G.add_edge("A", "B")
G.add_edge("B", "C")
G.add_edge("C", "A")
內容解密:
上述程式碼建立了一個簡單的無向網路 G,包含三個節點和三個邊緣。我們使用 add_node 方法新增節點,並使用 add_edge 方法新增邊緣。
從資料到網路:Wikipedia 網路的多種可能性
Wikipedia 作為一個網路,其樣貌多變,就像詢問彩虹的顏色一樣,沒有唯一的正確答案。讓我們來探索其中的一些可能性。
文章之間的關聯網路
在 Wikipedia 中,一種關係是文章之間的專題關聯。當文章提到相關主題時,可以連結到其他文章。例如,「貓」文章連結到「貓叫聲」文章。要研究主題之間的關係,我們可以構建一個網路,其中節點代表文章,連結代表邊緣。由於連結將你從一篇文章帶到另一篇文章,因此最好將它們表示為有向邊緣。
邊緣權重的選擇
對於邊緣權重,第一眼看來,使用一篇文章到另一篇文章的連結數量作為邊緣權重似乎是合理的。但是,在 Wikipedia 上,通常只在第一次提到某個主題時才會建立連結,這將導致所有邊緣權重都為一。因此,你可以選擇使用無向網路,如果你更關心哪些連結存在,而不是存在多少連結。或者,你可以根據某個主題被提到的次數來確定邊緣權重,即使它只被連結一次。又或者,如果你對連結如何引導讀者的注意力感興趣,你可能會給出更高的權重給在文章中較早出現的連結。
編輯者之間的合作網路
或者,如果你對 Wikipedia 編輯者如何共同工作感興趣,你可能會想研究編輯者之間的溝通和協作關係。在這種情況下,節點代表編輯者,而不是文章。仍然有很多方法可以定義邊緣。一種可能性是,當一個使用者在另一個使用者的討論頁上留言時(通常用於一對一的溝通),就建立一條邊緣,這表明是一條有向邊緣。或者,邊緣可以表示兩個編輯者是否曾經在同一篇文章上工作過。這樣的邊緣可以是無向的,或者可以使用方向來表示資訊從早期的編輯者流向後來的編輯者。
網路表示的多樣性
正如前面的例子所示,即使是一個結構相對簡單的資料集,也可以產生許多不同的網路,每個網路都適合回答不同型別的問題。就像許多事情一樣,當你使用合適的工具時,網路科學會更加有效(也更有趣)。一旦你決定了如何將資料建模為網路,就需要將資料載入到 NetworkX 物件中,例如 Graph,以便進行操作和分析。
讀取和寫入網路檔案
NetworkX 提供了對許多網路檔案格式的讀取和寫入支援。當然,如果一個網路已經以其中一種格式提供,那麼將其載入到 NetworkX 中將非常容易。但是,即使你的資料是以另一種格式儲存,通常也可以透過一些轉換將其轉換為支援的格式。電子試算表通常可以透過重新排序列並匯出為 tab 分隔值(TSV 格式)來轉換為合適的格式。
常見的網路檔案格式
本文將介紹幾種常見的格式,包括鄰接表、邊緣列表、GEXF 和 JSON。
邊緣列表格式
邊緣列表格式是一種簡單但有用的純文字格式。它支援邊緣屬性,但不支援節點屬性。邊緣列表可以使用 read_edgelist() 和 write_edgelist() 函式進行讀取和寫入。邊緣列表網路的每一行包含兩個節點的 ID,代表一條邊緣。
# 示例邊緣列表網路
# 源 目標
Winegroom Uptown
Winegroom Strawshop
Uptown Strawshop
Uptown Amazelake
Strawshop Province
讀取邊緣列表檔案
from pathlib import Path
data_dir = Path('.') / 'data'
G = nx.read_edgelist(data_dir / 'example.edgelist')
pos = nx.spring_layout(G)
nx.draw_networkx(G, pos)
plt.gca().margins(0.15, 0.15)
有向網路的讀取
G = nx.read_edgelist(data_dir / 'example.edgelist', create_using=nx.DiGraph)
nx.draw_networkx(G, pos)
plt.gca().margins(0.15, 0.15)
加權邊緣列表
邊緣列表格式也支援加權邊緣。邊緣權重可以透過在邊緣列表的每一行新增一個數字作為第三個條目來指定,並使用 read_weighted_edgelist() 來讀取檔案。權重將自動新增到名為 weight 的邊緣屬性中。
# 示例加權邊緣列表網路
# 源 目標 權重
Winegroom Uptown 1
Winegroom Strawshop 5
Uptown Strawshop 9
Uptown Amazelake 6
Strawshop Province 3
讀取加權邊緣列表檔案
G = nx.read_weighted_edgelist(data_dir / 'weighted.edgelist')
weights = [d['weight'] for s, t, d in G.edges(data=True)]
nx.draw_networkx(G, pos)
內容解密:
read_weighted_edgelist()函式:用於讀取包含權重的邊緣列表檔案,並自動將權重新增到邊緣屬性中。weights列表:透過遍歷G.edges(data=True)取得每條邊的權重,用於後續的網路視覺化。nx.draw_networkx(G, pos):用於繪製網路圖,其中節點的位置由pos指定。data_dir:定義資料儲存的目錄路徑,用於定位輸入檔案。create_using=nx.DiGraph:指定建立有向圖,用於處理有向網路資料。plt.gca().margins(0.15, 0.15):調整圖表的邊距,以便更好地顯示節點標籤。read_edgelist():用於讀取普通的邊緣列表檔案,將資料轉換為 NetworkX 圖物件。pos = nx.spring_layout(G):計算節點的位置佈局,用於網路視覺化時的節點定位。
透過這些步驟,可以有效地讀取和視覺化不同型別的網路資料,並根據具體需求選擇適合的資料載入方法。