談談Python實戰數據可視化之pygal模塊(實戰篇)

前沿

通過上一節談談Python實戰數據可視化之pygal模塊(基礎篇)的學習,我們對pygal模塊的使用有了初步的瞭解,本節將以實戰項目來加深pygal模塊的使用。從網上可以下載JSON格式的人口數據,並使用json模塊來處理它們,pygal模塊提供了一個適合初學者使用的地圖創建工具,我們將使用它來對人口數據進行可視化,以探索全球人口的分佈情況。針對JSON格式的人口數據文件,可以通過談談Python實戰數據可視化之matplotlib模塊(實戰篇)章節的配套資源來下載。對於本人在學習和編碼過程種遇到的問題,我都會逐一解決。

小插曲之執行效率

我在學習過程中,走各方論壇,發現一個有趣的帖子。是針對Python執行效率問題的探究------加一行代碼讓python的運行速度提高100倍。什麼代碼這麼強大?我們測試看看,從1一直累加到1億。
(1)原始代碼:

import time

def foo(x, y):
    tt = time.time()  # time.time()函數返回當前時間的時間戳(1970 紀元年後經過的浮點秒數)
    s = 0
    for i in range(x, y):
        s += i
    print('Time used: {} sec'.format(time.time() - tt))
    return s

print(foo(1, 100000000))

什麼是時間戳?時間戳表示的是從 1970 年 1 月 1 日 00:00:00 開始按秒計算的偏移量(time.gmtime(0))此模塊中的函數無法處理 1970 紀元年以前的日期和時間或太遙遠的未來(處理極限取決於 C 函數庫,對於 32 位系統來說,是 2038 年)。
運行結果如下:
談談Python實戰數據可視化之pygal模塊(實戰篇)
(2)加一行代碼,再看看結果:

from numba import jit  # 添加的代碼
import time

@jit  # 添加的代碼
def foo(x, y):
    tt = time.time()  # time.time()函數返回當前時間的時間戳(1970 紀元年後經過的浮點秒數)
    s = 0
    for i in range(x, y):
        s += i
    print('Time used: {} sec'.format(time.time() - tt))
    return s

print(foo(1, 100000000))

運行結果如下:
談談Python實戰數據可視化之pygal模塊(實戰篇)
總結:原始代碼測試出來的是23sec,加了一行代碼就編程0.25sec了,好像真的變快了將近100倍耶。具體實現原理貌似有點複雜,等以後知識面廣了再研究內部原理吧。

JSON格式數據

JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式,易於人閱讀和編寫。例如:

[
  {
    "Country Name": "Arab World",
    "Country Code": "ARB",
    "Year": "1960",
    "Value": "96388069"
  },
  {
    "Country Name": "Arab World",
    "Country Code": "ARB",
    "Year": "1961",
    "Value": "98882541.4"
  },
....
....
....

可以看出,這個文件實際上就是一個很長的Python列表,其中每個元素都是一個包含四個鍵的字典:國家名、國別碼、年份以及表示人口數量的值。

提取JSON格式文件的數據

在工程目錄下,創建一個world_population.py文件,並將population_data.json格式文件放到工程目錄下。然後編寫以下代碼嘗試提取經json模塊轉化後的格式數據:

# 導入json模塊分析JSON格式文件
import json

filename = 'population_data.json'
with open(filename) as f:
    # 函數json.load()將數據(文件對象)轉換爲Python能處理的格式,
    pop_data = json.load(f)  # pop_data是一個列表,每個元素都包含一個四個鍵的字典

for pop_dict in pop_data:
    # 只刷選出2010年份的國家人口數量
    if pop_dict['Year'] == '2010':
        # 將每個國家的國家名、國家人口數保存並打印輸出
        country_name = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))  
        print(country_name + ":" + str(population))

運行結果如下:
談談Python實戰數據可視化之pygal模塊(實戰篇)
需要注意的是,上面代碼獲取pop_dict['Value']的值是一個字符串,而後面我們進行數據可視化時,人口數量必須使用數值才行,所以,我們先轉化爲float類型,再轉化爲int類型。爲什麼不直接轉化爲Int類型?這是因爲當for循環遍歷到人口數值是包含小數點的字符串(例如:'1127437398.85751')時,Python不能直接轉化爲整數,不然會出現類似下面的報錯:
談談Python實戰數據可視化之pygal模塊(實戰篇)
爲了消除這種錯誤,正確的做法是先將'1127437398.85751'字符串轉化爲float類型(1127437398.85751),再轉化爲Int類型(1127437398)。

獲取兩個字母的國別碼

Pygal中的地圖製作工具要求數據爲特定的格式:用國別碼錶示國家,以及用數字表示人口數量。最重要的問題是,population_data.json中包含的是三個字母的國別碼,但Pygal使用兩個字母的國別碼(存儲在i18n模塊中,其實是在該模塊的一個字典COUNTRIES裏,該字典包含的鍵和值分別爲兩個字母的國別碼和國家名)來表示國家。所以我們要解決的問題就是根據國家名在i18n模塊中的字典COUNTRIES裏獲取兩個字母的國別碼。這樣就可以在世界地圖上表示的國別碼和人口數量分別使用字典COUNTRIES裏的兩個字母的國別碼和population_data.json文件裏的人口數量。好了,說了解決問題的流程,現在我們嘗試使用i18n模塊來獲取字典COUNTRIES裏的鍵和值,先在工程目錄下創建一個country_codes.py文件。
需要注意的是書本P327頁的16.2.4小節,導入i18n模塊的方法對於現在來說已經不適用了。如果導入模塊的代碼寫“from pygal.i18n import COUNTRIES”就會報以下的錯誤:
談談Python實戰數據可視化之pygal模塊(實戰篇)
應該改爲“from pygal_maps_world.i18n import COUNTRIES”才行。
代碼如下:

# 返回il8n模塊中COUNTRIES字典中對應國家名的國別碼
from pygal_maps_world.i18n import COUNTRIES

def get_country_code(country_name):
    for code, name in COUNTRIES.items():  # 返回字典的所有鍵值對
        if name == country_name:  # 根據國家名返回兩個字母的國別碼
            return code
    return None  # 如果沒有找到則返回None

修改world_population.py文件的代碼,代碼如下:

# 導入json模塊分析JSON格式文件
import json
from country_codes import get_country_code

filename = 'population_data.json'
with open(filename) as f:
    # 函數json.load()將數據(文件對象)轉換爲Python能處理的格式,
    pop_data = json.load(f)  # pop_data是一個列表,每個元素都包含一個四個鍵的字典

for pop_dict in pop_data:
    # 只刷選出2010年份的國家人口數量
    if pop_dict['Year'] == '2010':
        # 將每個國家的國家名、國家人口數保存並打印輸出
        country_name = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        code = get_country_code(country_name)  # 將population_data.json文件獲取的國家名傳入函數,若存在則返回對應的國別碼
        if code:  # 如果存在則輸出國家名對應的國別碼
            print(code + ":" + str(population))
        else:
            print('ERROR - '+country_name)

運行結果如下:
談談Python實戰數據可視化之pygal模塊(實戰篇)
從上圖可以看出,其實有相當一部分國家沒有對應的國別碼,導致顯示錯誤消息的原因有兩個。第一,並非所有人口數量都是國家,有些是地區和經濟類羣。第二,有些統計數據使用了不同的完整國家名,所以識別不到。

製作世界地圖

有了兩個字母的國別碼後,我們可以進行以下步驟:
1.構造虛擬數據製作一個世界地圖顯示指定了國別碼的國家有哪些,還有呈現人口數量,來感受一下世界地圖的宏偉。
2.繪製一個2010年真實數據的完整的世界人口地圖圖表。
3.根據人口數量將國家分組
4.世界人口地圖圖表進行樣式優化處理。
(1)製作一個模擬數據世界地圖
在此之前,P329頁16.2.5小節的代碼中調用函數創建世界地圖的方法對於現在也不適用了,如果代碼寫“wm = pygal.Worldmap() ”會報以下錯誤
談談Python實戰數據可視化之pygal模塊(實戰篇)
將代碼改爲“wm = pygal.maps.world.World()”就可以通過了。代碼如下:

import pygal

wm = pygal.maps.world.World()  # 創建一個實例
wm.title = 'North,Central America'

# 利用add函數添加標籤和國家名還有人口數量,若參數2是一個列表(只有國家名,沒有指定人口數量),默認指定人口數量爲1,那麼就是使用同一種顏色,並且顏色深淺一樣,除非人口數量不同。
# 而參數2如果是一個字典,那麼說明指定國家名的同時還指定了人口數量,那麼雖然使用同一種類型顏色,但根據人口數量的多少決定顏色的深淺
wm.add('North America', {'ca': 10000, 'mx': 20000, 'us': 30000})
wm.add('Central America', {'bz': 40000, 'cr': 50000, 'gt': 60000, 'hn': 70000, 'ni': 80000, 'pa': 90000, 'sv': 100000})

wm.render_to_file('americas.svg')

將americas.svg放入瀏覽器中顯示,運行結果如下:
談談Python實戰數據可視化之pygal模塊(實戰篇)
上面的人口數據純屬虛構。從上圖可以看出,將鼠標移至國家上方便可顯示國家名和人口數,North America的三個國家分別使用同一種、但深淺不一的顏色來表示,其中人口數量越大,顏色越深。而Central America的多個國家也是同樣如此。
(2)繪製完整的世界人口地圖
要呈現其他國家的人口數量,需要將前面處理的數據(兩個字母的國別碼和對應國家的人口數量)轉換爲Pygal要求的字典格式(即作爲實參傳入add函數的第二個形參)。代碼如下:

import json
import pygal
from country_codes import get_country_code

filename = 'population_data.json'
with open(filename) as f:
    # 函數json.load()將數據(文件對象)轉換爲Python能處理的格式,
    pop_data = json.load(f)  # pop_data是一個列表,每個元素都包含一個四個鍵的字典

cc_populations = {}
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country_name = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        code = get_country_code(country_name)
        if code:
            cc_populations[code] = population

wm = pygal.maps.world.World()
wm.title = 'World Population in 2010,by Country'
wm.add('2010',cc_populations)

wm.render_to_file('world_population.svg')

運行結果如下:
談談Python實戰數據可視化之pygal模塊(實戰篇)
根據上面可以總結出:若將整個世界所有國家的國別碼和人口數量都放進一個字典裏,再調用一個add函數添加進世界地圖,那麼就會把這個字典當成一個組,那麼在世界地圖顯示時,使用同一種、但深淺不一的紅顏色來表示,其中人口數量越大,顏色越深。再想想,這可不太行,因爲整體來看,很難反映其人口數量的差別,爲了解決這個問題,我們究其根源,主要是我們只使用了一個字典和只調用了一個add函數來表示整個世界的原因導致顏色單一,解決辦法是我們可以對其進行分組處理,人口數量多的爲一組,人口數量中等的爲一組,人口數量少的爲一組,那麼就分爲三組了。

根據人口數量將國家分組

針對上一節結論的分析,我們這一小節將採用分組的方式反映人口數量的差別。根據人口數量分成三組:少於1000萬的、介於1000萬和10億之間的以及超過10億的。
代碼如下:

import json
import pygal
from country_codes import get_country_code

filename = 'population_data.json'
with open(filename) as f:
    # 函數json.load()將數據(文件對象)轉換爲Python能處理的格式,
    pop_data = json.load(f)  # pop_data是一個列表,每個元素都包含一個四個鍵的字典

cc_populations = {}
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country_name = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        code = get_country_code(country_name)
        if code:
            cc_populations[code] = population

# 根據人口數量將所有的國家分成三組
cc_pops_1, cc_pops_2, cc_pops_3 = {}, {}, {}
for cc, pop in cc_populations.items():
    if pop < 10000000:
        cc_pops_1[cc] = pop
    elif pop < 1000000000:
        cc_pops_2[cc] = pop
    else:
        cc_pops_3[cc] = pop

wm = pygal.maps.world.World()  # 創建一個實例
wm.title = 'World Population in 2010,by Country'
wm.add('0-10m', cc_pops_1)
wm.add('10m-1bn', cc_pops_2)
wm.add('>1bn', cc_pops_3)

wm.render_to_file('world_population.svg')

運行結果如下:
談談Python實戰數據可視化之pygal模塊(實戰篇)
從上圖可以看出,世界地圖使用三種不同的顏色,更直觀地看出人口數量的差別,在每組中,各個國家都按人口從少到多以淺到深的顏色。其中中國和印度是大於10億人的國家。

世界人口地圖圖表進行樣式優化處理

前面的案例,採用默認的顏色設置不怎麼好看,我們可以使用Pygal樣式設置指令來調整顏色。Pygal樣式存儲在模塊style中,我們從這個模塊中導入了RotateStyle類,創建這個類的實例時,需要提供一個實參 —— 十六進制的 RGB 顏色。十六進制格式 的 RGB 顏色是一個以井號( # )打頭的字符串,後面跟着 6 個字符,其中前兩個字符表示紅色分量,接下來的兩個表示綠色分量,最後兩個表示藍色分量。每個分量的取值範圍爲 00 (沒有相應的顏色) ~FF (包含最多的相應顏色)。Pygal 通常默認使用較暗的顏色主題。使用 LightColorizedStyle 加亮了地圖的顏色。
代碼如下:

import json
import pygal
from country_codes import get_country_code
from pygal.style import LightColorizedStyle as LCS,RotateStyle as RS  # 導入RotateStyle和 LightColorizedStyle,並取了別名,後面調用就採用別名,方便多了

filename = 'population_data.json'
with open(filename) as f:
    # 函數json.load()將數據(文件對象)轉換爲Python能處理的格式,
    pop_data = json.load(f)  # pop_data是一個列表,每個元素都包含一個四個鍵的字典

cc_populations = {}
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country_name = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        code = get_country_code(country_name)
        if code:
            cc_populations[code] = population

# 根據人口數量將所有的國家分成三組
cc_pops_1, cc_pops_2, cc_pops_3 = {}, {}, {}
for cc, pop in cc_populations.items():
    if pop < 10000000:
        cc_pops_1[cc] = pop
    elif pop < 1000000000:
        cc_pops_2[cc] = pop
    else:
        cc_pops_3[cc] = pop

wm_style = RS('#336699',base_style=LCS)  # 一個樣式對象,參數指定一個十六進制的RGB顏色
wm = pygal.maps.world.World(style=wm_style)  # 創建一個實例,並傳入一個指定了顏色的樣式對象wm_style
wm.title = 'World Population in 2010,by Country'
wm.add('0-10m', cc_pops_1)
wm.add('10m-1bn', cc_pops_2)
wm.add('>1bn', cc_pops_3)

wm.render_to_file('world_population.svg')

運行結果如下:
談談Python實戰數據可視化之pygal模塊(實戰篇)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章