查看原文
其他

超火动态排序疫情变化图,这次我们用 Plotly来绘制

我是阳哥 Python数据之道 2022-09-04
前言
文章可以免费阅读前面将近80%的内容,若觉得有价值,可动动手指看完全文哦

超火动态排序疫情变化图,

这次我们用 Plotly来绘制

各位同学早上好,我是 Lemonbit 。
国内的疫情基本进入扫尾阶段了,但国外疫情还处于爆发的高峰期。以前是咱们国家各个省对口支援湖北的一个市,现在开启一个省支援一个国家的模式。
大国风范,为祖国点赞!!!
近期,我写了几篇用 Plotly 来演示全球疫情情况的文章,如下:

作为延伸,今天,来分享用 Plotly 来制作动态排名各个国家疫情变化情况的柱状图。先来看最终的效果:

上图中,国家排名以及确诊数量,都是动态变化的。类似的图,估计大伙已经看到用其他工具制作的,今天,我们来看看如何用 Plotly 来实现。

数据来源

本次我们主要来可视化分析国外疫情的发展情况。疫情的数据来源于开源项目 Akshare,由于使用该项目获取数据时,有时不太稳定,可能会遇到连接失败的情况。所以,这里我提供了保存好的数据供大家练习使用,本文的代码及数据文件在文末提供了获取方式。
当然,大家也可以使用 Akshare 的数据,因为会不时的更新,如果连接中断的话,换个时间重试下就好。

准备工作

照例,还是先介绍下我运行的环境。
  • Mac 系统

  • Anaconda(Python 3.7)

  • Jupyter Notebook

我是在 Jupyter Notebook 中运行代码的,本次使用到的 Python 库包括 akshare, pandas, plotly 等,导入如下:
  1. import akshare as ak

  2. import pandas as pd

  3. import plotly

  4. import plotly.graph_objs as go

  5. from plotly.offline import iplot, init_notebook_mode,plot

  6. import plotly.express as px

  7. from datetime import datetime


  8. # 在PyCharm、VS Code等IDE中运行时,

  9. # 需要注释下面这行代码

  10. init_notebook_mode()

使用的几个 Python 库的版本如下:
  1. print(f'pandas version: {pd.__version__}')

  2. print(f'akshare version: {ak.__version__}')

  3. print(f'plotly version: {plotly.__version__}')


  4. # pandas version: 1.0.1

  5. # akshare version: 0.4.27

  6. # plotly version: 4.5.0

接着,我们读取已获得的数据(已保存的数据是截至3月16日)。
  1. # 从 akshare 获取数据

  2. # df_all_history = ak.epidemic_history()


  3. # 从csv文件获取数据,这个数据文件的数据截止到3月10日

  4. df_all_history = pd.read_csv('epidemic_all_20200316.csv',index_col=0)


  5. df_all_history

从上面获取的数据,有些数据格式需要加以调整,对于日期,我们这里会组织两列数据,一列是时间格式的日期( ['date']),一列是字符串格式的日期 ( ['dates'])。这样设置的原因,是因为我们后续分别需要用到这两种格式的日期。
  1. # 将数据复制一份

  2. df_all = df_all_history


  3. # 将字符串格式的日期转换为 日期格式

  4. df_all['date'] = pd.to_datetime(df_all['date'])


  5. # 将时间格式转为字符串格式的日期,以 YYYY-mm-dd 的形式保存

  6. df_all['dates'] = df_all['date'].apply(lambda x:x.strftime('%Y-%m-%d'))


  7. # 添加现存确诊列

  8. df_all['current'] = df_all['confirmed'] - df_all['cured'] - df_all['dead']


  9. print(df_all.info())


  10. # df_all

全球概况

上面的数据,是全球的数据,其中也包括国内各个省市的数据。我们可以将数据进行整理,分别提取出中国和海外国家的数据。
  1. # 国内总计数量

  2. df_china_total = df_all.query("country=='中国' and province==''")

  3. df_china_total = df_china_total.sort_values('date',ascending=False)

  4. # df_china_total


  5. # ---------

  6. # 国外,按国家统计

  7. df_oversea = df_all.query("country!='中国'")

  8. df_oversea.fillna(value="", inplace=True)

  9. # df_oversea


  10. # ---------

  11. # 全球统计

  12. df_global = df_china_total.append(df_oversea)

  13. # df_global

