关键词: pyecharts; 玫瑰图; 饼图; 可视化;
说起玫瑰图,大家可能不是很熟悉,但是对它的表亲饼图(扇形图)应该都不陌生了。
在excel里,我们经常使用到饼图,像这样:

或这样:

还有这样:

今天我们来认识一下南丁格尔玫瑰图,看看与我们常见的饼图究竟有何不同。
2020年新冠病毒肆虐全球,人民日报刊载了下面这样一张图片:

从图上,我们可以清晰地看到各个国家确诊和死亡病例数量,它是以区域面积来表示数值大小,实际上我们可以将其理解为一个圆形的直方图。玫瑰图是由弗罗伦斯·南丁格尔所发明的,也称为极区图或鸡冠花图,最初也是用以表达军医院季节性死亡率。
玫瑰图本质上与饼图(扇形图)、柱状图等并没有什么不同,只是展示形式较为独特,配以缤纷的颜色和合理的排序,使得整图绚烂多彩,能够显示出不同类别之间的差异,令人印象更加深刻。
接下来我们就仿照人民日报的这张图,自己动手用pyecharts来制作一张玫瑰图。
首先,从百度的疫情实时大数据报告来获取我们所需要的数据,具体链接为https://voice.baidu.com/act/newpneumonia/newpneumonia/?from=osari_aladin_banner#tab4。
将数据保存为excel表格,然后导入我们本次需要使用的包(pyecharts和pandas),options和charts分别是pyecharts的配置项和图表类,JsCode可以将js代码传入配置项中。
from pyecharts import options, charts
from pyecharts.commons.utils import JsCode
import pandas as pd
# 玫瑰图
file = pd.read_excel('疫情数据.xlsx')
data = file.sort_values(by=['新增'], ascending=False)
items1 = [(y['地区'], y['新增']) for x, y in data.iterrows()][0:20]
data = file.sort_values(by=['死亡'], ascending=False)
items2 = [(y['地区'], y['死亡']) for x, y in data.iterrows()][0:20]
使用pandas读取我们保存好的疫情数据文件(内容如下图),分别按照'新增'、'死亡'两列从大到小排序,并保存为两个二元列表。由于地区分组太多,容易影响可视化效果,所以我们本次只选取每个指标的TOP20。

pyecharts中将玫瑰图划归为饼图,我们创建一个名为rose的对象(不要问我为啥初始化里宽高这么大,屏幕大就是任性)。本次画图会在标签中显示分类名称,所以我们先关闭图例的显示,以免占用空间。
rose = charts.Pie(init_opts=options.InitOpts(width='3800px', height='2200px'))
rose.set_global_opts(legend_opts=options.LegendOpts(is_show=False))
接下来我们开始配置第一个玫瑰图:
rose.add('新增', data_pair=items1,
rosetype='area', radius=['3%', '100%'], center=['30%', '70%'], is_clockwise=False,
label_opts=options.LabelOpts(is_show=True, position='inside', font_size=8, formatter='{b}:{c}')
).set_colors(
['rgb({r},{g},0)'.format(r=255 - 15 * x if x < 10 else 0, g=100 + 10 * (x - 10) if x >= 10 else 0) for x in range(20)])
使用add方法添加数据,第一个参数是数据系列名称,也就是我们excel中的'新增',然后data_pair是我们前面创建好的二元列表items1,其格式要求为列表每个元素都是一个长度为2的列表(list)或者元组(tuple),形如[(a1,b1),(a2,b2)],其中a为每一项的名称(本例中就是美国、印度等地区),b为该项数值(新增病例数)。
radius:长度为2的列表,第一个元素表示内径大小,第二个元素表示外径大小,默认设置成百分比,相对于容器高宽中较小的一项的一半
center:本图中心在绘图区域的相对位置
is_clockwise:各扇区是否顺时针排布
label_opts:数据标签配置项,包含标签是否显示、位置、字体大小、显示内容等多项,其中position有outside、inside、inner、center四个取值,inside和inner均指标签位于扇区内部,outside为外部,center为在饼图中心位置;formatter是标签区域内包含哪些内容,{a}系列名(新增),{b}数据项(美国、印度等),{c}为数值(新增病例数),{d}为各区域所占整体的百分比
⚠️注意:参数rosetype即为选择展示数值大小的方式,共有两个取值可选:
1)radius表示各扇区圆心角表示所占百分比,半径表示数值大小
2)area表示各扇区弧度相等,仅以半径来表示数值大小
3)缺省则为普通饼图
设置好所需的各个参数,然后就是设置各个扇区的颜色了,对于饼图类来说,可以通过set_colors来传入一个表示颜色的列表,列表内元素可以是rgb色值、十六进制颜色码以及“red”、“blue”这种单词。在本例中,由于扇区较多,为提高区分度且不至于太过混乱,我们利用rgb色值将前15个扇区设置为红色渐进,后5个设置为绿色渐进,最终效果如下:

接下来我们再看第二个:
rose.add('死亡', data_pair=items2,
rosetype='area', radius=['1%', '100%'], center=['70%', '70%'], is_clockwise=False,
label_opts=options.LabelOpts(is_show=True, position='inside', font_size=10, formatter='{b}:{c}'),
itemstyle_opts={
"color": JsCode(
"""new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(0, 160, 221, 1)'
}, {
offset: 1,
color: 'rgba(0, 255, 255, 1)'
}], false)"""
)
}
)
rose.render('rose.html')
这里我们不再使用set_colors来为每个扇区设置不同颜色,而是通过add方法中itemstyle_opts的参数来设置单个扇区的展示风格。itemstyle_opts本身支持从options中调用ItemStyleOpts来设置内部各项参数,但与生成html后的js配置不甚匹配(也可能是我没搞懂😭),所以此处我们直接使用字典传参。
color参数支持直接使用颜色,或使用js来定义渐变色(包括线性渐变LinearGradient和径向渐变Radial),具体js函数不做详细介绍,我们可以直接傻瓜式地修改其接收的参数,主要修改两处rgb的颜色即可,offset=0或1分别代表扇区的外部和内部。线性渐变色效果如下:

以上两种配色方法有不同的展示效果:扇区间多色渐变更有层次感,扇区内单色渐变显得较为立体,我们可以根据实际需求来选择最合适的方案。
玫瑰图不同于一般饼图,但同时也具有一般饼图的缺点,当扇区数量较多、部分扇区数值较小,就容易出现标签重叠、互相遮挡的问题,对此并没有较好的解决方案。像上面我们看到的人民日报刊载的这张图,其实是经过特殊处理的,可以在基本图形生成后,手动往上添加文字说明,并将其置于合适的位置。
另外,由于需要显示的数据可能分布不太均匀,离差较大,就像上面示例图中一样,美国一骑绝尘遥遥领先的情况下,导致整个图形极为狭长。这种情况下,我们可以对数据进行稍微的修饰,把极值按一定比例缩放,在不影响其大小顺序的前提下,让它在整体中稍微不那么“鹤立鸡群”,但同时在标签说明上要做一些修改,直接在数据项名称中加上数值,并且标签中只显示数据项名称,而不显示数值实际大小:
rose.add('新增', data_pair=[('{}:\n{}'.format(items1[x][0], items1[x][1]), items1[x][1] if x >= 1 else round(items1[x][1] / 2)) for x in range(20)], rosetype='area', radius=['3%', '100%'], center=['30%', '70%'], is_clockwise=False, label_opts=options.LabelOpts(is_show=True, position='inside', font_size=12, formatter='{b}',vertical_align='bottom') ).set_colors( ['rgb({r},{g},0)'.format(r=255 - 15 * x if x < 10 else 0, g=100 + 10 * (x - 10) if x >= 10 else 0) for x in range(20)])
修改后整体效果如下:

关于玫瑰图就说到这里,数据可视化作为数据分析的一项基本工具和表达方式,最重要的是不能“以图害意”,掌握多种可视化方法,并选取最合适的一种,切莫只图花哨而忽略了其对数据结论的传达效果。