在数据分析中,经常需要展示数据在不同实体之间的流动情况。例如,分析英国内部不同国家居民的迁移情况,观察从英格兰迁移到北爱尔兰、苏格兰和威尔士的居民数量。通过桑基图这种数据可视化方式,可以直观地看到数据流动的规模和方向。
桑基图是一种特殊类型的流图,它通过宽度不等的箭头直观地表示不同节点(实体)之间的流动量。在桑基图中,数据流动的起点被称为源节点,终点被称为目标节点。节点通常以带标签的矩形表示,而流动本身则由直线或曲线路径表示,称为链接。链接的宽度与流动量成正比。例如,在上述居民迁移的例子中,从英格兰到威尔士的流动宽度大于到苏格兰或北爱尔兰的流动,表明迁移到威尔士的居民数量多于其他地区。
桑基图不仅可以用于展示居民迁移,还可以用于展示能量、金钱、成本等任何具有流动概念的数据。拿破仑入侵俄罗斯的经典桑基图是最著名的例子之一,它有效地展示了法国军队在前往俄罗斯及返回途中的进展(或减少)。
以2021年奥运会奖牌数据为例,可以绘制一个桑基图来理解各国赢得的金牌、银牌和铜牌的数量。首先,需要使用Pandas库加载数据,并进行一些基本的数据预处理。
import pandas as pd
df_medals = pd.read_excel("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)
print(df_medals)
接下来,将使用Plotly的go接口中的Sankey方法来绘制桑基图。这个方法需要两个参数:节点和链接。所有节点,无论是源节点还是目标节点,都应该有唯一的标识符。
在这个例子中,源节点是国家,选择美国、中国和日本作为源节点,并为这些源节点分配以下(唯一的)标识符、标签和颜色:
目标节点是金、银、铜奖牌。为这些目标节点分配以下(唯一的)标识符、标签和颜色:
链接(源节点和目标节点之间的连接)将是每种奖牌(金、银、铜)的数量。从每个源节点,将有3个链接出发,每个链接都指向目标节点——金、银、铜。因此,将总共有9个链接。每个链接的宽度应等于金、银、铜奖牌的数量。将为这些链接分配以下源到目标、值和颜色:
需要创建两个Python字典对象来表示节点(包括源节点和目标节点):带有标签和颜色的单独列表,以及链接:源节点、目标节点、值(宽度)和链接的颜色作为单独的列表,并将这些传递给Plotly的go接口Sankey。
NODES = dict(
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],
color=["lightgreen", "lightgreen", "lightgreen", "lightskyblue", "lightskyblue", "lightskyblue", "bisque", "bisque", "bisque"],
)
data = go.Sankey(node=NODES, link=LINKS)
fig = go.Figure(data)
fig.show()
这是一个非常基础的桑基图。但有没有注意到图表太宽了,而且银牌出现在金牌之前?让调整节点的位置和图表的宽度。
NODES = dict(
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()
通过这种方式,得到了一个紧凑的图表。
以下是代码中传递的各种参数如何映射到图表中的节点和链接:
桑基图是交互式的。可以悬停在节点和链接上以获取更多信息。
目前,悬停标签中显示的信息是默认文本。当悬停在节点上时,会显示节点名称、进入流量的数量、外出流量的数量和总值。例如,节点“美国”总共有113枚奖牌(39金+41银+33铜)。节点“金牌”总共有104枚奖牌(来自美国的39枚,来自中国的38枚,来自日本的27枚)。对于链接,会显示源节点名称、目标节点名称和链接的值。例如,从源节点美国到目标节点银牌的链接有39枚奖牌。
难道不认为这些标签太冗长了吗?所有这些都可以改进。让使用hovertemplate参数改进悬停标签的格式。对于节点,由于悬停标签没有提供比已经存在的信息更多的信息,可以通过传递一个空的hovertemplate=" "来关闭悬停标签。对于链接,可以将标签格式简化为<国家>-<奖牌类型>。对于节点和链接,可以在显示值时添加后缀“奖牌”。例如,113奖牌而不是仅仅113。这可以通过使用update_traces函数和适当的valueformat和valuesuffix来实现。
NODES = dict(
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],
color=["lightgreen", "lightgreen", "lightgreen", "lightskyblue", "lightskyblue", "lightskyblue", "bisque", "bisque", "bisque"],
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)
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()
桑基图——带有改进的悬停标签