標籤:ref 建立 try 添加 from .json pytho 部分 初學者
前沿
通過上一節談談Python實戰資料視覺效果之pygal模組(基礎篇)的學習,我們對pygal模組的使用有了初步的瞭解,本節將以實戰項目來加深pygal模組的使用。從網上可以下載JSON格式的人口資料,並使用json模組來處理它們,pygal模組提供了一個適合初學者使用的地圖建立工具,我們將使用它來對人口資料進行可視化,以探索全球人口的分布情況。針對JSON格式的人口資料檔案,可以通過談談Python實戰資料視覺效果之matplotlib模組(實戰篇)章節的配套資源來下載。對於本人在學習和編碼過程種遇到的問題,我都會逐一解決。
小插曲之執行效率
我在學習過程中,走各方論壇,發現一個有趣的文章。是針對Python執行效率問題的探究------加一行代碼讓python的運行速度提高100倍。什麼代碼這麼強大?我們測試看看,從1一直累加到1億。
(1)原始代碼:
import timedef 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 sprint(foo(1, 100000000))
什麼是時間戳記?時間戳記表示的是從 1970 年 1 月 1 日 00:00:00 開始按秒計算的位移量(time.gmtime(0))此模組中的函數無法處理 1970 紀元年以前的日期和時間或太遙遠的未來(處理極限取決於 C 函數庫,對於 32 位系統來說,是 2038 年)。
運行結果如下:
(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 sprint(foo(1, 100000000))
運行結果如下:
總結:原始代碼測試出來的是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 jsonfilename = ‘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))
運行結果如下:
需要注意的是,上面代碼擷取pop_dict[‘Value‘]的值是一個字串,而後面我們進行資料視覺效果時,人口數量必須使用數值才行,所以,我們先轉化為float類型,再轉化為int類型。為什麼不直接轉化為Int類型?這是因為當for迴圈遍曆到人口數值是包含小數點的字串(例如:‘1127437398.85751‘)時,Python不能直接轉化為整數,不然會出現類似下面的報錯:
為了消除這種錯誤,正確的做法是先將‘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”就會報以下的錯誤:
應該改為“from pygal_maps_world.i18n import COUNTRIES”才行。
代碼如下:
# 返回il8n模組中COUNTRIES字典中對應國家名的國別碼from pygal_maps_world.i18n import COUNTRIESdef 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 jsonfrom country_codes import get_country_codefilename = ‘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)
運行結果如下:
從可以看出,其實有相當一部分國家沒有對應的國別碼,導致顯示錯誤訊息的原因有兩個。第一,並非所有人口數量都是國家,有些是地區和經濟類群。第二,有些統計資料使用了不同的完整國家名,所以識別不到。
製作世界地圖
有了兩個字母的國別碼後,我們可以進行以下步驟:
1.構造虛擬資料製作一個世界地圖顯示指定了國別碼的國家有哪些,還有呈現人口數量,來感受一下世界地圖的宏偉。
2.繪製一個2010年真實資料的完整的世界人口地圖圖表。
3.根據人口數量將國家分組
4.世界人口地圖圖表進行樣式最佳化處理。
(1)製作一個類比資料世界地圖
在此之前,P329頁16.2.5小節的代碼中調用函數建立世界地圖的方法對於現在也不適用了,如果代碼寫“wm = pygal.Worldmap() ”會報以下錯誤:
將代碼改為“wm = pygal.maps.world.World()”就可以通過了。代碼如下:
import pygalwm = 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放入瀏覽器中顯示,運行結果如下:
上面的人口資料純屬虛構。從可以看出,將滑鼠移至國家上方便可顯示國家名和人口數,North America的三個國家分別使用同一種、但深淺不一的顏色來表示,其中人口數量越大,顏色越深。而Central America的多個國家也是同樣如此。
(2)繪製完整的世界人口地圖
要呈現其他國家的人口數量,需要將前面處理的資料(兩個字母的國別碼和對應國家的人口數量)轉換為Pygal要求的字典格式(即作為實參傳入add函數的第二個形參)。代碼如下:
import jsonimport pygalfrom country_codes import get_country_codefilename = ‘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] = populationwm = pygal.maps.world.World()wm.title = ‘World Population in 2010,by Country‘wm.add(‘2010‘,cc_populations)wm.render_to_file(‘world_population.svg‘)
運行結果如下:
根據上面可以總結出:若將整個世界所有國家的國別碼和人口數量都放進一個字典裡,再調用一個add函數添加進世界地圖,那麼就會把這個字典當成一個組,那麼在世界地圖顯示時,使用同一種、但深淺不一的紅顏色來表示,其中人口數量越大,顏色越深。再想想,這可不太行,因為整體來看,很難反映其人口數量的差別,為瞭解決這個問題,我們究其根源,主要是我們只使用了一個字典和只調用了一個add函數來表示整個世界的原因導致顏色單一,解決辦法是我們可以對其進行分組處理,人口數量多的為一組,人口數量中等的為一組,人口數量少的為一組,那麼就分為三組了。
根據人口數量將國家分組
針對上一節結論的分析,我們這一小節將採用分組的方式反映人口數量的差別。根據人口數量分成三組:少於1000萬的、介於1000萬和10億之間的以及超過10億的。
代碼如下:
import jsonimport pygalfrom country_codes import get_country_codefilename = ‘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] = popwm = 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‘)
運行結果如下:
從可以看出,世界地圖使用三種不同的顏色,更直觀地看出人口數量的差別,在每組中,各個國家都按人口從少到多以淺到深的顏色。其中中國和印度是大於10億人的國家。
世界人口地圖圖表進行樣式最佳化處理
前面的案例,採用預設的顏色設定不怎麼好看,我們可以使用Pygal樣式設定指令來調整顏色。Pygal樣式儲存在模組style中,我們從這個模組中匯入了RotateStyle類,建立這個類的執行個體時,需要提供一個實參 —— 十六進位的 RGB 顏色。十六進位格式 的 RGB 顏色是一個以井號( # )打頭的字串,後面跟著 6 個字元,其中前兩個字元表示紅色分量,接下來的兩個表示綠色分量,最後兩個表示藍色分量。每個分量的取值範圍為 00 (沒有相應的顏色) ~FF (包含最多的相應顏色)。Pygal 通常預設使用較暗的顏色主題。使用 LightColorizedStyle 加亮了地圖的顏色。
代碼如下:
import jsonimport pygalfrom country_codes import get_country_codefrom 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] = popwm_style = RS(‘#336699‘,base_style=LCS) # 一個樣式對象,參數指定一個十六進位的RGB顏色wm = pygal.maps.world.World(style=wm_style) # 建立一個執行個體,並傳入一個指定了顏色的樣式對象wm_stylewm.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模組(實戰篇)