plotly 新型冠状病毒确诊人数动态跑图top15

Flourish 动态跑图

前段时间各国GDP以及各国新冠确诊人数动态条形排行榜跑图在微博火了一把,这些图都是由在线网站Flourish生成的,无需写代码,只需导入官方指定的数据格式便可以生成这种炫酷的跑图。


image.png

而要生成这样的视频需要将数据publish到Fourish官网上,所以对于公开的数据是免费的,私有数据需要收费。


image.png

当然如果我们要写代码制作这样的动态跑图应该怎么做的,毕竟并不是所有的数据都是需要公开的。其实这种图就是水平bar图,加上动画的效果,然后标签之类的随之改动。接下来我们来学学如何使用plotly python 包来生成这种类似的动态跑图。

Plotly

plotly Python库(plotly.py)是一个交互式的开源绘图库,支持40多种独特的图表类型,涵盖各种统计,财务,地理,科学和3维用例,是适用于Python,R和JavaScript的交互式图表。
plotly.py建立在Plotly JavaScript库(plotly.js)之上,使Python用户可以创建基于Web的漂亮的交互式可视化效果,这些可视化效果可以显示在Jupyter笔记本中,可以保存到独立的HTML文件中,也可以作为纯Python-使用Dash构建Web应用程序。Plotly包提供强大的数据可视化功能,官方文档上提供各种各样的图表库和接口。
首先我们需要弄懂plotly 图的结构。可参考官方API网址,还有官方示例代码

  1. Figure 是一张画布,跟matplotlib的figure是一样,数据是字典形式。
  2. Traces 轨迹,即所有的图表层都是在这里画的,轨迹的类型都是由type指定的(例如"bar","scatter","contour"等等)。轨迹是列表。
  3. Layout 层,设置标题,排版,页边距,轴,注释,形状,legend图例等等。布局配置选项适用于整个图形。
  4. Frames 帧幅轨迹,是在Animate中用到的渲染层,即每多少帧幅动画遍历的轨迹,与traces很像,都是列表形式,使用时需要在layout的updatemenus设置帧幅间隔等等。具体用法可以去看看官方文档用法。

画图

step1: 在画图之前我们首先需要获取数据,这里的数据是从约翰霍普金斯大学获取的。

image.png

step2: 对数据进行处理,对每个国家的每日确诊总数进行统计,再计算每日全球确诊人数。然后对每日确诊人数国家进行排序,将确诊人数最多的前15列提取出来,这边需要对每个国家进行条形图的颜色填充,所以需要记录相应的国家及其对应的颜色。核心代码如下:

def gen_data():
    cov_data = pd.read_csv('time_series_covid19_confirmed_global.csv')
    # 删除['Lat', 'Long']列
    cov_data = cov_data.drop(['Province/State', 'Lat', 'Long'], axis=1)
    # 对每个国家每日总数进行统计
    cov_data = cov_data.groupby(['Country/Region']).sum()
    # 计算每日全球总确诊人数
    cov_data.loc['Row_sum'] = cov_data.apply(lambda x: x.sum())
    all_top_countries = {}
    for i in cov_data.columns:
        #  统计每日总数前15的国家
        data_i = cov_data.sort_values(by=i, ascending=False)
        data_i = data_i[i]
        all_top_countries[i] = {'country': data_i.index.tolist()[1:number], 'confirmed': data_i.tolist()[1:number], 'total':data_i.tolist()[0]}

    return all_top_countries

接下来对这些已经排除顺序的top15确诊人数的国家添加颜色,核心代码如下:

def gen_color(country_data):
    color_country = {}
    for date, country in country_data.items():
        for _ in country['country']:
            if _ not in color_country:
                color_country[_] = f'rgb({np.random.randint(0,256)}, {np.random.randint(0,256)}, {np.random.randint(0,256)})'

    return color_country

step3: 数据已全部准备好开始画图

首先先建立一张figure,开始状态就是第一列日期这15个国家的确诊人数。
用水平的bar图来画轨迹,并且在条形图右侧标注国家和确诊人数text。layout 设置画图的标题和xy轴的属性,由于需要画动画图,所以必须再updatemenus设置帧幅可添加play和stop按钮来控制动画的开始和暂停。可在layout中设置一个sliders滚动条,可现实日期滚动。
接下来就是要遍历上面两个方法生成的数据和颜色来生成相对应的frames 和sliders的step。注意动画中的frames其实就是一个不包含frame的figure,里面包含trace轨迹层, layout层,在动画过程,frame中的轨迹和layout会覆盖一开始figure中设置好traces和layout。
核心代码如下:

def plot_animate(dict_data, color_country):
    first_record = dict_data[list(dict_data.keys())[0]]
    figure = {
      'data': [{
        'type': 'bar',
        'x':  first_record['confirmed'],
        'y':  first_record['country'],
        'orientation': 'h',  # 设置水平
        'width': 0.7,
        'text': [i + ', ' + str(j) for i, j in zip(first_record['country'], first_record['confirmed'])],
        'textfont': {
            'family': 'Arial',
        },
        'textposition': 'outside',
        'marker': {
              'color': [color_country[i] for i in first_record['country']],
          }
      }],
      'layout': {
        "autosize": True,
        "height": 880,
        'xaxis': {
          'gridcolor': '#FFFFFF',
          'linecolor': '#000',
          'linewidth': 1,
          'zeroline': False,
          'side': 'top',
        },
        'yaxis': {
          'gridcolor': '#FFFFFF',
          'linecolor': '#000',
          'linewidth': 1,
          'autorange': 'reversed'  # Y 轴倒置
        },
        'title': 'Global Countries Confirmed top {} (Data updated once a week) '.format(number),
        'hovermode': 'closest',
        'template': "plotly_white",
        'updatemenus': [{
          "type": "buttons",
          'buttons': [{
              'label': 'Play',
              'method': 'animate',
              'args': [None, {
                'frame': {
                  'duration': duration,
                  'redraw': True
                },
                'fromcurrent': True,
                "mode": "immediate",
                'transition': {
                  'duration': duration,
                  'easing':'quadratic-in-out' # 'easeOutQuad'  #'easeOutSine' # 'easeInSine' #'quadratic-in-out'
                }
              }]
            },
            {
              'label': 'Stop',
              'method': 'animate',
              "args": [[None],
                         {"frame": {"duration": 0, "redraw": False},
                          "mode": "immediate",
                          "transition": {"duration": 0}}]
            }
          ],
          'direction': 'left',
          'pad': {
            'r': 20,
            't': 87
          },
          'showactive': False,
          'x': 0.05,
          'xanchor': 'right',
          'y': 0,
          'yanchor': 'top'
        }],
        'sliders': []
      },
      'frames': [],
    }
    sliders_dict = {
        "active": 0,
        "yanchor": "top",
        "xanchor": "left",
        "currentvalue": {
            "font": {"size": 20},
            "prefix": "Date:",
            "visible": True,
            "xanchor": "left",
        },
        "transition": {"duration": duration, "easing": "cubic-in-out"},
        "pad": {"b": 10, "t": 50},
        "len": 0.9,
        "x": 0.05,
        "y": 0,
        "steps": []
    }
    for date, date_data in dict_data.items():
        frame = {
            'data': [{
                'type': 'bar',
                'x': date_data['confirmed'],
                'y': date_data['country'],
                'text':  [i + ', ' + str(j) for i, j in zip(date_data['country'], date_data['confirmed'])],
                'marker': {
                    'color': [color_country[i] for i in date_data['country']],
                },
                'textfont': {
                    'family': 'Arial',
                    'size': 10
                },
            }],
            'name': str(date),
            'layout': {
                'title': 'Global Countries Confirmed top {} (Data updated once a week) '.format(number) + '<br> total:'+ str(date_data['total']) ,
            }
        }
        figure['frames'].append(frame)
        slider_step = {
            'args': [
                [date],
                {
                    'frame': {
                        'duration': duration,
                        'redraw': True
                    },
                    'mode': 'immediate',
                    'transition': {
                        'duration': duration
                    }
                }
            ],
            'label': date,
            'method': 'animate'
        }

        sliders_dict['steps'].append(slider_step)

    figure['layout']['sliders'] = [sliders_dict]
    # To display the figure defined by this dict, use the low-level plotly.io.show function
    pio.show(figure)

最后出来的效果图如下:


image.png
image.png

最后附上完整代码githu地址:
https://github.com/LizzieDeng/covid_19_animation

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269