Flourish 动态跑图
前段时间各国GDP以及各国新冠确诊人数动态条形排行榜跑图在微博火了一把,这些图都是由在线网站Flourish生成的,无需写代码,只需导入官方指定的数据格式便可以生成这种炫酷的跑图。
而要生成这样的视频需要将数据publish到Fourish官网上,所以对于公开的数据是免费的,私有数据需要收费。
当然如果我们要写代码制作这样的动态跑图应该怎么做的,毕竟并不是所有的数据都是需要公开的。其实这种图就是水平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网址,还有官方示例代码。
- Figure 是一张画布,跟matplotlib的figure是一样,数据是字典形式。
- Traces 轨迹,即所有的图表层都是在这里画的,轨迹的类型都是由type指定的(例如"bar","scatter","contour"等等)。轨迹是列表。
- Layout 层,设置标题,排版,页边距,轴,注释,形状,legend图例等等。布局配置选项适用于整个图形。
- Frames 帧幅轨迹,是在Animate中用到的渲染层,即每多少帧幅动画遍历的轨迹,与traces很像,都是列表形式,使用时需要在layout的updatemenus设置帧幅间隔等等。具体用法可以去看看官方文档用法。
画图
step1: 在画图之前我们首先需要获取数据,这里的数据是从约翰霍普金斯大学获取的。
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) + '
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)
最后出来的效果图如下:
最后附上完整代码githu地址:
https://github.com/LizzieDeng/covid_19_animation