georgefan 发表于 2021-1-9 23:15

利用Python+Excel制作桑基(Sankey)图

本帖最后由 georgefan 于 2021-1-10 15:47 编辑

# 利用Python+Excel制作桑基(Sankey)图

## 桑基图的简介

桑基图 (SankeyDiagram),是一种特定类型的流图,用于描述一组值到另一组值的流向。下图为1869年,查尔斯米纳德(CharlesMinard)绘制的1812年拿破仑征俄图(Map of Napolean's Russian Campaign of1812),这是一个在地图上覆盖桑基图的流程图。

1898年爱尔兰人Matthew Henry Phineas Riall Sankey在土木工程师学会会报纪要的一篇关于蒸汽机能源效率的文章中首次推出了第一个能量流动图,此后便以其名字命名为 Sankey 图,中文音译为桑基图。



图中延伸的分支的宽度对应数据流量的大小。桑基图的特点如下:

- 起始流量和结束流量相同,所有主支宽度的总和与所有分出去的分支宽度总和相等,保持能量的平衡;
- 在内部,不同的线条代表了不同的流量分流情况,它的宽度成比例地显示此分支占有的流量;
- 节点不同的宽度代表了特定状态下的流量大小。

桑基图通常应用于能源、材料成分、金融等数据的可视化分析。

### 桑基图的构成

桑基图主要由边、流量和支点组成,其中边代表了流动的数据,流量代表了流动数据的具体数值,节点代表了不同分类。边的宽度与流量成比例地显示,边越宽,数值越大。



### 桑基图的应用场景

#### 适合的场景

**数据的流向** 桑基图即桑基能量分流图,也叫桑基能量平衡图。

例子1:下图为 2009 年美国能源产出的分布以及能源的用途和损耗图。从图中可以明显看出主要的能源浪费发生于发电和交通。



#### 不适合的场景

- 边的起始流量和结束流量不同:桑基图需要保持能量守恒,不能在中间过程创造出流量,流失(损耗)的流量应流向表示损耗的节点,所以每条边的宽度是保持不变的,需要改变边的宽度的数据推荐使用[和弦图](https://antv-2018.alipay.com/zh-cn/vis/chart/chord.html)。

## 利用Python+Excel绘制桑基图

可视化软件行业如此发达,制作此类桑基图已绝非难事,从最高端的JS库(D3、Echarts、highchart)到主流的数据科学编程工具(R、Python等)亦或者人人都能上手的自助式BI工具(PowerBI、Tableau等)都可以胜任此项工作。本文尝试采用Python和Excel两者制作桑基图。

### 用Excel处理数据

在模板(sankey.csv)中录入数据。其中,在A列中输入数据起点(Scource)名称,B列输入数据终点(Targets)名称,在C列输入对应的数值。

在F列中可自主输入图表的标题、副标题和图例名称。



### Python准备

利用Echarts作图。

> (https://github.com/ecomfe/echarts) 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而 Python 是一门富有表达力的语言,很适合用于数据处理。当数据分析遇上数据可视化时,(https://github.com/pyecharts/pyecharts) 诞生了。

**pip 安装**

```shell
$ pip(3) install pyecharts
```

**源码安装**

```shell
$ git clone https://github.com/pyecharts/pyecharts.git
$ cd pyecharts
$ pip install -r requirements.txt
$ python setup.py install
# 或者执行 python install.py
```

### 作图

将sankey.csv与下列源码的.py文件放在同一个文件夹下,运行下列代码即可。

为了自定义图形,代码中加入了大量的注释,可以相应更改该参数。

**如果不改变参数,也能顺利运行。**

```python
import pandas as pd
from pyecharts.charts import Page, Sankey
from pyecharts import options as opts


# 读取csv文件
data = pd.read_csv('sankey.csv', encoding='utf-8', header=None)

# 生成nodes
nodes = []

# 在第一列"source"中查找添加节点名称
for i in data.unique():
    dic = {'name': i}
    nodes.append(dic)
# 在第二列"target"中查找添加节点名称
for i in data.unique():
    dic = {'name': i}
    if dic not in nodes:
      nodes.append(dic)

# 生成links1
links = []
for i in data.values:
    dic = {'source': i, 'target': i, 'value': i}
    links.append(dic)

# 读取数据中的标题、副标题和图例名称
titleN = data.values
subtitleN = data.values
serialN = data.values

# 生成可视化结果
c = (
    Sankey()
      .add(
      serialN,
      nodes,
      links,
      # 设置主图在图片区域的相对位置
      pos_top="10%", pos_bottom="5%", pos_right="10%", pos_left="5%",

      # 桑基图中每个矩形节点的宽度,每一列任意两个矩形节点之间的间隔,
      # 节点的对齐方式,默认是双端对齐,可以设置为左对齐或右对齐,对应的值分别是"left""right""justify"
      node_width=20, node_gap=8, node_align="left",

      # 桑基图中节点的布局方向,可以是水平的从左往右,也可以是垂直的从上往下。
      # 对应的参数值分别是 horizontal, vertical。
      orient="horizontal",

      # 控制节点拖拽的交互,默认开启。开启后,用户可以将图中任意节点拖拽到任意位置。若想关闭此交互,只需将值设为 false 就行了。
      is_draggable=True,

      # 鼠标 hover 到节点或边上,相邻接的节点和边高亮的交互,默认关闭,可手动开启。
      # false:hover 到节点或边时,只有被 hover 的节点或边高亮。
      # true:同 'allEdges'。
      # 'allEdges':hover 到节点时,与节点邻接的所有边以及边对应的节点全部高亮。hover 到边时,边和相邻节点高亮。
      # 'outEdges':hover 的节点、节点的出边、出边邻接的另一节点 会被高亮。hover 到边时,边和相邻节点高亮。
      # 'inEdges':hover 的节点、节点的入边、入边邻接的另一节点 会被高亮。hover 到边时,边和相邻节点高亮。
      focus_node_adjacency=True,

      # 线条样式配置项,
      linestyle_opt=opts.LineStyleOpts(opacity=0.2,
                                       # 图形透明度。支持从 0 到 1 的数字,为 0 时不绘制该图形。

                                       curve=0.5,
                                       # 线的弯曲度,0 表示完全不弯曲

                                       color="source",
                                       type_="dotted"),
      # 线的类型。可选:'solid', 'dashed', 'dotted'

      label_opts=opts.LabelOpts(position="right",
                                  # 标签的位置。可选
                                  # 'top','left','right','bottom','inside','insideLeft','insideRight'
                                  # 'insideTop','insideBottom', 'insideTopLeft','insideBottomLeft'
                                  # 'insideTopRight','insideBottomRight'

                                  # 文字的字体大小
                                  font_size=12,

                                  ), )
      .set_global_opts(
      # 设置标题和副标题
      title_opts=opts.TitleOpts(title=titleN, subtitle=subtitleN),

      tooltip_opts=opts.TooltipOpts(trigger="item", trigger_on="mousemove"),

      # 是否显示图例
      legend_opts=opts.LegendOpts(is_show=True,
                                    item_width=25, item_height=14,
                                    legend_icon="pin",

                                    # 右侧靠齐
                                    pos_right="5%", )
    )

)

# 输出html可视化结果
c.render('sankey.html')

```

### 图像效果

运行完成后,在代码文件夹生成sankey.html文件。用Pycharm运行,选择相应浏览器即可展示。

浏览器中可以移动鼠标,观看相应的动态效果。

最终成图见附件。



## 后续改进

- 渲染图片
- Web 框架整合



```
参考文献:
1. https://antv-2018.alipay.com/zh-cn/vis/chart/sankey.html
2. https://zhuanlan.zhihu.com/p/147965195
3. https://pyecharts.org/#/zh-cn/quickstart
```

xjshuaishuai 发表于 2021-1-10 17:56

讲的非常好,学习了,谢谢!

qq9199 发表于 2021-1-11 08:25

支持楼主, 奶茶口罩容易看错,

tkw123s 发表于 2021-1-11 09:04

求主题分享。

tangyi606 发表于 2021-1-11 11:58

好高端,看了前面几个图劝退

oookim 发表于 2021-4-20 15:58

讲的非常好,学习了,谢谢!

zxf2287 发表于 2021-5-12 13:42

感谢楼主,正在做这个图。

ytf403 发表于 2021-6-3 15:08

有点虎头蛇尾

wkh9527 发表于 2021-6-4 08:45

谢谢楼主,学习下
页: [1]
查看完整版本: 利用Python+Excel制作桑基(Sankey)图