《数据分析实战-托马兹.卓巴斯》读书笔记第8章--图(NetworkX、Gephi)修订版

 python学习笔记-目录索引php

 第8章介绍了如何使用NetworkX和Gephi来对图数据进行处理、理解、可视化和分析。html

本章中,会学习如下技巧:
·使用NetworkX在Python中处理图对象
·使用Gephi将图可视化
·识别信用卡信息被盗的用户
·识别谁盗窃了信用卡

8.1导论

图无处不在;当你开GPS驾车时,你可能还没意识到这是在解决一个图问题,以最短路径或最短期从A点到达B点。
图论起源于18世纪,Leonard Euler提出了七桥问题的解法。(关于这个话题,能够参考http://www2.gsu.edu/~matgtc/origin%20of%20graph%20theory.pdf)从那时起,有些看上去不能解决的问题获得解决了;互联网(或者你的本地网络)能够当作一个图,航线调度问题能够建模为一个图,或者(咱们后面会看到)一个社交网络在咱们意识到这是一个图以后就更容易处理了。
图是由节点和链接两个节点的边组成:
邀月工做室

前面的例子多是最简单的网络,只有两个节点,和链接它们的一条边。这是一个无向图:节点之间的链接没有方向的差异。好比,若是节点是人,边能够表明人与人之间相互认识。这种状况下,咱们总不能指定一个方向,毕竟关系是双向的:A认识B,B也认识A。
图中也能够带方向。这能够是一个建筑的工程计划,这里节点是特定的任务,而边表明计划的行进。

8.2使用NetworkX在Python中处理图对象

社交网络的爆发,好比Facebook、Twitter或LinkedIn等,带来了不少问题。好比:谁是谁的朋友,我能经由朋友圈接触到中意公司的招聘者么,我和Obama总统是相连的么,谁是个人网络里最有影响力的人?
这些问题在当今社会很常见,做为数据科学家,应该知道怎么解决。
本技巧中,咱们会用一个假造的20人的Twitter网络。你会学到如何建立一个图,加入节点和边(以及附加的元数据),分析图,导出图以便用Gephi阅读。
准备:需装好NetworkX、collections和Matplotlib。node

步骤:NetworkX提供了一个构建和分析图的框架。能够处理无向图,也能处理有向图。另外,还能够建模多重图:两个节点之间能够有多条边以及带有闭环的图。python

/*
pip install Networkx
*/

本技巧中要用到的程序在本章文件夹(Codes/Chapter08)下的graph_handling.py文件中:git

1 import networkx as nx
2 import networkx.algorithms as alg
3 import numpy as np
4 import matplotlib.pyplot as plt
5 
6 # create graph object
7 twitter = nx.Graph()

原理:首先导入须要的模块;networkx.algorithms提供了后面要用到的各类图算法。
而后,咱们建立无向图的框架,两个节点,一条边。
要了解全部图类型,可查看NetworkX的文档:http://networkx.github.io/documentation/networkx-1.10/reference/classes.html
如今,咱们加一些节点。前面提过,咱们的网络中有20我的。用.add_node(...)方法加入:github

 1 # add users
 2 twitter.add_node('Tom', {'age': 34})
 3 twitter.add_node('Rachel', {'age': 33})
 4 twitter.add_node('Skye', {'age': 29})
 5 twitter.add_node('Bob', {'age': 45})
 6 twitter.add_node('Mike', {'age': 23})
 7 twitter.add_node('Peter', {'age': 46})
 8 twitter.add_node('Matt', {'age': 58})
 9 twitter.add_node('Lester', {'age': 65})
10 twitter.add_node('Jack', {'age': 32})
11 twitter.add_node('Max', {'age': 75})
12 twitter.add_node('Linda', {'age': 23})
13 twitter.add_node('Rory', {'age': 18})
14 twitter.add_node('Richard', {'age': 24})
15 twitter.add_node('Jackie', {'age': 25})
16 twitter.add_node('Alex', {'age': 24})
17 twitter.add_node('Bart', {'age': 33})
18 twitter.add_node('Greg', {'age': 45})
19 twitter.add_node('Rob', {'age': 19})
20 twitter.add_node('Markus', {'age': 21})
21 twitter.add_node('Glenn', {'age': 24})

 Tips:
 web

/*    
File "D:\Java2018\practicalDataAnalysis\Codes\Chapter08\graph_handling_org.py", line 10
    twitter.add_node('Tom', {'age': 34}))
                                        ^
SyntaxError: invalid syntax 
*/

 

解决方案:查阅官方资料,注意版本差别,使用UTM属性赋值算法

 1 /*  
 2 #Use keywords set/change node attributes:
 3 >>>
 4 G.add_node(1, size=10)
 5 G.add_node(3, weight=0.4, UTM=('13S', 382871, 3972649))
 6 
 7 import networkx as nx
 8 import numpy as np
 9 import matplotlib.pyplot as plt
10 
11 # create graph object
12 twitter = nx.Graph()
13 twitter.add_node('Tom', UTM={'age',33})
14 
15 print(twitter.nodes['Tom']['UTM'])
16 #------------------------------------
17 {33, 'age'}
18 
19 # add users
20 
21 twitter.add_node('Rachel')
22 twitter.nodes['Rachel']['age'] = 33
23 
24 twitter.add_node('Skye')
25 twitter.nodes['Skye']['age'] = 29
26 
27 twitter.add_node('Bob')
28 twitter.nodes['Bob']['age'] = 45
29 
30 twitter.add_node('Mike')
31 twitter.nodes['Mike']['age'] = 23
32 
33 twitter.add_node('Peter')
34 twitter.nodes['Peter']['age'] = 46
35 
36 twitter.add_node('Matt')
37 twitter.nodes['Matt']['age'] = 58
38 
39 twitter.add_node('Lester')
40 twitter.nodes['Lester']['age'] = 65
41 
42 twitter.add_node('Jack')
43 twitter.nodes['Jack']['age'] = 32
44 
45 twitter.add_node('Max')
46 twitter.nodes['Max']['age'] = 75
47 
48 twitter.add_node('Linda')
49 twitter.nodes['Linda']['age'] = 23
50 
51 twitter.add_node('Rory')
52 twitter.nodes['Rory']['age'] = 18
53 
54 twitter.add_node('Richard')
55 twitter.nodes['Richard']['age'] = 24
56 
57 twitter.add_node('Jackie')
58 twitter.nodes['Jackie']['age'] = 25
59 
60 twitter.add_node('Alex')
61 twitter.nodes['Alex']['age'] = 24
62 
63 twitter.add_node('Bart')
64 twitter.nodes['Bart']['age'] = 33
65 
66 twitter.add_node('Greg')
67 twitter.nodes['Greg']['age'] = 45
68 
69 twitter.add_node('Rob')
70 twitter.nodes['Rob']['age'] = 19
71 
72 twitter.add_node('Markus')
73 twitter.nodes['Markus']['age'] = 21
74 
75 twitter.add_node('Glenn')
76 twitter.nodes['Glenn']['age'] = 24
77 
78 */

方法以节点ID做为第一个参数,第二个参数是可选的;参数二是一个修饰节点的元数据字典。
节点须要是去重的,也就是说,若是有两个Peter,你得经过某种方式区分他们(好比用姓,或者序列号)。
你也能用列表添加节点。查看这里的.add_nodes_from(...)方法:数据库

http://networkx.github.io/documentation/networkx-1.10/reference/generated/networkx.Graph.add_nodes_from.html#networkx.Graph.add_nodes_from
https://networkx.github.io/documentation/networkx-2.4/reference/classes/generated/networkx.Graph.add_node.html#networkx.Graph.add_nodes_from

也能够经过直接访问节点的方式添加元数据。既然这是一个表明Twitter社交网络的图,那咱们就加一下发帖数吧:api

 1 # add posts
 2 twitter.node['Rory']['posts'] = 182
 3 twitter.node['Rob']['posts'] = 111
 4 twitter.node['Markus']['posts'] = 159
 5 twitter.node['Linda']['posts'] = 128
 6 twitter.node['Mike']['posts'] = 289
 7 twitter.node['Alex']['posts'] = 188
 8 twitter.node['Glenn']['posts'] = 252
 9 twitter.node['Richard']['posts'] = 106
10 twitter.node['Jackie']['posts'] = 138
11 twitter.node['Skye']['posts'] = 78
12 twitter.node['Jack']['posts'] = 62
13 twitter.node['Bart']['posts'] = 38
14 twitter.node['Rachel']['posts'] = 89
15 twitter.node['Tom']['posts'] = 23
16 twitter.node['Bob']['posts'] = 21
17 twitter.node['Greg']['posts'] = 41
18 twitter.node['Peter']['posts'] = 64
19 twitter.node['Matt']['posts'] = 8
20 twitter.node['Lester']['posts'] = 4
21 twitter.node['Max']['posts'] = 2

如你所见,你能够经过ID访问节点并设置元数据;当你想在建立节点时就设置一些固定参数(好比前面指定的年龄)并及时更新元数据(好比用户发推)时,这是颇有用的。
如今,看下谁认识谁:

 1 # add followers
 2 twitter.add_edge('Rob', 'Rory', {'Weight': 1})
 3 twitter.add_edge('Markus', 'Rory', {'Weight': 1})
 4 twitter.add_edge('Markus', 'Rob', {'Weight': 5})
 5 twitter.add_edge('Mike', 'Rory', {'Weight': 1})
 6 twitter.add_edge('Mike', 'Rob', {'Weight': 1})
 7 twitter.add_edge('Mike', 'Markus', {'Weight': 1})
 8 twitter.add_edge('Mike', 'Linda', {'Weight': 5})
 9 twitter.add_edge('Alex', 'Rob', {'Weight': 1})
10 twitter.add_edge('Alex', 'Markus', {'Weight': 1})
11 twitter.add_edge('Alex', 'Mike', {'Weight': 1})
12 twitter.add_edge('Glenn', 'Rory', {'Weight': 1})
13 twitter.add_edge('Glenn', 'Rob', {'Weight': 1})
14 twitter.add_edge('Glenn', 'Markus', {'Weight': 1})
15 twitter.add_edge('Glenn', 'Linda', {'Weight': 2})
16 twitter.add_edge('Glenn', 'Mike', {'Weight': 1})
17 twitter.add_edge('Glenn', 'Alex', {'Weight': 1})
18 twitter.add_edge('Richard', 'Rob', {'Weight': 1})
19 twitter.add_edge('Richard', 'Linda', {'Weight': 1})
20 twitter.add_edge('Richard', 'Mike', {'Weight': 1})
21 twitter.add_edge('Richard', 'Alex', {'Weight': 1})
22 twitter.add_edge('Richard', 'Glenn', {'Weight': 1})
23 twitter.add_edge('Jackie', 'Linda', {'Weight': 1})
24 twitter.add_edge('Jackie', 'Mike', {'Weight': 1})
25 twitter.add_edge('Jackie', 'Glenn', {'Weight': 1})
26 twitter.add_edge('Jackie', 'Skye', {'Weight': 1})
27 twitter.add_edge('Tom', 'Rachel', {'Weight': 5})
28 twitter.add_edge('Rachel', 'Bart', {'Weight': 1})
29 twitter.add_edge('Tom', 'Bart', {'Weight': 2})
30 twitter.add_edge('Jack', 'Skye', {'Weight': 1})
31 twitter.add_edge('Bart', 'Skye', {'Weight': 1})
32 twitter.add_edge('Rachel', 'Skye', {'Weight': 1})
33 twitter.add_edge('Greg', 'Bob', {'Weight': 1})
34 twitter.add_edge('Peter', 'Greg', {'Weight': 1})
35 twitter.add_edge('Lester', 'Matt', {'Weight': 1})
36 twitter.add_edge('Max', 'Matt', {'Weight': 1})
37 twitter.add_edge('Rachel', 'Linda', {'Weight': 1})
38 twitter.add_edge('Tom', 'Linda', {'Weight': 1})
39 twitter.add_edge('Bart', 'Greg', {'Weight': 2})
40 twitter.add_edge('Tom', 'Greg', {'Weight': 2})
41 twitter.add_edge('Peter', 'Lester', {'Weight': 2})
42 twitter.add_edge('Tom', 'Mike', {'Weight': 1})
43 twitter.add_edge('Rachel', 'Mike', {'Weight': 1})
44 twitter.add_edge('Rachel', 'Glenn', {'Weight': 1})
45 twitter.add_edge('Lester', 'Max', {'Weight': 1})
46 twitter.add_edge('Matt', 'Peter', {'Weight': 1})

.add_edge(...)方法以源节点为第一个参数,以目标节点做为第二个参数;在咱们这个图中,顺序并不重要,由于是无向图。你能够只提供这两个参数;元数据字典是可选的(和节点的状况相似)。咱们使用Weight参数区分某些链接(下面会用到)。让咱们加上描述这些区别的关系:

 1 # add relationship
 2 twitter['Rob']['Rory']['relationship'] = 'friend'
 3 twitter['Markus']['Rory']['relationship'] = 'friend'
 4 twitter['Markus']['Rob']['relationship'] = 'spouse'
 5 twitter['Mike']['Rory']['relationship'] = 'friend'
 6 twitter['Mike']['Rob']['relationship'] = 'friend'
 7 twitter['Mike']['Markus']['relationship'] = 'friend'
 8 twitter['Mike']['Linda']['relationship'] = 'spouse'
 9 twitter['Alex']['Rob']['relationship'] = 'friend'
10 twitter['Alex']['Markus']['relationship'] = 'friend'
11 twitter['Alex']['Mike']['relationship'] = 'friend'
12 twitter['Glenn']['Rory']['relationship'] = 'friend'
13 twitter['Glenn']['Rob']['relationship'] = 'friend'
14 twitter['Glenn']['Markus']['relationship'] = 'friend'
15 twitter['Glenn']['Linda']['relationship'] = 'sibling'
16 twitter['Glenn']['Mike']['relationship'] = 'friend'
17 twitter['Glenn']['Alex']['relationship'] = 'friend'
18 twitter['Richard']['Rob']['relationship'] = 'friend'
19 twitter['Richard']['Linda']['relationship'] = 'friend'
20 twitter['Richard']['Mike']['relationship'] = 'friend'
21 twitter['Richard']['Alex']['relationship'] = 'friend'
22 twitter['Richard']['Glenn']['relationship'] = 'friend'
23 twitter['Jackie']['Linda']['relationship'] = 'friend'
24 twitter['Jackie']['Mike']['relationship'] = 'friend'
25 twitter['Jackie']['Glenn']['relationship'] = 'friend'
26 twitter['Jackie']['Skye']['relationship'] = 'friend'
27 twitter['Tom']['Rachel']['relationship'] = 'spouse'
28 twitter['Rachel']['Bart']['relationship'] = 'friend'
29 twitter['Tom']['Bart']['relationship'] = 'sibling'
30 twitter['Jack']['Skye']['relationship'] = 'friend'
31 twitter['Bart']['Skye']['relationship'] = 'friend'
32 twitter['Rachel']['Skye']['relationship'] = 'friend'
33 twitter['Greg']['Bob']['relationship'] = 'friend'
34 twitter['Peter']['Greg']['relationship'] = 'friend'
35 twitter['Lester']['Matt']['relationship'] = 'friend'
36 twitter['Max']['Matt']['relationship'] = 'friend'
37 twitter['Rachel']['Linda']['relationship'] = 'friend'
38 twitter['Tom']['Linda']['relationship'] = 'friend'
39 twitter['Bart']['Greg']['relationship'] = 'sibling'
40 twitter['Tom']['Greg']['relationship'] = 'sibling'
41 twitter['Peter']['Lester']['relationship'] = 'generation'
42 twitter['Tom']['Mike']['relationship'] = 'friend'
43 twitter['Rachel']['Mike']['relationship'] = 'friend'
44 twitter['Rachel']['Glenn']['relationship'] = 'friend'
45 twitter['Lester']['Max']['relationship'] = 'friend'
46 twitter['Matt']['Peter']['relationship'] = 'friend'

咱们的网络中有四种关系:friend、spouse、sibling和generation。最后一个是父子关系。相应的Weight值是一、五、2和2。
注意咱们是如何访问边并设置元数据的,例如,twitter['Rachel']['Tom'],而后设置interest属性。
更多:NetworkX提供了多种辅助访问、操做以及分析图的有用方法。
要得到图中全部节点的列表,能够不带任何参数调用.nodes(...)方法,即.nodes()。打印出来,相似这样:

邀月工做室

.nodes(...)方法也接受data参数;.nodes(data=True)会返回每一个节点的元数据:

邀月工做室
能够经过相似的方式访问边;调用.edges(...)方法;调用.edges(Data=True)会返回下面的列表(已简化):
邀月工做室

建立了图,咱们来分析其结构。咱们看的第一个指标就是图的密度:

1 # graph's density and centrality
2 print('\nDensity of the graph: ', nx.density(twitter)) 

.density(...)参数度量图中节点之间的连通度;一个图中,全部节点都与其余全部节点相连(没有环)时密度是1。简单来讲,图的密度就是图中边的数目与可能的边的数目的比例。对于咱们的图,咱们有以下结果:

/* 
Density of the graph:  0.23684210526315788
 */

这说明咱们的图是稀疏的:可能的边的数目用等式n*(n-1)/2计算,因此咱们获得20*19/2=190。咱们图中边的总数是45。即,图中呈现的可能链接只占23.7%。
若是你看不明白为何是n*(n-1)/2,查看这里:http://jwilson.coe.uga.edu/EMAT6680 Fa2013/Hendricks/Essay%202/Essay2.html
另外一个有用的指标是度数。节点的度数是其全部邻居的数目。节点相邻指的是有边直接相连。也就是说,节点的度数不过就是邻接的节点的总数。
.centrality.degree_centrality(...)方法计算的是节点的度数与图中最大可能度数(即节点数减1)的比例:

1 centrality = sorted(
2     alg.centrality.degree_centrality(twitter).items(),
3     key=lambda e: e[1], reverse=True)

计算出来的结果根据中心度降序排列;这样咱们能够看出图中谁联系的最多:
邀月工做室
看来Mike和Glenn是图中链接最多的人(下面咱们会从图像上推断这一点)。
.assortativity.average_neighbor_degree(...)方法,对每个节点,计算邻居的平均度数。这个指标能让咱们找出谁是网络中链接最多的人的好友——若是你要和某个你不知道的人创建联系,这个指标就颇有用了:

1 average_degree = sorted(
2     alg.assortativity.average_neighbor_degree(twitter)\
3     .items(), key=lambda e: e[1], reverse=True)

咱们看看最有影响力的人:
邀月工做室

可见,当你想扩展网络时,Rory、Jackie、Richard和Alex是你的最优选择,好比他们都认识Mike和Glenn。
其余还有一些有用的指标。做为新手,能够查看这个网站,http://webwhompers.com/graph-theory.html

以及学习NetworkX的文档,http://networkx.github.io/documentation/networkx-1.10/reference/algorithms.html
NetworkX框架内置有绘图功能。
NetworkX可调用Graphviz和pydot,但这两个模块只能在Python 2.7下使用,尚未导入到Python 3.4中,咱们无法利用它们。
要绘制咱们建立的网络,咱们调用.draw_networkx(...)方法:

1  # draw the graph
2 nx.draw_networkx(twitter)
3 plt.savefig('../../Data/Chapter08/twitter_networkx.png')

获得的图看上去不太有吸引力,也不太有信息性,由于内容有重叠,很难阅读。不过仍是显示了图的结构。注意,你的图即便有彻底相同的链接,也颇有可能看上去有个不一样的布局。
邀月工做室
幸运的是,咱们能够将图导出成Gephi能处理的GraphML格式,这是理解咱们社交网络的下一站:

1 # save graph
2 nx.write_graphml(twitter,
3     '../../Data/Chapter08/twitter.graphml')

Tips:

某些状况下会报这个

/*
Traceback (most recent call last):
  File "D:\Java2018\practicalDataAnalysis\Codes\Chapter08\graph_handling.py", line 182, in <module>
    '../../Data/Chapter08/twitter.graphml')
  File "<D:\tools\Python37\lib\site-packages\decorator.py:decorator-gen-658>", line 2, in write_graphml_lxml
  File "D:\tools\Python37\lib\site-packages\networkx\utils\decorators.py", line 240, in _open_file
    result = func_to_be_decorated(*new_args, **kwargs)
  File "D:\tools\Python37\lib\site-packages\networkx\readwrite\graphml.py", line 149, in write_graphml_lxml
    infer_numeric_types=infer_numeric_types)
  File "D:\tools\Python37\lib\site-packages\networkx\readwrite\graphml.py", line 613, in __init__
    self.add_graph_element(graph)
  File "D:\tools\Python37\lib\site-packages\networkx\readwrite\graphml.py", line 652, in add_graph_element
    T = self.xml_type[self.attr_type(k, "node", v)]
KeyError: <class 'set'>
 */


参考:这里有NetworkX的另外一个快速介绍:http://www.python-course.eu/networkx.php

8.3使用Gephi将图可视化

Gephi是一个用于分析和可视化复杂网络的开源应用。可在任何运行Java的平台上运行,因此在Windows、Linux或Mac环境下均可以使用。
要得到Gephi,访问https://gephi.org/users/download/,下载适合你的系统的包。下载后,根据弹窗安装程序。

/*
咱们在Mac OS X El Capitan上运行0.8.2-beta版的Gephi时遇到了不少问题。
Gephi的最新版本使用了与移植到Mac OS X上的Java不兼容的库(好比,https://github.com/gephi/gephi/issues/1141)。
即便安装Java 6历史版本也无法运行Gephi 0.8.2-beta;只有降到0.8.1-beta才可行。本技巧中全部的可视化都是用0.8.1-beta版本实现的。
*/

邀月安装的是0.9.2版本。多语言版本,若是你以为不爽,能够切换到中文。
准备:需装好Gephi。
步骤:用你的平台的特定方式打开软件包。窗口的顶部是视图控制(以下图所示);程序默认视图是Overview,Data Laboratory视图让你能够访问并编辑图的基础数据(节点和边),而Preview视图是打印前的预览:

邀月工做室


视图控制让你能够控制节点和边的展示;你能够更改节点和边的颜色,以及大小和标签。

布局控制让你能够控制图的布局;咱们会简略地看下如何使用包括的算法。

统计与过滤部分让你能够计算图表的统计数据(好比前一技巧里介绍过的平均度数或图密度)。这里也容许你过滤某些节点或边,让你聚焦于图中感兴趣的那一小块。

图窗口展现了图。访问File|Open,导航至Data/Chapter08文件夹,选择twitter.graphml。打开图时,你看到的应该相似这样:
邀月工做室
不是很信息性。做者喜欢作的第一件事就是给节点染色以知道人群的年龄,以及更改节点的大小以体现发帖的数目(原谅我切换到中文界面了。^_^)
邀月工做室
导航至图控制部分,并选择Ranking页卡。在Nodes页,从下拉菜单中选择age,你看到的应该相似这样:


邀月工做室
新版本Gephi也许和这里长得不同,Color选项也可能有不一样的选项。要熟悉Gephi,这就不应是个问题,跟着本技巧中的例子走应该不难。
咱们保持原来的颜色,只改颜色范围的界限点(或转换函数)。单击Spline...,将曲线调整成上图。
单击Apply,节点的颜色应该变了,不过你可能注意不到,由于节点的大小也变了;咱们如今修正这一点。
仍然是Nodes页,首先点击图控制部分右上角的钻石图标;鼠标悬浮其上,它会显示Size/Weight。如今,从下拉菜单中选择posts。你看到的应该相似这样:
邀月工做室
单击Apply,这时节点的大小应该反映发帖数,如上图。
如今颜色能够辨识。然而,要认出节点表明哪一个人,咱们要给每一个节点加上标签。
导航至图窗口。底部有一排图标。点击T(下面的截图中高亮部分):
邀月工做室
好了,咱们知道谁是谁了。
图还不能展现任何特殊的形状。因此,咱们要发掘更有意义的结构。导航至Layout控制页卡。从下拉菜单中选择Force Atlas——一个能够帮咱们发现图的隐藏形式的算法。
这个算法对图的树进行平衡,方式是链接的节点以引力的做用聚在一块儿,而未链接的节点受斥力做用。你能够控制这两种做用的强度(以下图所示):
邀月工做室
Force Atlas算法在指定下列参数后,会循环做用,达到最佳布局:
·Inertia参数控制每次处理时一个节点保留多少速度;0.5意味着节点几乎是静止的。
·Repulsion strength定义了斥力的强度;5000.0意味着图是分散的。对比右边值为15000的图。
·Attraction strength定义了相连节点之间引力的强度;吸引和排斥之间的差异在于,排斥做用于全部节点,而吸引只做用于相连的节点。
·Maximum displacement限制了节点偏离初始位置的最大距离。
·Auto stabilize function固定了给定排斥和吸引参数后会振动的点。
·Autostab Strength参数控制自动稳定函数的强度;较高的值意味着摆动的节点会较快地稳定。
·Autostab sensibility定义了算法执行过程当中,Inertia参数变化的程度。
·Gravity参数指定了每一个节点向图中心的引力强度。
·Attraction Distrib参数控制引力中心的分布,以使得图看起来平衡。这会是一个轴辐式分布。
·Adjust by Sizes控制节点的交叠;选上后,节点不会重叠。
·Speed控制算法的速度;高的值(必须大于0)会加速算法的收敛,代价是精度的损失。
下面的图展现了不一样斥力强度下咱们的图:
你能够设置对比5000与15000,略去。
能够看出,两边几乎是一样的形状,但右边的更分散——咱们都认不出名字。
Weight参数控制边的大小。然而,细线不明显。你可使用左下角的滑动按钮控制厚薄:
见上图
咱们根据关系的类型给边染上不一样的颜色。到图控制面板上,选择Partition。再前往Edges页卡,从下拉菜单中选择relationship:

注意新版本中,Ranking和Partition已经合并到了Appearance页卡。
应用这些变更后,你能够看到图中颜色的变化。注意关系不只用颜色表示,也用权重表示。最后图看上去是这样:
邀月工做室
能够明显看到轴,准确标出了社交网络中年龄的差别。
更多:如今看看Gephi是否和以前NetworkX获得的结果相同。前往Statistics和Filter面板。比较图密度:
邀月工做室
单击Graph Density旁边的Run;在个人例子中,获得同NetworkX彻底相同的结果:0.237。
最后,让咱们探索数据中的联系。咱们使用过滤器。首先,看看谁结婚了:
邀月工做室
从Library,导航至Attributes|Equal(咱们只选择等于spouse的边),选择relationship,拖拽至Queries。应该会出现Equal(relationship)Settings窗口。在Pattern中输入spouse,单击OK。根据单击的是Select仍是Filter,你会看到不一样的图:
邀月工做室
也能够加上过滤器。咱们来过滤年龄在18和32之间的已婚人士:
邀月工做室
咱们以在年龄过滤器中使用Range开始。选出年龄在18和32之间的节点。在Range过滤器的底部,你会看到(不是前一张图里,那里位置已经被Equal过滤器占了)有个地方显示Drag Subfilter;拖一个关系的Equal过滤器到那里,指定为spouse。如今,若是单击Range(age)过滤器并选中Select,你会看到这样的图:
如上图合并。
能够看到,咱们选出了没到32的人,并将其中结了婚的突出显示了。
参考:若是要了解更多,我强烈推荐探索Gephi网站上的资源:https://gephi.org/users/
参看这本书:https://www.packtpub.com/big-data-and-business-intelligence/network-graph-analysis-and-visualization-gephi

8.4识别信用卡信息被盗的用户

当今社会,要作一个诈骗犯,不像之前人们想的那么遥远了。在网上使用双重加密和强密码的你可能以为很安全,然而传统的信用卡信息盗窃起来仍是相对容易的。信用卡诈骗正以惊人的速率增加(http://www.economist.com/news/finance-and-economics/21596547-why-america-has-such-high-rate-payment-card-fraud-skimming-top),2012年便达到了55亿美圆的规模,这可不是一件好玩的事。
本技巧将聚焦于一种特殊的信用卡诈骗形式——网上购物。咱们假设部分(大额)交易,有些卖家会要求买家通话并确认信用卡信息。
为了使用本技巧,咱们生成一个数据集,有1000个买家和20个卖家。50多天里,咱们的买家进行了22.5万多笔交易,总额超过5700万美圆。咱们也知道有一个卖家(卖家4)不诚实,时不时地从买家偷取信用卡信息。而后将信息卖到网上,有人用了这张信用卡,卡的全部者便会报告有未受权的付款。
本技巧中,会学到如何从图中抽取数据,并找到诈骗的受害者。
准备:需装好NetworkX、collections和NumPy。
步骤:咱们用(压缩后的)GraphML格式将数据保存到Data/Chapter08文件夹。NetworkX让读取GraphML数据变得方便,即使是压缩到了文档中( graph_fraudTransactions.py文件):

1 import networkx as nx
2 import numpy as np
3 import collections as c
4 
5 # import the graph
6 graph_file = '../../Data/Chapter08/fraud.gz'
7 fraud = nx.read_graphml(graph_file)

原理:首先,读入数据后,咱们看看处理的图是什么类型:

 print('\nType of the graph: ', type(fraud))

因为任何人均可以与任何卖家进行多笔交易,因此咱们处理的是一个有并行边的有向图,每条边表明一个交易。NetworkX确认了这一点:

/*
Type of the graph:  <class 'networkx.classes.multidigraph.MultiDiGraph'>

*/

咱们确认节点和边的数目:

 1 # population and merchants
 2 nodes = fraud.nodes()
 3 
 4 nodes_population = [n for n in nodes if 'p_' in n]
 5 nodes_merchants  = [n for n in nodes if 'm_' in n]
 6 
 7 n_population = len(nodes_population)
 8 n_merchants  = len(nodes_merchants)
 9 
10 print('\nTotal population: {0}, number of merchants: {1}' \
11     .format(n_population, n_merchants))
12 
13 # number of transactions
14 n_transactions = fraud.number_of_edges()
15 print('Total number of transactions: {0}' \
16     .format(n_transactions))
17 
18 # what do we know about a transaction
19 p_1_transactions = fraud.out_edges('p_1', data=True)
20 print('\nMetadata for a transaction: ',
21     list(p_1_transactions[0][2].keys()))
22 
23 print('Total value of all transactions: {0}' \
24     .format(np.sum([t[2]['amount']
25         for t in fraud.edges(data=True)])))
26 
27 # identify customers with stolen credit cards
28 all_disputed_transactions = \
29     [dt for dt in fraud.edges(data=True) if dt[2]['disputed']]
30 
31 print('\nDISPUTED TRANSACTIONS')
32 print('Total number of disputed transactions: {0}' \
33     .format(len(all_disputed_transactions)))
34 print('Total value of disputed transactions: {0}' \
35     .format(np.sum([dt[2]['amount']
36         for dt in all_disputed_transactions])))

首先,咱们从图中调出全部的节点。咱们知道买家节点的前缀是p_而卖家节点的前缀是m_;咱们建立双方的列表,查看列表长度:

/*
Total population: 1000, number of merchants: 20
 */

.number_of_edges()方法返回图中边的总数,即交易总数:

/*
Total number of transactions: 225037
*/

而后,咱们查看交易有哪些元数据:咱们使用.out_edges(...)方法获取p_1的所有交易。这个方法返回p_1出发的全部边的列表,(指定data=True参数)带上全部的元数据。如同8.2节中展现的,这个列表的元素是三元组:(起点,终点,元数据);元数据元素是一个字典,因此咱们提取出全部的键:

/*
Metadata for a transaction:['type'  'time', 'amount', 'disputed', 'key' ]


#===============================================================================
# print('\nMetadata for a transaction: ',
#     list(p_1_transactions[0][2].keys()))
#===============================================================================
#===============================================================================
#
# [('p_1', 'm_1', {'type': 'purchase', 'time': 0, 'amount': 410, 'disputed': False, 'key': 0}),
#('p_1', 'm_1', {'type': 'purchase', 'time': 1, 'amount': 386, 'disputed': False, 'key': 1}) ]
#                                                                                                
#===============================================================================

 File "D:\Java2018\practicalDataAnalysis\Codes\Chapter08\graph_fraudTransactions.py", line 45, in <module> list(p_1_transactions[0][2].keys())) TypeError: 'OutMultiEdgeDataView' object is not subscriptable */

 


在咱们的图中,type老是购买,time是交易发生的时间,amount是交易的额度。disputed标明交易是否有争议。
咱们看看50天内,人们在网上消费了多少。咱们遍历全部边,用交易额度建立一个列表。最后,NumPy的.sum(...)方法将列表中的元素加总,获得最终值:

/*
-- Total value of all transactions: 57273724
 */
/*
Type of the graph:  <class 'networkx.classes.multidigraph.MultiDiGraph'>

Total population: 1000, number of merchants: 20
Total number of transactions: 225037
Total value of all transactions: 57273724

DISPUTED TRANSACTIONS
Total number of disputed transactions: 49
Total value of disputed transactions: 14277
Total number of people scammed: 33
 */

更多:既然咱们了解了图的基本状况,那么要辨别出诈骗的源头,则先要辨别出受害的消费者:

 1 # identify customers with stolen credit cards 辨别出被信用卡被盗的消费者
 2 all_disputed_transactions = \
 3     [dt for dt in fraud.edges(data=True) if dt[2]['disputed']]
 4 
 5 print('\nDISPUTED TRANSACTIONS')
 6 print('Total number of disputed transactions: {0}' \
 7     .format(len(all_disputed_transactions)))
 8 print('Total value of disputed transactions: {0}' \
 9     .format(np.sum([dt[2]['amount']
10         for dt in all_disputed_transactions])))
11 
12 # a list of people scammed受害者列表
13 people_scammed = list(set(
14     [p[0] for p in all_disputed_transactions]))
15 
16 print('Total number of people scammed: {0}' \
17     .format(len(people_scammed)))
18 
19 # a list of all disputed transactions全部有争议交易的列表
20 print('All disputed transactions:')
21 
22 for dt in sorted(all_disputed_transactions,
23     key=lambda e: e[0]):
24     print('({0}, {1}: {{time:{2}, amount:{3}}})'\
25         .format(dt[0], dt[1],
26          dt[2]['amount'], dt[2]['amount']))      

咱们先检查全部边的disputed标志位,找出全部的争议交易:

/*
Total number of disputed transactions: 49
*/

225037笔交易中,49笔是欺诈。这个数字并不大,不过,若是咱们听任无论,不找出欺诈的源头,这是在给将来埋雷。另外,这并无算无争议的交易,也许真实的数字要高得多。
而后看看盗刷金额。如同计算总额同样,咱们遍历全部争议交易并加总:

/*
Total value of disputed transactions: 14277
 */

超过14000千美圆被盗;大约每笔290美圆。
咱们还不知道有多少受害者。遍历全部交易,取出全部被盗用户。set(...)方法生成一个去重的列表(和数学上的集合同样,不能有重复元素)。将集合变回列表:

/*
Total number of people scammed: 33
 */

总共,有33名受害者。平均到每人,1.48笔交易,金额约430美圆。
看下这些交易(简化过了)。
.format(...)方法使用{.}表明模板字符串中要放置数字的位置,因此你要使用双重花括号{{}}来打印出{}(花括号)。
前10笔争议交易是:

/*
All disputed transactions:
(p_114, m_12: {time:290, amount:290})
(p_123, m_12: {time:273, amount:273})
(p_154, m_2: {time:448, amount:448})
(p_164, m_3: {time:98, amount:98})
(p_224, m_2: {time:162, amount:162})
(p_272, m_2: {time:489, amount:489})
(p_276, m_3: {time:122, amount:122})
(p_325, m_2: {time:409, amount:409})
(p_389, m_2: {time:262, amount:262})
(p_389, m_2: {time:247, amount:247})
(p_389, m_3: {time:233, amount:233})
(p_389, m_3: {time:251, amount:251})
(p_389, m_12: {time:460, amount:460})
(p_392, m_2: {time:117, amount:117})
(p_415, m_12: {time:410, amount:410})
...
 */

最后看看每一个人的损失:

 1 # how much each person lost 每一个人的损失
 2 transactions = c.defaultdict(list)
 3 
 4 for p in all_disputed_transactions:
 5     transactions[p[0]].append(p[2]['amount'])
 6 
 7 for p in sorted(transactions.items(),
 8     key=lambda e: np.sum(e[1]), reverse=True):
 9     print('Value lost by {0}: \t{1}'\
10         .format(p[0], np.sum(p[1])))

咱们从建立.defaultdict(...)开始。.defaultdict(...)是一个相似字典的对象。不过,在正常的字典中,若是键值是列表而键名不存在,此时你不能用.append(...)加值。(若是这么作,Python会抛出一个异常。)使用.defaultdict(...)的话,不会抛出异常,这个数据结构先插入一个新键,键值是一个空列表,而后将值附加到新建立的列表中。因此咱们能够用transactions[p[0]].append(p[2][‘amount’]),不用像下面这样:

1 for p in all_disputed_transactions:
2     try:
3     transactions[p[0]].append(p[2]['amount'])
4     except:
5     transactions[p[0]]=[p[2]['amount']]

将全部交易拆开后,咱们能够打印出受害者的列表(指定reverse=True,从受影响最严重开始排序):

/*
Value lost by p_389:     1453
Value lost by p_721:     1383
Value lost by p_583:     878
Value lost by p_607:     750
Value lost by p_471:     675
Value lost by p_504:     581
Value lost by p_70:     519
Value lost by p_272:     489
Value lost by p_8:     486
Value lost by p_684:     484
Value lost by p_545:     477
Value lost by p_514:     463
Value lost by p_154:     448
Value lost by p_415:     410
Value lost by p_325:     409
Value lost by p_637:     365
Value lost by p_865:     361
Value lost by p_54:     356
Value lost by p_540:     343
Value lost by p_709:     342
Value lost by p_590:     328
Value lost by p_114:     290
Value lost by p_542:     282
Value lost by p_123:     273
Value lost by p_577:     224
Value lost by p_482:     215
Value lost by p_734:     197
Value lost by p_418:     163
Value lost by p_224:     162
Value lost by p_908:     134
Value lost by p_276:     122
Value lost by p_392:     117
Value lost by p_164:     98
 */

能够看出,分布并不平均;有些人的损失超过了1000美金,有7人的损失超过了500美圆。这可不是个小数目,应该调查。

8.5识别谁盗窃了信用卡

识别出了信用卡信息被盗的用户,也知道了他们的损失,让咱们找出谁该为此负责。
准备:需装好NetworkX、collections和NumPy。

步骤:
本技巧将试着找出全部受害者在第一笔欺诈交易发生前都消费过的卖家(graph_fraudOrigin.py文件):

 1 import networkx as nx
 2 import numpy as np
 3 import collections as c
 4 
 5 # import the graph
 6 graph_file = '../../Data/Chapter08/fraud.gz'
 7 fraud = nx.read_graphml(graph_file)
 8 
 9 # identify customers with stolen credit cards
10 people_scammed = c.defaultdict(list)
11 
12 for (person, merchant, data) in fraud.edges(data=True):
13     if data['disputed']:
14         people_scammed[person].append(data['time'])
15 
16 print('\nTotal number of people scammed: {0}' \
17     .format(len(people_scammed)))
18 
19 # what was the time of the first disputed transaction for each
20 # scammed person
21 stolen_time = {}
22 
23 for person in people_scammed:
24     stolen_time[person] = \
25         np.min(people_scammed[person])
26 
27 # let's find the common merchants for all those scammed
28 merchants = c.defaultdict(list)
29 for person in people_scammed:
30     edges = fraud.out_edges(person, data=True)
31     
32     for (person, merchant, data) in edges:
33         if  stolen_time[person] - data['time'] <= 1 and \
34             stolen_time[person] - data['time'] >= 0:
35 
36             merchants[merchant].append(person)
37 
38 merchants = [(merch, len(set(merchants[merch])))
39     for merch in merchants]
40 
41 print('\nTop 5 merchants where people made purchases')
42 print('shortly before their credit cards were stolen')
43 print(sorted(merchants, key=lambda e: e[1], reverse=True)[:5])

原理:咱们先用与以前相似的风格读入数据。而后,建立scammed_people列表,但方式与以前稍有不一样。咱们要找出第一笔争议交易的时间。因此咱们再次使用.defaultdict(...),遍历全部的争议交易,并建立一个字典,字典中对每一个人都获取报告争议的时间列表。
这样作是为了检查先于争议交易的全部交易;诈骗犯先得偷取信用卡信息,而后才能进行欺诈。
对于全部的受害者,咱们遍历列表中的scammed_people元素,找到争议交易的最小时间。存到stolen_time字典。
如今是时候检查第一次争议以前的交易了。在for循环中,咱们遍历每一个受害者在第一次争议以前的全部交易。
代码中,咱们只检查前一天的交易,stolen_time[person]-data['time']<=1和stolen_time[person]-data['time']>=0。
但时间窗口能够调整,看你何时找到全部受害者的共同卖家。在咱们的例子中,咱们回溯一天就找到了:33个受害者都在同一个卖家消费过,而后一天以内就发生了第一笔争议。
有了交易列表,咱们找出卖家。咱们再次使用set(...)操做,对每一个买家选出去重后的卖家(毕竟这段时间内可能与同一个卖家产生多笔交易),统计个数。
最后看看谁是赢家:

/* Total number of people scammed: 33

Top 5 merchants where people made purchases
shortly before their credit cards were stolen
[('m_4', 33), ('m_2', 16), ('m_3', 14), ('m_12', 9), ('m_6', 8)]
 */

可见,全部受害者在信用卡被盗以前都在卖家m_4处消费过。这里的数据是咱们生成的,咱们知道这就是答案。然而在现实世界,这五个卖家可能有关联,或者职员中藏着一匹害群之马,要展开调查。
参考:咱们适配了图数据库Neo4j的方法。关于Neo4j以及用图数据库侦查欺诈,你能够在这里了解更多:https://linkurio.us/stolen-credit-cards-and-fraud-detection-with-neo4j/

 

第8章完。

 python学习笔记-目录索引

 

随书源码官方下载:
http://www.hzcourse.com/web/refbook/detail/7821/92

相关文章
相关标签/搜索