# 绘制桑基图

# 桑基图亦称为桑基能量分流图、桑基能量平衡图,是一种特定类型的流程图,用于展示数据的 “流动” 变化。桑基图中包含若干条从左到右延展的分支,每条分支的宽度代表着数据流量的大小,且所有主支宽度的总和等于所有分支宽度的总和,常见于能源、材料成分等场景或金融领域。例如,中国 2012 年能源的流动状况如图 8-17 所示。
matplotlib.sankey 模块中专门提供了表示桑基图的类 Sankey, 通过创建 Sankey 类的对象可以创建桑基图,之后可以调用 add() 方法为桑基图添加一些配置选项,最后调用 finish() 方法完成桑基图的绘制。下面将分步骤介绍桑基图的绘制过程,具体内容如下。

# 创建桑基图

# matplotlib 中使用构造方法 Sankey() 创建桑基图。Sankey() 方法的语法格式如下所示 :

Sankey(ax=None, scale=1.0, unit='', format='%G', gap=0.25, radius=0.1,
       shoulder=0.03, offset=0.15, head_angle=100, margin=0.4,
       tolerance=le-06, **kwargs)

# 该方法常用参数的含义如下。

# · ax :若不提供该参数,则会创建一个新的坐标轴。
· scale :表示流量比例因子,用于按比例调整分支的宽度。
· unit :表示与流量相关的物理单位的字符串。若设为 None, 则不会做数量标记。
· gap :表示进入或离开顶部或底部的分支间距,默认为 0.25。

# 例如,创建一个桑基图的代码如下。

sankey = Sankey(gap=0.3)

# 需要说明的是,若开发者需要绘制较为复杂的桑基图,则应使用无参构造方法创建 Sankey 实例,之后再使用 add() 方法进行配置。

# 添加桑基图的选项

# Sankey 类对象可以调用 add() 方法为桑基图添加数据流量、标签等选项。add() 方法的语法格式如下所示 :

add(self, patchlabel='', flows=None, orientations=None, labels='',
    trunklength=1.0, pathlengths=0.25, prior=None, connect=(0, 0),
    rotation=0, **kwargs)

# 该方法常用参数的含义如下。

# · patchlabel :表示位于图表中心的标签。
· flows :表示流量数据的数组,其中投入数据为正值,产生数据为负值。
· orientations :表示流的方向列表或用于所有流的单个方向,可以取值为 0 (从左侧输入、右侧输出)、1 (从顶部到顶部) 或 -1 (从底部到底部)。
· labels :表示流的标签列表或用于所有流的单个标签。
· trunklength :表示输入组和输出组的基之间的长度。

# 例如,为刚刚创建的桑基图 sankey 添加流的数据和标签,具体代码如下。

flows =[0.7, 0.3, -0.3, -0.1, -0.3, -0.1, -0.1, -0.1]
labels = ["工资", "副业", "生活", "购物", "深造", "运动", "其他", "买书"]
sankey.add(flows=flows, labels=labels)

# 返回桑基图绘制完成的对象

# Sankey 类对象在添加数据之后需要调用 finish() 方法完成绘制,并返回包含多个桑基子图的列表。桑基子图包含以下字段。

# · patch :表示桑基子图的轮廓。
· flows :表示流量值 (输入为正,输出为负)。
· angles :表示箭头角度的列表。
· tips :表示流路径的尖端或凹陷位置的数组,其中每一行是一个 (x, y)。
· text :表示中心标签的 Text 实例。
· texts :表示流分支标签的 Text 实例。

# 假设现在小明家日常生活的开支主要分为工资、副业、生活、购物、深造、运动、其他和买书几类,且其中每项投入或产出值分别为 0.7、0.3、-0.3、-0.1、-0.3、-0.1、-0.1、-0.1。下面结合这些日常生活开支的数据绘制一个桑基图,示例代码如下。

In [8]:
import matplotlib.pyplot as plt
from marplotlib.sankey import Sankey
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 消费收入与支出数据
flows = [0.7, 0.3, -0.3, -0.1, -0.3, -0.1, -0.1, -0.1]
# 流的标签列表
labels = ["工资", "副业", "生活", "购物", "深造", "运动", "其他", "买书"]
# 流的方向
orientation = [1, 1, 0, -1, -1, 1, 0]
# 创建 Sankey 类对象
sankey = Sankey()
# 为桑基图添加数据
sankey.add(flow=flows                       # 收入与支出数据
           labels=labels,                   # 数据标签
           orientations=orientations,       # 标签显示的方向
           color="black"                    # 边缘线条颜色
           fc="lightgreen",                 # 填充颜色
           patchlabel="生活消费",            # 图表忠心的标签
           alpha=0.7)                       # 透明度
# 桑基图绘制完成的对象
diagrams = sankey.finish()                  
diagrams[0].texts[4].set_color("r")         # 将下标为 4 的数据标签设为红色
diagrams[0].texts[4].set_weight("bold”)     # 将下标为 4 的数据标签设为字体加粗
diagrams[0].text.set_fontsize(20)           # 将中心标签的字体大小设为20
diagrams[0].text.set_fontweight("bold")     # 将中心标签的字体设为加粗
plt.title("日常生活开支的桑基图")
plt.show()

# 运行程序,效果如图 8-18 所示。

# 图 8-18 中,桑基图的各个分支代表生活消费的每个选项,其中分支末端呈内凹形状的分支代表收入的数据,呈箭头形状的分支代表支出的数据。由图 8-18 可知,工资和副业这 2 个选项代表的分支均属于生活消费的收人数据,其余选项的分支均属于生活消费的支出数据,且深造选项的支出最多。