「一張照片包含了萬千信息」,這句話經常被人們引用。可是一張圖能表達的信息要更多。以圖的形式可視化數據,幫助咱們得到了更可行的看法,並基於此做出更好的數據驅動的決策。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、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。