隨著資料量和功能需求的增長,原先將社群資訊儲存為使用者節點屬性的設計已不敷使用。為了支援使用者加入多個社群,我們需要將社群資訊從使用者節點的屬性中分離出來,建立獨立的社群節點,並使用 MEMBER_OF 關係連線使用者和社群。這個調整能有效提升查詢效率,避免在大型屬性列表中進行搜尋,更能發揮圖資料函式庫的優勢。同時,文章也引入了圖投影的概念,說明如何將複雜的異質圖簡化成更易於分析的結構,並以演員和電影的關係為例,展示了圖投影的建立和應用方法,為更深入的圖資料分析奠定了基礎。
重構與演進資料函式庫結構
在前面的章節中,我們瞭解了原始資料的結構,包括 Twitter 使用者、他們之間的關注關係以及所屬的社群。現在,我們將探討如何將這些資料匯入圖形資料函式庫,並逐步演進其結構。
初始資料結構
首先,讓我們檢視一下 edges1.csv 檔案的前幾行:
166712218,106118793
27617013,192318065
116798290,19294743
106118793,197401382
192318065,111310036
這是一個典型的邊緣列表(edgelist),包含了關注者與被關注者之間的關係。其中,第一欄是關注者的 ID,第二欄是被關注者的 ID。
接下來,我們檢視 circles1.csv 檔案:
2,5402612
5,106118793,166712218,14157134
10,47069130,43996908,19281401,116798290,19294743,115430599,27617013,74
265849,111310036,192318065,264224758,197401382,108352527,130104942
這裡,我們看到了每個 Twitter 使用者所屬的社群。每行的第一欄是社群 ID,其餘欄位則是屬於該社群的使用者 ID。
匯入資料到 Neo4j
首先,我們需要使用 Python 將資料匯入 Neo4j 資料函式庫。我們將使用 Neo4jConnect 類別來建立與資料函式庫的連線,並使用 csv 函式庫來讀取 edges1.csv 檔案。
import csv
from graphtastic.database.neo4j import Neo4jConnect
from neo4j import GraphDatabase
# 讀取 edges1.csv 檔案
with open('./data/edges1.csv', 'r') as c:
reader = csv.reader(c)
edgelist = [edge for edge in reader]
# 建立與 Neo4j 的連線
connection = Neo4jConnect('bolt://localhost:7687', 'admin', 'testpython')
# 定義新增邊緣的函式
def add_edge_neo4j(n, m, connection):
cypher = f'MERGE (u1:User {{userID: "{n}"}}) ' \
f'MERGE (u2:User {{userID: "{m}"}}) ' \
'MERGE (u1)-[:FOLLOWS]->(u2)'
connection.query(cypher)
# 新增邊緣到 Neo4j
for n, m in edgelist:
add_edge_neo4j(n, m, connection)
# 關閉連線
connection.close()
程式碼解析:
- 使用
csv.reader讀取edges1.csv檔案,並將其轉換為列表。 - 建立
Neo4jConnect物件以連線至 Neo4j 資料函式庫。 - 定義
add_edge_neo4j函式,使用 Cypher 的MERGE語法新增使用者節點和關注關係。 - 迴圈遍歷
edgelist,並呼叫add_edge_neo4j將每條邊緣新增至 Neo4j。
新增社群資料
現在,我們需要新增社群資料到圖形資料函式庫。我們將定義一個新的函式來新增社群到使用者節點。
# 定義新增社群的函式
def add_circle_neo4j(circle_id, user_id, connection):
cypher = f'MERGE (c:Circle {{circleID: "{circle_id}"}}) ' \
f'MERGE (u:User {{userID: "{user_id}"}}) ' \
'MERGE (u)-[:MEMBER_OF]->(c)'
connection.query(cypher)
# 讀取 circles1.csv 檔案
with open('./data/circles1.csv', 'r') as c:
reader = csv.reader(c)
circle_data = [row for row in reader]
# 新增社群資料到 Neo4j
for row in circle_data:
circle_id = row[0]
user_ids = row[1:]
for user_id in user_ids:
add_circle_neo4j(circle_id, user_id, connection)
程式碼解析:
- 定義
add_circle_neo4j函式,使用 Cypher 的MERGE語法新增社群節點和使用者之間的隸屬關係。 - 讀取
circles1.csv檔案,並將其轉換為列表。 - 迴圈遍歷
circle_data,並呼叫add_circle_neo4j將每個使用者新增至對應的社群。
資料匯入與圖資料函式庫更新
在進行 Twitter 資料的圖資料函式庫建置時,我們需要將 Circle 資料匯入 Neo4j。首先,我們使用 csv 函式庫讀取 circle1.csv 檔案:
with open('./data/circle1.csv', 'r') as c:
reader = csv.reader(c)
circles_raw = [row for row in reader]
print(circles_raw)
內容解密:
- 使用
with open陳述式開啟circle1.csv檔案,確保檔案在讀取後正確關閉。 csv.reader(c)用於讀取 CSV 檔案內容,並將每一行轉換為列表。circles_raw儲存了原始的 Circle 資料,每個列表的第一個元素是 Circle ID,其餘元素是屬於該 Circle 的使用者 ID。
資料格式轉換
為了將 circles_raw 中的資料轉換為更易於處理的格式,我們定義了一個名為 get_user_circle_pairs 的函式:
def get_user_circle_pairs(circles):
pairs = []
for circle in circles:
circle_pairs = [[user, circle[0]] for user in circle[1:]]
[pairs.append(pair) for pair in circle_pairs]
return pairs
內容解密:
- 函式
get_user_circle_pairs將原始 Circle 資料轉換為使用者 ID 和 Circle ID 的配對。 - 對於每個 Circle,
circle_pairs列表生成式用於建立使用者 ID 與 Circle ID 的配對。 - 將每個配對加入
pairs列表,最終傳回所有配對。
更新 Neo4j 資料函式庫
接下來,我們定義一個名為 add_circles_neo4j 的函式,用於將 Circle ID 新增到對應的使用者節點:
def add_circles_neo4j(user_id, circle_id, connection):
cypher = f'MATCH (u:User {{userID: "{user_id}"}})' \
f'SET u.circle = "{circle_id}"'
connection.query(cypher)
內容解密:
- 使用 Cypher 查詢陳述式,透過
MATCH找到具有特定userID的使用者節點。 - 使用
SET將circle屬性新增到該使用者節點,並設定為對應的 Circle ID。 connection.query(cypher)用於執行 Cypher 查詢。
執行資料更新
我們遍歷所有使用者 ID 和 Circle ID 的配對,並呼叫 add_circles_neo4j 函式更新 Neo4j 資料函式庫:
pairs = get_user_circle_pairs(circles_raw)
for user_id, circle_id in pairs:
connection = Neo4jConnect('bolt://localhost:7687', 'admin', 'testpython')
add_circles_neo4j(user_id, circle_id, connection)
connection.close()
內容解密:
- 首先呼叫
get_user_circle_pairs取得使用者 ID 和 Circle ID 的配對。 - 遍歷所有配對,並為每個配對建立 Neo4j 連線,呼叫
add_circles_neo4j更新資料函式庫。 - 更新完成後關閉連線。
圖資料函式庫結構最佳化
隨著新功能的引入,使用者可以加入多個 Circle,因此需要更新資料函式庫結構以支援這一變更。
當前查詢方式
目前,查詢特定 Circle 中的使用者可以使用簡單的 MATCH 陳述式:
MATCH (u:User {circle: '5'}) RETURN u
新功能的查詢挑戰
如果將多個 Circle 的資訊儲存在使用者節點的屬性中,則需要修改查詢陳述式,例如:
MATCH (u:User)
WHERE '5' in u.circle RETURN u
內容解密:
- 當使用者可以屬於多個 Circle 時,將 Circle 資訊儲存為列表形式。
- 使用
WHERE子句和IN運算子來查詢特定 Circle 中的使用者。
結構最佳化的必要性
隨著使用者加入的 Circle 增加,上述查詢方式可能導致效能問題。因此,我們需要最佳化資料函式庫結構。
結構最佳化方案
我們將 Circle 資訊從使用者節點的屬性轉變為獨立的節點,如下圖所示:
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333
title 結構最佳化方案
rectangle "結構最佳化方案" as n1
rectangle "實作" as n2
rectangle "應用" as n3
n1 --> n2
n2 --> n3
@enduml此圖示說明瞭使用者與 Circle 之間的多對多關係。
內容解密:
- 將 Circle 建模為獨立的節點,可以更好地利用圖資料函式庫的優勢。
- 這種結構使得查詢使用者與 Circle 之間的關係更加高效。
更新現有資料函式庫
為了保留現有資料,我們需要逐步更新資料函式庫結構,最終移除舊結構的資料。
這樣,我們不僅更新了資料函式庫以支援新功能,還最佳化了資料函式庫結構以提高未來查詢的效率。
重構與演進資料結構
在進行資料結構變更的同時,我們仍需將新的使用者、追蹤關係和社群納入資料函式庫中。有兩個檔案包含了已新增至Neo4j資料函式庫的資料以及其他資訊:edges2.csv和circle2.csv。讓我們先將它們匯入Python,轉換成二維列表,就像之前處理edgelist和circles一樣:
with open('./data/edges2.csv', 'r') as c:
reader = csv.reader(c)
edgelist2 = [edge for edge in reader]
with open('./data/circle2.csv', 'r') as c:
reader = csv.reader(c)
circles2 = [row for row in reader]
內容解密:
這段程式碼讀取了兩個CSV檔案,分別是edges2.csv和circle2.csv,並將其內容轉換為二維列表。這裡使用了Python的csv.reader功能來讀取檔案,並透過列表推導式將讀取到的資料存入edgelist2和circles2變數中。
新增節點與邊緣至Neo4j
由於User節點及其FOLLOWS關係的處理方式未變,我們可以直接使用之前定義的add_edge_neo4j()函式,將新匯入的節點和邊緣加入Neo4j:
for n, m in edgelist2:
connection = Neo4jConnect('bolt://localhost:7687', 'admin', 'testpython')
add_edge_neo4j(n, m, connection)
connection.close()
內容解密:
這段程式碼遍歷了edgelist2中的每一個邊緣,並使用add_edge_neo4j()函式將其加入Neo4j資料函式庫。這裡建立了一個Neo4j連線,呼叫函式新增邊緣後關閉連線。每個邊緣代表了兩個使用者之間的追蹤關係。
解析社群資料
接下來,我們需要像之前一樣,使用get_user_circle_pairs()函式來解析circles2中的社群資料:
pairs2 = get_user_circle_pairs(circles2)
內容解密:
這段程式碼呼叫了get_user_circle_pairs()函式,將circles2中的社群資料解析成使用者ID和社群ID的配對,並將結果存入pairs2變數中。
新增社群至Neo4j(新資料結構)
為了滿足新的資料結構需求,我們需要準備一個新的函式add_circles_neo4j_new_schema(),用於新增社群至資料函式庫。這個函式的輸入引數包括使用者ID、社群ID和Neo4j連線:
def add_circles_neo4j_new_schema(user_id, circle_id, connection):
cypher = f'MATCH (u:User {{userID: "{user_id}"}})' \
f'MERGE (c:Circle {{circleID: "{circle_id}"}})' \
f'MERGE (u)-[:MEMBER_OF]->(c)'
connection.query(cypher)
內容解密:
這個函式執行了一個Cypher查詢,首先根據使用者ID匹配一個User節點,然後合併(或建立)一個具有指定ID的Circle節點,最後在這兩個節點之間建立一個MEMBER_OF關係。這裡使用了引數化的Cypher查詢,以避免注入攻擊。
執行新增社群的操作
現在,我們可以使用add_circles_neo4j_new_schema()函式,透過遍歷pairs2中的使用者/社群配對,將社群節點新增至圖中:
for user_id, circle_id in pairs2:
connection = Neo4jConnect('bolt://localhost:7687', 'admin', 'testpython')
add_circles_neo4j_new_schema(user_id, circle_id, connection)
connection.close()
內容解密:
這段程式碼遍歷了所有的使用者/社群配對,並為每個配對呼叫add_circles_neo4j_new_schema()函式,將對應的關係新增至Neo4j資料函式庫。
驗證新資料結構
新增完社群節點後,我們可以轉到Neo4j Browser,執行一個Cypher查詢來提取使用者和社群的資訊。現在,我們可以使用MATCH來尋找路徑,而不是在大型屬性列表中使用WHERE進行搜尋,這更適合圖資料函式庫:
MATCH (u:User)-[:MEMBER_OF]->(c:Circle {circleID: '5'})
RETURN u, c
內容解密:
這個Cypher查詢尋找所有與ID為'5’的社群有MEMBER_OF關係的使用者,並傳回這些使用者和社群節點。
清理舊資料
最後,為了清理舊資料,我們可以匹配所有User節點,並使用REMOVE函式移除它們的circle屬性:
MATCH (u:User)
REMOVE u.circle
內容解密:
這個Cypher查詢移除了所有User節點上的circle屬性,因為在新的資料結構中,這些資訊已經被轉移到獨立的Circle節點和相關關係中。
圖投影的藝術
在前面的章節中,我們探討瞭如何建立初始的資料函式庫結構(稱為變更前的結構),這涉及結合Python和Neo4j的指令碼(使用Cypher查詢語言)。最後一個階段是考慮到我們所學的一切,並將變更應用到更新的結構設計中。本章節重點在於建立處理不斷演變和更新結構所需的一切,這是在設計時應盡可能考慮的事情。
圖投影的概念
在圖資料模型中,資料通常包含事物之間的關係,無論是人、語言、運輸樞紐或其他例子。許多資料模型具有多種型別的節點和關係,使得一個節點或邊緣與另一個不相等同。這些型別的圖被稱為異質圖,我們在這本文中已經見過多次。異質圖的一種是二分圖,其中有兩種型別的節點。在二分圖中,只有不同型別的節點可以分享邊緣。例如,參照網路可以表示為二分圖,作者與他們所寫的文章相連。
圖投影的必要性
分析具有多種節點和邊緣型別的異質圖可能會很棘手。為了說明這一點,首先考慮圖8.1中的作者圖。我們可以輕鬆地使用這個圖的原生結構來回答一些簡單的問題,例如使用節點度來查詢作者撰寫的文章數量,或者相反,使用相同的方法查詢一篇文章的作者數量。
# 計算作者的文章數量
MATCH (a:Author)-[:WROTE]->(article:Article)
RETURN a.name, COUNT(article) AS num_articles
圖投影的作用
然而,為了衡量作者群體共同合作的趨勢,例如,使用社群檢測演算法(正如我們在第4章節,構建知識圖譜中所使用的),我們需要遍歷透過文章和作者的路徑,以便找到合理的社群。對二分圖執行社群檢測等演算法的結果可能不一定有意義,當試圖檢查單純作者或單純文章群體的連線性時。
如何使用圖投影
在本章節中,我們將重點放在建立圖投影上,您將能夠對其進行複雜的分析,甚至使用其他演算法(如機器學習和統計方法)來形成洞察。我們將首先解釋什麼是投影,接著是如何在實踐中使用投影。我們將利用Cypher查詢語言和Python,在igraph和Neo4j中建立投影。
技術需求
- Jupyter Notebook(需要python>=3.8.0)
- neo4j==5.5.0
- igraph==0.9.8
- matplotlib==3.6.3
- Neo4j Desktop
圖投影的實作
我們將使用一個使用案例,專注於使用圖資料科學來查詢演員出演的電影、共同出演的電影以及其他我們可以用靈活的圖結構定義的多重關係。透過本章節的學習,您將能夠建立一個投影並將其用於您的使用案例。
# 在Neo4j中建立圖投影
CALL gds.graph.project(
'myGraph',
['Actor', 'Movie'],
['ACTED_IN', 'STARRED_WITH']
)
圖投影的應用
透過本章節,您將瞭解如何建立圖投影並將其用於下游分析任務。這些投影可以包含分析相關的、可能聚合的拓撲和屬性資訊。儲存的檢視將包含圖的分析相關、可能聚合的拓撲屬性資訊。
圖投影的好處
- 可以對異質圖進行複雜的分析
- 可以使用機器學習和統計方法來形成洞察
- 可以用於下游分析任務