圖資料函式庫在處理具有複雜關聯的資料時,相較於關聯式資料函式庫展現出更高的效率和靈活性。尤其在需要遍歷路徑或頻繁更新資料結構的應用場景中,圖資料函式庫更能有效降低操作複雜度。例如,在火車網路的案例中,圖資料函式庫能更直觀地表示車站和路線之間的關係,簡化路徑查詢的過程。此外,圖資料函式庫能更彈性地應對資料模型的變化,例如新增公車路線等資訊,無需修改既有表格結構,只需新增節點和邊緣即可。本文將進一步探討圖資料函式庫的建構方法,並使用 Python 的 NetworkX 和 igraph 函式庫示範如何操作圖資料。
圖資料函式庫與關聯式資料函式庫的比較
在探討資料儲存與查詢的最佳實踐時,我們經常會對比圖資料函式庫(Graph Databases, GDBs)與傳統的關聯式資料函式庫(Relational Databases, RDBs)。本章將深入分析這兩種資料函式庫系統的差異,並介紹圖資料函式庫在各個產業中的實際應用。
使用圖結構表示資料的優勢
以一個火車網路為例,當我們使用圖結構來表示這個網路時,我們更強調資料點之間的關係。從Truro車站開始,我們可以找到服務該車站的火車。然而,在遍歷圖形以找到Truro和Glasgow Central之間可能的行駛路線時,在每個車站或火車節點,我們考慮的資料點較少,因此選項也較少。這與RDB示例形成對比,在RDB中,需要重複的表連線來傳回路徑。在這種情況下,圖形表示所需的操作複雜度較低,這意味著更快、更有效的方法。許多其他使用案例中,那些需要某種路徑尋找的任務通常受益於圖資料模型。
圖資料函式庫的靈活性
除了更適合特定型別的查詢之外,圖形通常在需要靈活、演化的資料模型時很有用。同樣使用火車網路的例子,假設作為資料函式倉管理員,您收到了一個將公交運輸連結新增到資料模型的請求。使用RDB時,需要一個新表,因為幾個公交服務可能會服務於每個火車站。在這個新表中,需要從現有表中複製每個車站的名稱,以便與相關的公交服務一起列出。這種重複不僅增加了儲存的資料大小,還增加了資料函式庫模式的複雜性。
圖 1.8 – 將新資料型別(公車)新增到火車站圖
在圖形中表示火車站資料時,可以直接將有關公車的新資訊作為新的節點型別新增到現有資料函式庫中。無需新的表格,也不需要複製每個車站節點來表示所需的資訊;現有的火車節點可以直接連結到新的公車節點。這適用於任何需要在傳統RDB中新增新表的新的資料型別。
圖資料科學在各個產業中的應用
圖資料科學廣泛應用於各個產業。主要的使用領域包括:
- 金融:用於欺詐檢測和投資組合風險評估。
- 政府:用於情報分析和供應鏈分析。
- 生命科學:用於研究患者在醫院中的路徑、藥物反應率以及感染在人群中的傳播。
- 網路與IT:用於安全網路和使用者存取管理。
- 電信:用於網路最佳化和客戶流失預測。
- 行銷:主要用於客戶和市場細分。
- 社交媒體分析:用於防禦品牌攻擊、發現弱點以及審核最嚴重的內容。
NetworkX和igraph簡介
本章將介紹兩個用於建立記憶體圖形的Python套件:NetworkX和igraph。NetworkX允許您建立圖形、進行圖形操作、研究和視覺化其結構,並在處理圖形時執行多個圖形操作函式。igraph包含一套實用且實用的分析工具,旨在使其高效且易於使用,並且可重現。
NetworkX基礎
NetworkX是Python中原有的圖形函式庫之一,特別注重使用者友好性和Pythonic風格。它還原生包含計算一些經典網路分析測量的方法。
- 要將NetworkX匯入Python,請使用以下命令:
import networkx as nx
- 要建立一個空圖
g,請使用以下命令:
g = nx.Graph()
內容解密:
這段程式碼首先匯入了NetworkX套件並賦予別名nx,以便於後續呼叫。然後,它建立了一個空的無向圖g。這是使用NetworkX進行圖分析的第一步,為後續新增節點和邊奠定了基礎。
圖表在現實世界的應用
在前一章中,我們探討了為什麼應該開始思考圖表(Graph),以及這些方法在各個行業中變得越來越流行和被廣泛討論的原因。在本章中,我們將深入瞭解圖表的基本元素,包括節點(Node)、邊(Edge)和屬性(Property),並介紹兩種流行的圖表分析函式庫:NetworkX和igraph。
NetworkX基礎
NetworkX是一個用於建立、操作和研究複雜網路結構的Python函式庫。以下是使用NetworkX建立圖表的基本步驟:
首先,需要匯入NetworkX函式庫並建立一個空的圖表物件:
import networkx as nx g = nx.Graph()可以使用
add_node方法新增節點到圖表中:g.add_node("Jeremy")或者,可以使用
add_nodes_from方法一次新增多個節點:g.add_nodes_from(["Mark", "Jeremy"])在新增節點時,可以透過傳遞節點和字典元組來為節點新增屬性:
g.add_nodes_from([("Mark", {"followers": 2100}), ("Jeremy", {"followers": 130})])使用
add_edge方法可以新增邊到圖表中,並參照已經存在的節點:g.add_edge("Jeremy", "Mark")需要注意的是,在NetworkX中,當新增邊時,如果指定的節點尚未在圖表中,則會被隱式新增。
為了確認圖表包含節點和邊,可以使用
matplotlib和networkx.draw進行繪製:import matplotlib.pyplot as plt nx.draw(g, with_labels=True) plt.show()
程式碼解析:
import networkx as nx
import matplotlib.pyplot as plt
# 建立一個空的圖表物件
g = nx.Graph()
# 新增節點到圖表中
g.add_nodes_from([("Mark", {"followers": 2100}), ("Jeremy", {"followers": 130})])
# 新增邊到圖表中
g.add_edge("Jeremy", "Mark")
# 繪製圖表
nx.draw(g, with_labels=True)
plt.show()
內容解密:
此程式碼首先匯入必要的函式庫,然後建立一個空的圖表物件。接著,它使用add_nodes_from方法新增兩個節點(“Mark"和"Jeremy”)並為它們新增屬性(followers)。之後,使用add_edge方法在兩個節點之間新增一條邊。最後,使用matplotlib繪製圖表並顯示節點名稱。
igraph基礎
igraph是另一個用於圖表分析和操作的函式庫,它在處理大型圖表時比NetworkX更快。以下是使用igraph建立圖表的基本步驟:
匯入igraph函式庫並建立一個空的圖表物件:
import igraph as ig g = ig.Graph()使用
add_vertices方法新增節點到圖表中:g.add_vertices(2)為節點新增屬性,可以透過存取圖表物件的頂點(vertices)來實作:
g.vs[0]["name"] = "Jeremy" g.vs[1]["name"] = "Mark" g.vs[0]["followers"] = 130 g.vs[1]["followers"] = 2100或者,可以使用列表方式為節點新增屬性:
g.vs["name"] = ["Jeremy", "Mark"] g.vs["followers"] = [130, 2100]使用
add_edges方法新增邊到圖表中:g.add_edges([(0, 1)])
程式碼解析:
import igraph as ig
# 建立一個空的圖表物件
g = ig.Graph()
# 新增節點到圖表中
g.add_vertices(2)
# 為節點新增屬性
g.vs["name"] = ["Jeremy", "Mark"]
g.vs["followers"] = [130, 2100]
# 新增邊到圖表中
g.add_edges([(0, 1)])
內容解密:
此程式碼首先匯入igraph函式庫並建立一個空的圖表物件。然後,它使用add_vertices方法新增兩個節點。接著,為這些節點新增屬性(name和followers)。最後,使用add_edges方法在兩個節點之間新增一條邊。
從表格資料轉換為圖表資料模型
在介紹圖表資料模型的強大功能時,我們首先會使用一個來自Facebook的真實社交媒體資料集。這個開源資料包含了Facebook頁面的資訊,包括頁面名稱和型別,共包含四種型別的頁面:電視節目、公司、政治人物和政府組織。此外,我們還擁有頁面之間的互贊資料,如果兩個頁面互相在Facebook上點讚,便會在我們的資料中呈現。
檢查資料
我們需要更仔細地觀察我們的資料,以確定如何將其轉換為圖表格式。我們的資料以.csv格式提供,因此本質上是表格形式的。檢視Stanford資料集的前幾行(關於一個大型頁面對頁面網路),以及該資料集中的一個檔案——musae_facebook_target.csv:
id,facebook_id,page_name,page_type
0,145647315578475,The Voice of China 中國好聲音,tvshow
1,191483281412,U.S. Consulate General Mumbai,government
2,144761358898518,ESET,company
從中我們可以觀察到,標題和前幾行包含了ID、Facebook ID、頁面名稱和頁面型別。這些資料將代表我們的節點型別和節點屬性。
另一個名為musae_facebook_edges.csv的檔案包含了Facebook頁面節點之間的關係。檢視這些關係的前幾行,我們看到:
id_1,id_2
0,18427
1,21708
1,22208
資料顯示,每一行對應於兩個Facebook頁面之間的邊緣。檢視.csv檔案標題,這些頁面透過其ID(id_1和id_2)來參照。這種以表格形式儲存關係的方式被稱為邊緣列表(edgelist)。簡單來說,邊緣列表是一種用於表示圖表的資料結構,它以列表形式列出圖表的所有邊緣。
設計架構
根據我們在CSV檔案中找到的資訊,我們現在可以開始考慮資料的架構。在我們的Facebook資料中,我們有透過互贊(在Facebook或類別似的社交媒體網站上,雙方對相關內容的點讚)連線起來的頁面。因此,將頁面建模為節點,將互贊建模為節點之間的關係是很合理的。
由於我們只處理Facebook頁面的點讚,因此只需要考慮一種型別的邊緣。因此,在這種情況下,不需要明確命名它們,因為每個邊緣都是等價的。此外,由於我們只有關於互贊的資訊,而不是單方面的點讚,因此將邊緣視為無向的是合理的(我們在第一章《在真實世界中介紹圖表》中瞭解到,無向圖是指節點與其邊緣連線之間沒有隱含方向的圖表)。
我們的資料集中的節點可以具有不同的型別,取決於它們所代表的Facebook頁面型別。我們可以將節點的型別視為頁面型別,並將我們擁有的其他資訊視為節點屬性——即ID和頁面名稱。由於Facebook ID與任何其他欄位無關,並且在沒有存取Facebook內部資料的情況下,對後續分析幾乎沒有幫助,因此我們可以在圖表架構中丟棄它——讓我們稱之為冗餘特徵。
因此,在這種情況下,我們將建立一個無向的異質圖表(heterogeneous graph,一種特殊的資訊網路,包含多種型別的物件或多種型別的連結),其架構如下圖所示:
圖2.1 – 無向異質圖表
此架構表示每種型別的節點之間可能存在邊緣。迴繞到同一型別節點的邊緣(稱為自環)表示相同型別的節點之間也可以存在關係。最後,列出了每個屬性的資料型別,這些對於每個節點都是相同的:ID作為唯一的鍵整數,名稱作為字串。
在Python中實作模型
在接下來的步驟中,我們將載入要使用的資料。首先,我們可以使用標準的Python csv函式庫從musae_facebook_target.csv匯入節點:
import csv
with open('./data/facebook_large/musae_facebook_target.csv', 'r', encoding='utf-8') as csv_file:
reader = csv.reader(csv_file)
內容解密:
import csv:匯入Python的內建csv模組,用於讀取CSV檔案。with open(...) as csv_file::使用with陳述式開啟檔案,能夠自動管理檔案的關閉,無論程式碼塊是否正常執行或發生異常。'./data/facebook_large/musae_facebook_target.csv':指定要開啟的CSV檔案的路徑。'r':指設定檔案的開啟模式為只讀模式。encoding='utf-8':指設定檔案的編碼格式為UTF-8,以正確處理檔案中的字元。reader = csv.reader(csv_file):建立一個CSV閱讀器物件,用於逐行讀取CSV檔案中的內容。
在Python中實作模型
載入與處理資料
首先,我們需要載入CSV檔案中的資料。由於某些節點名稱包含非標準字元,因此我們使用utf-8編碼開啟檔案。
import csv
with open('./data/musae_facebook_target.csv', 'r', encoding='utf-8') as csv_file:
reader = csv.reader(csv_file)
data = [line for line in reader]
print(data[:10])
print(len(data))
內容解密:
- 使用
csv.reader讀取CSV檔案,並將其轉換為列表的列表。 - 列表解析式(list comprehension)是一種特殊的結構,用於在列表內封裝迴圈,以傳回根據迴圈邏輯的新列表。
- 透過檢查前幾行和計算匯入列表的長度,確認CSV檔案是否正確載入。
新增節點與屬性
在igraph中,我們可以一次新增一個節點來建立圖形。但是,為了提高效率,我們採用列表方式一次新增多個節點。
- 準備節點資料:使用列表解析式準備節點名稱和屬性列表。
node_ids = [int(row[0]) for row in data[1:]]
page_names = [row[2] for row in data[1:]]
page_types = [row[3] for row in data[1:]]
內容解密:
[1:]列表切片用於移除CSV標頭行。- 準備好的列表將用於新增節點屬性。
- 驗證ID列的連續性:比較ID列與Python的
range()函式,確保ID是連續的整數。
assert node_ids == list(range(len(node_ids)))
內容解密:
assert陳述式用於確認node_ids列表與range()生成的列表是否相同。
- 建立圖形並新增節點:使用igraph建立一個新的無向圖,並新增節點。
import igraph as ig
g = ig.Graph(directed=False)
g.add_vertices(len(node_ids))
內容解密:
g.add_vertices()方法用於新增指定數量的節點。
- 驗證節點數量:檢查新增的節點數量是否正確。
print(len(g.vs))
assert len(node_ids) == len(g.vs)
內容解密:
g.vs屬性代表圖中的所有節點。- 使用
assert陳述式確認節點數量是否正確。
- 新增節點屬性:將準備好的屬性列表新增到節點。
g.vs['page_name'] = page_names
g.vs['page_type'] = page_types
內容解密:
- 使用
g.vs屬性的字典式存取方式,將屬性列表分配給對應的節點屬性。
新增邊緣
- 載入邊緣資料:匯入包含邊緣資訊的CSV檔案。
with open('./data/musae_facebook_edges.csv', 'r') as csv_file_2:
reader = csv.reader(csv_file_2)
edge_data = [row for row in reader]
print(edge_data[:10])
print(len(edge_data))
內容解密:
- 使用
csv.reader讀取邊緣CSV檔案,並將其轉換為列表的列表。
- 準備邊緣資料:將邊緣資料轉換為整數,並移除標頭行。
edges = [[int(row[0]), int(row[1])] for row in edge_data[1:]]
print(edges[:10])
內容解密:
- 使用列表解析式將邊緣資料轉換為整數,並移除標頭行。
- 新增邊緣:使用
g.add_edges()方法將邊緣新增到圖中。
g.add_edges(edges)
內容解密:
g.add_edges()方法用於一次性新增多個邊緣。
- 驗證邊緣數量:檢查新增的邊緣數量是否正確。
print(len(g.es))
內容解密:
g.es屬性代表圖中的所有邊緣。
- 驗證特定邊緣:檢查特定的邊緣是否正確新增。
first_edge = g.es[0]
print(first_edge.source)
print(first_edge.target)
print(g.vs[0]['page_name'])
print(g.vs[18427]['page_name'])
內容解密:
- 使用
g.es[0]存取第一個邊緣,並檢查其源節點和目標節點的ID。 - 使用
g.vs存取節點屬性,確認邊緣所連線的Facebook頁面名稱。