统计图表的优雅变换:Altair|可视化系列06

Altair简介

Altair是一个强大且简明的声明式统计可视化Python库,它能够快速绘制出各种优雅可交互的统计图表。
Altair基于一个前端图表库Vega-Lite,因此绘图的成果也可以通过 chart.to_json() 在前端项目中结合vega库使用。Vega-Lite是一套交互式图形语法,通过简洁的JSON键值对配置快速生成可视化结果,来支持数据分析工作。Altair可读作阿泰尔,天文学中指牵牛星、牛郎星,天鹰座中最明亮的恒星,用这个意象也表达了这个库的野望。

Altair的语法特点是:建立chart对象后,通过约定是什么图形(点、线、柱或其他)、轴域映射方式、颜色输出可视化图表。理念通关实践去理解,直接看绘图的框架。

基础绘图

Altair绘图代码框架如下:

1
2
3
4
5
6
7
import altair as alt
import pandas as pd
df=pd.DataFrame({'x':['Mon.','Tue.','Wed.','Thu.','Fri.'], 'y':[76,37,90,60,50]})
alt.Chart(df,width=400,height=300).mark_point().encode(
x='x',
y='y'
).interactive()

Altair散点图绘制效果

从上面的语句能看到的是其写法很像ggplot2、plotnine这类库,建立chart对象再声明图表类型和x、y轴数据映射,并可以用链式写法不断垒个性化配置上去,这也是声明式(declarative)语法的特点。通过 pip install altair 安装好Altair库之后,通过alt.Chart(df,width=400,height=300)创建一个绘图对象,.mark_point()表明是映射为散点图,散点图需要对应的x,y序列坐标,在.encode()里声明,.interactive()让输出的图表有基础的交互功能。

.encode()对应的配置参数有:

  • x: x轴的数据,水平方向数据序列;
  • y: y轴的数据列表,对应竖直方向;
  • size: 点状类型的半径,或其他类型的长度等;
  • color: 图形元素的颜色,支持CSS颜色的各种写法,可以是一列也可以是一个16进制的字符串;
  • opacity: 图元透明度,用的是opacity,不是alpha喔;
  • shape: 点状元素的形状;
  • tooltip: 鼠标放到图元上时显示的提示文本列;
  • order: 声明图层顺序;

另外columnrow是在分面时用的,根据选定的列进行分面的水平拆分和垂直拆分。
Altair更擅长画统计图表,因此下面的案例在数据方面全用数据框(DataFrame)格式进行实践。

同样的数据,同样的列映射到x轴和y轴,encode的写法是一致的,改变图表类型只需要.mark_point变成.mark_line绘制折线图。

几种mark

从图中可以看出,Altair有好几种方法绘制出散点图,mark_point()统领散点图,而mark_circle()、mark_square()等和_point()的不同之处在于circle限制了用圆形,而point是可通过size配置形状的。

Altair可以轻松地绘制出折线图及阶梯线图。

1
2
3
4
alt.Chart(df,width=400,height=300).mark_line(color='#1EAFAE').encode(
x='x',
y='y'
)

【Altair绘制折线图】 0-903-06-02-1.PNG

在mark_line里设置interpolate为step-after则折线图变阶梯线图。

1
2
3
4
5
6
7
8
9
10
#所用到的数据集
df=pd.DataFrame({'x':['Mon.','Tue.','Wed.','Thu.','Fri.'],
'y':[76,37,90,60,50],
'z':[37,46,53,81,60],
'h':[3,1,5,4,2],
'k':['a','a','c','d','c']})
alt.Chart(df).mark_line(interpolate='step-after').encode(
x='x',
y='y'
) #阶梯线

【添加参数折线图变阶梯线】 0-903-06-03

将mark_line变成mark_bar是绘制柱图,包括柱状图和条形图。只需要互换x和y的映射赋值,可以实现条形图和柱状图的调换。因为默认的柱宽度比较小,画布宽度显得小了,在Chart对象里使用width参数声明图表整体宽度。

1
2
3
4
alt.Chart(df,width=400).mark_bar().encode(
x='x',
y='y'
)

【基础柱图 0-905-06-05-altair-1.PNG】

图中一个小细节是Fri.在最前面,因为x轴默认进行了排序,这一点和plotnine很像,要保持原来的['Mon.','Tue.',...]顺序,需要设置x轴的配置,那如何设置呢?先验知识是,在encode中,最简单的参数写法是.encode(x='x')这种传入列名的写法,在Python中,一切皆是对象,Altair关于X轴对象封装为了altair.X(),因此另一种写法是.encode( x= alt.X('x')),故在altair.X()里可以设置排序顺序。

1
2
3
4
alt.Chart(df,width=350).mark_bar(color='#1EAFAE').encode(
x=alt.X('x',sort=None),
y=alt.Y('y'),
)

