用 NetworkX + Gephi + Nebula Graph 分析<權力的遊戲>人物關係(下篇)

上一篇[1]中,我們通過 NetworkX 和 Gephi 展示了<權力的遊戲>中的人物關係。在本篇中,我們將展示如何通過 NetworkX 訪問圖數據庫 Nebula Graph

NetworkX

NetworkX [2] 是一個用 Python 語言開發的圖論與複雜網絡建模工具,內置了大量常用的圖與複雜網絡分析算法,可以方便地進行復雜網絡數據分析、仿真建模等工作,功能豐富,簡單易用。

在 NetworkX 中,圖是由頂點、邊和可選的屬性構成的數據結構。頂點表示數據,邊是由兩個頂點唯一確定的,表示兩個頂點之間的關係。頂點和邊也可以擁有更多的屬性,以存儲更多的信息。

NetworkX 支持 4 種類型的圖:

  • Graph:無向圖
  • DiGraph: 有向圖
  • MultiGraph: 多重無向圖
  • MultiDiGraph: 多重有向圖

在 NetworkX 中創建一個無向圖:

import networkx as nx
G = nx.Graph()

添加頂點:

G.add_node(1)
G.add_nodes_from([2,3,4])
G.add_node(2,name='Tom',age=23)

添加邊:

G.add_edge(2,3)
G.add_edges_from([(1,2),(1,3)])
g.add_edge(1, 2, start_year=1996, end_year=2019)

在上一篇文章(一)中,我們已經演示了 NetworkX 的 Girvan-Newman 社區發現算法。

圖數據庫 Nebula Graph

NetworkX 通常使用本地文件作爲數據源,這在靜態網絡研究的時候沒什麼問題,但如果圖網絡經常會發生變化——例如某些中心節點已經不存在(Fig.1)或者引入了重要的網絡拓撲變化(Fig.2)——每次生成全新的靜態文件再加載分析就有些麻煩,最好整個變化過程可以持久化在一個數據庫中,並且可以實時地直接從數據庫中加載子圖或者全圖做分析。本文選用 Nebula Graph [3]作爲存儲圖數據的圖數據庫。


Fig. 1


Fig. 2

Nebula Graph 提供了兩種方式來獲取圖結構:

  1. 編寫一個查詢語句,拉取一個子圖;
  2. 全量掃描底層存儲,獲取一個完整的全圖。

第一種方式適合在一個大規模的圖網絡中通過精細的過濾和剪枝條件來獲取符合需求的若干個點和邊。第二種方式更適合於全圖的分析,這通常是在項目前期對全圖進行一些啓發式探索,當有進一步認知後再用第一種方式做精細的剪枝分析。

分析完 Nebula Graph 兩種獲取圖結構方式後,下面來查看 Nebula Graph 的 Python 客戶端代碼,nebula-python/nebula/ngStorage/StorageClient.py 與 nebula-python/nebula/ngMeta/MetaClient.py 就是和底層存儲交互的 API, 裏面有掃描點、掃描邊、讀取一堆屬性等等一系列豐富的接口。

下面兩個接口可以用來讀取所有的點、邊數據:

def scan_vertex(self, space, return_cols, all_cols, limit, start_time, end_time)
def scan_edge(self, space, return_cols, all_cols, limit, start_time, end_time)
  1. 初始化一個客戶端,和一個 scan_edge_processor。scan_edge_processor 用來對讀出來的邊數據進行解碼:
meta_client = MetaClient([('192.168.8.16', 45500)])
meta_client.connect()
storage_client = StorageClient(meta_client)
scan_edge_processor = ScanEdgeProcessor(meta_client)
  1. 初始化 scan_edge 接口的各項參數:
space_name = 'nba' # 要讀取的圖空間名稱
return_cols = {} # 要返回的邊(或點)及其屬性列
return_cols['serve'] = ['start_year', 'end_year']
return_cols['follow'] = ['degree']
allCols = False # 是否返回所有屬性列,當該值爲 False 時,僅返回在 returnCols 裏指定的屬性列,當爲 True 時,返回所有屬性列
limit = 100 # 最多返回的數據條數
start_time = 0 
end_time = sys.maxsize
  1. 調用 scan_part_edge 接口,該接口會返回一個 scan_edge_response 對象的迭代器:
scan_edge_response_iterator = storage_client.scan_edge(space_name, return_cols, all_cols, limit, start_time, end_time)
  1. 不斷讀取該迭代器所指向的 scan_edge_response 對象中的數據,直到讀取完所有數據:
while scan_edge_response_iterator.has_next():
    scan_edge_response = scan_edge_response_iterator.next()
    if scan_edge_response is None:
        print("Error occurs while scaning edge")
        break
    process_edge(space, scan_edge_response)

