“一张照片包含了万千信息”,这句话经常被人们引用。可是一张图能表达的信息要更多。以图的形式可视化数据,帮助咱们得到了更可行的看法,并基于此做出更好的数据驱动的决策。html
可是,为了真正理解图究竟是什么,以及为何咱们要使用它,咱们还须要知道图论的概念。知道了这个,能够帮助咱们更好的编程。前端
若是你以前曾经学习过图论,你必定知道你须要学习成千上万的公式和枯燥的理论概念。因此咱们决定写这篇博客。咱们会首先解释概念而后提供说明示例,方便你跟上咱们的进度,并直观的理解函数是如何运做的。本篇博客会写的很详细,由于咱们相信,提供正确的解释,是一种比只给出简单定义更受欢迎的选择。node
在本篇文章中,咱们会了解图是什么,它的应用,以及一些图的历史。同时文章中也会涵盖一些图论概念,而后咱们会学习一个基于 Python 的示例,来巩固理解。python
准备好了吗?那咱们开始进入学习吧!android
让咱们先来观察一个以下所示的简单的图,从而帮助理解概念:ios
设想这个图表明了城市中人们常常会光顾的不一样地点,而后一位城市游客按照这个路径行进。咱们设定 V 表示地点,E 表示地点之间的路径git
V = {v1, v2, v3, v4, v5}
E = {(v1,v2), (v2,v5), (v5, v5), (v4,v5), (v4,v4)}
复制代码
边 (u,v) 和边 (v,u) 是同样的 —— 它们是无序数对。github
具体来说 —— 图是一种用来学习对象间和实体间配对关系的数学结构。它是离散数学的一个分支,而且在计算机科学、化学、语言学、运筹学、社会学等多个领域有普遍的应用。正则表达式
数据科学和分析领域也一样使用图来模拟不一样的结构和问题。做为一个数据学的科学家,你应该可以以高效的方法来解决问题,而在不少场景下,图就能够提供这样高效的机制,由于数据被以一种特别的方式组织了起来。算法
正式来说:
G = (V,E)
。V 是一个顶点集合。E 是一个边的集合。E 是 V 中元素对组合而来的(无序对)。D = (V,A)
。V 是顶点的集合。A 是弧的集合。A 是 V 中元素配对组合(有序对)。若是是有向图,那么 (u,v)
和 (v,u)
就是有区别的。这时,边被称为弧,来代表方向的概念。
R 和 Python 中有不少用图论来分析数据的库。在本篇文章中,咱们将会使用 Networkx Python 包来简单的学习一些这方面的概念,并作一些数据分析。
from IPython.display import Image
Image('images/network.PNG')
复制代码
Image('images/usecase.PNG')
复制代码
在上面的例子中能够很清晰的看出,图在数据分析中的应用很是普遍。咱们来看几个案例:
若是你想知道图的理论是如何被创建起来的 —— 继续读下去吧!
图论的起源能够追溯到七桥(Konigsberg bridge)问题(大约在 1730 年左右)。这个问题提出,哥尼斯堡城里的七座桥是否可以在知足如下条件的前提下所有被走过一遍:
这个问题等同于,有四节点和七边的图是否能拥有一个欧拉圆(欧拉圆指的是,一个开始点和终止点相同的欧拉路径。而欧拉路径指的是,在图中刚好经过每一条边一次的路径。更多的术语将在下文介绍)。这个问题引出了欧拉图的概念。而关于哥尼斯堡桥问题,答案是不能,第一个回答出这个问题的人正是欧拉,你必定已经猜到了。
在 1840 年,A.F Mobius 给出了彻底图和二分图的概念,同时 Kuratowski 经过 recreational 问题证实了它们都是平面图。树的概念(无环全链接图)则在 1845 被 Gustav Kirchhoff 提出,而且他在计算电网或电路中的电流时使用了图论的思想。
在 1852 年,Thomas Gutherie 建立了著名的四色问题。然后在 1856 年,Thomas. P. Kirkman 和 William R.Hamilton 共同在多面体上研究圆环,并经过研究如何找出经过指定的每一个点仅一次的路径,建立了哈密顿图的概念。1913 年,H.Dudeney 也提到了一个难题。尽管四色问题很早就被提出,而在一个世纪后才被 Kenneth Appel 和 Wolfgang Haken 解答。这个时间才被认为是图论的诞生时间。
Caley 研究了微分学的特定分析形式,从而研究树结构。这在理论化学上已经有了不少应用。这也激发了枚举图论的建立。而在 1878 年,Sylvester 在“量子不变量”与代数和分子图的协变量之间进行了类比时,使用了“图”这个术语。
在 1941 年,Ramsey 研究了着色问题,从而引出了图论另外一个分支的定义,即极值图论。在 1969 年,四色问题被 Heinrich 经过计算机解决。渐进图的学习也连带着激发了随机图论的发展。图论和拓扑学的历史一样紧密相关,它们之间有不少共同的概念和理论。
Image('images/Konigsberg.PNG', width = 800)
复制代码
如下几点能够激励你在平常数据科学问题中使用图论:
在继续深刻以前,咱们建议你熟悉下面这些术语。
u
和 v
被称为边 (u,v)
的端点(end vertices
)(v,v)
的边是一个环Empty
)。也就是 E
是空的Null Graph
)。也就是 V
和 E
全都是空的Trivial
graph)Adjacent
)边。若是两个顶点有一条公共边,则它们是相邻顶点d(v)
,表示以该顶点做为端点的边的数量。按照惯例,环对应端点的度为边的两倍,平行边分别对应的两个端点的度都要加d(1)
的顶点是孤立的。G = (V,E)
的一个路径(Walk
)Open
)。而若是开始顶点和结束顶点相同,则称为闭合的(Closed
)Trail
)称为路径Path
)称为迹(除了闭路)Circuit
)—— 相似于一个电路在这个章节中,咱们将会学习一些数据分析相关的有用的概念(内容不分前后)。记住,本文章涉及的内容以外还有不少须要深度学习的概念。如今让咱们开始吧。
全部可能的配对点的平均最短路径长度。它给图了一个“紧密”程度的度量,能够被用于描述网络中流的快慢/是否易于经过。
宽度优先搜索和深度优先搜索是两个用于搜索图中节点的不一样的算法。它们一般用于查看从已知节点出发,是否能找到某个节点。也被称为图的遍历
BFS 的目标是依次搜索距离根节点最近的节点以遍历图,而 DFS 的目标是依次搜索距离根节点尽量远的节点,从而遍历图。
它的用途最普遍,而且是网络分析最重要的概念工具。中心性的目标是找到网络中最重要的节点。如何定义“重要”能够多种方式,因此就有不少中心的度量方法。中心性的度量方法自己就有分类(或者说中心度量方法的类别)。有的是经过边的流量来度量,而有的则经过图的路径结构。
一些最多见的应用以下:
这些中心性度量各有不一样,它们的定义能够应用于不一样的算法中。总而言之,这意味着会引出大量的定义和算法。
图中有多少边的度量。定义会随着图的种类以及问题所处的情景而变化。对于一个彻底无向图,则网络密度为 1,而对于一个空图,度则为 0。图的网络密度在一些场景下也能够大于一(好比图中包含环的时候)。
一些图的指标定义也许很容易计算,可是想要弄清楚它们的相关重要性却并不容易。这时咱们就会用到网络/图形随机化。咱们同时计算当前图和另外一个随机生成的类似图的某个指标。类似性能够是图的度和节点数量相等。一般状况下,咱们会生成 1000 个类似的随机图,并计算每一个图的指标,而后将结果与手头上的图的相同指标进行对比,以得出基准概念。
在数据科学领域中,当你尝试对图做出某个声明的时候,将它与随机生成的图作对比将会颇有帮助。
咱们将会使用 Python 的 networkx
工具包。若是你使用的是 Python 的 Anaconda 发行版,则它能够被安装在 Anaconda 的根环境下。你也可使用 pip install
来安装。
下面咱们来看看使用 Networkx 包能作的一些事情。包括引入和建立图,以及图的可视化。
import networkx as nx
# 建立一个图
G = nx.Graph() # 如今 G 是空的
# 添加一个节点
G.add_node(1)
G.add_nodes_from([2,3]) # 你也能经过传入一个列表来添加一系列的节点
# 添加边
G.add_edge(1,2)
e = (2,3)
G.add_edge(*e) # * 表示解包元组
G.add_edges_from([(1,2), (1,3)]) # 正如节点的添加,咱们也能够这样添加边
复制代码
点和边属性能够随着它们的建立被添加,方法是传入一个包含了点和属性的字典。
除了一个一个点或者一条一条边的来建立图,还能够经过应用经典的图操做来建立,例如:
subgraph(G, nbunch) - 生成由节点集合 nbunch 组成的 G 的子图
union(G1,G2) - 求图的并集
disjoint_union(G1,G2) - 图中全部不一样节点组成的单元
cartesian_product(G1,G2) - 返回笛卡尔积图(Cartesian product graph)
compose(G1,G2) - 两图中都有的点所组成的图
complement(G) - 补图
create_empty_copy(G) - 返回同一个图的空副本
convert_to_undirected(G) - 返回图的无向形式
convert_to_directed(G) - 返回图的有向形式
复制代码
对于不一样类别的图,有单独的类。例如类 nx.DiGraph()
支持新建有向图。包含特定路径的图也可使用某一个方法直接建立出来。若是想了解全部的建立图的方法,能够参见文档。参考列表在文末给出。
Image('images/graphclasses.PNG', width = 400)
复制代码
图的全部边和节点可使用方法 G.nodes()
和 G.edges()
获取。单独的边和节点可使用括号/下标的方式获取。
G.nodes()
复制代码
NodeView((1, 2, 3))
G.edges()
复制代码
EdgeView([(1, 2), (1, 3), (2, 3)])
G[1] # 与 G.adj[1] 相同
复制代码
AtlasView({2: {}, 3: {}})
G[1][2]
复制代码
{}
G.edges[1, 2]
复制代码
{}
Networkx 提供了基础的图的可视化功能,可是它的主要目标是分析图而不是图的可视化。图的可视化比较难,咱们将会使用专门针对它的特殊工具。Matplotlib
提供了不少方便的函数。可是 GraphViz
则多是最好的工具,由于它以 PyGraphViz
的形式提供了 Python 接口(下面给出了它的文档连接)。
%matplotlib inline
import matplotlib.pyplot as plt
nx.draw(G)
复制代码
首先你须要从网站安装 Graphviz(以下是下载连接)。而后运行 pip install pygraphviz --install-option=" <>
。在安装选项中你须要提供 Graphviz 的库和依赖的文件夹地址。
import pygraphviz as pgv
d={'1': {'2': None}, '2': {'1': None, '3': None}, '3': {'1': None}}
A = pgv.AGraph(data=d)
print(A) # 这是图的字符串形式或者简单展现形式
复制代码
Output:
strict graph "" {
1 -- 2;
2 -- 3;
3 -- 1;
}
复制代码
PyGraphviz 提供了对边和节点的每一个属性的强大掌控能力。咱们能够用它获得很是美观的可视化图形。
# 让咱们建立另外一个图,咱们能够控制它每一个节点的颜色
B = pgv.AGraph()
# 设置全部节点的共同属性
B.node_attr['style']='filled'
B.node_attr['shape']='circle'
B.node_attr['fixedsize']='true'
B.node_attr['fontcolor']='#FFFFFF'
# 建立并设置每一个节点不一样的属性(使用循环)
for i in range(16):
B.add_edge(0,i)
n=B.get_node(i)
n.attr['fillcolor']="#%2x0000"%(i*16)
n.attr['height']="%s"%(i/16.0+0.5)
n.attr['width']="%s"%(i/16.0+0.5)
B.draw('star.png',prog="circo") # 这行代码会在本地建立一个 .png 格式的文件。以下所示。
Image('images/star.png', width=650) # 咱们所建立的图的可视化图片
复制代码
一般状况下,可视化被认为是图分析的一个独立任务。分析后的图形会导出为点文件。而后这个点文件被另作可视化处理,来展现咱们试图证实的观点。
咱们将会学习一个通用数据集(并非专门用于图分析的),而后作一些操做(使用 panda 库),这样数据才能以边列表的形式被插入到图中。边列表是一个元组的列表,包含了定义每一条边的顶点对。
这个数据集来自于航空业。它包含了航线的一些基本信息,以及旅程和目的地的资源,对于每一个旅程还包含了几栏到达和起飞时间的说明。你可以想象,这个数据集自己就很是适合做为图来分析。想象一下航线(边)链接城市(节点)。若是你在运营航空公司,接下来你能够问以下这几个问题
import pandas as pd
import numpy as np
data = pd.read_csv('data/Airlines.csv')
复制代码
data.shape
(100, 16)
复制代码
data.dtypes
year int64
month int64
day int64
dep_time float64
sched_dep_time int64
dep_delay float64
arr_time float64
sched_arr_time int64
arr_delay float64
carrier object
flight int64
tailnum object
origin object
dest object
air_time float64
distance int64
dtype: object
复制代码
# 将 sched_dep_time 转化为 'std' —— 预计起飞时间
data['std'] = data.sched_dep_time.astype(str).str.replace('(\d{2}$)', '') + ':' + data.sched_dep_time.astype(str).str.extract('(\d{2}$)', expand=False) + ':00'
# 将 sched_arr_time 转化为 'sta' —— 预计抵达时间
data['sta'] = data.sched_arr_time.astype(str).str.replace('(\d{2}$)', '') + ':' + data.sched_arr_time.astype(str).str.extract('(\d{2}$)', expand=False) + ':00'
# 将 dep_time 转化为 'atd' —— 实际起飞时间
data['atd'] = data.dep_time.fillna(0).astype(np.int64).astype(str).str.replace('(\d{2}$)', '') + ':' + data.dep_time.fillna(0).astype(np.int64).astype(str).str.extract('(\d{2}$)', expand=False) + ':00'
# 将 arr_time 转化为 'ata' —— 实际抵达时间
data['ata'] = data.arr_time.fillna(0).astype(np.int64).astype(str).str.replace('(\d{2}$)', '') + ':' + data.arr_time.fillna(0).astype(np.int64).astype(str).str.extract('(\d{2}$)', expand=False) + ':00'
复制代码
如今咱们有了咱们指望的格式时间栏。最后,咱们指望将year
、month
和 day
合并为一个时间栏。这一步并非必需的,可是一旦时间被转化为 datetime
的格式,咱们能够很容易的获取到年月日以及其余信息。
data['date'] = pd.to_datetime(data[['year', 'month', 'day']])
# 最后,咱们删除掉不须要的栏
data = data.drop(columns = ['year', 'month', 'day'])
复制代码
如今使用 networkx 函数导入数据,该函数能够直接获取 pandas 的数据帧。正如图的建立,这里也有不少将不一样格式的数据插入图的方法。
import networkx as nx
FG = nx.from_pandas_edgelist(data, source='origin', target='dest', edge_attr=True,)
复制代码
FG.nodes()
复制代码
输出:
NodeView(('EWR', 'MEM', 'LGA', 'FLL', 'SEA', 'JFK', 'DEN', 'ORD', 'MIA', 'PBI', 'MCO', 'CMH', 'MSP', 'IAD', 'CLT', 'TPA', 'DCA', 'SJU', 'ATL', 'BHM', 'SRQ', 'MSY', 'DTW', 'LAX', 'JAX', 'RDU', 'MDW', 'DFW', 'IAH', 'SFO', 'STL', 'CVG', 'IND', 'RSW', 'BOS', 'CLE'))
复制代码
FG.edges()
复制代码
输出:
EdgeView([('EWR', 'MEM'), ('EWR', 'SEA'), ('EWR', 'MIA'), ('EWR', 'ORD'), ('EWR', 'MSP'), ('EWR', 'TPA'), ('EWR', 'MSY'), ('EWR', 'DFW'), ('EWR', 'IAH'), ('EWR', 'SFO'), ('EWR', 'CVG'), ('EWR', 'IND'), ('EWR', 'RDU'), ('EWR', 'IAD'), ('EWR', 'RSW'), ('EWR', 'BOS'), ('EWR', 'PBI'), ('EWR', 'LAX'), ('EWR', 'MCO'), ('EWR', 'SJU'), ('LGA', 'FLL'), ('LGA', 'ORD'), ('LGA', 'PBI'), ('LGA', 'CMH'), ('LGA', 'IAD'), ('LGA', 'CLT'), ('LGA', 'MIA'), ('LGA', 'DCA'), ('LGA', 'BHM'), ('LGA', 'RDU'), ('LGA', 'ATL'), ('LGA', 'TPA'), ('LGA', 'MDW'), ('LGA', 'DEN'), ('LGA', 'MSP'), ('LGA', 'DTW'), ('LGA', 'STL'), ('LGA', 'MCO'), ('LGA', 'CVG'), ('LGA', 'IAH'), ('FLL', 'JFK'), ('SEA', 'JFK'), ('JFK', 'DEN'), ('JFK', 'MCO'), ('JFK', 'TPA'), ('JFK', 'SJU'), ('JFK', 'ATL'), ('JFK', 'SRQ'), ('JFK', 'DCA'), ('JFK', 'DTW'), ('JFK', 'LAX'), ('JFK', 'JAX'), ('JFK', 'CLT'), ('JFK', 'PBI'), ('JFK', 'CLE'), ('JFK', 'IAD'), ('JFK', 'BOS')])
复制代码
nx.draw_networkx(FG, with_labels=True) # 图的快照。正如咱们指望的,咱们看到了三个很繁忙的机场
复制代码
nx.algorithms.degree_centrality(FG) # Notice the 3 airports from which all of our 100 rows of data originates
nx.algorithms.degree_centrality(FG) # 从一百多行的全部源数据中标注出这三个机场
nx.density(FG) # 图的平均边度
复制代码
输出:
0.09047619047619047
复制代码
nx.average_shortest_path_length(FG) # 图中全部路径中的最短平均路径
复制代码
输出:
2.36984126984127
复制代码
nx.average_degree_connectivity(FG) # 对于一个度为 k 的节点 —— 它的邻居节点的平均值是什么?
复制代码
输出:
{1: 19.307692307692307, 2: 19.0625, 3: 19.0, 17: 2.0588235294117645, 20: 1.95}
复制代码
很明显的能够从上文的图的可视化看出 —— 一些机场之间有不少路径。加入咱们但愿计算两个机场之间可能的最短路径。咱们能够想到这几种方法
咱们能作的是,经过对比距离或者时间路径,计算最短路径的算法。注意,这是一个近似的答案 —— 实际须要解决的问题是,当你到达起色机场时可选择的航班 + 等待起色的时间共同决定的最短方法。这是一个更加复杂的方法,而也是人们一般用于计划旅行的方法。鉴于本篇文章的目标,咱们仅仅假设当你到达机场的时候航班刚好能够搭乘,并在计算最短路径的时候以时间做为计算对象。
咱们以 JAX
和 DFW
机场为例:
# 找到全部可用路径
for path in nx.all_simple_paths(FG, source='JAX', target='DFW'):
print(path)
# 站到从 JAX 到 DFW 的 dijkstra 路径
# 你能够在这里阅读更多更深刻关于 dijkstra 是如何计算的信息 —— https://courses.csail.mit.edu/6.006/fall11/lectures/lecture16.pdf
dijpath = nx.dijkstra_path(FG, source='JAX', target='DFW')
dijpath
复制代码
输出:
['JAX', 'JFK', 'SEA', 'EWR', 'DFW']
复制代码
# 咱们来试着找出飞行时间的 dijkstra 路径(近似状况)
shortpath = nx.dijkstra_path(FG, source='JAX', target='DFW', weight='air_time')
shortpath
复制代码
输出:
['JAX', 'JFK', 'BOS', 'EWR', 'DFW']
复制代码
本文只是对图论与网络分析这一很是有趣的领域进行了很简单的介绍。图论的知识和 Python 包能做为任何一个数据科学家很是有价值的工具。关于上文使用的数据集,还有一系列能够提出的问题,例如:
若是你真的解决了这些问题,请在评论区评论,好让咱们知道!
网络分析将会帮助咱们解决一些常见的数据科学问题,并以更大规模和抽象的方式进行可视化。若是你想在某个特定方面了解更多,请留言给咱们。
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。