首頁  >  文章  >  後端開發  >  Python 繪製驚豔的桑基圖,你學會了嗎?

Python 繪製驚豔的桑基圖,你學會了嗎?

王林
王林轉載
2023-04-12 14:28:081951瀏覽

桑基圖簡介

很多時候,我們需要一個必須視覺化資料如何在實體之間流動的情況。例如,以居民如何從一個國家遷移到另一個國家為例。這裡展示了有多少居民從英格蘭遷移到北愛爾蘭、蘇格蘭和威爾斯。

Python 繪製驚豔的桑基圖,你學會了嗎?

從這個 桑基圖 (Sankey)視覺化中可以明顯看出,從England遷移到Wales的居民多於從Scotland或Northern Ireland遷移的居民。

什麼是桑基圖?

桑基圖通常描繪 從一個實體(或節點)到另一個實體(或節點)的資料流。

資料流向的實體稱為節點,資料流起源的節點是來源節點(例如左側的England),流結束的節點是 目標節點(例如右側的Wales)。來源節點和目標節點通常表示為帶有標籤的矩形。

流動本身由直線或曲線路徑表示,稱為連結。流/連結的寬度與流的量/數量成正比。在上面的例子中,從英格蘭到威爾斯的流動(即居民遷移)比從英格蘭到蘇格蘭或北愛爾蘭的流動(即居民遷移)更廣泛(更多),表明遷移到威爾斯的居民數量多於其他國家。

桑基圖可用來表示能量、金錢、成本的流動,以及任何具有流動概念的事物。

米納爾關於拿破崙入侵俄羅斯的經典圖表可能是桑基圖表最著名的例子。這種使用桑基圖的可視化非常有效地顯示了法國軍隊在前往俄羅斯和返回的途中是如何進步(或減少?)的。

Python 繪製驚豔的桑基圖,你學會了嗎?

在本文中,我們使用 python 的 plotly 來繪製桑基圖。

如何繪製桑基圖?

本文使用 2021 年奧運資料集繪製桑基圖。該資料集包含有關獎牌總數的詳細資訊——國家、獎牌總數以及金牌、銀牌和銅牌的單項總數。我們透過繪製桑基圖來了解一個國家贏得的金牌、銀牌和銅牌數。

df_medals = pd.read_excel("data/Medals.xlsx")
print(df_medals.info())
df_medals.rename(columns={'Team/NOC':'Country', 'Total': 'Total Medals', 'Gold':'Gold Medals', 'Silver': 'Silver Medals', 'Bronze': 'Bronze Medals'}, inplace=True)
df_medals.drop(columns=['Unnamed: 7','Unnamed: 8','Rank by Total'], inplace=True)

df_medals
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 93 entries, 0 to 92
Data columns (total 9 columns):
 # Column Non-Null CountDtype
--------- -------------------
 0 Rank 93 non-null int64
 1 Team/NOC 93 non-null object 
 2 Gold 93 non-null int64
 3 Silver 93 non-null int64
 4 Bronze 93 non-null int64
 5 Total93 non-null int64
 6 Rank by Total93 non-null int64
 7 Unnamed: 7 0 non-nullfloat64
 8 Unnamed: 8 1 non-nullfloat64
dtypes: float64(2), int64(6), object(1)
memory usage: 6.7+ KB
None

Python 繪製驚豔的桑基圖,你學會了嗎?

桑基圖繪圖基礎

#使用plotly 的go.Sankey,該方法帶有2 個參數——nodes 和links (節點和鏈接)。

注意:所有節點-來源和目標都應該有唯一的識別碼。

在本文奧林匹克獎牌資料集情況:

Source是國家。將前 3 個國家(美國、中國和日本)視為來源節點。用以下(唯一的)標識符、標籤和顏色來標記這些來源節點:

  • 0:美國:綠色
  • 1:中國:藍色
  • # 2:日本:橘色

Target是金牌、銀牌或銅牌。用以下(唯一的)標識符、標籤和顏色來標記這些目標節點:

  • 3:金牌:金色
  • 4:銀牌:銀色
  • 5 :銅牌:棕色

Link(源節點和目標節點之間)是每種類型獎牌的數量。在每個來源中有3個鏈接,每個鏈接都以目標結尾——金牌、銀牌和銅牌。所以總共有9個連結。每個環節的寬度應為金牌、銀牌和銅牌的數量。用以下來源標記這些連結到目標、值和顏色:

  • 0 (美國) 至3,4,5 : 39, 41, 33
  • 1 (中國) 至3 ,4,5 : 38, 32, 18
  • 2 (日本) 至3,4,5 : 27, 14, 17

需要實例化2 個python dict 物件來表示

  • nodes (來源和目標):標籤和顏色作為單獨的清單和
  • links:來源節點、目標節點、值(寬度)和連結的顏色作為單獨的清單

並將其傳遞給plotly的go.Sankey。

清單的每個索引(標籤、來源、目標、值和顏色)分別對應一個節點或連結。

NODES = dict( 
# 0 1 23 4 5 
label = ["United States of America", "People's Republic of China", "Japan", "Gold", "Silver", "Bronze"],
color = ["seagreen", "dodgerblue", "orange", "gold", "silver", "brown" ],)
LINKS = dict( 
source = [0,0,0,1,1,1,2,2,2], # 链接的起点或源节点
target = [3,4,5,3,4,5,3,4,5], # 链接的目的地或目标节点
value =[ 39, 41, 33, 38, 32, 18, 27, 14, 17], # 链接的宽度(数量)
# 链接的颜色
# 目标节点: 3-Gold4-Silver5-Bronze
color = [ 
"lightgreen", "lightgreen", "lightgreen",# 源节点:0 - 美国 States of America
"lightskyblue", "lightskyblue", "lightskyblue",# 源节点:1 - 中华人民共和国China
"bisque", "bisque", "bisque"],)# 源节点:2 - 日本
data = go.Sankey(node = NODES, link = LINKS)
fig = go.Figure(data)
fig.show()

Python 繪製驚豔的桑基圖,你學會了嗎?

這是一個非常基本的桑基圖。但是否注意到圖表太寬並且銀牌出現在金牌之前?

接下來介紹如何調整節點的位置和寬度。

調整節點位置和圖表寬度

為節點新增 x 和 y 位置以明確指定節點的位置。值應介於 0 和 1 之間。

NODES = dict( 
# 0 1 23 4 5 
label = ["United States of America", "People's Republic of China", "Japan", "Gold", "Silver", "Bronze"],
color = ["seagreen", "dodgerblue", "orange", "gold", "silver", "brown" ],)
x = [ 0,0,0,0.5,0.5,0.5],
y = [ 0,0.5,1,0.1,0.5,1],)
data = go.Sankey(node = NODES, link = LINKS)
fig = go.Figure(data)
fig.update_layout(title="Olympics - 2021: Country &Medals",font_size=16)
fig.show()

於是得到了一個緊湊的桑基圖:

Python 繪製驚豔的桑基圖,你學會了嗎?

#下面看看程式碼中傳遞的各種參數如何映射到圖中的節點和鏈接。

Python 繪製驚豔的桑基圖,你學會了嗎?

代码如何映射到桑基图

添加有意义的悬停标签

我们都知道plotly绘图是交互的,我们可以将鼠标悬停在节点和链接上以获取更多信息。

Python 繪製驚豔的桑基圖,你學會了嗎?

带有默认悬停标签的桑基图

当将鼠标悬停在图上,将会显示详细信息。悬停标签中显示的信息是默认文本:节点、节点名称、传入流数、传出流数和总值。

例如:

  • 节点美国共获得11枚奖牌(=39金+41银+33铜)
  • 节点金牌共有104枚奖牌(=美国39枚,中国38枚,日本27枚)

如果我们觉得这些标签太冗长了,我们可以对此进程改进。使用hovertemplate参数改进悬停标签的格式

  • 对于节点,由于hoverlabels 没有提供新信息,通过传递一个空hovertemplate = ""来去掉hoverlabel
  • 对于链接,可以使标签简洁,格式为-
  • 对于节点和链接,让我们使用后缀"Medals"显示值。例如 113 枚奖牌而不是 113 枚。这可以通过使用具有适当valueformat和valuesuffix的update_traces函数来实现。
NODES = dict( 
# 0 1 23 4 5
label = ["United States of America", "People's Republic of China", "Japan", "Gold", "Silver", "Bronze"],
color = ["seagreen", "dodgerblue","orange", "gold", "silver", "brown" ],
x = [ 0,0, 0,0.5,0.5,0.5],
y = [ 0,0.5, 1,0.1,0.5,1],
hovertemplate=" ",)

LINK_LABELS = []
for country in ["USA","China","Japan"]:
for medal in ["Gold","Silver","Bronze"]:
LINK_LABELS.append(f"{country}-{medal}")
LINKS = dict(source = [0,0,0,1,1,1,2,2,2], 
 # 链接的起点或源节点
 target = [3,4,5,3,4,5,3,4,5], 
 # 链接的目的地或目标节点
 value =[ 39, 41, 33, 38, 32, 18, 27, 14, 17], 
 # 链接的宽度(数量) 
 # 链接的颜色
 # 目标节点:3-Gold4 -Silver5-Bronze
 color = ["lightgreen", "lightgreen", "lightgreen", # 源节点:0 - 美国
"lightskyblue", "lightskyblue", "lightskyblue", # 源节点:1 - 中国
"bisque", "bisque", "bisque"],# 源节点:2 - 日本
 label = LINK_LABELS, 
 hovertemplate="%{label}",)

data = go.Sankey(node = NODES, link = LINKS)
fig = go.Figure(data)
fig.update_layout(title="Olympics - 2021: Country &Medals",
font_size=16, width=1200, height=500,)
fig.update_traces(valueformat='3d', 
valuesuffix='Medals', 
selector=dict(type='sankey'))
fig.update_layout(hoverlabel=dict(bgcolor="lightgray",
font_size=16,
font_family="Rockwell"))
fig.show("png") #fig.show()

Python 繪製驚豔的桑基圖,你學會了嗎?

带有改进的悬停标签的桑基图

对多个节点和级别进行泛化相对于链接,节点被称为源和目标。作为一个链接目标的节点可以是另一个链接的源。

该代码可以推广到处理数据集中的所有国家。

还可以将图表扩展到另一个层次,以可视化各国的奖牌总数。

NUM_COUNTRIES = 5
X_POS, Y_POS = 0.5, 1/(NUM_COUNTRIES-1)
NODE_COLORS = ["seagreen", "dodgerblue", "orange", "palevioletred", "darkcyan"]
LINK_COLORS = ["lightgreen", "lightskyblue", "bisque", "pink", "lightcyan"]

source = []
node_x_pos, node_y_pos = [], []
node_labels, node_colors = [], NODE_COLORS[0:NUM_COUNTRIES]
link_labels, link_colors, link_values = [], [], [] 

# 第一组链接和节点
for i in range(NUM_COUNTRIES):
source.extend([i]*3)
node_x_pos.append(0.01)
node_y_pos.append(round(i*Y_POS+0.01,2))
country = df_medals['Country'][i]
node_labels.append(country) 
for medal in ["Gold", "Silver", "Bronze"]:
link_labels.append(f"{country}-{medal}")
link_values.append(df_medals[f"{medal} Medals"][i])
link_colors.extend([LINK_COLORS[i]]*3)

source_last = max(source)+1
target = [ source_last, source_last+1, source_last+2] * NUM_COUNTRIES
target_last = max(target)+1

node_labels.extend(["Gold", "Silver", "Bronze"])
node_colors.extend(["gold", "silver", "brown"])
node_x_pos.extend([X_POS, X_POS, X_POS])
node_y_pos.extend([0.01, 0.5, 1])

# 最后一组链接和节点
source.extend([ source_last, source_last+1, source_last+2])
target.extend([target_last]*3)
node_labels.extend(["Total Medals"])
node_colors.extend(["grey"])
node_x_pos.extend([X_POS+0.25])
node_y_pos.extend([0.5])

for medal in ["Gold","Silver","Bronze"]:
link_labels.append(f"{medal}")
link_values.append(df_medals[f"{medal} Medals"][:i+1].sum())
link_colors.extend(["gold", "silver", "brown"])

print("node_labels", node_labels)
print("node_x_pos", node_x_pos); print("node_y_pos", node_y_pos)
node_labels ['United States of America', "People's Republic of China", 
 'Japan', 'Great Britain', 'ROC', 'Gold', 'Silver', 
 'Bronze', 'Total Medals']
node_x_pos [0.01, 0.01, 0.01, 0.01, 0.01, 0.5, 0.5, 0.5, 0.75]
node_y_pos [0.01, 0.26, 0.51, 0.76, 1.01, 0.01, 0.5, 1, 0.5]
# 显示的图
NODES = dict(pad= 20, thickness = 20, 
 line = dict(color = "lightslategrey",
 width = 0.5),
 hovertemplate=" ",
 label = node_labels, 
 color = node_colors,
 x = node_x_pos, 
 y = node_y_pos, )
LINKS = dict(source = source, 
 target = target, 
 value = link_values, 
 label = link_labels, 
 color = link_colors,
 hovertemplate="%{label}",)
data = go.Sankey(arrangement='snap', 
 node = NODES, 
 link = LINKS)
fig = go.Figure(data)
fig.update_traces(valueformat='3d', 
valuesuffix=' Medals', 
selector=dict(type='sankey'))
fig.update_layout(title="Olympics - 2021: Country &Medals",
font_size=16,
width=1200,
height=500,)
fig.update_layout(hoverlabel=dict(bgcolor="grey", 
font_size=14, 
font_family="Rockwell"))
fig.show("png") 

Python 繪製驚豔的桑基圖,你學會了嗎?

以上是Python 繪製驚豔的桑基圖,你學會了嗎?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:51cto.com。如有侵權,請聯絡admin@php.cn刪除