近日,在笔者的微信群里,白垩老师问如何用Python画武汉肺炎疫情地图。白垩老师是研究海洋生态与地球生物的学者,国家重点实验室成员,于不惑之年学习Python,实为我等学习楷模。

先前我并没有关注武汉肺炎的具体数据,也没有画过类似的数据分布图。于是就拿了两个小时,专门研究了一下,遂成此文。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

如何用 Python 画出新型冠状病毒疫情地图? Python 第1张如何用 Python 画出新型冠状病毒疫情地图? Python 第2张

 

如何用 Python 画出新型冠状病毒疫情地图? Python 第3张如何用 Python 画出新型冠状病毒疫情地图? Python 第4张

数据下载

 

网上一搜,首先搜到的是腾讯的疫情实时追踪,那就用这个数据源吧。

如何用 Python 画出新型冠状病毒疫情地图? Python 第5张如何用 Python 画出新型冠状病毒疫情地图? Python 第6张

有了网址怎么抓数据呢?这里,我送大家一双火眼金睛,可以从纷乱中找到最靠谱的下载方式。我习惯用Firefox浏览器,下面的讲解就以Firefox为例(其他浏览器基本类似)。

打开菜单,点击“Web开发者”,在递进菜单中选择"网络":

如何用 Python 画出新型冠状病毒疫情地图? Python 第7张如何用 Python 画出新型冠状病毒疫情地图? Python 第8张

如何用 Python 画出新型冠状病毒疫情地图? Python 第9张如何用 Python 画出新型冠状病毒疫情地图? Python 第10张

刷新页面,我们很快就能发现,应答类型为JSON格式的这个请求,最有可能包含我们需要的数据了:

如何用 Python 画出新型冠状病毒疫情地图? Python 第11张如何用 Python 画出新型冠状病毒疫情地图? Python 第12张

如何用 Python 画出新型冠状病毒疫情地图? Python 第13张如何用 Python 画出新型冠状病毒疫情地图? Python 第14张

如何用 Python 画出新型冠状病毒疫情地图? Python 第15张如何用 Python 画出新型冠状病毒疫情地图? Python 第16张

如何用 Python 画出新型冠状病毒疫情地图? Python 第17张如何用 Python 画出新型冠状病毒疫情地图? Python 第18张

深入分析,我们就得到了URL地址、请求方法、参数、应答格式等信息。查询参数中,Callback是回调函数名,我们可以尝试置空,而“_”应该是以毫秒为单位的当前时间戳。

有了这些信息,分分钟就可以抓到数据了。我们先在IDLE中以交互方式抓一下看看效果:

 

>>> import time, json, requests
>>> url = 'https://view.inews.qq.com/g2/getOnsInfo?name=wuwei_ww_area_counts&callback=&_=%d'%int(time.time()*1000)
>>> data = json.loads(requests.get(url=url).json()['data'])
>>> print(len(data))
301
>>> print(data[0])
{'country': '中国', 'area': '湖北', 'city': '武汉', 'confirm': 698, 'suspect': 0, 'dead': 63, 'heal': 42}
>>> print(data[-1])
{'country': '中国', 'area': '山东', 'city': '枣庄', 'confirm': 2, 'suspect': 0, 'dead': 0, 'heal': 0}

只要两行代码,就可以抓到数据了。怎么样,是不是超级简单?

 

如何用 Python 画出新型冠状病毒疫情地图? Python 第19张如何用 Python 画出新型冠状病毒疫情地图? Python 第20张

数据处理

 

以省为单位画疫情图,我们只需要统计同属一个省的所有地市的确诊数据即可。最终的数据抓取代码如下:

 

import time, json, requests

def catch_distribution():
    """抓取行政区域确诊分布数据"""

    data = dict()
    url = 'https://view.inews.qq.com/g2/getOnsInfo?name=wuwei_ww_area_counts&callback=&_=%d'%int(time.time()*1000)
    for item in json.loads(requests.get(url=url).json()['data']):
        if item['area'] not in data:
            data.update({item['area']:0})
        data[item['area']] += int(item['confirm'])

    return data

 

如何用 Python 画出新型冠状病毒疫情地图? Python 第21张如何用 Python 画出新型冠状病毒疫情地图? Python 第22张

数据可视化

 

数据可视化,我习惯使用Matplotlib模块。Matplotlib有很多扩展工具包(Toolkits),比如,画3D需要mplot3d工具包,画地图的话,则需要Basemap工具包,以及处理地图投影的Pyproj模块。

