Matplotlib玩转动态可视化
最近看到很多盆友们用pyecharts、Bokeh和plotly等绘图库制作动态图,还有用pbi制作的,以及网页工具flourish等。其实matplotlib这个经典绘图库也是可以的,这不就来了嘛~
目录
1.效果预览 2.数据获取 3.数据预处理 4.matplotlib动态可视化
1.效果预览
我们从国家统计局 下载最近30年全国各地区生产总值(实际上是1993年-2019年),使用matplotlib绘制动态可视化图,效果如下:
2.数据获取
直接从国家统计局-「国家数据」(http://data.stats.gov.cn/
)下载原始数据即可,数据长这样:
3.数据预处理
# 需要引入的库
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import matplotlib.animation as animation
from IPython.display import HTML
原始数据的行索引是地区,列索引是年份,我们后续作图需要的数据结构偏向于窄表(当然宽表其实也可以做到,这里我个人习惯用窄表处理)。
为了实现宽表变窄表,用到pandas
里的melt
方法。
import pandas as pd
# 读取下载后的数据
df = pd.read_excel(r'F:\微信公众号\matplotlib动态图\各地区生产总值.xlsx')
df.head()
# 使用melt方法进行处理
data = df.melt(id_vars='地区',value_vars=range(1993,2020),var_name='年份',value_name='生产总值(亿)')
data.head()
4.matplotlib动态可视化
matplotlib动态图用到的是animation.FuncAnimation
方法,其实动态就是N张图一张一张按照一定频率刷新,我们也有其他方法实现,这里不展开。
在我们的效果展示中,可以看到 类型是条形图,数值高低排序,每个条形图颜色不一样,我们来一步一步看看如何做出最终效果~
4.1.朴实无华的条形图
barh
是条形图,就是横着的柱状图,以下我们先取2019年的年度数据展示前10地区
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
# 以下代码解决显示中文问题
plt.rcParams['font.sans-serif'] = ['SimHei']
# 绘制 2019年 各地区生产总值(前10)
currentYear = 2019
ddata = (data[data['年份']==currentYear]
.sort_values(by='生产总值(亿)',ascending = True)
.tail(10))
fix, ax = plt.subplots(figsize=(15,8))
ax.barh(ddata['地区'], ddata['生产总值(亿)'])
我们看到上面这张图平平无奇,朴实无华的配色,没有多一分的元素(标题、数据标签等等),接下来我们先把条形图美化一下
4.2.有点还行的条形图
通过自定义条形图配色,再附上一些text说明。
关于配色,直接从网上(https://www.beejson.com/word/rgb.html
)找 16进制配色表,然后取31个即可(咱们一共有31个地区数据不含港澳台)。然后将31个地区与31个颜色进行组合成字典备用!
「构造地区-颜色
字典」
# 我直接从网上批量复制了 30多个颜色值,然后随机抽取31个和31个地区配对
a = ['#FFFFCC #FFCC00 #CC9909 #663300 #FF6600 #663333 #CC6666 #FF6666 #FF0000 #FFFF99 #FFCC66 #FF9900 #FF9966 #CC3300 #996666 #FFCCCC #660000 #FF3300 #FF6666 #FFCC33 #CC6600 #FF6633 #996633 #CC9999 #FF3333 #990000 #CC9966 #FFFF33 #CC9933 #993300 #FF9933 #330000 #993333 #CC3333 #CC0000 #FFCC99 #FFFF00 #996600']
b =' '.join(a)
c = b.split(' \t')
import random
# 随机不放回抽取31个
color = random.sample(c,31)
# 获取原数据中地区列表
province = list(data['地区'].unique())
# 组合成 地区-颜色值 字典
colors = dict(zip(province,color))
「绘制有颜的条形图」
fig, ax = plt.subplots(figsize=(15,8))
ax.barh(ddata['地区'], ddata['生产总值(亿)'],color = [colors[x] for x in ddata['地区']])
# 在每个条形图末端显示 归属地区 及 生产总值(亿)
for i, (num, pro) in enumerate(zip(ddata['生产总值(亿)'], ddata['地区'])):
ax.text(num,i, pro, ha = 'right')
ax.text(num,i, f':{num}亿', ha = 'left')
# 在右侧中部偏下显示当前年份
ax.text(1, 0.4, currentYear, transform= ax.transAxes, size=40, ha='right')
Text(1, 0.4, '2019')
有人就要说了,上面这个图也没啥好看的,除了增加了单独的配色以及数据显示外。。讲的太对了,字体还丑、颜色搭配也是难看,当然这些都是可以自己配置的 因为后续 我们会换个plt.xkcd()
**「手绘卡通风格」**的形式,但是卡通风格的形式需要特别处理中文字体显示问题,这里先介绍下来自好朋友 「'小明哥'」 的帮助,如下代码(设置字体为我系统里的微软雅黑,字号16)
# 字体管理
from matplotlib import font_manager
my_font = font_manager.FontProperties(fname=r"C:\Windows\Fonts\msyh.ttc",size=14)
# 再加入其他一些元素(如标题、刻度线、刻度放在最上方等)
fig, ax = plt.subplots(figsize=(15,8))
ax.barh(ddata['地区'], ddata['生产总值(亿)'],color = [colors[x] for x in ddata['地区']])
for i, (num, pro) in enumerate(zip(ddata['生产总值(亿)'], ddata['地区'])):
ax.text(num,i, pro, ha = 'right',fontproperties=my_font)
ax.text(num,i, f' :{num}亿', ha = 'left',fontproperties=my_font)
ax.text(1, 0.4, currentYear, transform= ax.transAxes,fontproperties=my_font, size=40, ha='right')
# x刻度设置在顶部
ax.xaxis.set_ticks_position('top')
# x刻度颜色设置为灰色,大小为12
ax.tick_params(axis= 'x',colors= '#777777',labelsize= 12)
# 去掉y刻度
ax.set_yticks([])
# 设置 xy轴内边距
ax.margins(0, 0.01)
# 显示网格(x轴向虚线)
ax.grid(which= 'major', axis= 'x',linestyle= '--')
# 网格线至于底部
ax.set_axisbelow(True)
# 在左上角显示 标题(不是用的title方法)
ax.text(0, 1.06,'全国各地区生产总值(1993-2019)',transform= ax.transAxes,weight =600,ha = 'left',fontproperties= my_font,size=24)
# 去掉边框
plt.box(False)
4.3.会动的条形图
既然动图是一张张图刷新而来,那我们把每年的数据都做一张图再定时刷新替换不就好了,这样当然是可以的。
所以,我们先看看「animation.FuncAnimation」(https://matplotlib.org/api/animation_api.html
)吧
官网有个简单的例子:sin(x)的动态演示。这里不做介绍,源码清晰,我们直接现学现做~
先把上面作图代码封装成函数
这个函数只需要一个参数,year(年份)
fig, ax = plt.subplots(figsize=(12,16))
def drawBarh(year):
ddata = (data[data['年份']==year]
.sort_values(by='生产总值(亿)',ascending = True)
.tail(31))
ax.clear()
ax.barh(ddata['地区'], ddata['生产总值(亿)'],color = [colors[x] for x in ddata['地区']])
for i, (num, pro) in enumerate(zip(ddata['生产总值(亿)'], ddata['地区'])):
ax.text(num,i, pro, ha = 'right',fontproperties=my_font)
ax.text(num,i, f' :{num}亿', ha = 'left',fontproperties=my_font)
ax.text(1, 0.4, year, transform= ax.transAxes,fontproperties=my_font, size=40, ha='right')
# x刻度设置在顶部
ax.xaxis.set_ticks_position('top')
# x刻度颜色设置为灰色,大小为12
ax.tick_params(axis= 'x',colors= '#777777',labelsize= 12)
# 去掉y刻度
ax.set_yticks([])
# 设置 xy轴内边距
ax.margins(0, 0.01)
# 显示网格(x轴向虚线)
ax.grid(which= 'major', axis= 'x',linestyle= '--')
# 网格线至于底部
ax.set_axisbelow(True)
# 在左上角显示 标题(不是用的title方法)
ax.text(0, 1.06,'全国各地区生产总值(1993-2019)',transform= ax.transAxes,weight =600,ha = 'left',fontproperties= my_font,size=24)
# 去掉边框
plt.box(False)
# 演示2019年数据
drawBarh(2019)
animation动图制作
再调用animation.FuncAnimation
方法进行动图制作,我们在输出的页面可以进行动画演示(快捷、后退、开始、暂停等等)。
fig ,ax =plt.subplots(figsize = (12,16))
# 手绘卡通风格
plt.xkcd()
animator = animation.FuncAnimation(fig,drawBarh,frames=range(1993,2020),interval=500)
HTML(animator.to_jshtml())
# 通过以下方式可以保存为动图(保存为视频方式我们单独介绍吧)
animator.save('生产总值动态图.gif',bitrate=1800,writer ='pillow')
❝「交流与思考」:我们在效果动图中发现其实没那么顺滑,这是因为我们是按照每一年的数据绘制一次导致的,那么如何让效果更加顺滑呢?
(一般来说,可以把每年的数据分为多份,比如我们认为每两年之间存在N组值,那么就是有N-2个缺失值,通过pandas的缺失值插值处理可以补充一些值作为绘图的辅助值,从而让效果更加顺滑,那么如何进行插值呢?pandas其实有现成的方式,这里也不展开说明了)
❞
-END-