今天逛B站,看到《三体》三部曲全部人物关系数据可视化视频

于是动手写了今天的技术文。绘制人物网络关系图,需要有

  • 节点数据; 节点名、节点属性
  • 边数据; source、target、weight
  • 可视化工具(Gephi软件或Python的可视化包)

本文代码

点击下载



节点-人物数据

绘制人物关系网络图,首先需要有网络节点数据

  • name 人物名称
  • desc 人物简介
  • stage 人物出现在三体小说的哪个阶段
import pandas as pd

nodes_info_df = pd.read_excel('data/三体人物.xlsx')
nodes_info_df

Run



边-人物关系数据

将节点数据依次在三体小说文本中按行进行检索,如果每行同时出现两个人物,两个人物会构建一个边。人物关系网络图可以用gephi软件进行绘制, 绘制需要两个csv文件,即

  • 三体_nodes.csv
  • 三体_edges.csv

实现代码如下

import jieba
import codecs
import csv

for name in df['name'].tolist():
    jieba.add_word(name)

nodes = {}			# 姓名字典,保存人物,该字典的键为人物名称,值为该人物在全文中出现的次数
relationships = {}	# 关系字典,保存人物关系的有向边,该字典的键为有向边的起点,值为一个字典edge,edge的键是有向边的终点,值是有向边的权值,代表两个人物之间联系的紧密程度
lineNodes = []		# 每段内人物关系,是一个缓存变量,保存对每一段分词得到当前段中出现的人物名称,lineName[i]是一个列表,列表中存储第i段中出现过的人物


with open("data/三体.txt", "r", encoding="utf-8") as f:
	for line in f.readlines():
		words = jieba.lcut(line)		# 分词并返回该词词性
		lineNodes.append([])		# 为新读入的一段添加人物名称列表
		for word in words:
			if word in df['name'].tolist():
				lineNodes[-1].append(word)		# 为当前段的环境增加一个人物
				if nodes.get(word) is None:
					nodes[word] = 0
				relationships[word] = {}
				nodes[word] += 1					# 该人物出现次数加 1
                

# explore relationships   对于 lineNames 中每一行,我们为该行中出现的所有人物两两相连。如果两个人物之间尚未有边建立,则将新建的边权值设为 1,否则将已存在的边的权值加 1。这种方法将产生很多的冗余边,这些冗余边将在最后处理。
for line in lineNodes:					# 对于每一段
	for node1 in line:					
		for node2 in line:				# 每段中的任意两个人
			if node1 == node2:
				continue
			if relationships[node1].get(node2) is None:		# 若两人尚未同时出现则新建项
				relationships[node1][node2]= 1
			else:
				relationships[node1][node2] = relationships[node1][node2]+ 1		# 两人共同出现次数加 1


# output   将已经建好的 names 和 relationships 输出到文本,以方便 gephi 可视化处理。输出边的过程中可以过滤可能是冗余的边,这里假设共同出现次数少于 3 次的是冗余边,则在输出时跳过这样的边。输出的节点集合保存为 busan_node.txt ,边集合保存为 busan_edge.node 。
with open("output/三体_nodes.csv", "w", encoding="utf-8") as csvf1:
	writer1 = csv.DictWriter(csvf1, fieldnames=['id', 'label', 'weight'])
	writer1.writeheader()
	for node, times in nodes.items():
		line = {'id': node, 'label': node, 'weight': times}
		writer1.writerow(line)



with open("output/三体_edges.csv", "w", encoding="utf-8") as csvf2:
	writer2 = csv.DictWriter(csvf2, fieldnames=['source', 'target', 'weight'])
	writer2.writeheader()
	for node, edges in relationships.items():
		for target, weight in edges.items():
			if weight>3:
				line = {'source': node, 'target':target, 'weight':w}
				writer2.writerow(line)

查看 三体_nodes.csv 文件样式

nodes_df = pd.read_csv('output/三体_nodes.csv', encoding='utf-8')
nodes_df

Run


查看 三体_edges.csv 文件样式

edges_df = pd.read_csv('output/三体_edges.csv', encoding='utf-8')
edges_df

Run



绘制关系图

本文不讲解gephi,感兴趣的可以去B站搜gephi使用方法。 实现可视化,除了gephi, 也可以使用pyechart库实现不错的可视化效果。

import numpy as np
from pyecharts import options as opts
from pyecharts.charts import Graph
from pyecharts.globals import CurrentConfig, NotebookType
CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK


nodes_data = []
edges_data = []
categories_data = []

for node, weight in zip(nodes_df.label, nodes_df.weight):
    category = nodes_info_df[nodes_info_df['name']==node]['stage'].values[0]
    ##使用weight作为圆点的值,除以10,防止圆点太大占满整个屏幕.
    node_info = {"name": str(node), "symbolSize": weight/10, 'category': category, "value": weight/10}
    nodes_data.append(node_info)

for ix, source, target in zip(edges_df.index, edges_df.source, edges_df.target):
    edges_data.append({"id": ix, "source": source, "target": target})

for category in set(df['stage'].tolist()): 
    categories_data.append({'name': category})


c = (
    Graph(init_opts=opts.InitOpts(width="1000px", height="600px"))
    .add(
        "",
        nodes=nodes_data,
        links=edges_data,
        categories=categories_data,
        layout="circular",
        is_rotate_label=True,
        linestyle_opts=opts.LineStyleOpts(color="source", curve=0.3),
        label_opts=opts.LabelOpts(position="right"),
    )
    .set_global_opts(
        title_opts=opts.TitleOpts(title="三体人物关系图"),
        legend_opts=opts.LegendOpts(orient="vertical", pos_left="2%", pos_top="20%"),
    )
)

c.render('output/三体_graph.html')
c.render_notebook()

Run

Awesome-pyecharts



广而告之