CategoryResourceRepost/极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/33丨PageRank(下):分析希拉里邮件中的人物关系.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

219 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="33丨PageRank分析希拉里邮件中的人物关系" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/26/29/26184d17ba65dc29e7af467ae4c59a29.mp3"></audio>
上节课我们讲到PageRank算法经常被用到网络关系的分析中比如在社交网络中计算个人的影响力计算论文的影响力或者网站的影响力等。
今天我们就来做一个关于PageRank算法的实战在这之前你需要思考三个问题
<li>
如何使用工具完成PageRank算法包括使用工具创建网络图设置节点、边、权重等并通过创建好的网络图计算节点的PR值
</li>
<li>
对于一个实际的项目比如希拉里的9306封邮件工具包中邮件的数量如何使用PageRank算法挖掘出有影响力的节点并且绘制网络图
</li>
<li>
如何对创建好的网络图进行可视化,如果网络中的节点数较多,如何筛选重要的节点进行可视化,从而得到精简的网络关系图。
</li>
## 如何使用工具实现PageRank算法
PageRank算法工具在sklearn中并不存在我们需要找到新的工具包。实际上有一个关于图论和网络建模的工具叫NetworkX它是用Python语言开发的工具内置了常用的图与网络分析算法可以方便我们进行网络数据分析。
上节课我举了一个网页权重的例子假设一共有4个网页A、B、C、D它们之间的链接信息如图所示
<img src="https://static001.geekbang.org/resource/image/47/ea/47e5f21d16b15a98d4a32a73ebd477ea.png" alt=""><br>
针对这个例子我们看下用NetworkX如何计算A、B、C、D四个网页的PR值具体代码如下
```
import networkx as nx
# 创建有向图
G = nx.DiGraph()
# 有向图之间边的关系
edges = [(&quot;A&quot;, &quot;B&quot;), (&quot;A&quot;, &quot;C&quot;), (&quot;A&quot;, &quot;D&quot;), (&quot;B&quot;, &quot;A&quot;), (&quot;B&quot;, &quot;D&quot;), (&quot;C&quot;, &quot;A&quot;), (&quot;D&quot;, &quot;B&quot;), (&quot;D&quot;, &quot;C&quot;)]
for edge in edges:
G.add_edge(edge[0], edge[1])
pagerank_list = nx.pagerank(G, alpha=1)
print(&quot;pagerank值是&quot;, pagerank_list)
```
NetworkX工具把中间的计算细节都已经封装起来了我们直接调用PageRank函数就可以得到结果
```
pagerank值是 {'A': 0.33333396911621094, 'B': 0.22222201029459634, 'C': 0.22222201029459634, 'D': 0.22222201029459634}
```
我们通过NetworkX创建了一个有向图之后设置了节点之间的边然后使用PageRank函数就可以求得节点的PR值结果和上节课中我们人工模拟的结果一致。
好了运行完这个例子之后我们来看下NetworkX工具都有哪些常用的操作。
**1.关于图的创建**
图可以分为无向图和有向图在NetworkX中分别采用不同的函数进行创建。无向图指的是不用节点之间的边的方向使用nx.Graph() 进行创建有向图指的是节点之间的边是有方向的使用nx.DiGraph()来创建。在上面这个例子中存在A→D的边但不存在D→A的边。
**2.关于节点的增加、删除和查询**
如果想在网络中增加节点可以使用G.add_node(A)添加一个节点也可以使用G.add_nodes_from([B,C,D,E])添加节点集合。如果想要删除节点可以使用G.remove_node(node)删除一个指定的节点也可以使用G.remove_nodes_from([B,C,D,E])删除集合中的节点。
那么该如何查询节点呢?
如果你想要得到图中所有的节点就可以使用G.nodes()也可以用G.number_of_nodes()得到图中节点的个数。
**3.关于边的增加、删除、查询**
增加边与添加节点的方式相同使用G.add_edge(“A”, “B”)添加指定的“从A到B”的边也可以使用add_edges_from函数从边集合中添加。我们也可以做一个加权图也就是说边是带有权重的使用add_weighted_edges_from函数从带有权重的边的集合中添加。在这个函数的参数中接收的是1个或多个三元组[u,v,w]作为参数u、v、w分别代表起点、终点和权重。
另外我们可以使用remove_edge函数和remove_edges_from函数删除指定边和从边集合中删除。
另外可以使用edges()函数访问图中所有的边使用number_of_edges()函数得到图中边的个数。
以上是关于图的基本操作如果我们创建了一个图并且对节点和边进行了设置就可以找到其中有影响力的节点原理就是通过PageRank算法使用nx.pagerank(G)这个函数函数中的参数G代表创建好的图。
## 如何用PageRank揭秘希拉里邮件中的人物关系
了解了NetworkX工具的基础使用之后我们来看一个实际的案例希拉里邮件人物关系分析。
希拉里邮件事件相信你也有耳闻对这个数据的背景我们就不做介绍了。你可以从GitHub上下载这个数据集[https://github.com/cystanford/PageRank](https://github.com/cystanford/PageRank)。
整个数据集由三个文件组成Aliases.csvEmails.csv和Persons.csv其中Emails文件记录了所有公开邮件的内容发送者和接收者的信息。Persons这个文件统计了邮件中所有人物的姓名及对应的ID。因为姓名存在别名的情况为了将邮件中的人物进行统一我们还需要用Aliases文件来查询别名和人物的对应关系。
整个数据集包括了9306封邮件和513个人名数据集还是比较大的。不过这一次我们不需要对邮件的内容进行分析只需要通过邮件中的发送者和接收者对应Emails.csv文件中的MetadataFrom和MetadataTo字段来绘制整个关系网络。因为涉及到的人物很多因此我们需要通过PageRank算法计算每个人物在邮件关系网络中的权重最后筛选出来最有价值的人物来进行关系网络图的绘制。
了解了数据集和项目背景之后,我们来设计到执行的流程步骤:
<img src="https://static001.geekbang.org/resource/image/72/c9/72132ffbc1209301f0876178c75927c9.jpg" alt="">
<li>
首先我们需要加载数据源;
</li>
<li>
在准备阶段我们需要对数据进行探索在数据清洗过程中因为邮件中存在别名的情况因此我们需要统一人物名称。另外邮件的正文并不在我们考虑的范围内只统计邮件中的发送者和接收者因此我们筛选MetadataFrom和MetadataTo这两个字段作为特征。同时发送者和接收者可能存在多次邮件往来需要设置权重来统计两人邮件往来的次数。次数越多代表这个边从发送者到接收者的边的权重越高
</li>
<li>
在挖掘阶段我们主要是对已经设置好的网络图进行PR值的计算但邮件中的人物有500多人有些人的权重可能不高我们需要筛选PR值高的人物绘制出他们之间的往来关系。在可视化的过程中我们可以通过节点的PR值来绘制节点的大小PR值越大节点的绘制尺寸越大。
</li>
设置好流程之后,实现的代码如下:
```
# -*- coding: utf-8 -*-
# 用 PageRank 挖掘希拉里邮件中的重要任务关系
import pandas as pd
import networkx as nx
import numpy as np
from collections import defaultdict
import matplotlib.pyplot as plt
# 数据加载
emails = pd.read_csv(&quot;./input/Emails.csv&quot;)
# 读取别名文件
file = pd.read_csv(&quot;./input/Aliases.csv&quot;)
aliases = {}
for index, row in file.iterrows():
aliases[row['Alias']] = row['PersonId']
# 读取人名文件
file = pd.read_csv(&quot;./input/Persons.csv&quot;)
persons = {}
for index, row in file.iterrows():
persons[row['Id']] = row['Name']
# 针对别名进行转换
def unify_name(name):
# 姓名统一小写
name = str(name).lower()
# 去掉, 和 @后面的内容
name = name.replace(&quot;,&quot;,&quot;&quot;).split(&quot;@&quot;)[0]
# 别名转换
if name in aliases.keys():
return persons[aliases[name]]
return name
# 画网络图
def show_graph(graph, layout='spring_layout'):
# 使用 Spring Layout 布局,类似中心放射状
if layout == 'circular_layout':
positions=nx.circular_layout(graph)
else:
positions=nx.spring_layout(graph)
# 设置网络图中的节点大小,大小与 pagerank 值相关,因为 pagerank 值很小所以需要 *20000
nodesize = [x['pagerank']*20000 for v,x in graph.nodes(data=True)]
# 设置网络图中的边长度
edgesize = [np.sqrt(e[2]['weight']) for e in graph.edges(data=True)]
# 绘制节点
nx.draw_networkx_nodes(graph, positions, node_size=nodesize, alpha=0.4)
# 绘制边
nx.draw_networkx_edges(graph, positions, edge_size=edgesize, alpha=0.2)
# 绘制节点的 label
nx.draw_networkx_labels(graph, positions, font_size=10)
# 输出希拉里邮件中的所有人物关系图
plt.show()
# 将寄件人和收件人的姓名进行规范化
emails.MetadataFrom = emails.MetadataFrom.apply(unify_name)
emails.MetadataTo = emails.MetadataTo.apply(unify_name)
# 设置遍的权重等于发邮件的次数
edges_weights_temp = defaultdict(list)
for row in zip(emails.MetadataFrom, emails.MetadataTo, emails.RawText):
temp = (row[0], row[1])
if temp not in edges_weights_temp:
edges_weights_temp[temp] = 1
else:
edges_weights_temp[temp] = edges_weights_temp[temp] + 1
# 转化格式 (from, to), weight =&gt; from, to, weight
edges_weights = [(key[0], key[1], val) for key, val in edges_weights_temp.items()]
# 创建一个有向图
graph = nx.DiGraph()
# 设置有向图中的路径及权重 (from, to, weight)
graph.add_weighted_edges_from(edges_weights)
# 计算每个节点(人)的 PR 值,并作为节点的 pagerank 属性
pagerank = nx.pagerank(graph)
# 将 pagerank 数值作为节点的属性
nx.set_node_attributes(graph, name = 'pagerank', values=pagerank)
# 画网络图
show_graph(graph)
# 将完整的图谱进行精简
# 设置 PR 值的阈值,筛选大于阈值的重要核心节点
pagerank_threshold = 0.005
# 复制一份计算好的网络图
small_graph = graph.copy()
# 剪掉 PR 值小于 pagerank_threshold 的节点
for n, p_rank in graph.nodes(data=True):
if p_rank['pagerank'] &lt; pagerank_threshold:
small_graph.remove_node(n)
# 画网络图,采用circular_layout布局让筛选出来的点组成一个圆
show_graph(small_graph, 'circular_layout')
```
运行结果如下:
<img src="https://static001.geekbang.org/resource/image/41/b1/419f7621392045f07bcd03f9e4c7c8b1.png" alt=""><br>
<img src="https://static001.geekbang.org/resource/image/3f/e1/3f08f61360e8a82a23a16e44d2b973e1.png" alt=""><br>
针对代码中的几个模块我做个简单的说明:
**1.函数定义**
人物的名称需要统一因此我设置了unify_name函数同时设置了show_graph函数将网络图可视化。NetworkX提供了多种可视化布局这里我使用spring_layout布局也就是呈中心放射状。
除了spring_layout外NetworkX还有另外三种可视化布局circular_layout在一个圆环上均匀分布节点random_layout随机分布节点 shell_layout节点都在同心圆上
**2.计算边权重**
邮件的发送者和接收者的邮件往来可能不止一次我们需要用两者之间邮件往来的次数计算这两者之间边的权重所以我用edges_weights_temp数组存储权重。而上面介绍过在NetworkX中添加权重边即使用add_weighted_edges_from函数的时候接受的是u、v、w的三元数组因此我们还需要对格式进行转换具体转换方式见代码。
**3.PR值计算及筛选**
我使用nx.pagerank(graph)计算了节点的PR值。由于节点数量很多我们设置了PR值阈值即pagerank_threshold=0.005然后遍历节点删除小于PR值阈值的节点形成新的图small_graph最后对small_graph进行可视化对应运行结果的第二张图
## 总结
在上节课中我们通过矩阵乘法求得网页的权重这节课我们使用NetworkX可以得到相同的结果。
另外我带你用PageRank算法做了一次实战我们将一个复杂的网络图通过PR值的计算、筛选最终得到了一张精简的网络图。在这个过程中我们学习了NetworkX工具的使用包括创建图、节点、边及PR值的计算。
实际上掌握了PageRank的理论之后在实战中往往就是一行代码的事。但项目与理论不同项目中涉及到的数据量比较大你会花80%的时间或80%的代码量)在预处理过程中,比如今天的项目中,我们对别名进行了统一,对边的权重进行计算,同时还需要把计算好的结果以可视化的方式呈现。
<img src="https://static001.geekbang.org/resource/image/30/42/307055050e005ba5092028a074a5c142.png" alt=""><br>
今天我举了一个网页权重的例子假设一共有4个网页A、B、C、D。它们之间的链接信息如文章中的图示。我们假设用户有15%的概率随机跳转请你编写代码重新计算这4个节点的PR值。
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。