【x轴按默认顺序的柱状图】
【0-905-06-06-altair-1.PNG】

alt.X里的sort参数可以取值为”ascending”、”descending”或者None。alt.X对象常用的配置参数还有:title(设置标题)、stack(堆叠图的起始偏移处)、aggregate(数据聚合方式,可以是sum、count、min等)
。完整的可视化信息图还需要配置文本标签、图例、标题等,Altair相关的参数也挺容易用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bar=alt.Chart(d1,width=400).mark_bar(color='#1EAFAE').encode(
x=alt.X('x',sort=None,title='date'),
y=alt.Y('y',title='count')
)
t=bar.mark_text(
baseline='top',
).encode(text='y')

(bar+t).properties(
title='weekly ride'
).configure_title(
fontSize=20,
anchor='middle',
color='gray'
)

Altair还能绘制茎叶图,其他可视化库要绘制茎叶图挺复杂的甚至画不出来,而Altair能优雅画出。
【数据列轻松绘茎叶图】

统计制图

特别能体现Altair优雅的地方在于,x,y的映射可以包含汇总函数,例如对于一个二维的订单表,统计各月份单数绘制为柱状图,只需要把x赋值为month(order_time),令y的值为count()即可。一个简单的例子如下,可视化展示df表中列k里各元素的出现次数。

1
2
3
4
alt.Chart(df,width=300).mark_bar(color='#1EAFAE').encode(
x='k',
y='count()'
)

【在Altair里使用聚合运算】
【0-905-07-02-altair-1.PNG】

Altair内置了一些经典的数据科学数据集,封装在vega_datasets里,在使用pip安装Altair时也会同步安装上。下面通过著名的iris鸢尾花数据集展现Altair在统计制图方面的优势。

1
2
3
4
5
6
7
from vega_datasets import data
iris = data.iris() #使用Altair的内置数据集
alt.Chart(iris).mark_bar().encode(
alt.X('sepalWidth:Q', bin=alt.BinParams(maxbins=20)),
alt.Y('count()')
)

【绘制iris数据集的花萼宽度直方图】
0-905-07-03-altair-1.PNG

在alt.X和alt.Y中,还可以对数据集进行排序和筛选再绘图,很多处理细节都封装好了,调用起来写的语句可以很优雅。
通过配置以上提到的各种参数绘制出蝴蝶图,如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
left =  alt.Chart(d1,height=230,width=270).encode(
y=alt.Y('x:O', axis=None),
x=alt.X('y',
title='pop',
sort=alt.SortOrder('descending'))
).mark_bar(color='#1EAFAE').properties(title='Female')

middle =alt.Chart(d1).encode(
y=alt.Y('x', axis=None),
text=alt.Text('x')
).mark_text().properties(height=230,width=30)

right = alt.Chart(d1,height=230,width=270).encode(
y=alt.Y('x', axis=None),
x=alt.X('z', title='pop')
).mark_bar(color='#FFA069',
cornerRadiusTopRight=5, #设置圆角矩形
cornerRadiusBottomRight=5).properties(title='Male')

alt.concat(left,middle, right, spacing=5)

【蝴蝶图】
0-905-07-01-altair-1.PNG

分面

将一系列图排成一横排可以使用concat方法alt.concat(c1,c2),上图就用到了,或者使用更优雅的c1|c2进行分面,而要实现上下排列的分面,使用c1&c2

1
2
3
4
5
6
7
8
9
10
11
h=alt.Chart(iris,width=300).mark_bar(color='#1EAFAE').encode(
alt.X('sepalWidth:Q', bin=alt.BinParams(maxbins=20)),
alt.Y('count()')
).interactive()
box=alt.Chart(iris,width=300).mark_boxplot(color='#1EAFAE').encode(
x='species',
y='petalLength'
).interactive()

h|box #横向排列
# h & box #上下排列

【横向排列分面】

1
2
3
4
5
6
7
8
9
10
11
12
13
df = data.cars()

