使用Neo4j分析《權力的游戲》


幾個月前,數學家 Andrew Beveridge和Jie Shan在數學雜志上發表《權力的網絡》,主要分析暢銷小說《冰與火之歌》第三部《冰雨的風暴》中人物關系,其已經拍成電視劇《權力的游戲》系列。他們在論文中介紹了如何通過文本分析和實體提取構建人物關系的網絡。緊接着,使用社交網絡分析算法對人物關系網絡分析找出最重要的角色;應用社區發現算法來找到人物聚類。

#! pip install py2neo
from py2neo import Graph
graph = Graph()

安裝py2neo

!pip install py2neo --upgrade
Collecting py2neo
  Downloading py2neo-3.1.0-py2.py3-none-any.whl (140kB)
    100% |████████████████████████████████| 143kB 3.0MB/s 
[?25hInstalling collected packages: py2neo
  Found existing installation: py2neo 2.0.8
    Uninstalling py2neo-2.0.8:
      Successfully uninstalled py2neo-2.0.8
Successfully installed py2neo-3.1.0

Import into Neo4j

首先創建節點c,並做唯一限制性約束,c.name唯一,保證schema的完整性:

帶有標簽Character的節點代表小說中的角色,用單向關系類型INTERACTS代表小說中的角色有過接觸。節點屬性會存儲角色的名字name,兩角色間接觸的次數作為關系的屬性:權重(weight)。

一旦約束創建即相應的創建索引,這將有助於通過角色的名字查詢的性能。作者使用Neo4j的Cypher(Cypher是一種聲明式圖查詢語言,能表達高效查詢和更新圖數據庫)LOAD CSV語句導入數據:

# 創建節點,並唯一性約束
graph.run("CREATE CONSTRAINT ON (c:Character) ASSERT c.name IS UNIQUE;")

# 導入節點,關系和關系的屬性
for record in graph.run('''
LOAD CSV WITH HEADERS FROM "https://www.macalester.edu/~abeverid/data/stormofswords.csv" AS row
MERGE (src:Character {name: row.Source})
MERGE (tgt:Character {name: row.Target})
MERGE (src)-[r:INTERACTS]->(tgt)
SET r.weight = toInt(row.Weight)
RETURN count(*) AS paths_written
'''):
    print(record)
('paths_written': 352)
# match
for r in graph.run('''
MATCH p=(:Character)-[:INTERACTS]-(:Character)
RETURN p limit 10
'''):
    print(r)
<Record p=(Aemon)-[:INTERACTS {weight: 31}]->(Samwell)>
<Record p=(Aemon)<-[:INTERACTS {weight: 4}]-(Stannis)>
<Record p=(Aemon)-[:INTERACTS {weight: 5}]->(Grenn)>
<Record p=(Aemon)<-[:INTERACTS {weight: 4}]-(Robert)>
<Record p=(Aemon)<-[:INTERACTS {weight: 30}]-(Jon)>
<Record p=(Aerys)-[:INTERACTS {weight: 8}]->(Tywin)>
<Record p=(Aerys)-[:INTERACTS {weight: 5}]->(Tyrion)>
<Record p=(Aerys)-[:INTERACTS {weight: 18}]->(Jaime)>
<Record p=(Aerys)-[:INTERACTS {weight: 6}]->(Robert)>
<Record p=(Alliser)<-[:INTERACTS {weight: 15}]-(Jon)>

Analyzing the network 分析網絡

Number of characters 人物數量

萬事以簡單開始。先看看上圖上由有多少人物:

# count
for record in graph.run("MATCH (c:Character) RETURN count(c) AS num"):
    print(record)
<Record num=107>

Summary statistics 概要統計

統計每個角色接觸的其它角色的數目:

# with min max avg stdev
for record in graph.run('''
MATCH (c:Character)-[:INTERACTS]->()
WITH c, count(*) AS num
RETURN min(num) AS min, max(num) AS max, avg(num) AS avg_characters, stdev(num) AS stdev
'''):
    print(record)
<Record min=1 max=24 avg_characters=4.957746478873241 stdev=6.2276723918750845>

Diameter of the network 網絡直徑

網絡的直徑或者測底線或者最長最短路徑

for r in graph.run('''
// Find maximum diameter of network
// maximum shortest path between two nodes
MATCH (a:Character), (b:Character) WHERE id(a) > id(b)
MATCH p=shortestPath((a)-[:INTERACTS*]-(b))
RETURN length(p) AS len, extract(x IN nodes(p) | x.name) AS path
ORDER BY len DESC LIMIT 4
'''):
    print(r)
('len': 6, 'path': ['Illyrio', 'Belwas', 'Daenerys', 'Robert', 'Tywin', 'Oberyn', 'Amory'])
('len': 6, 'path': ['Illyrio', 'Belwas', 'Daenerys', 'Robert', 'Sansa', 'Bran', 'Jojen'])
('len': 6, 'path': ['Illyrio', 'Belwas', 'Daenerys', 'Robert', 'Stannis', 'Davos', 'Shireen'])
('len': 6, 'path': ['Illyrio', 'Belwas', 'Daenerys', 'Robert', 'Sansa', 'Bran', 'Luwin'])

我們能看到網絡中有許多長度為6的路徑。

Shortest path 最短路徑

使用Cypher 的shortestPath函數找到圖中任意兩個角色之間的最短路徑。讓我們找出凱特琳·史塔克(Catelyn Stark )和卓戈·卡奧(Kahl Drogo)之間的最短路徑:

for r in graph.run('''
// Shortest path from Catelyn Stark to Khal Drogo
MATCH (catelyn:Character {name: "Catelyn"}), (drogo:Character {name: "Drogo"})
MATCH p=shortestPath((catelyn)-[INTERACTS*]-(drogo))
RETURN p
'''):
    print(r)
<Record p=(Catelyn)-[:INTERACTS {weight: 8}]->(Sansa)-[:INTERACTS {weight: 5}]->(Robert)<-[:INTERACTS {weight: 5}]-(Daenerys)-[:INTERACTS {weight: 18}]->(Drogo)>

All shortest paths 所有的最短路徑

聯結凱特琳·史塔克(Catelyn Stark )和卓戈·卡奧(Kahl Drogo)之間的最短路徑可能還有其它路徑,我們可以使用Cypher的allShortestPaths函數來查找:

for r in graph.run('''
// All shortest paths from Catelyn Stark to Khal Drogo
MATCH (catelyn:Character {name: "Catelyn"}), (drogo:Character {name: "Drogo"})
MATCH p=allShortestPaths((catelyn)-[INTERACTS*]-(drogo))
RETURN p
'''):
    print(r)
<Record p=(Catelyn)-[:INTERACTS {weight: 8}]->(Sansa)-[:INTERACTS {weight: 5}]->(Robert)<-[:INTERACTS {weight: 5}]-(Daenerys)-[:INTERACTS {weight: 18}]->(Drogo)>
<Record p=(Catelyn)-[:INTERACTS {weight: 19}]->(Jaime)-[:INTERACTS {weight: 4}]->(Barristan)<-[:INTERACTS {weight: 11}]-(Jorah)-[:INTERACTS {weight: 6}]->(Drogo)>
<Record p=(Catelyn)-[:INTERACTS {weight: 19}]->(Jaime)-[:INTERACTS {weight: 4}]->(Barristan)<-[:INTERACTS {weight: 20}]-(Daenerys)-[:INTERACTS {weight: 18}]->(Drogo)>
<Record p=(Catelyn)-[:INTERACTS {weight: 19}]->(Jaime)-[:INTERACTS {weight: 17}]->(Robert)<-[:INTERACTS {weight: 5}]-(Daenerys)-[:INTERACTS {weight: 18}]->(Drogo)>
<Record p=(Catelyn)-[:INTERACTS {weight: 4}]->(Cersei)-[:INTERACTS {weight: 16}]->(Robert)<-[:INTERACTS {weight: 5}]-(Daenerys)-[:INTERACTS {weight: 18}]->(Drogo)>
<Record p=(Catelyn)-[:INTERACTS {weight: 4}]->(Stannis)<-[:INTERACTS {weight: 5}]-(Robert)<-[:INTERACTS {weight: 5}]-(Daenerys)-[:INTERACTS {weight: 18}]->(Drogo)>
<Record p=(Catelyn)-[:INTERACTS {weight: 5}]->(Tyrion)<-[:INTERACTS {weight: 4}]-(Viserys)<-[:INTERACTS {weight: 8}]-(Daenerys)-[:INTERACTS {weight: 18}]->(Drogo)>
<Record p=(Catelyn)-[:INTERACTS {weight: 5}]->(Tyrion)-[:INTERACTS {weight: 9}]->(Robert)<-[:INTERACTS {weight: 5}]-(Daenerys)-[:INTERACTS {weight: 18}]->(Drogo)>
<Record p=(Catelyn)<-[:INTERACTS {weight: 5}]-(Eddard)-[:INTERACTS {weight: 10}]->(Robert)<-[:INTERACTS {weight: 5}]-(Daenerys)-[:INTERACTS {weight: 18}]->(Drogo)>

Pivotal nodes 關鍵節點

在網絡中,如果一個節點位於其它兩個節點所有的最短路徑上,即稱為關鍵節點。下面我們找出網絡中所有的關鍵節點:

for r in graph.run('''
// Find all pivotal nodes in network
MATCH (a:Character), (b:Character)
MATCH p=allShortestPaths((a)-[:INTERACTS*]-(b)) 
WITH collect(p) AS paths, a, b
MATCH (c:Character) WHERE all(x IN paths WHERE c IN nodes(x)) AND NOT c IN [a,b]
RETURN a.name, b.name, c.name AS PivotalNode SKIP 490 LIMIT 10
'''):
    print(r)
<Record a.name='Balon' b.name='Lothar' PivotalNode='Robb'>
<Record a.name='Balon' b.name='Luwin' PivotalNode='Bran'>
<Record a.name='Balon' b.name='Luwin' PivotalNode='Robb'>
<Record a.name='Balon' b.name='Melisandre' PivotalNode='Stannis'>
<Record a.name='Balon' b.name='Missandei' PivotalNode='Daenerys'>
<Record a.name='Balon' b.name='Myrcella' PivotalNode='Tyrion'>
<Record a.name='Balon' b.name='Rattleshirt' PivotalNode='Jon'>
<Record a.name='Balon' b.name='Rickard' PivotalNode='Robb'>
<Record a.name='Balon' b.name='Rickon' PivotalNode='Robb'>
<Record a.name='Balon' b.name='Roose' PivotalNode='Robb'>

從結果表格中我們可以看出有趣的結果:羅柏·史塔克(Robb)是卓戈·卡奧(Drogo)和拉姆塞·波頓(Ramsay)的關鍵節點。這意味着,所有聯結卓戈·卡奧(Drogo)和拉姆塞·波頓(Ramsay)的最短路徑都要經過羅柏·史塔克(Robb)。我們可以通過可視化卓戈·卡奧(Drogo)和拉姆塞·波頓(Ramsay)之間的所有最短路徑來驗證:

for r in graph.run('''
MATCH (a:Character {name: "Drogo"}), (b:Character {name: "Ramsay"})
MATCH p=allShortestPaths((a)-[:INTERACTS*]-(b))
RETURN p
'''):
    print(r)
<Record p=(Drogo)<-[:INTERACTS {weight: 18}]-(Daenerys)-[:INTERACTS {weight: 5}]->(Robert)<-[:INTERACTS {weight: 5}]-(Sansa)<-[:INTERACTS {weight: 15}]-(Robb)-[:INTERACTS {weight: 4}]->(Ramsay)>
<Record p=(Drogo)<-[:INTERACTS {weight: 6}]-(Jorah)-[:INTERACTS {weight: 11}]->(Barristan)<-[:INTERACTS {weight: 4}]-(Jaime)<-[:INTERACTS {weight: 15}]-(Robb)-[:INTERACTS {weight: 4}]->(Ramsay)>
<Record p=(Drogo)<-[:INTERACTS {weight: 18}]-(Daenerys)-[:INTERACTS {weight: 20}]->(Barristan)<-[:INTERACTS {weight: 4}]-(Jaime)<-[:INTERACTS {weight: 15}]-(Robb)-[:INTERACTS {weight: 4}]->(Ramsay)>
<Record p=(Drogo)<-[:INTERACTS {weight: 18}]-(Daenerys)-[:INTERACTS {weight: 5}]->(Robert)<-[:INTERACTS {weight: 17}]-(Jaime)<-[:INTERACTS {weight: 15}]-(Robb)-[:INTERACTS {weight: 4}]->(Ramsay)>
<Record p=(Drogo)<-[:INTERACTS {weight: 18}]-(Daenerys)-[:INTERACTS {weight: 5}]->(Robert)-[:INTERACTS {weight: 5}]->(Stannis)<-[:INTERACTS {weight: 4}]-(Robb)-[:INTERACTS {weight: 4}]->(Ramsay)>
<Record p=(Drogo)<-[:INTERACTS {weight: 18}]-(Daenerys)-[:INTERACTS {weight: 8}]->(Viserys)-[:INTERACTS {weight: 4}]->(Tyrion)<-[:INTERACTS {weight: 12}]-(Robb)-[:INTERACTS {weight: 4}]->(Ramsay)>
<Record p=(Drogo)<-[:INTERACTS {weight: 18}]-(Daenerys)-[:INTERACTS {weight: 5}]->(Robert)<-[:INTERACTS {weight: 9}]-(Tyrion)<-[:INTERACTS {weight: 12}]-(Robb)-[:INTERACTS {weight: 4}]->(Ramsay)>
<Record p=(Drogo)<-[:INTERACTS {weight: 18}]-(Daenerys)-[:INTERACTS {weight: 5}]->(Robert)<-[:INTERACTS {weight: 5}]-(Jon)<-[:INTERACTS {weight: 14}]-(Robb)-[:INTERACTS {weight: 4}]->(Ramsay)>
<Record p=(Drogo)<-[:INTERACTS {weight: 18}]-(Daenerys)-[:INTERACTS {weight: 5}]->(Robert)<-[:INTERACTS {weight: 11}]-(Tywin)<-[:INTERACTS {weight: 12}]-(Robb)-[:INTERACTS {weight: 4}]->(Ramsay)>
<Record p=(Drogo)<-[:INTERACTS {weight: 18}]-(Daenerys)-[:INTERACTS {weight: 5}]->(Robert)<-[:INTERACTS {weight: 10}]-(Eddard)-[:INTERACTS {weight: 13}]->(Robb)-[:INTERACTS {weight: 4}]->(Ramsay)>
<Record p=(Drogo)<-[:INTERACTS {weight: 18}]-(Daenerys)-[:INTERACTS {weight: 5}]->(Robert)<-[:INTERACTS {weight: 4}]-(Arya)<-[:INTERACTS {weight: 15}]-(Robb)-[:INTERACTS {weight: 4}]->(Ramsay)>

Centrality measures 中心度度量

給出網絡中節點的重要性的相對度量。有許多不同的方式來度量中心度,每種方式都代表不同類型的“重要性”。

Degree centrality 度中心性

度中心性是最簡單度量,即為某個節點在網絡中的聯結數。在《權力的游戲》的圖中,某個角色的度中心性是指該角色接觸的其他角色數。作者使用Cypher計算度中心性:

for r in graph.run('''
MATCH (c:Character)-[:INTERACTS]-()
RETURN c.name AS character, count(*) AS degree ORDER BY degree DESC LIMIT 10
'''):
    print(r)
<Record character='Tyrion' degree=36>
<Record character='Sansa' degree=26>
<Record character='Jon' degree=26>
<Record character='Robb' degree=25>
<Record character='Jaime' degree=24>
<Record character='Tywin' degree=22>
<Record character='Cersei' degree=20>
<Record character='Arya' degree=19>
<Record character='Catelyn' degree=18>
<Record character='Joffrey' degree=18>

從上面可以發現,在《權力的游戲》網絡中提利昂·蘭尼斯特(Tyrion)和最多的角色有接觸。鑒於他的心計,我們覺得這是有道理的。

Weighted degree centrality 加權度中心性

作者存儲一對角色接觸的次數作為INTERACTS關系的weight屬性。對該角色的INTERACTS關系的所有weight相加得到加權度中心性。作者使用Cypher計算所有角色的這個度量:

for r in graph.run('''
MATCH (c:Character)-[r:INTERACTS]-()
RETURN c.name AS character, sum(r.weight) AS weightedDegree ORDER BY weightedDegree DESC LIMIT 10
'''):
    print(r)
<Record character='Tyrion' weightedDegree=551>
<Record character='Jon' weightedDegree=442>
<Record character='Sansa' weightedDegree=383>
<Record character='Jaime' weightedDegree=372>
<Record character='Bran' weightedDegree=344>
<Record character='Robb' weightedDegree=342>
<Record character='Samwell' weightedDegree=282>
<Record character='Arya' weightedDegree=269>
<Record character='Joffrey' weightedDegree=255>
<Record character='Daenerys' weightedDegree=232>

Betweenness centrality 介數中心性

介數中心性:在網絡中,一個節點的介數中心性是指其它兩個節點的所有最短路徑都經過這個節點,則這些所有最短路徑數即為此節點的介數中心性。介數中心性是一種重要的度量,因為它可以鑒別出網絡中的“信息中間人”或者網絡聚類后的聯結點。

for r in graph.run('''
CALL algo.betweenness.stream('Character')
YIELD nodeId, centrality
MATCH (c:Character) WHERE id(c) = nodeId
RETURN c.name AS c,centrality
ORDER BY centrality DESC limit 10;
'''):
    print(r)
<Record c='Tyrion' centrality=332.97460317460315>
<Record c='Samwell' centrality=244.6357142857143>
<Record c='Stannis' centrality=226.20476190476188>
<Record c='Robert' centrality=208.62301587301593>
<Record c='Mance' centrality=138.66666666666669>
<Record c='Jaime' centrality=119.99563492063493>
<Record c='Sandor' centrality=114.33333333333333>
<Record c='Jon' centrality=111.26666666666667>
<Record c='Janos' centrality=90.65>
<Record c='Aemon' centrality=64.59761904761905>

Closeness centrality 緊度中心性

緊度中心性是指到網絡中所有其他角色的平均距離的倒數。在圖中,具有高緊度中心性的節點在聚類社區之間被高度聯結,但在社區之外不一定是高度聯結的。

cql = '''CALL algo.closeness.stream('Character', 'INTERACTS')
YIELD nodeId, centrality
MATCH (c:Character) WHERE id(c) = nodeId
RETURN c.name AS c,centrality
ORDER BY centrality DESC limit 10;'''
for r in graph.run(cql):
    print(r)
<Record c='Tyrion' centrality=0.5120772946859904>
<Record c='Sansa' centrality=0.5096153846153846>
<Record c='Robert' centrality=0.5>
<Record c='Robb' centrality=0.48847926267281105>
<Record c='Arya' centrality=0.48623853211009177>
<Record c='Jon' centrality=0.4796380090497738>
<Record c='Jaime' centrality=0.4796380090497738>
<Record c='Stannis' centrality=0.4796380090497738>
<Record c='Tywin' centrality=0.4690265486725664>
<Record c='Eddard' centrality=0.4608695652173913>

Using python-igraph 使用python-igraph

Neo4j與其它工具(比如,R和Python數據科學工具)完美結合。我們繼續使用apoc運行 PageRank和社區發現(community detection)算法。這里接着使用python-igraph計算分析。Python-igraph移植自R的igraph圖形分析庫。 使用pip install python-igraph安裝它。

Building an igraph instance from Neo4j 構建一個igraph實例

為了在《權力的游戲》的數據的圖分析中使用igraph,首先需要從Neo4j拉取數據,用Python建立igraph實例。作者使用 Neo4j 的Python驅動庫py2neo。我們能直接傳入Py2neo查詢結果對象到igraph的TupleList構造器,創建igraph實例:

#! pip install python-igraph
from igraph import Graph as IGraph

query = '''
MATCH (c1:Character)-[r:INTERACTS]->(c2:Character)
RETURN c1.name, c2.name, r.weight AS weight
'''
# 從元組列表表示形式構造一個圖
ig = IGraph.TupleList(graph.run(query), weights=True)

ig
<igraph.Graph at 0x2089631d68>

現在有了igraph對象,可以運行igraph實現的各種圖算法了。

PageRank

PageRank算法源自Google的網頁排名。它是一種特征向量中心性(eigenvector centrality)算法。

在igraph實例中運行PageRank算法,然后把結果寫回Neo4j,在角色節點創建一個pagerank屬性存儲igraph計算的值:

PageRank

# Calculates the Google PageRank values of a graph.
pg = ig.pagerank()

pgvs = []
# ig.vs:圖的頂點序列
for p in zip(ig.vs, pg):
    pgvs.append({"name": p[0]["name"], "pg": p[1]})
print(pgvs[:5])

write_clusters_query = '''
UNWIND {nodes} AS n
MATCH (c:Character) WHERE c.name = n.name
SET c.pagerank = n.pg
'''

graph.run(write_clusters_query, nodes=pgvs)
[{'name': 'Stannis', 'pg': 0.018020131765195593}, {'name': 'Aemon', 'pg': 0.007328980991947571}, {'name': 'Robert', 'pg': 0.022292016521362857}, {'name': 'Jon', 'pg': 0.035828696691635555}, {'name': 'Alliser', 'pg': 0.005162125869510499}]





<py2neo.database.Cursor at 0x208962d7f0>

現在可以在Neo4j的圖中查詢最高PageRank值的節點:

for r in graph.run('''
MATCH (n:Character)
RETURN n.name AS name, n.pagerank AS pagerank ORDER BY pagerank DESC LIMIT 10
'''):
    print(r)
('name': 'Tyrion', 'pagerank': 0.042884981999963316)
('name': 'Jon', 'pagerank': 0.03582869669163558)
('name': 'Robb', 'pagerank': 0.03017114665594764)
('name': 'Sansa', 'pagerank': 0.030009716660108578)
('name': 'Daenerys', 'pagerank': 0.02881425425830273)
('name': 'Jaime', 'pagerank': 0.028727587587471206)
('name': 'Tywin', 'pagerank': 0.02570016262642541)
('name': 'Robert', 'pagerank': 0.022292016521362864)
('name': 'Cersei', 'pagerank': 0.022287327589773507)
('name': 'Arya', 'pagerank': 0.022050209663844467)

Community detection

社區發現算法用來找出圖中的社區聚類。作者使用igraph實現的隨機游走算法( walktrap)來找到在社區中頻繁有接觸的角色社區,在社區之外角色不怎么接觸。

在igraph中運行隨機游走的社區發現算法,然后把社區發現的結果導入Neo4j,其中每個角色所屬的社區用一個整數來表示:

clusters = IGraph.community_walktrap(ig, weights="weight").as_clustering()

nodes = [{"name": node["name"]} for node in ig.vs]
for node in nodes:
    idx = ig.vs.find(name=node["name"]).index
    node["community"] = clusters.membership[idx]

print(nodes[:5])

write_clusters_query = '''
UNWIND {nodes} AS n
MATCH (c:Character) WHERE c.name = n.name
SET c.community = toInt(n.community)
'''

graph.run(write_clusters_query, nodes=nodes)
[{'name': 'Stannis', 'community': 0}, {'name': 'Aemon', 'community': 1}, {'name': 'Robert', 'community': 2}, {'name': 'Jon', 'community': 1}, {'name': 'Alliser', 'community': 1}]





<py2neo.database.Cursor at 0x208962ae48>

我們能在Neo4j中查詢有多少個社區以及每個社區的成員數:

for r in graph.run('''
MATCH (c:Character)
WITH c.community AS cluster, collect(c.name) AS  members
RETURN cluster, members ORDER BY cluster ASC
'''):
    print(r)
<Record cluster=0 members=['Davos', 'Melisandre', 'Shireen', 'Stannis', 'Cressen', 'Salladhor']>
<Record cluster=1 members=['Aemon', 'Alliser', 'Craster', 'Eddison', 'Gilly', 'Janos', 'Jon', 'Mance', 'Rattleshirt', 'Samwell', 'Val', 'Ygritte', 'Grenn', 'Karl', 'Bowen', 'Dalla', 'Orell', 'Qhorin', 'Styr']>
<Record cluster=2 members=['Aerys', 'Amory', 'Balon', 'Brienne', 'Bronn', 'Cersei', 'Gregor', 'Jaime', 'Joffrey', 'Jon Arryn', 'Kevan', 'Loras', 'Lysa', 'Meryn', 'Myrcella', 'Oberyn', 'Podrick', 'Renly', 'Robert', 'Robert Arryn', 'Sansa', 'Shae', 'Tommen', 'Tyrion', 'Tywin', 'Varys', 'Walton', 'Petyr', 'Elia', 'Ilyn', 'Pycelle', 'Qyburn', 'Margaery', 'Olenna', 'Marillion', 'Ellaria', 'Mace', 'Chataya', 'Doran']>
<Record cluster=3 members=['Arya', 'Beric', 'Eddard', 'Gendry', 'Sandor', 'Anguy', 'Thoros']>
<Record cluster=4 members=['Brynden', 'Catelyn', 'Edmure', 'Hoster', 'Lothar', 'Rickard', 'Robb', 'Roose', 'Walder', 'Jeyne', 'Roslin', 'Ramsay']>
<Record cluster=5 members=['Belwas', 'Daario', 'Daenerys', 'Irri', 'Jorah', 'Missandei', 'Rhaegar', 'Viserys', 'Barristan', 'Illyrio', 'Drogo', 'Aegon', 'Kraznys', 'Rakharo', 'Worm']>
<Record cluster=6 members=['Bran', 'Hodor', 'Jojen', 'Luwin', 'Meera', 'Rickon', 'Nan', 'Theon']>
<Record cluster=7 members=['Lancel']>

Visualization

See neovis.js


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM