1. 程式人生 > >用Python繪製全球疫情變化地圖

用Python繪製全球疫情變化地圖

目前全球疫情仍然比較嚴重,為了能清晰地看到疫情爆發以來至現在全球疫情的變化趨勢,我繪製了一張疫情變化地圖,完整程式碼共 230 行,需要的朋友在公眾號回覆關鍵字 疫情地圖 即可。 廢話不多說,先上圖

下面就來重點介紹下上面這張圖的繪製過程,主要分為以下三個步驟:

  • 資料收集

  • 資料處理

  • 畫圖

下面一個一個來說。
資料收集 

這是萬里長城的第一步,俗話說“巧婦難為無米之炊”,既然是變化圖,當然需要每個國家、每天的現有確診病例數。好在現在各大網站都有疫情相關的專題頁,我們可以直接抓資料。以網易為例

 

 

 

我們選擇 XHR,重新重新整理下網頁可以看到有幾個介面,其中 list-total 介面是獲取當前所有有疫情的國家,以及對應的國家id。另外,我們看到還有一個 list-by-area-code 介面,它是獲取每個國家歷史上每天的疫情資料,請求這個介面需要帶 areaCode 引數,這個引數就是我們剛剛說的國家id。所以對我們來說這兩個介面是最重要的。下面我們就看看請求 list-total 介面的程式碼

def get_and_save_all_countries():
    """
    獲取所有的國家名以及對應的id,儲存為檔案
    """

    url = 'https://c.m.163.com/ug/api/wuhan/app/data/list-total?t=317452696323'
    list_total_req = requests.get(url, headers=headers)
    if list_total_req.status_code == 200:
        area_tree = list_total_req.json()['data']['areaTree']

        area_dict = {}
        for area in area_tree:
            country_id = area['id']
            name = area['name']
            area_dict[country_id] = name

        area_json = json.dumps(area_dict, ensure_ascii=False)  # ensure_ascii=False 防止json編碼後中文程式設計\u開頭的字元
        write_file('./config/countries_id2name.json', area_json)

 

這裡將請求下來的資料臨時存放在檔案裡。有了所有的疫情國家的id,我們就可以請求 list-by-area-code 介面來獲取每個國家的疫情資料了。程式碼與上面的類似,不同的是將請求結果存在了 mongodb 而不是檔案,目的是為了方便增刪改查。當然為了大家方便使用,我將mongodb中的資料匯入了檔案 counties_daily.json 中,大家可以在原始碼根目錄找到它。

資料處理 

這一步的處理主要是為第三步畫圖做準備的。因為我們畫圖用的是pyecharts框架,它繪製世界地圖需要輸入的國家名是英文的,而我們收集的國家名是中文的,所以要將中文國家名對應到英文國家名。最終的效果如下

網上能找到這樣的對應關係,但想要用起來還需要解決兩個問題。第一,兩邊中文名統一,比如:我們收集的國家名是中非共和國,而對應關係裡是中非,那還是對應不上。第二,需要自己增加對映關係,網上找的一般都不全,我們需要根據收集的資料自行增加。經過上面兩個步驟處理後,我們就可以將大部分國家名對應到pyechars能識別的英文名了。相關程式碼如下

def get_cy_properties():
    # 獲取配置檔案資訊
    countries_id2name = read_file('./config/countries_id2name.json')
    cy_id2name_dict = json.loads(countries_id2name)
    cy_ch2en = {v: k for k, v in countries_dict.items()}

    # 調整國家的名字與配置檔案一致
    cy_id2name_dict['879'] = '波斯尼亞和黑塞哥維那'
    cy_id2name_dict['8102'] = '多哥'
    cy_id2name_dict['8143'] = '剛果民主共和國'
    cy_id2name_dict['95983'] = '剛果'
    cy_id2name_dict['8144'] = '中非'
    cy_id2name_dict['95000011'] = '多明尼加'

    cy_props = {}
    for key in cy_id2name_dict:
        cy_name = cy_id2name_dict[key]
        if cy_name in cy_ch2en:
            cy_props[cy_name] = {}
            cy_props[cy_name]['id'] = key
            cy_props[cy_name]['en_name'] = cy_ch2en[cy_name]

    return cy_props

 

畫圖 

這一步涉及到兩個核心過程——構造資料結構和畫圖。首先,我構造了3個數據結構,分別是date_list、cy_name_list 和 ncov_data。date_list存放的是日期列表,因為我們畫動圖,所以需要一段時間;cy_name_list 存放收集的所有國家列表(英文名);ncov_data是一個字典,key是日期,value是陣列,存放各個國家當天的確診病例數。生成這三個資料結構的程式碼如下

def parse_ncov_data(start_date, end_date, records):
    if not records:
        return

    date_list = get_date_range(start_date, end_date)
    cy_name_list = []
    res = {}
    # 獲取各國每天現有確認病例
    for i, record in enumerate(records):
        cy_name = record['cy_en_name']
        cy_name_list.append(cy_name)

        # 解析每天資料並計算現有確認病例
        existing_case_dict = {}
        for ncov_daily in record['data']['list']:
            date_str = ncov_daily['date']
            confirm = ncov_daily['total']['confirm']  # 累計確診
            heal = ncov_daily['total']['heal']  # 累計確診
            dead = ncov_daily['total']['dead']  # 累計死亡

            existing_case = confirm - heal - dead
            existing_case_dict[date_str] = existing_case

        last_existing_case = 0
        # 將每天確診病例數合併到res中
        for date_str in date_list:
            if date_str not in res:  # 初始化
                res[date_str] = []

            existing_case = existing_case_dict.get(date_str)
            if existing_case is None:
                existing_case = last_existing_case
            res[date_str].append(existing_case)

            last_existing_case = existing_case

    return date_list, cy_name_list, res

引數 records 是一個數組,陣列每個元素代表一個國家,內容便是我們在第一步請求 list-by-area-code 介面的資料。最後,用 pyecharts 來畫圖,直接上程式碼

def render_map(date_list, cy_name_list, ncov_data):
    tl = Timeline()  # 建立時間線輪播多圖,可以讓圖形按照輸入的時間動起來
    #  is_auto_play:自動播放
    #  play_interval:播放時間間隔,單位:毫秒
    #  is_loop_play:是否迴圈播放
    tl.add_schema(is_auto_play=True, play_interval=50, is_loop_play=False)

    for date_str in date_list:  # 遍歷時間列表
        map0 = (
            Map()  # 建立地圖圖表
            # 將國家名 cy_name_list 以及各國當天確診病例 ncov_data[date_str] 加入地圖中
            .add("全球疫情趨勢", [list(z) for z in zip(cy_name_list, ncov_data[date_str])], 
                "world", is_map_symbol_show=False)
            .set_series_opts(label_opts=opts.LabelOpts(is_show=False))  # 不顯示國家名
            .set_global_opts(
                title_opts=opts.TitleOpts(title="%s日" % date_str),  # 圖表標題
                visualmap_opts=opts.VisualMapOpts(max_=80),   # 當確診病例大於80 ,地圖顏色是紅色
            )
        )
        tl.add(map0, "%s" % date_str)  # 將當天的地圖狀態加入時間線中

    tl.render()  # 生成最終輪播多圖,會在當前目錄建立 render.html 檔案

程式碼里加了註釋,這裡就不再贅述了。
執行 render_map 函式會在當前目錄生成 render.html 檔案,開啟後便自動播放疫情變化趨勢,如文章開頭 gif。另外,有些朋友可能會問,能不能直接輸出 gif。這一點我也嘗試過,百度、谷歌、GitHub上的教程基本上都試了一遍,比較遺憾沒有找到靠譜的方法。所以勸大家還是放棄這條路,曲線救國,錄製一個視訊轉成 gif 即可,方便快捷。畢竟人生苦短,Python 為我們節省下的時間不能再被這些無謂的坑再填回去。這樣整個過程就介紹完了,雖然思路不復雜,但區域性細節上還是需要花一些時間處理的。完整程式碼共 230 行,需要的朋友在公眾號回覆關鍵字 疫情地圖 即可。

最近國內某些地方出現了反彈的跡象,希望大家無論是在工作還是生活上都能繼續保持警惕。希望這次疫情早點過去,等待全球地圖變白的那一天。

歡迎公眾號「渡碼」,輸出別地兒看不到的乾貨。