超火动态排序疫情变化图,
这次我们用 Plotly来绘制
各位同学早上好,我是 Lemonbit 。
国内的疫情基本进入扫尾阶段了,但国外疫情还处于爆发的高峰期。以前是咱们国家各个省对口支援湖北的一个市,现在开启一个省支援一个国家的模式。
大国风范,为祖国点赞!!!
近期,我写了几篇用 Plotly 来演示全球疫情情况的文章,如下:
用 Plotly 动态柱状图来演示全球疫情变化趋势
用 Python可视化神器 Plotly 动态演示全球疫情变化趋势
作为延伸,今天,来分享用 Plotly 来制作动态排名各个国家疫情变化情况的柱状图。先来看最终的效果:
上图中,国家排名以及确诊数量,都是动态变化的。类似的图,估计大伙已经看到用其他工具制作的,今天,我们来看看如何用 Plotly 来实现。
本次我们主要来可视化分析国外疫情的发展情况。疫情的数据来源于开源项目 Akshare,由于使用该项目获取数据时,有时不太稳定,可能会遇到连接失败的情况。所以,这里我提供了保存好的数据供大家练习使用,本文的代码及数据文件在文末提供了获取方式。
当然,大家也可以使用 Akshare 的数据,因为会不时的更新,如果连接中断的话,换个时间重试下就好。
照例,还是先介绍下我运行的环境。
Mac 系统
Anaconda(Python 3.7)
Jupyter Notebook
我是在 Jupyter Notebook 中运行代码的,本次使用到的 Python 库包括 akshare, pandas, plotly 等,导入如下:
import akshare as ak
import pandas as pd
import plotly
import plotly.graph_objs as go
from plotly.offline import iplot, init_notebook_mode,plot
import plotly.express as px
from datetime import datetime
# 在PyCharm、VS Code等IDE中运行时,
# 需要注释下面这行代码
init_notebook_mode()
使用的几个 Python 库的版本如下:
print(f'pandas version: {pd.__version__}')
print(f'akshare version: {ak.__version__}')
print(f'plotly version: {plotly.__version__}')
# pandas version: 1.0.1
# akshare version: 0.4.27
# plotly version: 4.5.0
接着,我们读取已获得的数据(已保存的数据是截至3月16日)。
# 从 akshare 获取数据
# df_all_history = ak.epidemic_history()
# 从csv文件获取数据,这个数据文件的数据截止到3月10日
df_all_history = pd.read_csv('epidemic_all_20200316.csv',index_col=0)
df_all_history
从上面获取的数据,有些数据格式需要加以调整,对于日期,我们这里会组织两列数据,一列是时间格式的日期( ['date']
),一列是字符串格式的日期 ( ['dates']
)。这样设置的原因,是因为我们后续分别需要用到这两种格式的日期。
# 将数据复制一份
df_all = df_all_history
# 将字符串格式的日期转换为 日期格式
df_all['date'] = pd.to_datetime(df_all['date'])
# 将时间格式转为字符串格式的日期,以 YYYY-mm-dd 的形式保存
df_all['dates'] = df_all['date'].apply(lambda x:x.strftime('%Y-%m-%d'))
# 添加现存确诊列
df_all['current'] = df_all['confirmed'] - df_all['cured'] - df_all['dead']
print(df_all.info())
# df_all
上面的数据,是全球的数据,其中也包括国内各个省市的数据。我们可以将数据进行整理,分别提取出中国和海外国家的数据。
# 国内总计数量
df_china_total = df_all.query("country=='中国' and province==''")
df_china_total = df_china_total.sort_values('date',ascending=False)
# df_china_total
# ---------
# 国外,按国家统计
df_oversea = df_all.query("country!='中国'")
df_oversea.fillna(value="", inplace=True)
# df_oversea
# ---------
# 全球统计
df_global = df_china_total.append(df_oversea)
# df_global
进一步,我们可以梳理出全球各个国家最近的疫情情况,这里,我们是按累计确诊数量进行排序的。
# 全球最近的概况
# 按日期进行排序
df_global_date = df_global.sort_values('date',ascending=False)
# 获取最新的日期
latest_day = df_global_date['dates'].values[0]
# query 函数中,变量需要加 @ ,
df_global_latest = df_global_date.query('dates==@latest_day')
# 按累计确诊数量排序
df_global_latest = df_global_latest.sort_values('confirmed',ascending=False)
df_global_latest
可以看出,累计确诊人数,中国最多,但现存确诊人数,3月16日,是意大利最多了。
国外的疫情发展情况,大部分国家从2月10日起,发展趋势较为明显,因此,后面我们重点分析这段时间之后的情况。
# 现有数据演示从 2020年2月10日开始
df_oversea_recent = df_oversea.set_index('date')
df_oversea_recent = df_oversea_recent['2020-02-10':]
# df_oversea_recent
由于部分国家的数据不是从2020年2月10日开始记录的,所以要补充数据。我们可以手动新建一个 excel数据表,将补充日期的数值填充为 0 。
至于为什么要补充数据呢,因为如果没有补充的话,这个国家在绘制的图里就不会出来,有兴趣的同学可以测试下。
2月10日开始的数据,没有伊朗的,因为伊朗的数据是很靠前的所以必须纳入分析的范围内。其他国家,如果有需要补充的,后续可以继续完善。
补充的数据在 epidemic_buchong.xlsx
这个文件里。
# 由于部分国家,数据不是从2020年2月10日开始的,所以要补充数据,数值为 0
# 数据在 excel 表格中进行补充,这里进行读取
df_oversea_buchong = pd.read_excel('epidemic_buchong.xlsx')
df_oversea_buchong['dates'] = df_oversea_buchong['date'].apply(lambda x:x.strftime('%Y-%m-%d'))
df_oversea_buchong.set_index('date', inplace=True)
df_oversea_buchong.fillna(value="", inplace=True)
print(df_oversea_buchong.info())
# df_oversea_buchong
将需要补充的数据弄好后,我们可以合并上面这两部分数据,一起进行分析。
# 合并补充数据
df_oversea_recent_new = df_oversea_recent.append(df_oversea_buchong)
df_oversea_recent_new.sort_index(inplace=True)
# df_oversea_recent_new
对于上面的数据,有一点要说明下,在数据源中,日本的数据前期是包含了钻石公主号邮轮的数量的,后续是将钻石公主号的数据从日本单独列出来了,所以你会发现日本的数据有下降的现象。
得到合并的数据后,我们就可以来进行可视化了,之前 Lemonbit 已经展示了如何用动态气泡图和动态柱状图来可视化,但上次的柱状图没有实现动态排名这个功能,这次我们用动态排名的柱状图来可视化。
当需要对国家进行动态排名时,首先给每个国家定义一个确定的颜色,以便国家在上下移动的时候,每个国家的颜色保持不变。
# 准备国家列表和 国家的颜色列表
countries = list(df_oversea_recent_new['country'].unique())
countries_count = len(countries) # 国家数量
# 为每一个国家定义颜色
color_1 = [px.colors.qualitative.Alphabet[i] for i in range(26)]
color_2 = [px.colors.qualitative.Light24[i] for i in range(24)]
color_3 = [px.colors.qualitative.Dark24[i] for i in range(24)]
color_list = (color_1 + color_2 + color_3)*3 # 颜色数量,比国家数量大
color_list = color_list[:countries_count] # 颜色数量跟国家数量一致
print(f'国家数量:{countries_count}')
print(f'国家颜色数量:{len(color_list)}')
截止3月16日,全球已经有 150多个国家和区域有疫情的数据了。
后续,还需要做一些数据上的准备,就是计算出海外单个国家累计确诊人数的最高值。以及,在原来的 DataFrame 中增加了颜色列,为每个国家指定一种颜色。
计算海外单个国家累计确诊人数的最高值
# 海外单个国家死亡人数的最高值
max_dead = df_oversea_recent_new['dead'].max()
# 海外单个国家累计确诊人数的最高值
max_confirmed = df_oversea_recent_new['confirmed'].max()
print(f'海外单个国家累计确诊人数的最高值为{max_confirmed}')
print(f'海外单个国家死亡人数的最高值为{max_dead}')
为每个国家指定一种颜色
df = df_oversea_recent_new
# df 添加颜色列,每个国家给定一种颜色
for country,color in zip(countries, color_list):
df.loc[df['country']==country, 'color'] = color
# 保存数据到 csv文件
# df.to_csv(f'epidemic_all_{today}_update.csv')
# df
到这里,我们准备工作告一段落,可以来进行可视化了。下面的代码有些长,我们实现的是海外国家累计确诊人数排名前 20 的国家进行动态排名,这个是通过 pandas 的 nlargest
功能来实现的。
对于需要动态排名的柱状图,这里可视化用的是原生的 Plotly , 而不是 plotly.express 。
在下面代码中,核心的思路是,需要构造一个字典,以 key:value
的形式保存每天的 疫情数据,其中疫情数据是以 DataFrame 的形式保存于 value 中。
dates_list = list(df['dates'].unique())
dict_keys = [str(i) for i in range(len(dates_list))]
n_frame={}
for date, d in zip(dates_list, dict_keys):
dataframe=df[df['dates']==date]
# 排名前20的国家
dataframe=dataframe.nlargest(n=20,columns=['confirmed'])
dataframe=dataframe.sort_values(by=['dates','confirmed'])
n_frame[d]=dataframe
# print (n_frame)
#-------------------------------------------
fig = go.Figure(
data=[
go.Bar(
x=n_frame['0']['confirmed'], y=n_frame['0']['country'],orientation='h',
text=n_frame['0']['confirmed'], texttemplate='%{text:.3s}',
textfont={'size':12}, textposition='inside', insidetextanchor='middle',
width=0.9, marker={'color':n_frame['0']['color']})
],
layout=go.Layout(
xaxis=dict(range=[0, max_confirmed*1.1], autorange=False, title=dict(text='confirmed',
font=dict(size=12))),
yaxis=dict(range=[-0.5, 20.5], autorange=False,tickfont=dict(size=14)),
title=dict(text='Confirmed numbers per Country: 2020-02-10',font=dict(size=28),x=0.5,xanchor='center'),
# 添加按钮
updatemenus=[dict(
type="buttons",
buttons=[dict(label="Play",
method="animate",
# https://github.com/plotly/plotly.js/blob/master/src/plots/animation_attributes.js
args=[None,
{"frame": {"duration": 1000, "redraw": True},
"transition": {"duration":250,
"easing": "linear"}}]
)]
)]
),
frames=[
go.Frame(
data=[
go.Bar(x=value['confirmed'], y=value['country'],
orientation='h',text=value['confirmed'],
marker={'color':value['color']})
],
layout=go.Layout(
xaxis=dict(range=[0, max_confirmed*1.1], autorange=False),
yaxis=dict(range=[-0.5, 20.5], autorange=False,tickfont=dict(size=10)),
title=dict(text='Confirmed numbers per Country: '+str(value['dates'].values[0]),
font=dict(size=28))
)
)
for key, value in n_frame.items()
]
)
#-------------------------------------------
fig.show()
上面代码中,动态排名时通过每天对 go.Frame
中的数据进行更新来实现的。上面的 duration
可以来控制按钮点击后变化的速度。
运行上述代码后,得到的效果如下:
是不是很棒啊。
在文末,Lemonbit 给大家提供了本文完整的PDF版内容(含代码)以及数据文件。
但这里,通过运行效果,可以看出,由于现在的确诊数量较大,而刚开始的时候数据较小,所以当 x轴
固定不动时,动态化演示的效果不是太理想,我们希望 x轴
上的数据也能动态变化,这样效果会更理想。
要使 x轴
上的数据也能动态变化,实现文章开始图中的效果,核心是定义 x轴
的范围时,实现动态变化, x轴
的核心代码如下: