2020年有个很魔幻的开局,从一只蝙蝠开始,迎来了不弱于03年非典的新冠状病毒。这一次疫情期间,全国上下减少外出,团结一心,共同抗疫,取得了奇迹般的成就。
疫情期间,信息的及时公布显得尤为重要。正是政府不断公布无死角的发布信息,分析解读,才使得公众对疫情的了解不断深入,对疫情状况有充分了解,并将恐惧与不安降到最低。
信息的及时公布很重要,但是如何把信息直观的展现出来也很重要,大量的数据涌来时,可视化无疑是最好的选择。
本文将记录我如何借助Python来完成一张疫情期间数据可视化的地图。
契机
停课不停学期间,作为计算机专业的大学生,自然是通过互联网与老师联系并进行新知识的学习。而在我们的GIS课程上,老师结合实际提出了制作疫情地图的作业要求。
为了完成作业,我开始了此次的制作。
过程
准备
制作疫情地图的话,很容易想到的就是一张静态地图,不同的区域因感染人数的多少而配以不同的颜色,制作起来就非常简单。但实际上,这样的工作一次还好,不适合大量重复实践,而且做出的图交互性比较差。
那么与之相对的,需要有一个可以帮我省去这个大量重复的工具,就自然而然的想到了程序设计,尤其的,python程序设计。
python语言作为一门热门语言,有着大量优秀的第三方库,且语法简单,开发迅速,因此自然成为了我此次不二的选择。
那么,接下来是选择一个合适的第三方库以提供数据的载体地图,在经过众多比较后,我选择了pyecharts。pyecharts是基于echart的,封装成python的函数,并支持渲染成网页,比较利于制作完成后的展示。由于是国产的作品,所以在地图的使用上便可以放轻松。
有了载体后,数据从何而来呢?有个不错的选择,github上有一个更新及时的数据库CSSEGISandData,维护者是疫情期间知名可视化网站COVID-19 Dashboard by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University (JHU)的作者,通过爬虫来及时获取数据,格式良好,只需要clone下来即可,后续通过pull进行更新,可以免去爬虫的编写,是非常理想的数据源。
于是我选择了FlyAI的数据源。其实相较而言,FlyAI的数据并没有非常大的优势,使用起来也不如github上的数据库方便。之所以选择FlyAI纯粹是因为我想起github时疫情地图已经制作完成了emm。
我选取了CSSEGISandData的数据作为数据来源(鬼知道交作业前一晚上发现FlyAI突然不提供数据后我心里是何等的卧槽),这样后续的更新可以通过命令行随时pull获取,比较方便。
准备阶段还差一样工具,获取到的数据需要经过处理才能使用,需要一款数据处理的工具,我的选择是numpy与pandas,pandas是python著名的数据处理库,经常与numpy联合使用,用途广泛,它也将在本次的疫情地图制作中发挥出重要的作用。
开始
通过venv创建好虚拟环境,再使用pip安装pyecharts和pandas。
1 2 3
| python3 -m venv GIS_venv source GIS_venv/bin/activate pip3 install pyecharts pydata
|
pyecharts的API封装十分友好,把所有的图表封装成了类,并且支持链式调用,调用起来几乎只是填写各个配置项而已。
比如说对于一个地图图表,只要在生成一个实例时填写各个配置项(参数)即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| map_chart = ( Map() .add( series_name="", data_pair=map_data, zoom=0.85, center=[119.5, 34.5], is_map_symbol_show=False, itemstyle_opts={ "normal": {"areaColor": "#323c48", "borderColor": "#404a59"}, "emphasis": { "label": {"show": Timeline}, "areaColor": "rgba(255,255,255, 0.5)", }, }, ) )
|
最后再通过map_chart.render('index.html')
进行渲染,输出一个html文件,再通过浏览器就可以进行查看。
而在本次的实际制作过程中,我没有从零开始一点点构建整个的图表,一是初次接触pyecharts,使用并不熟练,另外是希望尽快完成,于是我参考了官方的画廊,选取了一个示例china_gdp_from_1993_to_2018进行修改。
示例中是将数据写死在了代码中,这自然是为了展示的方便,但是实际上,数据必定是单独分离开来的。
首次获取数据的话需要把git库给clone下来
1
| git clone https://github.com/CSSEGISandData/COVID-19.git
|
后续想要更新的话就pull一下
1 2
| cd COVID-19/ git pull https://github.com/CSSEGISandData/COVID-19.git
|
从CSSEGISandData中获取到的数据格式是每日各市级的数量
需要做的基本处理是筛选出中国的数据,并按照省进行分组求和,求出每天各省的三项数据,只保留日期,省份,确诊,治愈,死亡这些表项,删除其它无用数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| def __get_provinces_base_data(): global BASE_DATA if (not isinstance(BASE_DATA, pd.DataFrame)): Country = 'Country/Region' Province = 'Province/State' China = 'Mainland China' Hong_Kong = 'Hong Kong' Taiwan = 'Taiwan' Macau = 'Macau' Date = 'Last Update' Recovered = 'Recovered' Confirmed = 'Confirmed' Deaths = 'Deaths'
Province_Map = {'Hubei': '湖北', 'Zhejiang': '浙江', 'Guangdong': '广东', 'Henan': '河南', ...}
Data_dir = '/COVID-19/csse_covid_19_data/csse_covid_19_daily_reports/'
date = datetime.date(2020, 1, 22)
df = pd.DataFrame( {DATE: {}, PROVINCE: {}, CURED: {}, CONFIRMED: {}, DEAD: {}}) df = pd.DataFrame() while date < datetime.date.today()+datetime.timedelta(days=1): Data_path = os.getcwd() + Data_dir + date.strftime('%m-%d-%Y') + '.csv' print('cope with data comes from '+Data_path) if not os.path.exists(Data_path): print( '请通过\n \'cd COVID-19/\'\n \'git pull https://github.com/CSSEGISandData/COVID-19.git\'\n更新数据!') break if (date == datetime.date(2020, 3, 22)): Country = 'Country_Region' Province = 'Province_State' Date = 'Last_Update' if (date == datetime.date(2020, 3, 11)): China = 'China' d = pd.read_csv(Data_path) d = d[d[Country].isin( [China, Hong_Kong, Taiwan, Macau])] d = d.loc[:, [Date, Province, Recovered, Confirmed, Deaths]] d = d.fillna(value=0.0) for key in Province_Map.keys(): d.loc[d[Province] == key, Province] = Province_Map[key] d = d.rename(columns={Date: DATE, Province: PROVINCE, Recovered: CURED, Confirmed: CONFIRMED, Deaths: DEAD}) d[DATE] = date.strftime('%Y-%m-%d') d = d.set_index(PROVINCE) df = pd.concat([df, d]) date = date + datetime.timedelta(days=1) df[[CURED, CONFIRMED, DEAD]] = df[[ CURED, CONFIRMED, DEAD]].astype(float) BASE_DATA = df else: df = BASE_DATA return df
|
需要吐槽的是,因为数据来自国外整理,所以有时会对中国的一些省份做地区性质的描述。而且数据中中国的名称,以及表格的表项发生过变化,最终就导致需要在意的细节比较多。为了可以在pyecharts中使用,我还需要把英文的城市名称翻译成对应的中文。
二次加工成适合与图表的数据格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| def get_provinces_sum_data(column: str): df = __get_provinces_base_data()
dates = get_date_list() provinces = get_provinces_list()
df = [df.groupby(DATE).get_group(date) for date in dates]
data = [{i: item.at[i, column] for i in item.index} for item in df]
for item in data: for province in provinces: if province not in item: item[province] = 0.0
return data
|
使用数据完成地图的构建:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| provinces_daily_sick_data = getData.get_provinces_sick_daily_data()
date_data = provinces_daily_sick_data[date_index]
map_data = [[key, date_data[key]] for key in date_data.keys()]
map_chart = ( Map() .add( series_name="", data_pair=map_data, zoom=0.85, center=[119.5, 34.5], is_map_symbol_show=False, itemstyle_opts={ "normal": {"areaColor": "#323c48", "borderColor": "#404a59"}, "emphasis": { "label": {"show": Timeline}, "areaColor": "rgba(255,255,255, 0.5)", }, }, ) )
|
所有代码我都上传到了Github中:COVID-map
效果
类似的思路完成其它图表的数据填充,得到的最终效果:
因为渲染结果是一个HTML文件,于是我将它发布在了我的博客中:新冠肺炎疫情地图
思考
在最开始的时候,我有想过把疫情地图做成实时的,但是实施起来后发现会有些难度。如果基于pyecharts进行实时修改,那么就需要将修改程序放置在服务器上,而且博客本身又是基于hexo的,需要在服务器端与本地同步,这点倒是可以通过git实现;除此之外也许可以通过github的action进行自动更新,但是都过于麻烦,考虑到自身时间并不富裕,我最终决定只完成一定时间之前的部分,但为实时更新打下基础。
事实上,借由发布到博客上来展示最终成果的契机,我得以重新完成博客环境的搭建。在此之前,因为一些缘由,自闭了相当长时间,而且博客环境也在那个时候收到了损坏。因此,算是重新开始写博客吧,如果时间充分的话,会慢慢写些这段时间的成就。
写在最后的,是和新冠病毒疫情有关。这点我特别赞同这节课老师的一个看法,我所制作的可视化作品,炫酷本身并不值得炫耀(况且也说不上效果特别好),每个展示出来的数字,都有着生命般沉甸甸的重量,这不是一个可以拿来开玩笑的事情。正因如此,本篇文章进行了灰白处理,用以缅怀在这场战疫中逝去的人们。
愿逝者安息,生者坚强。