alt.Chart(df.mark_circle().encode(
alt.X(alt.repeat("column"), type='quantitative'),
alt.Y(alt.repeat("row"), type='quantitative'),
color='Origin:N'
).properties(
width=100,
height=100
).repeat(
row=['Horsepower', 'Acceleration', 'Miles_per_Gallon'],
column=['Miles_per_Gallon', 'Acceleration', 'Horsepower']
).interactive()

【一个挺完整的分面图】
【0-903-06-06-2.PNG】

除了对图表进行分面排列外,
组合多种类型的图到一个坐标系里也很重要。 在Altair中,|&用于分面,而+号用于把多个图表对象(alt.Chart)组合在一个坐标系里,组合图有两种写法,写法1是从头新建多个图表对象,最后用+号连接,写法2是公用一些配置,通过bar.mark_circle()覆盖掉原先的mark_x形成新的对象,再通过+号连接,这种写法通过公用一些属性再覆盖图的类型简化代码,两种写法的例子如下:

1
2
3
4
5
6
bar= alt.Chart(d3).mark_bar(height=2).encode(x='y',y='x')
point = alt.Chart(d2).mark_point(filled=True).encode(x='y',y='x')
bar + point # 写法1
# 写法2
bar= alt.Chart(d3).mark_bar(height=2).encode(x='y',y='x')
bar + bar.mark_circle()

【组合绘制棒棒糖图】
0-905-07-06-altair-1

要让Altair绘制的图具有交互性是很容易的(交互的细节都封装了,直接调用高差的方法就行),通过给图表对象加上.interactive() 便可获得基础的缩放和拖拽交互。而且在分面图里使用interactive之后,对其中的子图进行缩放,其他图也会联动缩放,非常实用。一些深入的交互功能可以通过封装的chart.add_selection()alt.selection_multi().transform_filter()等实现。一些细节是,在Altair中,散点图的交互,只有y轴缩放,x轴不变,而柱状图xy都会缩放,直方图则是只在x轴缩放,挺有趣的。

深入学习

一个成熟的可视化库都应该有定制主题进行颜色、图元的配置。在Altair中,定制主题的写法如下,很像直接写一个json对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def lyns_marks():
return {
'config': {
'view': {
'height': 300,
'width': 400,
},
'mark': {
'color':'#1eafae',
#'fill':'#1eafae'
}
}
}
alt.themes.register('lyns_theme',lyns_marks)

alt.themes.enable('lyns_theme') #使用该主题

Altair也支持地理信息的可视化,用Altair库画地图的示例代码如下,为了方便和容易复现,继续使用vega_datasets里的数据集。

1
2
3
4
5
6
7
8
9
10
sphere = alt.sphere()
graticule = alt.graticule()
world=data.world_110m.url
source = alt.topo_feature(world, 'countries')
alt.layer(
alt.Chart(sphere).mark_geoshape(fill='lightblue'),
alt.Chart(graticule).mark_geoshape(stroke='white', strokeWidth=0.5),
alt.Chart(source).mark_geoshape(fill='#1eafae', stroke='black')
).project('naturalEarth1'
).properties(width=600, height=400).configure_view(stroke=None)

【Altair绘制世界地图】
0-905-07-08-altair-1.PNG

将绘制的图表保存到本地可以用bar.save('altair_bar.html')保存为HTML文档,保留着交互功能,要直接保存为PNG图片或PDF需要再安装一个altair_saver包,再调用bar.save('altair_bar.png')

总结

Altair是一个语法挺简洁、功能挺强大的Python可视化库,它封装了很多实用的方法用于统计数据的可视化,能用优雅的语法方便地对二维表格数据(代表格式是pandas的DataFrame)进行筛选分组汇总映射为各种图表类型,配置各种图元参数也很便捷,对地理空间数据支持也不错。可惜的是目前还没有支持饼图这一图表类型。
Altair基于Vega-lite套件封装的可视化功能,再底层是D3。在Vega体系里,封装的Python可视化库还有vincent及PdVega等。其中vincent在2016年之后就不再更新,其github页面上建议去了解Altair。而PdVega本身就和Altair关系密切,其文档的地址就是altair-viz.github.io/pdvega,很能说明问题,当然这两个库是差异化发展的,PdVega的特点是针对Jypyter Notebook环境进行了针对性的适配,并且API向pandas内置的plotting看齐。

在公众号后台回复 Altair 获取本文的xmind笔记和ipynb代码文件。
【Altair库思维导图 Altair可视化XMIND】

最后,用Altair绘制一个Altair的icon;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
d3=pd.DataFrame({'a':[0,10.1],'b':[0,100.2]})
d4=pd.DataFrame({'a':[10,20],'b':[100,100]})
d5=pd.DataFrame({'a':[18,28],'b':[70,70]})
d6=pd.DataFrame({'a':[26,36],'b':[40,40]})
a1=alt.Chart(d3).mark_area(color='#FBC02D').encode(
x='a',
y='b'
).interactive()
a2=alt.Chart(d4).mark_area(color='#03A9F4').encode(
x='a',
y='b'
).interactive()
a3=alt.Chart(d5).mark_area(color='#4FC3F7').encode(
x='a',
y='b'
).interactive()
a4=alt.Chart(d6).mark_area(color='#B3E5FC').encode(
x='a',
y='b'
).interactive()
a5=a1+a2+a3+a4
a5.configure_axis(grid=False)

【动图,gif 序列 01-2185-1】

参考资料: