使用Python+Matplotlib制作时序动态图

一、项目效果及说明

最终的效果如下图所示

使用Python+Matplotlib制作时序动态图_第1张图片

有几点说明:

        1.  图中所展示的是从2000年到2021年全国人口最多的5个省市的人口数量、顺序位次以及变化情况;

        2.  图中数据来源于“国家统计局”网站上的公开资料,但是,在最终使用的时候,并没有进行数据的核对;因为,仅仅是为了展示Python+Matplotlib在制作动态图的使用,而不是进行人口或者某个地区的研究;总之,本展示图,不针对任何地区,也不构成任何学术研究结论;

        3.  图表中的颜色是系统随机选取的,后面的程序中有详细的说明;颜色不构成任何指射或者含义,仅仅是为了区分不同的数据;

        4.  图表中仅仅展示了人口最多的5个省市区,如有需要,还可以更多,都可以在程序中设置。

        5.  此文只是为了记录自己的学习过程,与同好交流,仅此而已。

二、项目环境说明

        这个程序是在Jupyter Notebook下调试运行的,使用的Anaconda建立的虚拟环境,具体版本如下:

        Python:3.9.16

        Pandas:1.5.3

        Matplotlib:3.7.1

        Ipython:8.13.2

        Jupyter Notebook:6.5.4

三、项目思路

        利用Matplotlib制作动态图,我自己知道的思路有两个:

        第一个,就是利用Matplotlib画出每一帧图片,保存成为“.png”或者是“jpg”格式,然后再使用imageio包,把这些文件按照顺序生成一个“GIF”文件,也会产生动画的效果;

        第二个,就是利用Matplotlib中的Animation函数直接生成“GIF”格式的动画文件。

        因为没有看过源码,所以,仅从自己的感受来看,第一个方法需要产生大量的中间文件,而且生成出来的动画,总有跳帧的感觉,变化不是十分平滑,也许是我制作水平的问题,也欢迎有高手指教。我测试过以后,决定使用第二个办法。这个办法不仅是Matplotlib“内生”的函数,而且还支持包括“MP4”在内的多种格式,还没有中间文件的产生。

        Matplotlib的Animation函数还有很多的子类和功能,这次使用的只是其中之一。函数签名具体大致如下:

         FuncAnimation(fig,func,frames,init_func,interval,blit)

            其参数为:① fig为绘制动图的画布名称;

                              ② func为自定义动画函数,在本文中这个函数是draw_barchart(),在程序中会有详细的功能说明;

                              ③ frames为动画长度,一次循环包含的帧数,在函数运行时,其值会传递给函数func中定义的形参,本文的函数只定义了一个形参“year”,其具体含义,程序中会有说明;

                               ④ init_func为自定义开始帧,即初始化函数,可省略;

                               ⑤ interval为更新频率,以ms计算;

 四、程序及相关说明

from IPython.display import HTML
import pandas as pd
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker 
import matplotlib.animation as animation
import seaborn as sns
import matplotlib.pyplot as plt
import random

        以上是程序中用到所有库,都是必须的。

plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False

        这两句之所以要特别拿出来说,是因为我曾经“踩坑”;如果没有这两个设置,那么,Matplotlib在生成图像以后,标签上的中文就会成为乱码。需要说明的是,第一个设置在这里直接使用了“赋值”,也可以把两个列表(list)加起来,但是,必须要让“['SimHei']”在整个列表的最前面,否则,还是没有效果。

result = prepare_data()

        这个是我自己加载数据的模块,无非就是“pd.read_csv”来读取一个数据文件,因为,和本文的主旨无关,就不详细说明了。把结果列出来(见下图),并做适当说明,是因为,后文的程序中和数据被“读出来”以后的存储结果相关。

使用Python+Matplotlib制作时序动态图_第2张图片

         数据的“columns”的名字(name)是“地区”,“index”的名字(name)是“年份”,这个顺序只是“统计年鉴”上的顺序,并没有进行处理。后文的排序,每次都是根据“index”来进行“行排序”的。

# 生成了一批颜色,给每个地区赋予了一个不同的颜色,以后,用到这个地区,就用这个颜色来渲染
# 为了防止颜色比较接近,在颜色生成以后,随机打乱了,效果好了一点
color = sns.husl_palette(len(result.columns),h=15/360, l=.65, s=1).as_hex() 
random.shuffle(color)
colors = dict(zip(result.columns.tolist(),color))

        这个就是前文所说的“随机选择颜色”的内容,利用random.shuffle,打乱颜色列表(list),然后再做成了一个字典(dict),以后,每个省市区都有了固定的颜色,每次画图的时候,都来这个字典中读取颜色,然后再进行渲染。确保在每次生成动画中,每个数据的颜色保持不变。

def draw_barchart(year):
    # 这里年份,只是一个顺序号码
    # 一般是个浮点数,小数点后面代表的是:一个时点的图像需要几次变换到下一个时点,
    # 小数就是次数的倒数,用于计算每次,图像的位移量
    # 整数在这里代表的是,索引的顺序

    # 这个表示的是x坐标的大小,也就是显示出来的数据的个数
    N_Display=5
    # 这个取整是用来确认现在图像属于哪个时点的
    year1=int(year)
    # 加一表示,图像下一个时点的顺序号
    year2=year1+1
    # 这个就是上面的注释里说的小数的部分
    location_x=year-year1
    
    # 对不同的时点的数据进行排序,才能得到不同顺序下的“地区”也就是“省市”的名称
    result_sort1 = result.sort_values(by=result.index[year1], axis=1, ascending=False)
    result_sort2 = result.sort_values(by=result.index[year2], axis=1, ascending=False)
    # 截取前5个省份,并倒序,可以让人口最多的省份在上,而其他的则是依次递减
    area1 = list(reversed(list((result_sort1.columns)[:5])))
    area2 = list(reversed(list((result_sort2.columns)[:5])))
    # 这个列表表达式是为了计算出,本期排名榜中的各个成员,在下一期中的位置
    # 同时,计算出,相对现在位置的偏移量
    # 如果没有出现在下一期中,则设置为‘0’
    # 具体计算内容是:i, elem in enumerate(area1)表示当前某个位置的名称和在列表中的顺序
    # elem in area2 判断某个当前位置在下一个时点中是否存在,
    # 如果不存在,则设置为‘0’,否则按照下面的公式计算
    # i +1 +(area2.index(elem)-i)*location_x 两个时点的位置之差,乘以偏移量,表示每帧动画移动的位置
    # i +1 +就是确定下一帧动画时,图像的位置,加一是因为坐标是从“1”开始的
    # 这个列表就是每一帧动画的x轴的坐标
    diff = [i +1 +(area2.index(elem)-i)*location_x if elem in area2 else 0 for i, elem in enumerate(area1)]
    # 取出要显示的“人口数”,第二时点的数据取出来也是没有用的,为了对称,就这么写了
    pop_num1 = list(reversed(list(result_sort1.iloc[year1, :5])))
    pop_num2 = list(reversed(list(result_sort2.iloc[year2, :5])))
    # 开始画图,颜色就是按照前面设置的字典来去的,这样,每一个元素的颜色会始终不变
    ax.clear()
    ax.barh(diff, pop_num1, color=[colors[x] for x in area1])
    # 给每一个柱状图的最右边加上名字和数字,包括设定大小等
    for i, (value, name, x) in enumerate(zip(pop_num1, area1, diff)):
        ax.text(value, x, name, size=16, ha='right')
        ax.text(value, x, value, size=16, ha='left')
    # 在画布右方添加年份
    ax.text(1, 0.1, result.index[year1], transform=ax.transAxes, size=46, ha='right')
    # 设置坐标轴的大小,确保在动态图的过程中,图像的外壳不动,好看一点
    ax.set_xlim(0,15000)
    ax.set_ylim(0.5,N_Display+0.5)
    ax.set_xticks(ticks=np.arange(0,14000,1000))
    ax.set_yticks(ticks=np.arange(N_Display,0,-1))
    ax.set_yticklabels(labels=np.arange(N_Display,0, -1))
    ax.margins(0, 0.01)
    ax.text(0.2, 1.01, '2000——2021年间人口最多的地区',
            transform=ax.transAxes, size=26, weight='light', ha='left')
    # 取消了边框
    plt.box(False)

        自我感觉,程序中的注释大约应该比较清楚了。这里就不再赘述了。放一张图,来表示一下函数的执行效果。

使用Python+Matplotlib制作时序动态图_第3张图片

         接下来,开始制作动画并保存

# 开始制作动画
# 这个才是整个图像的设置
fig, ax = plt.subplots(figsize=(8, 7))
plt.subplots_adjust(left=0.12, right=0.98, top=0.85, bottom=0.1)   
# 这个语句是产生动画的关键,第一个参数就是“画布”,第二个参数是画图的函数,
# 第三个产生参数的函数,这里产生的参数自动送到第二个参数中的函数,作为实参,
# 这里就是一连串的数字,表示数据的索引,以及几次移动到位的次数的倒数
# 在后面的参数是“间隔”的毫秒数
animator = animation.FuncAnimation(fig, draw_barchart, frames=np.arange(0, 21, 0.1),interval=20)
# 这个直接在网页上生成动画
# HTML(animator.to_jshtml()) 
# 这个是用一个文件名直接保存成动画,系统自动识别文件后缀,还可以是“MP4”格式的
animator.save('result.gif')

        这个过程大约需要一点时间,然后就能在目录文件下发现保存完成的“GIF”图片了。

        至此,时序动态图制作完成。

        欢迎各位批评指正。

你可能感兴趣的:(python,matplotlib,pandas,经验分享)