隨著資料量和功能需求的增長,原先將社群資訊儲存為使用者節點屬性的設計已不敷使用。為了支援使用者加入多個社群,我們需要將社群資訊從使用者節點的屬性中分離出來,建立獨立的社群節點,並使用 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()

程式碼解析:

  1. 使用 csv.reader 讀取 edges1.csv 檔案,並將其轉換為列表。
  2. 建立 Neo4jConnect 物件以連線至 Neo4j 資料函式庫。
  3. 定義 add_edge_neo4j 函式,使用 Cypher 的 MERGE 語法新增使用者節點和關注關係。
  4. 迴圈遍歷 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)

程式碼解析:

  1. 定義 add_circle_neo4j 函式,使用 Cypher 的 MERGE 語法新增社群節點和使用者之間的隸屬關係。
  2. 讀取 circles1.csv 檔案,並將其轉換為列表。
  3. 迴圈遍歷 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 的使用者節點。
  • 使用 SETcircle 屬性新增到該使用者節點,並設定為對應的 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.csvcircle2.csv。讓我們先將它們匯入Python,轉換成二維列表,就像之前處理edgelistcircles一樣:

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.csvcircle2.csv,並將其內容轉換為二維列表。這裡使用了Python的csv.reader功能來讀取檔案,並透過列表推導式將讀取到的資料存入edgelist2circles2變數中。

新增節點與邊緣至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']
)

圖投影的應用

透過本章節,您將瞭解如何建立圖投影並將其用於下游分析任務。這些投影可以包含分析相關的、可能聚合的拓撲和屬性資訊。儲存的檢視將包含圖的分析相關、可能聚合的拓撲屬性資訊。

圖投影的好處
  • 可以對異質圖進行複雜的分析
  • 可以使用機器學習和統計方法來形成洞察
  • 可以用於下游分析任務