《數據分析實戰-托馬茲.卓巴斯》讀書筆記第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

相關文章
相關標籤/搜索