本篇博客中的代码可以在jupyter notebook中运行。
2023/01大修博客。
因为在学习Tableau的时候遇到了一个GDP动态排名图的问题,写这篇博客主要是想用Python来实现该动态图,最终实现效果如下图1。

1. 数据准备
1.1 读入原始数据
本篇博客用到的GDP数据下载地址如下:
链接:https://pan.baidu.com/s/1Joa4i4mM2vHgRVS9p8I2SQ
提取码:e3tc
先读入数据,并将无用字段删除,具体代码如下:
import pandas as pd
data=pd.read_csv(r'C:\Users\sunta\Downloads\main_country.csv')
gdp_rank_data=data.drop(['Country_Code','Indicator_Name','Indicator_Code'],
axis=1)
经过处理之后的部分数据截图如图2:

1.2 计算GDP排名
接下来需要计算出各国在不同年份的GDP排名,从图1上可以发现以下几点:
- 每一年GDP最高的国家排在最上边;
- 这张GDP动态图只展示排名前10的国家及其GDP数据;
基于以上两点,我们要计算的GDP排名数据需要满足以下条件:逆序排序;GDP最大值对应的排名值设为10。这样的排名可以保证GDP最大的10个国家的排名值为10到1,方便排除未进入前10的国家。GDP排名计算代码如下:
for year in gdp_rank_data.columns[1:]:
#rank()逆序时GDP最大值的计算结果为1,为了将其映射到10上,用11减去rank()的计算值
gdp_rank_data[year+'_rank']=11-gdp_rank_data[[year]].rank(ascending=False)
#更改GDP的单位,除以10亿(1e10为其科学表示法)
gdp_rank_data[year]=gdp_rank_data[year]/1e10
经过处理之后的1960年的GDP及其排名数据如下图3:

2. 具体画图
在具体写代码画图之前,关于图1有以下几点需要说明:
- 图1中的动态图能够看到GDP变化的动态过程,为了实现这种动态的效果,这里将一个年份到下一年份的变化分5步来完成。以1960年到1961年GDP变化为例,中国的GDP从59亿下降为50亿。将这9亿的分5步来完成,第
步下降到[59-(9/5)
]亿元,其对应的排名数值也这样分5步来变化。所以图1这张动态图其实是由286((2017-1960)*5+1)张常规的水平条形图组成的。这也是for循环中286这个数字的来源。
- 图1中代表每个国家的柱子的四个角各自加了一个圆点,这是为了美观而做,也可以不加;
- 图1中X轴需要随着每个GDP数据的增长而延长,这里是通过扩大比例尺而实现的。
- 为了在视觉上固定右下角的年份标签,将年份标签的水平位置固定到0.8*X轴最大值的位置上。
具体代码如下:
import matplotlib.pyplot as plt
import matplotlib
import matplotlib.animation as animation
import imageio
%matplotlib auto
matplotlib.rc('font', family='SimHei', weight='bold')
plt.rcParams['axes.unicode_minus'] = False
fig,ax=plt.subplots()
#去掉右侧和上边的边框线
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.spines['left'].set_color('none')
colors=['cornflowerblue','tomato','coral','darksalmon','orangered','sienna','chocolate',
'saddlebrown','orange','burlywood','olive','olivedrab','greenyellow','lawngreen',
'orchid','palegreen','aqua','deepskyblue','dodgerblue','darkviolet','crimson']
image_list=[]
for i in range(286):
ax.cla()
ax.set_ylim(0,11)
y_ind=str(1960+i//5)
if i==285:
data=gdp_rank_data[['Country_Name',y_ind,y_ind+'_rank']]
data['step_rank']=data[y_ind+'_rank']
x_gdp=data[y_ind]
else:
next_y_ind=str(1960+i//5+1)
data=gdp_rank_data[['Country_Name',y_ind,y_ind+'_rank',next_y_ind,
next_y_ind+'_rank']]
data['step_rank']=data[y_ind+'_rank']+(data[next_y_ind+'_rank']
-data[y_ind+"_rank"])/5*(i%5)
x_gdp=data[y_ind]+(data[next_y_ind]-data[y_ind])/5*(i%5)
y_labels=data.index
bars=ax.barh(data['step_rank'],x_gdp,color=[colors[i] for i in data.index],height=0.5)
data=data.sort_values('step_rank',na_position='first')
ax.set_yticks(data[data['step_rank']>=0]['step_rank'].values[-10:])
ax.set_yticklabels(data[data['step_rank']>=0]['Country_Name'].values[-10:])
ax.set_title("世界GDP动态排名(1960年-2017年)(单位:十亿)")
for bar in bars:
if bar.get_y()>0:
width=bar.get_width()
plt.text(width+ax.get_xlim()[1]*0.02,bar.get_y()+0.125,"{:.2f}".format(width))
#在柱子的四个角上加上四个点
plt.plot([0,width,width,0],
[bar.get_y(),bar.get_y(),bar.get_y()+0.5,bar.get_y()+0.5],color=bar.get_facecolor(),
marker='o')
plt.text(int(ax.get_xlim()[1]*0.8),2,y_ind+'年',fontsize=18)
if i%5==0:
plt.pause(3)
else:
plt.pause(0.3)
plt.savefig('tmp.png')
image_list.append(imageio.imread('tmp.png'))
imageio.mimsave('test.gif',image_list,duration=1)