另外画海陆分界线、国界线、行政分界线等还需要Shape数据。所需模块请自行安装,Shape文件可以从这里下载(https://github.com/dongli/china-shapefiles)。

绘图用到的矢量字库可以从自己的电脑上随便找一个(我用的是simsun.ttf)。我的主程序是2019nCoV.py,Shape文件下载下来之后,是这样保存的:

如何用 Python 画出新型冠状病毒疫情地图? Python 第23张如何用 Python 画出新型冠状病毒疫情地图? Python 第24张

如何用 Python 画出新型冠状病毒疫情地图? Python 第25张如何用 Python 画出新型冠状病毒疫情地图? Python 第26张

如何用 Python 画出新型冠状病毒疫情地图? Python 第27张如何用 Python 画出新型冠状病毒疫情地图? Python 第28张

以下为全部代码,除了疫情地图,还包括了全国每日武汉肺炎确诊数据的下载和可视化。

 

# -*- coding: utf-8 -*-

import time
import json
import requests
from datetime import datetime
import numpy as np
import matplotlib
import matplotlib.figure
from matplotlib.font_manager import FontProperties
from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

plt.rcParams['font.sans-serif'] = ['FangSong']  # 设置默认字体
plt.rcParams['axes.unicode_minus'] = False  # 解决保存图像时'-'显示为方块的问题

def catch_daily():
    """抓取每日确诊和死亡数据"""

    url = 'https://view.inews.qq.com/g2/getOnsInfo?name=wuwei_ww_cn_day_counts&callback=&_=%d'%int(time.time()*1000)
    data = json.loads(requests.get(url=url).json()['data'])
    data.sort(key=lambda x:x['date'])

    date_list = list() # 日期
    confirm_list = list() # 确诊
    suspect_list = list() # 疑似
    dead_list = list() # 死亡
    heal_list = list() # 治愈
    for item in data:
        month, day = item['date'].split('.')
        date_list.append(datetime.strptime('2020-%s-%s'%(month, day), '%Y-%m-%d'))
        confirm_list.append(int(item['confirm']))
        suspect_list.append(int(item['suspect']))
        dead_list.append(int(item['dead']))
        heal_list.append(int(item['heal']))

    return date_list, confirm_list, suspect_list, dead_list, heal_list

def catch_distribution():
    """抓取行政区域确诊分布数据"""

    data = {'西藏':0}
    url = 'https://view.inews.qq.com/g2/getOnsInfo?name=wuwei_ww_area_counts&callback=&_=%d'%int(time.time()*1000)
    for item in json.loads(requests.get(url=url).json()['data']):
        if item['area'] not in data:
            data.update({item['area']:0})
        data[item['area']] += int(item['confirm'])

    return data

def plot_daily():
    """绘制每日确诊和死亡数据"""

    date_list, confirm_list, suspect_list, dead_list, heal_list = catch_daily() # 获取数据

    plt.figure('2019-nCoV疫情统计图表', facecolor='#f4f4f4', figsize=(10, 8))
    plt.title('2019-nCoV疫情曲线', fontsize=20)

    plt.plot(date_list, confirm_list, label='确诊')
    plt.plot(date_list, suspect_list, label='疑似')
    plt.plot(date_list, dead_list, label='死亡')
    plt.plot(date_list, heal_list, label='治愈')

    plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m-%d')) # 格式化时间轴标注
    plt.gcf().autofmt_xdate() # 优化标注(自动倾斜)
    plt.grid(linestyle=':') # 显示网格
    plt.legend(loc='best') # 显示图例
    plt.savefig('2019-nCoV疫情曲线.png') # 保存为文件
    #plt.show()

def plot_distribution():
    """绘制行政区域确诊分布数据"""

    data = catch_distribution()

    font = FontProperties(fname='res/simsun.ttf', size=14)
    lat_min = 0
    lat_max = 60
    lon_min = 70
    lon_max = 140

    handles = [
            matplotlib.patches.Patch(color='#ffaa85', alpha=1, linewidth=0),
            matplotlib.patches.Patch(color='#ff7b69', alpha=1, linewidth=0),
            matplotlib.patches.Patch(color='#bf2121', alpha=1, linewidth=0),
            matplotlib.patches.Patch(color='#7f1818', alpha=1, linewidth=0),
]
    labels = [ '1-9人', '10-99人', '100-999人', '>1000人']

    fig = matplotlib.figure.Figure()
    fig.set_size_inches(10, 8) # 设置绘图板尺寸
    axes = fig.add_axes((0.1, 0.12, 0.8, 0.8)) # rect = l,b,w,h
    m = Basemap(llcrnrlon=lon_min, urcrnrlon=lon_max, llcrnrlat=lat_min, urcrnrlat=lat_max, resolution='l', ax=axes)
    m.readshapefile('res/china-shapefiles-master/china', 'province', drawbounds=True)
    m.readshapefile('res/china-shapefiles-master/china_nine_dotted_line', 'section', drawbounds=True)
    m.drawcoastlines(color='black') # 洲际线
    m.drawcountries(color='black')  # 国界线
    m.drawparallels(np.arange(lat_min,lat_max,10), labels=[1,0,0,0]) #画经度线
    m.drawmeridians(np.arange(lon_min,lon_max,10), labels=[0,0,0,1]) #画纬度线

    for info, shape in zip(m.province_info, m.province):
        pname = info['OWNER'].strip('\x00')
        fcname = info['FCNAME'].strip('\x00')
        if pname != fcname: # 不绘制海岛
            continue

        for key in data.keys():
            if key in pname:
                if data[key] == 0:
                    color = '#f0f0f0'
                elif data[key] < 10:
                    color = '#ffaa85'
                elif data[key] <100:
                    color = '#ff7b69'
                elif  data[key] < 1000:
                    color = '#bf2121'
                else:
                    color = '#7f1818'
                break

        poly = Polygon(shape, facecolor=color, edgecolor=color)
        axes.add_patch(poly)

    axes.legend(handles, labels, bbox_to_anchor=(0.5, -0.11), loc='lower center', ncol=4, prop=font)
    axes.set_title("2019-nCoV疫情地图", fontproperties=font)
    FigureCanvasAgg(fig)
    fig.savefig('2019-nCoV疫情地图.png')

if __name__ == '__main__':
    plot_daily()
    plot_distribution()

2019-nCoV疫情曲线:

如何用 Python 画出新型冠状病毒疫情地图? Python 第29张如何用 Python 画出新型冠状病毒疫情地图? Python 第30张

2019-nCoV疫情地图:

如何用 Python 画出新型冠状病毒疫情地图? Python 第31张如何用 Python 画出新型冠状病毒疫情地图? Python 第32张

上图为圆柱投影,这也是Basemap默认的投影模式,我们还可以换用其他投影模式,比如兰勃托等角投影,只需要将97行代码改为:

 

m = Basemap(projection='lcc', width=5000000, height=5000000, lat_0=36, lon_0=102, resolution='l', ax=axes)

兰勃托投影效果如下:

如何用 Python 画出新型冠状病毒疫情地图? Python 第33张如何用 Python 画出新型冠状病毒疫情地图? Python 第34张

还可以使用正射投影:

 

m = Basemap(projection='ortho', lat_0=30, lon_0=105, resolution='l', ax=axes)

正射投影效果如下:

如何用 Python 画出新型冠状病毒疫情地图? Python 第35张如何用 Python 画出新型冠状病毒疫情地图? Python 第36张

近日,在笔者的微信群里,白垩老师问如何用Python画武汉肺炎疫情地图。白垩老师是研究海洋生态与地球生物的学者,国家重点实验室成员,于不惑之年学习Python,实为我等学习楷模。先前我并没有关注武汉肺炎的具体数据,也没有画过类似的数据分布图。于是就拿了两个小时,专门研究了一下,遂成此文。
如何用 Python 画出新型冠状病毒疫情地图? Python 第37张数据下载

网上一搜,首先搜到的是腾讯的疫情实时追踪,那就用这个数据源吧。如何用 Python 画出新型冠状病毒疫情地图? Python 第38张有了网址怎么抓数据呢?这里,我送大家一双火眼金睛,可以从纷乱中找到最靠谱的下载方式。我习惯用Firefox浏览器,下面的讲解就以Firefox为例(其他浏览器基本类似)。打开菜单,点击“Web开发者”,在递进菜单中选择"网络":如何用 Python 画出新型冠状病毒疫情地图? Python 第39张如何用 Python 画出新型冠状病毒疫情地图? Python 第40张刷新页面,我们很快就能发现,应答类型为JSON格式的这个请求,最有可能包含我们需要的数据了:如何用 Python 画出新型冠状病毒疫情地图? Python 第41张如何用 Python 画出新型冠状病毒疫情地图? Python 第42张如何用 Python 画出新型冠状病毒疫情地图? Python 第43张如何用 Python 画出新型冠状病毒疫情地图? Python 第44张深入分析,我们就得到了URL地址、请求方法、参数、应答格式等信息。查询参数中,Callback是回调函数名,我们可以尝试置空,而“_”应该是以毫秒为单位的当前时间戳。有了这些信息,分分钟就可以抓到数据了。我们先在IDLE中以交互方式抓一下看看效果:

>>> import time, json, requests
>>> url = 'https://view.inews.qq.com/g2/getOnsInfo?name=wuwei_ww_area_counts&callback=&_=%d'%int(time.time()*1000)
>>> data = json.loads(requests.get(url=url).json()['data'])
>>> print(len(data))
301
>>> print(data[0])
{'country''中国''area''湖北''city''武汉''confirm'698'suspect'0'dead'63'heal'42}
>>> print(data[-1])
{'country''中国''area''山东''city''枣庄''confirm'2'suspect'0'dead'0'heal'0}

只要两行代码,就可以抓到数据了。怎么样,是不是超级简单?


如何用 Python 画出新型冠状病毒疫情地图? Python 第45张数据处理
以省为单位画疫情图,我们只需要统计同属一个省的所有地市的确诊数据即可。最终的数据抓取代码如下:

import time, json, requests

def catch_distribution():
    """抓取行政区域确诊分布数据"""

    data = dict()
    url = 'https://view.inews.qq.com/g2/getOnsInfo?name=wuwei_ww_area_counts&callback=&_=%d'%int(time.time()*1000)
    for item in json.loads(requests.get(url=url).json()['data']):
        if item['area'not in data:
            data.update({item['area']:0})
        data[item['area']] += int(item['confirm'])

    return data

 

如何用 Python 画出新型冠状病毒疫情地图? Python 第46张数据可视化

数据可视化,我习惯使用Matplotlib模块。Matplotlib有很多扩展工具包(Toolkits),比如,画3D需要mplot3d工具包,画地图的话,则需要Basemap工具包,以及处理地图投影的Pyproj模块。另外画海陆分界线、国界线、行政分界线等还需要Shape数据。所需模块请自行安装,Shape文件可以从这里下载(https://github.com/dongli/china-shapefiles)。绘图用到的矢量字库可以从自己的电脑上随便找一个(我用的是simsun.ttf)。我的主程序是2019nCoV.py,Shape文件下载下来之后,是这样保存的:如何用 Python 画出新型冠状病毒疫情地图? Python 第47张如何用 Python 画出新型冠状病毒疫情地图? Python 第48张如何用 Python 画出新型冠状病毒疫情地图? Python 第49张以下为全部代码,除了疫情地图,还包括了全国每日武汉肺炎确诊数据的下载和可视化。

# -*- coding: utf-8 -*-

import time
import json
import requests
from datetime import datetime
import numpy as np
import matplotlib
import matplotlib.figure
from matplotlib.font_manager import FontProperties
from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

plt.rcParams['font.sans-serif'] = ['FangSong']  # 设置默认字体
plt.rcParams['axes.unicode_minus'] = False  # 解决保存图像时'-'显示为方块的问题

def catch_daily():
    """抓取每日确诊和死亡数据"""

    url = 'https://view.inews.qq.com/g2/getOnsInfo?name=wuwei_ww_cn_day_counts&callback=&_=%d'%int(time.time()*1000)
    data = json.loads(requests.get(url=url).json()['data'])
    data.sort(key=lambda x:x['date'])

    date_list = list() # 日期
    confirm_list = list() # 确诊
    suspect_list = list() # 疑似
    dead_list = list() # 死亡
    heal_list = list() # 治愈
    for item in data:
        month, day = item['date'].split('.')
        date_list.append(datetime.strptime('2020-%s-%s'%(month, day), '%Y-%m-%d'))
        confirm_list.append(int(item['confirm']))
        suspect_list.append(int(item['suspect']))
        dead_list.append(int(item['dead']))
        heal_list.append(int(item['heal']))

    return date_list, confirm_list, suspect_list, dead_list, heal_list

def catch_distribution():
    """抓取行政区域确诊分布数据"""

    data = {'西藏':0}
    url = 'https://view.inews.qq.com/g2/getOnsInfo?name=wuwei_ww_area_counts&callback=&_=%d'%int(time.time()*1000)
    for item in json.loads(requests.get(url=url).json()['data']):
        if item['area'not in data:
            data.update({item['area']:0})
        data[item['area']] += int(item['confirm'])

    return data

def plot_daily():
    """绘制每日确诊和死亡数据"""

    date_list, confirm_list, suspect_list, dead_list, heal_list = catch_daily() # 获取数据

    plt.figure('2019-nCoV疫情统计图表', facecolor='#f4f4f4', figsize=(108))
    plt.title('2019-nCoV疫情曲线', fontsize=20)

    plt.plot(date_list, confirm_list, label='确诊')
    plt.plot(date_list, suspect_list, label='疑似')
    plt.plot(date_list, dead_list, label='死亡')
    plt.plot(date_list, heal_list, label='治愈')

    plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m-%d')) # 格式化时间轴标注
    plt.gcf().autofmt_xdate() # 优化标注(自动倾斜)
    plt.grid(linestyle=':'# 显示网格
    plt.legend(loc='best'# 显示图例
    plt.savefig('2019-nCoV疫情曲线.png'# 保存为文件
    #plt.show()

def plot_distribution():
    """绘制行政区域确诊分布数据"""

    data = catch_distribution()

    font = FontProperties(fname='res/simsun.ttf', size=14)
    lat_min = 0
    lat_max = 60
    lon_min = 70
    lon_max = 140

    handles = [
            matplotlib.patches.Patch(color='#ffaa85', alpha=1, linewidth=0),
            matplotlib.patches.Patch(color='#ff7b69', alpha=1, linewidth=0),
            matplotlib.patches.Patch(color='#bf2121', alpha=1, linewidth=0),
            matplotlib.patches.Patch(color='#7f1818', alpha=1, linewidth=0),
]
    labels = [ '1-9人''10-99人''100-999人''>1000人']

    fig = matplotlib.figure.Figure()
    fig.set_size_inches(108# 设置绘图板尺寸
    axes = fig.add_axes((0.10.120.80.8)) # rect = l,b,w,h
    m = Basemap(llcrnrlon=lon_min, urcrnrlon=lon_max, llcrnrlat=lat_min, urcrnrlat=lat_max, resolution='l', ax=axes)
    m.readshapefile('res/china-shapefiles-master/china''province', drawbounds=True)
    m.readshapefile('res/china-shapefiles-master/china_nine_dotted_line''section', drawbounds=True)
    m.drawcoastlines(color='black'# 洲际线
    m.drawcountries(color='black')  # 国界线
    m.drawparallels(np.arange(lat_min,lat_max,10), labels=[1,0,0,0]) #画经度线
    m.drawmeridians(np.arange(lon_min,lon_max,10), labels=[0,0,0,1]) #画纬度线

    for info, shape in zip(m.province_info, m.province):
        pname = info['OWNER'].strip('\x00')
        fcname = info['FCNAME'].strip('\x00')
        if pname != fcname: # 不绘制海岛
            continue

        for key in data.keys():
            if key in pname:
                if data[key] == 0:
                    color = '#f0f0f0'
                elif data[key] < 10:
                    color = '#ffaa85'
                elif data[key] <100:
                    color = '#ff7b69'
                elif  data[key] < 1000:
                    color = '#bf2121'
                else:
                    color = '#7f1818'
                break

        poly = Polygon(shape, facecolor=color, edgecolor=color)
        axes.add_patch(poly)

    axes.legend(handles, labels, bbox_to_anchor=(0.5-0.11), loc='lower center', ncol=4, prop=font)
    axes.set_title("2019-nCoV疫情地图", fontproperties=font)
    FigureCanvasAgg(fig)
    fig.savefig('2019-nCoV疫情地图.png')

if __name__ == '__main__':
    plot_daily()
    plot_distribution()

2019-nCoV疫情曲线

如何用 Python 画出新型冠状病毒疫情地图? Python 第50张2019-nCoV疫情地图:如何用 Python 画出新型冠状病毒疫情地图? Python 第51张上图为圆柱投影,这也是Basemap默认的投影模式,我们还可以换用其他投影模式,比如兰勃托等角投影,只需要将97行代码改为:

m = Basemap(projection='lcc', width=5000000, height=5000000, lat_0=36, lon_0=102, resolution='l', ax=axes)

兰勃托投影效果如下

如何用 Python 画出新型冠状病毒疫情地图? Python 第52张还可以使用正射投影:

m = Basemap(projection='ortho', lat_0=30, lon_0=105, resolution='l', ax=axes)

正射投影效果如下:

如何用 Python 画出新型冠状病毒疫情地图? Python 第53张

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