其中,process_edge 是自定義的一個處理讀出來邊數據的函數,該函數可以先使用 scan_edge_processor 對 scan_edge_response 中的數據進行解碼,解碼後的數據可以直接打印出來,也可以做一些簡單處理,另作他用,比如:將這些數據讀入計算框架 NetworkX 裏。

  1. 處理數據。在這裏我們將讀出來的所有邊都添加到 NetworkX 中的圖G 裏:
def process_edge(space, scan_edge_response):
    result = scan_edge_processor.process(space, scan_edge_response)
    # Get the corresponding rows by edge_name
    for edge_name, edge_rows in result.rows.items():
        for row in edge_rows:
            srcId = row.default_properties[0].get_value()
            dstId = row.default_properties[2].get_value()
            print('%d -> %d' % (srcId, dstId))
            props = {}
            for prop in row.properties:
                prop_name = prop.get_name()
                prop_value = prop.get_value()
                props[prop_name] = prop_value
            G.add_edges_from([(srcId, dstId, props)]) # 添加邊到 NetworkX 中的圖G

讀取頂點數據的方法和上面的流程類似。

此外,對於分佈式的一些圖計算框架[4]來說,Nebula Graph 還提供了根據分片 (partition) 併發地批量讀取存儲的功能,這會在之後的文章中演示。

在 NetworkX 中進行圖分析

當我們把所有點和邊數據都按照上述流程讀入 NetworkX 後,我們還可以做一些基本的圖分析和圖計算:

  1. 繪製圖:
nx.draw(G, with_labels=True, font_weight='bold')
import matplotlib.pyplot as plt
plt.show()
plt.savefig('./test.png')

繪製出來的圖:

  1. 打印出圖中的所有點和邊:
print('nodes: ', list(G.nodes))
print('edges: ', list(G.edges))
輸出的結果:
nodes:  [109, 119, 129, 139, 149, 209, 219, 229, 108, 118, 128, 138, 148, 208, 218, 228, 107, 117, 127, 137, 147, 207, 217, 227, 106, 116, 126, 136, 146, 206, 216, 226, 101, 111, 121, 131, 141, 201, 211, 221, 100, 110, 120, 130, 140, 150, 200, 210, 220, 102, 112, 122, 132, 142, 202, 212, 222, 103, 113, 123, 133, 143, 203, 213, 223, 104, 114, 124, 134, 144, 204, 214, 224, 105, 115, 125, 135, 145, 205, 215, 225]
edges:  [(109, 100), (109, 125), (109, 204), (109, 219), (109, 222), (119, 200), (119, 205), (119, 113), (129, 116), (129, 121), (129, 128), (129, 216), (129, 221), (129, 229), (129, 137), (139, 138), (139, 212), (139, 218), (149, 130), (149, 219), (209, 123), (219, 130), (219, 112), (219, 104), (229, 147), (229, 116), (229, 141), (229, 144), (108, 100), (108, 101), (108, 204), (108, 206), (108, 214), (108, 215), (108, 222), (118, 120), (118, 131), (118, 205), (118, 113), (128, 116), (128, 121), (128, 201), (128, 202), (128, 205), (128, 223), (138, 115), (138, 204), (138, 210), (138, 212), (138, 221), (138, 225), (148, 127), (148, 136), (148, 137), (148, 214), (148, 223), (148, 227), (148, 213), (208, 127), (208, 103), (208, 104), (208, 124), (218, 127), (218, 110), (218, 103), (218, 104), (218, 114), (218, 105), (228, 146), (228, 145), (107, 100), (107, 204), (107, 217), (107, 224), (117, 200), (117, 136), (117, 142), (127, 114), (127, 212), (127, 213), (127, 214), (127, 222), (127, 226), (127, 227), (137, 136), (137, 213), (137, 150), (147, 136), (147, 214), (147, 223), (207, 121), (207, 140), (207, 122), (207, 134), (217, 126), (217, 141), (217, 124), (217, 144), (106, 204), (106, 212), (106, 113), (116, 141), (116, 126), (116, 210), (116, 216), (116, 121), (116, 113), (116, 105), (126, 216), (136, 210), (136, 213), (136, 214), (146, 202), (146, 210), (146, 215), (146, 222), (146, 226), (206, 123), (216, 144), (216, 105), (226, 140), (226, 112), (226, 114), (226, 144), (101, 100), (101, 102), (101, 125), (101, 204), (101, 215), (101, 113), (101, 104), (111, 200), (111, 204), (111, 215), (111, 220), (121, 202), (121, 215), (121, 113), (121, 134), (131, 205), (131, 220), (141, 124), (141, 205), (141, 225), (201, 145), (211, 124), (221, 104), (221, 124), (100, 125), (100, 204), (100, 102), (100, 113), (100, 104), (100, 144), (100, 105), (110, 204), (110, 220), (120, 150), (120, 202), (120, 205), (120, 113), (140, 114), (140, 214), (140, 224), (150, 143), (150, 213), (200, 142), (200, 104), (200, 145), (210, 124), (210, 144), (210, 115), (210, 145), (102, 203), (102, 204), (102, 103), (102, 135), (112, 204), (122, 213), (122, 223), (132, 225), (202, 133), (202, 114), (212, 103), (222, 104), (103, 204), (103, 114), (113, 104), (113, 105), (113, 125), (113, 204), (133, 114), (133, 144), (143, 213), (143, 223), (203, 135), (213, 124), (213, 145), (104, 105), (104, 204), (104, 215), (114, 115), (114, 204), (134, 224), (144, 145), (144, 214), (204, 105), (204, 125)]
  1. 常見的,可以計算兩個點之間的最短路徑:
p1 = nx.shortest_path(G, source=114, target=211)
print('頂點 114 到頂點 211 的最短路徑: ', p1)
輸出的結果:
頂點 114 到頂點 211 的最短路徑:  [114, 127, 208, 124, 211]
  1. 也計算圖中每個點的 PageRank 值,來看各自的影響力:
print(nx.pagerank(G))

輸出的結果:

{109: 0.011507076520104863, 119: 0.007835838669313514, 129: 0.015304593799331218, 139: 0.007772926737873626, 149: 0.0073896601012629825, 209: 0.0065558926178649985, 219: 0.014100908598251508, 229: 0.011454115940170253, 108: 0.01645334474680034, 118: 0.01010598371500564, 128: 0.01594717876199238, 138: 0.01671097227127263, 148: 0.015898676579503977, 208: 0.009437234075904938, 218: 0.0153795416919104, 228: 0.005900393773635255, 107: 0.009745182763645681, 117: 0.008716335675518244, 127: 0.021565565312365507, 137: 0.011642680498867146, 147: 0.009721031073465738, 207: 0.01040504770909835, 217: 0.012054472529765329, 227: 0.005615576255373405, 106: 0.007371191843767635, 116: 0.020955704443679106, 126: 0.007589432032220849, 136: 0.015987209357117116, 146: 0.013922108926721374, 206: 0.008554794629575304, 216: 0.011219193251536395, 226: 0.013613173390725904, 101: 0.016680863106330837, 111: 0.010121524312495604, 121: 0.017545503989576015, 131: 0.008531567756846938, 141: 0.014598319866130227, 201: 0.0058643663430632525, 211: 0.003936285336338021, 221: 0.009587911774927793, 100: 0.02243017302167168, 110: 0.007928429795381916, 120: 0.011875669801396205, 130: 0.0073896601012629825, 140: 0.01205992633948699, 150: 0.010045605782606326, 200: 0.015289870550944322, 210: 0.017716629501785937, 220: 0.008666577509181518, 102: 0.014865431161046641, 112: 0.007931095811770324, 122: 0.008087439927630492, 132: 0.004659566123187912, 142: 0.006487446038191551, 202: 0.013579313206377282, 212: 0.01190888044566142, 222: 0.011376739416933006, 103: 0.013438110749144392, 113: 0.02458154500563397, 123: 0.01104978432213578, 133: 0.00743370900670294, 143: 0.008011123394996112, 203: 0.006883198710237787, 213: 0.020392557117890422, 223: 0.012345866520333572, 104: 0.024902235588979776, 114: 0.019369722463816744, 124: 0.017165705442951484, 134: 0.008284361176173354, 144: 0.019363506469972095, 204: 0.03507634139024834, 214: 0.015500649025348538, 224: 0.008320315540621754, 105: 0.01439975542831122, 115: 0.007592722237637133, 125: 0.010808523955754608, 135: 0.006883198710237788, 145: 0.014654713389044883, 205: 0.014660118545887803, 215: 0.01337467974572934, 225: 0.009909720748343093}

此外,也可以和上一篇中一樣,接入Gephi [5]來得到更好的圖可視化效果。

本文的代碼可以參見[6].

Reference

[1] https://nebula-graph.com.cn/posts/game-of-thrones-relationship-networkx-gephi-nebula-graph/

[2] https://networkx.github.io/

[3] https://github.com/vesoft-inc/nebula

[4] https://spark.apache.org/graphx/

[5] https://gephi.org/

[6] https://github.com/vesoft-inc/nebula-python/pull/31

喜歡這篇文章?來來來,給我們的 GitHub 點個 star 表鼓勵啦~~ 🙇‍♂️🙇‍♀️ [手動跪謝]

交流圖數據庫技術?交個朋友,Nebula Graph 官方小助手微信:NebulaGraphbot 拉你進交流羣~~

作者有話說:Hi,我是王傑,是圖數據 Nebula Graph 研發工程師,希望本次的經驗分享能給大家帶來幫助,如有不當之處也希望能幫忙糾正,謝謝~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章