进一步,我们可以梳理出全球各个国家最近的疫情情况,这里,我们是按累计确诊数量进行排序的。
  1. # 全球最近的概况


  2. # 按日期进行排序

  3. df_global_date = df_global.sort_values('date',ascending=False)


  4. # 获取最新的日期

  5. latest_day = df_global_date['dates'].values[0]


  6. # query 函数中,变量需要加 @ ,

  7. df_global_latest = df_global_date.query('dates==@latest_day')


  8. # 按累计确诊数量排序

  9. df_global_latest = df_global_latest.sort_values('confirmed',ascending=False)

  10. df_global_latest

可以看出,累计确诊人数,中国最多,但现存确诊人数,3月16日,是意大利最多了。

海外疫情情况

国外的疫情发展情况,大部分国家从2月10日起,发展趋势较为明显,因此,后面我们重点分析这段时间之后的情况。
  1. # 现有数据演示从 2020年2月10日开始

  2. df_oversea_recent = df_oversea.set_index('date')

  3. df_oversea_recent = df_oversea_recent['2020-02-10':]

  4. # df_oversea_recent

由于部分国家的数据不是从2020年2月10日开始记录的,所以要补充数据。我们可以手动新建一个 excel数据表,将补充日期的数值填充为 0 。
至于为什么要补充数据呢,因为如果没有补充的话,这个国家在绘制的图里就不会出来,有兴趣的同学可以测试下。
2月10日开始的数据,没有伊朗的,因为伊朗的数据是很靠前的所以必须纳入分析的范围内。其他国家,如果有需要补充的,后续可以继续完善。
补充的数据在 epidemic_buchong.xlsx 这个文件里。
  1. # 由于部分国家,数据不是从2020年2月10日开始的,所以要补充数据,数值为 0

  2. # 数据在 excel 表格中进行补充,这里进行读取


  3. df_oversea_buchong = pd.read_excel('epidemic_buchong.xlsx')

  4. df_oversea_buchong['dates'] = df_oversea_buchong['date'].apply(lambda x:x.strftime('%Y-%m-%d'))

  5. df_oversea_buchong.set_index('date', inplace=True)

  6. df_oversea_buchong.fillna(value="", inplace=True)

  7. print(df_oversea_buchong.info())

  8. # df_oversea_buchong

将需要补充的数据弄好后,我们可以合并上面这两部分数据,一起进行分析。
  1. # 合并补充数据


  2. df_oversea_recent_new = df_oversea_recent.append(df_oversea_buchong)


  3. df_oversea_recent_new.sort_index(inplace=True)


  4. # df_oversea_recent_new

对于上面的数据,有一点要说明下,在数据源中,日本的数据前期是包含了钻石公主号邮轮的数量的,后续是将钻石公主号的数据从日本单独列出来了,所以你会发现日本的数据有下降的现象。

动态排名的柱状图

得到合并的数据后,我们就可以来进行可视化了,之前 Lemonbit 已经展示了如何用动态气泡图和动态柱状图来可视化,但上次的柱状图没有实现动态排名这个功能,这次我们用动态排名的柱状图来可视化。
当需要对国家进行动态排名时,首先给每个国家定义一个确定的颜色,以便国家在上下移动的时候,每个国家的颜色保持不变。
  1. # 准备国家列表和 国家的颜色列表


  2. countries = list(df_oversea_recent_new['country'].unique())

  3. countries_count = len(countries) # 国家数量


  4. # 为每一个国家定义颜色

  5. color_1 = [px.colors.qualitative.Alphabet[i] for i in range(26)]

  6. color_2 = [px.colors.qualitative.Light24[i] for i in range(24)]

  7. color_3 = [px.colors.qualitative.Dark24[i] for i in range(24)]


  8. color_list = (color_1 + color_2 + color_3)*3 # 颜色数量,比国家数量大

  9. color_list = color_list[:countries_count] # 颜色数量跟国家数量一致


  10. print(f'国家数量:{countries_count}')

  11. print(f'国家颜色数量:{len(color_list)}')

截止3月16日,全球已经有 150多个国家和区域有疫情的数据了。
后续,还需要做一些数据上的准备,就是计算出海外单个国家累计确诊人数的最高值。以及,在原来的 DataFrame 中增加了颜色列,为每个国家指定一种颜色。
计算海外单个国家累计确诊人数的最高值
  1. # 海外单个国家死亡人数的最高值

  2. max_dead = df_oversea_recent_new['dead'].max()


  3. # 海外单个国家累计确诊人数的最高值

  4. max_confirmed = df_oversea_recent_new['confirmed'].max()


  5. print(f'海外单个国家累计确诊人数的最高值为{max_confirmed}')

  6. print(f'海外单个国家死亡人数的最高值为{max_dead}')

为每个国家指定一种颜色
  1. df = df_oversea_recent_new


  2. # df 添加颜色列,每个国家给定一种颜色

  3. for country,color in zip(countries, color_list):

  4. df.loc[df['country']==country, 'color'] = color


  5. # 保存数据到 csv文件

  6. # df.to_csv(f'epidemic_all_{today}_update.csv')

  7. # df

动态排名可视化

到这里,我们准备工作告一段落,可以来进行可视化了。下面的代码有些长,我们实现的是海外国家累计确诊人数排名前 20 的国家进行动态排名,这个是通过 pandas 的 nlargest 功能来实现的。
对于需要动态排名的柱状图,这里可视化用的是原生的 Plotly , 而不是 plotly.express 。
在下面代码中,核心的思路是,需要构造一个字典,以 key:value 的形式保存每天的 疫情数据,其中疫情数据是以 DataFrame 的形式保存于 value 中。
  1. dates_list = list(df['dates'].unique())

  2. dict_keys = [str(i) for i in range(len(dates_list))]


  3. n_frame={}


  4. for date, d in zip(dates_list, dict_keys):

  5. dataframe=df[df['dates']==date]

  6. # 排名前20的国家

  7. dataframe=dataframe.nlargest(n=20,columns=['confirmed'])

  8. dataframe=dataframe.sort_values(by=['dates','confirmed'])


  9. n_frame[d]=dataframe


  10. # print (n_frame)


  11. #-------------------------------------------

  12. fig = go.Figure(

  13. data=[

  14. go.Bar(

  15. x=n_frame['0']['confirmed'], y=n_frame['0']['country'],orientation='h',

  16. text=n_frame['0']['confirmed'], texttemplate='%{text:.3s}',

  17. textfont={'size':12}, textposition='inside', insidetextanchor='middle',

  18. width=0.9, marker={'color':n_frame['0']['color']})

  19. ],

  20. layout=go.Layout(

  21. xaxis=dict(range=[0, max_confirmed*1.1], autorange=False, title=dict(text='confirmed',

  22. font=dict(size=12))),

  23. yaxis=dict(range=[-0.5, 20.5], autorange=False,tickfont=dict(size=14)),

  24. title=dict(text='Confirmed numbers per Country: 2020-02-10',font=dict(size=28),x=0.5,xanchor='center'),

  25. # 添加按钮

  26. updatemenus=[dict(

  27. type="buttons",

  28. buttons=[dict(label="Play",

  29. method="animate",

  30. # https://github.com/plotly/plotly.js/blob/master/src/plots/animation_attributes.js

  31. args=[None,

  32. {"frame": {"duration": 1000, "redraw": True},

  33. "transition": {"duration":250,

  34. "easing": "linear"}}]

  35. )]

  36. )]

  37. ),

  38. frames=[

  39. go.Frame(

  40. data=[

  41. go.Bar(x=value['confirmed'], y=value['country'],

  42. orientation='h',text=value['confirmed'],

  43. marker={'color':value['color']})

  44. ],

  45. layout=go.Layout(

  46. xaxis=dict(range=[0, max_confirmed*1.1], autorange=False),

  47. yaxis=dict(range=[-0.5, 20.5], autorange=False,tickfont=dict(size=10)),

  48. title=dict(text='Confirmed numbers per Country: '+str(value['dates'].values[0]),

  49. font=dict(size=28))

  50. )

  51. )

  52. for key, value in n_frame.items()

  53. ]

  54. )


  55. #-------------------------------------------

  56. fig.show()

上面代码中,动态排名时通过每天对 go.Frame 中的数据进行更新来实现的。上面的 duration 可以来控制按钮点击后变化的速度。
运行上述代码后,得到的效果如下:

是不是很棒啊。
在文末,Lemonbit 给大家提供了本文完整的PDF版内容(含代码)以及数据文件。


但这里,通过运行效果,可以看出,由于现在的确诊数量较大,而刚开始的时候数据较小,所以当 x 固定不动时,动态化演示的效果不是太理想,我们希望 x 上的数据也能动态变化,这样效果会更理想。

要使 x 上的数据也能动态变化,实现文章开始图中的效果,核心是定义 x 的范围时,实现动态变化, x 的核心代码如下:

微信扫一扫付费阅读本文

可试读77%

微信扫一扫付费阅读本文

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存