Python系列17-數(shù)據(jù)可視化之下載數(shù)據(jù)

一.下載數(shù)據(jù)簡介

在本博客中,你將從網(wǎng)上下載數(shù)據(jù),并對(duì)這些數(shù)據(jù)進(jìn)行可視化。網(wǎng)上的數(shù)據(jù)多得難以置信,且大多未經(jīng)過仔細(xì)檢查。如果能夠?qū)@些數(shù)據(jù)進(jìn)行分析,你就能發(fā)現(xiàn)別人沒有發(fā)現(xiàn)的規(guī)律和關(guān)聯(lián)。

我們將訪問并可視化以兩種常見格式存儲(chǔ)的數(shù)據(jù):CSV 和JSON。我們將使用Python模塊csv 來處理以CSV(逗號(hào)分隔的值)格式存儲(chǔ)的天氣數(shù)據(jù),找出兩個(gè)不同地區(qū)在一段時(shí)間內(nèi)的最高溫度和最低溫度。然后,我們將使用matplotlib根據(jù)下載的數(shù)據(jù)創(chuàng)建一個(gè)圖表,展示兩個(gè)不同地區(qū)的氣溫變化:阿拉斯加錫特卡和加利福尼亞死亡谷。在本博客的后面,我們將使用模塊json 來訪問以JSON格式存儲(chǔ)的人口數(shù)據(jù),并使用Pygal繪制一幅按國別劃分的人口地圖。

閱讀本博客后,你將能夠處理各種類型和格式的數(shù)據(jù)集,并對(duì)如何創(chuàng)建復(fù)雜的圖表有更深入的認(rèn)識(shí)。要處理各種真實(shí)世界的數(shù)據(jù)集,必須能夠訪問并可視化各種類型和格式的在線數(shù)據(jù)。

1.1 CSV文件格式

要在文本文件中存儲(chǔ)數(shù)據(jù),最簡單的方式是將數(shù)據(jù)作為一系列以逗號(hào)分隔的值 (CSV)寫入文件。這樣的文件稱為CSV文件。例如,下面是一行CSV格式的天氣數(shù)據(jù):

2014-1-5,61,44,26,18,7,-1,56,30,9,30.34,30.27,30.15,,,,10,4,,0.00,0,,195

這是阿拉斯加錫特卡2014年1月5日的天氣數(shù)據(jù),其中包含當(dāng)天的最高氣溫和最低氣溫,還有眾多其他數(shù)據(jù)。CSV文件對(duì)人來說閱讀起來比較麻煩,但程序可輕松地提取并處理其中的值,這有助于加快數(shù)據(jù)分析過程。

我們將首先處理少量錫特卡的CSV格式的天氣數(shù)據(jù),這些數(shù)據(jù)可在本書的配套資源(https://www.nostarch.com/pythoncrashcourse/ )中找到。請將文件sitka_weather_07-2014.csv復(fù)制到
存儲(chǔ)本章程序的文件夾中(下載本書的配套資源后,你就有了這個(gè)項(xiàng)目所需的所有文件)。

1.1.1 分析CSV文件頭

csv 模塊包含在Python標(biāo)準(zhǔn)庫中,可用于分析CSV文件中的數(shù)據(jù)行,讓我們能夠快速提取感興趣的值。下面先來查看這個(gè)文件的第一行,其中包含一系列有關(guān)數(shù)據(jù)的描述

代碼:
highs_lows.py

import csv

filename = 'sitka_weather_07-2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)
    print(header_row)

測試記錄:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/數(shù)據(jù)可視化/highs_lows.py
['AKDT', 'Max TemperatureF', 'Mean TemperatureF', 'Min TemperatureF', 'Max Dew PointF', 'MeanDew PointF', 'Min DewpointF', 'Max Humidity', ' Mean Humidity', ' Min Humidity', ' Max Sea Level PressureIn', ' Mean Sea Level PressureIn', ' Min Sea Level PressureIn', ' Max VisibilityMiles', ' Mean VisibilityMiles', ' Min VisibilityMiles', ' Max Wind SpeedMPH', ' Mean Wind SpeedMPH', ' Max Gust SpeedMPH', 'PrecipitationIn', ' CloudCover', ' Events', ' WindDirDegrees']

Process finished with exit code 0

reader處理文件中以逗號(hào)分隔的第一行數(shù)據(jù),并將每項(xiàng)數(shù)據(jù)都作為一個(gè)元素存儲(chǔ)在列表中。文件頭AKDT 表示阿拉斯加時(shí)間(Alaska Daylight Time),其位置表明每行的第一個(gè)值都
是日期或時(shí)間。文件頭Max TemperatureF 指出每行的第二個(gè)值都是當(dāng)天的最高華氏溫度。可通過閱讀其他的文件頭來確定文件包含的信息類型。

注意  文件頭的格式并非總是一致的,空格和單位可能出現(xiàn)在奇怪的地方。這在原始數(shù)據(jù)文件中很常見,但不會(huì)帶來任何問題。

1.1.2 打印文件頭及其位置

為讓文件頭數(shù)據(jù)更容易理解,將列表中的每個(gè)文件頭及其位置打印出來

代碼:
highs_lows.py

import csv

filename = 'sitka_weather_07-2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)
    
    for index, column_header in enumerate(header_row):
        print(index, column_header)

測試記錄:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/數(shù)據(jù)可視化/highs_lows.py
0 AKDT
1 Max TemperatureF
2 Mean TemperatureF
3 Min TemperatureF
4 Max Dew PointF
5 MeanDew PointF
6 Min DewpointF
7 Max Humidity
8  Mean Humidity
9  Min Humidity
10  Max Sea Level PressureIn
11  Mean Sea Level PressureIn
12  Min Sea Level PressureIn
13  Max VisibilityMiles
14  Mean VisibilityMiles
15  Min VisibilityMiles
16  Max Wind SpeedMPH
17  Mean Wind SpeedMPH
18  Max Gust SpeedMPH
19 PrecipitationIn
20  CloudCover
21  Events
22  WindDirDegrees

Process finished with exit code 0

1.1.3 提取并讀取數(shù)據(jù)

知道需要哪些列中的數(shù)據(jù)后,我們來讀取一些數(shù)據(jù)。首先讀取每天的最高氣溫

代碼:
highs_lows.py

import csv

# 從文件中獲取最高氣溫
filename = 'sitka_weather_07-2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    highs = []
    for row in reader:
        highs.append(row[1])

    print(highs)

測試記錄:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/數(shù)據(jù)可視化/highs_lows.py
['64', '71', '64', '59', '69', '62', '61', '55', '57', '61', '57', '59', '57', '61', '64', '61', '59', '63', '60', '57', '69', '63', '62', '59', '57', '57', '61', '59', '61', '61', '66']

Process finished with exit code 0

我們創(chuàng)建了一個(gè)名為highs 的空列表,再遍歷文件中余下的各行。閱讀器對(duì)象從其停留的地方繼續(xù)往下讀取CSV文件,每次都自動(dòng)返回當(dāng)前所處位置的下一行。由于我們已經(jīng)讀取了文件頭行,這個(gè)循環(huán)將從第二行開始——從這行開始包含的是實(shí)際數(shù)據(jù)。每次執(zhí)行該循環(huán)時(shí),我們都將索引1處(第2列)的數(shù)據(jù)附加到highs 末尾.

我們提取了每天的最高氣溫,并將它們作為字符串整潔地存儲(chǔ)在一個(gè)列表中。
下面使用int() 將這些字符串轉(zhuǎn)換為數(shù)字,讓matplotlib能夠讀取它們:

-- snip
    for row in reader:
        high = int(row[1])
        highs.append(high)
-- snip

1.1.4 繪制氣溫圖表

為可視化這些氣溫?cái)?shù)據(jù),我們首先使用matplotlib創(chuàng)建一個(gè)顯示每日最高氣溫的簡單圖形

代碼:
highs_lows.py

import csv
from matplotlib import pyplot as plt

# 從文件中獲取最高氣溫
filename = 'sitka_weather_07-2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    highs = []
    for row in reader:
        high = int(row[1])
        highs.append(high)

    # print(highs)

# 根據(jù)數(shù)據(jù)繪制圖形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(highs, c='red')

# 設(shè)置圖形的格式
plt.title("Daily high temperatures, July 2014", fontsize=24)
plt.xlabel('', fontsize=16)
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()

測試記錄:

image.png

我們將最高氣溫列表傳給plot() ,并傳遞c='red' 以便將數(shù)據(jù)點(diǎn)繪制為紅色(紅色顯示最高氣溫,藍(lán)色顯示最低氣溫)。接下來,我們設(shè)置了一些其他的格式,如字體大小和標(biāo)簽。鑒于我們還沒有添加日期,因此沒有給x 軸添加標(biāo)簽,但plt.xlabel() 確實(shí)修改了字體大小,讓默認(rèn)標(biāo)簽更容易看清。上圖:一個(gè)簡單的折線圖,顯示了阿拉斯加錫特卡2014年7月每天的最高氣溫。

1.1.5 模塊datetime

下面在圖表中添加日期,使其更有用。在天氣數(shù)據(jù)文件中,第一個(gè)日期在第二行

2014-7-1,64,56,50,53,51,48,96,83,58,30.19,--snip--

讀取該數(shù)據(jù)時(shí),獲得的是一個(gè)字符串,因?yàn)槲覀冃枰朕k法將字符串'2014-7-1' 轉(zhuǎn)換為一個(gè)表示相應(yīng)日期的對(duì)象。為創(chuàng)建一個(gè)表示2014年7月1日的對(duì)象,可使用模塊datetime 中的方法strptime() 。我們在終端會(huì)話中看看strptime() 的工作原理

C:\>python
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> from datetime import datetime
>>> first_date = datetime.strptime('2014-7-1', '%Y-%m-%d')
>>> print(first_date)
2014-07-01 00:00:00
>>>

我們首先導(dǎo)入了模塊datetime 中的datetime 類,然后調(diào)用方法strptime() ,并將包含所需日期的字符串作為第一個(gè)實(shí)參。第二個(gè)實(shí)參告訴Python如何設(shè)置日期的格式。在這個(gè)示例中,'%Y-' 讓Python將字符串中第一個(gè)連字符前面的部分視為四位的年份;'%m-' 讓Python將第二個(gè)連字符前面的部分視為表示月份的數(shù)字;而'%d' 讓Python將字符串的最后一部分視為月份中的一天(1~31)。

方法strptime() 可接受各種實(shí)參,并根據(jù)它們來決定如何解讀日期。表16-1列出了其中一些這樣的實(shí)參。

實(shí)參 含義
%A 星期的名稱,如Monday
%B 月份名,如January
%m 用數(shù)字表示的月份(01~12)
%d 用數(shù)字表示月份中的一天(01~31)
%Y 四位的年份,如2015
%y 兩位的年份,如15
%H 24小時(shí)制的小時(shí)數(shù)(00~23)
%I 12小時(shí)制的小時(shí)數(shù)(01~12)
%p am或pm
%M 分鐘數(shù)(00~59)
%S 秒數(shù)(00~61)

1.1.6 在圖表中添加日期

知道如何處理CSV文件中的日期后,就可對(duì)氣溫圖形進(jìn)行改進(jìn)了,即提取日期和最高氣溫,并將它們傳遞給plot()

代碼:
highs_lows.py

import csv
from datetime import datetime
from matplotlib import pyplot as plt

# 從文件中獲取最高氣溫
filename = 'sitka_weather_07-2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    dates, highs = [], []
    for row in reader:
        current_date = datetime.strptime(row[0], "%Y-%m-%d")
        dates.append(current_date)

        high = int(row[1])
        highs.append(high)

    # print(highs)

# 根據(jù)數(shù)據(jù)繪制圖形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates ,highs, c='red')

# 設(shè)置圖形的格式
plt.title("Daily high temperatures, July 2014", fontsize=24)
plt.xlabel('', fontsize=16)
fig.autofmt_xdate()
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()

測試記錄:

image.png

1.1.7 涵蓋更長的時(shí)間

設(shè)置好圖表后,我們來添加更多的數(shù)據(jù),以成一幅更復(fù)雜的錫特卡天氣圖。請將文件sitka_weather_2014.csv復(fù)制到存儲(chǔ)本章程序的文件夾中,該文件包含Weather Underground提供的整年的錫特卡天氣數(shù)據(jù)。

現(xiàn)在可以創(chuàng)建覆蓋整年的天氣圖了:
代碼:
highs_lows.py

import csv
from datetime import datetime
from matplotlib import pyplot as plt

# 從文件中獲取最高氣溫
filename = 'sitka_weather_2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    dates, highs = [], []
    for row in reader:
        current_date = datetime.strptime(row[0], "%Y-%m-%d")
        dates.append(current_date)

        high = int(row[1])
        highs.append(high)

    # print(highs)

# 根據(jù)數(shù)據(jù)繪制圖形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates ,highs, c='red')

# 設(shè)置圖形的格式
plt.title("Daily high temperatures - 2014", fontsize=24)
plt.xlabel('', fontsize=16)
fig.autofmt_xdate()
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()

測試記錄:

image.png

1.1.8 再繪制一個(gè)數(shù)據(jù)系列

上圖改進(jìn)后的圖表顯示了大量意義深遠(yuǎn)的數(shù)據(jù),但我們可以在其中再添加最低氣溫?cái)?shù)據(jù),使其更有用。為此,需要從數(shù)據(jù)文件中提取最低氣溫,并將它們添加到圖表中,如下所示:
代碼:
highs_lows.py

import csv
from datetime import datetime
from matplotlib import pyplot as plt

# 從文件中獲取最高氣溫
filename = 'sitka_weather_2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    dates, highs, lows = [], [], []
    for row in reader:
        current_date = datetime.strptime(row[0], "%Y-%m-%d")
        dates.append(current_date)

        high = int(row[1])
        highs.append(high)

        low = int(row[3])
        lows.append(low)

    # print(highs)

# 根據(jù)數(shù)據(jù)繪制圖形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates ,highs, c='red')
plt.plot(dates ,lows , c='blue')

# 設(shè)置圖形的格式
plt.title("Daily high and low temperatures - 2014", fontsize=24)
plt.xlabel('', fontsize=16)
fig.autofmt_xdate()
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()

測試記錄:

image.png

1.1.9 給圖表區(qū)域著色

添加兩個(gè)數(shù)據(jù)系列后,我們就可以了解每天的氣溫范圍了。下面來給這個(gè)圖表做最后的修飾,通過著色來呈現(xiàn)每天的氣溫范圍。為此,我們將使用方法fill_between() ,它接受一個(gè) x 值系列和兩個(gè) y 值系列,并填充兩個(gè) y 值系列之間的空間

代碼:
highs_lows.py

import csv
from datetime import datetime
from matplotlib import pyplot as plt

# 從文件中獲取最高氣溫
filename = 'sitka_weather_2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    dates, highs, lows = [], [], []
    for row in reader:
        current_date = datetime.strptime(row[0], "%Y-%m-%d")
        dates.append(current_date)

        high = int(row[1])
        highs.append(high)

        low = int(row[3])
        lows.append(low)

    # print(highs)

# 根據(jù)數(shù)據(jù)繪制圖形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates ,highs, c='red', alpha=0.5)
plt.plot(dates ,lows , c='blue', alpha=0.5)
plt.fill_between(dates, highs, lows, facecolor='blue', alpha=0.1)

# 設(shè)置圖形的格式
plt.title("Daily high and low temperatures - 2014", fontsize=24)
plt.xlabel('', fontsize=16)
fig.autofmt_xdate()
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()

測試記錄:

image.png

通過著色,讓兩個(gè)數(shù)據(jù)集之間的區(qū)域顯而易見。

1.1.10 錯(cuò)誤檢查

我們應(yīng)該能夠使用有關(guān)任何地方的天氣數(shù)據(jù)來運(yùn)行highs_lows.py中的代碼,但有些氣象站會(huì)偶爾出現(xiàn)故障,未能收集部分或全部其應(yīng)該收集的數(shù)據(jù)。缺失數(shù)據(jù)可能會(huì)引發(fā)異常,如果不妥善地處理,還可能導(dǎo)致程序崩潰。

例如,我們來看看生成加利福尼亞死亡谷的氣溫圖時(shí)出現(xiàn)的情況。將文件death_valley_2014.csv復(fù)制到本章程序所在的文件夾,再修改highs_lows.py,使其生成死亡谷的氣溫圖
highs_lows.py

--snip--
# 從文件中獲取日期、最高氣溫和最低氣溫
filename = 'death_valley_2014.csv'
with open(filename) as f:
--snip--

運(yùn)行這個(gè)程序時(shí),出現(xiàn)了一個(gè)錯(cuò)誤,如下述輸出的最后一行所示:

Traceback (most recent call last):
File "highs_lows.py", line 17, in <module>
high = int(row[1])
ValueError: invalid literal for int() with base 10: ''

該traceback指出,Python無法處理其中一天的最高氣溫,因?yàn)樗鼰o法將空字符串(' ' )轉(zhuǎn)換為整數(shù)。只要看一下death_valley_2014.csv,就能發(fā)現(xiàn)其中的問題

2014-2-16,,,,,,,,,,,,,,,,,,,0.00,,,-1

其中好像沒有記錄2014年2月16日的數(shù)據(jù),表示最高溫度的字符串為空。為解決這種問題,我們在從CSV文件中讀取值時(shí)執(zhí)行錯(cuò)誤檢查代碼,對(duì)分析數(shù)據(jù)集時(shí)可能出現(xiàn)的異常進(jìn)行處理

highs_lows.py

import csv
from datetime import datetime
from matplotlib import pyplot as plt

# 從文件中獲取最高氣溫
filename = 'death_valley_2014.csv'

with open(filename) as f:
    reader = csv.reader(f)
    header_row = next(reader)

    dates, highs, lows = [], [], []
    for row in reader:
        try:
            current_date = datetime.strptime(row[0], "%Y-%m-%d")
            high = int(row[1])
            low = int(row[3])
        except ValueError:
            print(current_date, 'missing data')
        else:
            dates.append(current_date)
            highs.append(high)
            lows.append(low)

    # print(highs)

# 根據(jù)數(shù)據(jù)繪制圖形
fig = plt.figure(dpi=128, figsize=(10, 6))
plt.plot(dates ,highs, c='red', alpha=0.5)
plt.plot(dates ,lows , c='blue', alpha=0.5)
plt.fill_between(dates, highs, lows, facecolor='blue', alpha=0.1)

# 設(shè)置圖形的格式
plt.title("Daily high and low temperatures - 2014\nDeath Valley, CA", fontsize=24)
plt.xlabel('', fontsize=16)
fig.autofmt_xdate()
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', labelsize=16)
plt.show()

對(duì)于每一行,我們都嘗試從中提取日期、最高氣溫和最低氣溫。只要缺失其中一項(xiàng)數(shù)據(jù),Python就會(huì)引發(fā)ValueError 異常,而我們可這樣處理:打印一條錯(cuò)誤消息,指出缺失數(shù)據(jù)的日期。打印錯(cuò)誤消息后,循環(huán)將接著處理下一行。如果獲取特定日期的所有數(shù)據(jù)時(shí)沒有發(fā)生錯(cuò)誤,將運(yùn)行else 代碼塊,并將數(shù)據(jù)附加到相應(yīng)列表的末尾。鑒于我們繪圖時(shí)使用的是有關(guān)另一個(gè)地方的信息,我們修改了標(biāo)題,在圖表中指出了這個(gè)地方。

測試記錄:
如果你現(xiàn)在運(yùn)行highs_lows.py ,將發(fā)現(xiàn)缺失數(shù)據(jù)的日期只有一個(gè)

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/數(shù)據(jù)可視化/highs_lows.py
2014-02-16 00:00:00 missing data
image.png

1.2 制作世界人口地圖:JSON格式

在本節(jié)中,你將下載JSON格式的人口數(shù)據(jù),并使用json 模塊來處理它們。Pygal提供了一個(gè)適合初學(xué)者使用的地圖創(chuàng)建工具,你將使用它來對(duì)人口數(shù)據(jù)進(jìn)行可視化,以探索全球人口的分布情況。

1.2.1 下載世界人口數(shù)據(jù)

將文件population_data.json復(fù)制到本章程序所在的文件夾中,這個(gè)文件包含全球大部分國家1960~2010年的人口數(shù)據(jù)。Open Knowledge Foundation(http://data.okfn.org/ )提供了大量可以免費(fèi)使用的數(shù)據(jù)集,這些數(shù)據(jù)就來自其中一個(gè)數(shù)據(jù)集。

1.2.2 提取相關(guān)的數(shù)據(jù)

我們來研究一下population_data.json,看看如何著手處理這個(gè)文件中的數(shù)據(jù):
population_data.json

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

這個(gè)文件實(shí)際上就是一個(gè)很長的Python列表,其中每個(gè)元素都是一個(gè)包含四個(gè)鍵的字典:國家名、國別碼、年份以及表示人口數(shù)量的值。我們只關(guān)心每個(gè)國家2010年的人口數(shù)量,因此我們首先編寫一個(gè)打印這些信息的程序.
代碼:
world_population.py

import json

# 將數(shù)據(jù)加載到一個(gè)列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 打印每個(gè)國家2010年的人口數(shù)量
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country_name = pop_dict['Country Name']
        population = pop_dict['Value']
        print(country_name + ": " + population)

測試記錄:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/數(shù)據(jù)可視化/world_population.py
Arab World: 357868000
Caribbean small states: 6880000
East Asia & Pacific (all income levels): 2201536674
East Asia & Pacific (developing only): 1961558757
Euro area: 331766000
--snip--
Uzbekistan: 28228000
Vanuatu: 240000
Venezuela, RB: 28834000
Vietnam: 86928000
Virgin Islands (U.S.): 110000
West Bank and Gaza: 4152000
Yemen, Rep.: 24053000
Zambia: 12927000
Zimbabwe: 12571000

Process finished with exit code 0

1.2.3 將字符串轉(zhuǎn)換為數(shù)字值

population_data.json中的每個(gè)鍵和值都是字符串。為處理這些人口數(shù)據(jù),我們需要將表示人口數(shù)量的字符串轉(zhuǎn)換為數(shù)字值,為此我們使用函數(shù)int()
代碼:
world_population.py

import json

# 將數(shù)據(jù)加載到一個(gè)列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 打印每個(gè)國家2010年的人口數(shù)量
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country_name = pop_dict['Country Name']
        population = int(pop_dict['Value'])
        print(country_name + ": " + str(population))

測試記錄:
對(duì)于有些值,這種轉(zhuǎn)換會(huì)導(dǎo)致錯(cuò)誤:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/數(shù)據(jù)可視化/world_population.py
Arab World: 357868000
Traceback (most recent call last):
  File "E:/python/learn_python1/數(shù)據(jù)可視化/world_population.py", line 12, in <module>
    population = int(pop_dict['Value'])
ValueError: invalid literal for int() with base 10: '1127437398.85751'
Caribbean small states: 6880000
East Asia & Pacific (all income levels): 2201536674
East Asia & Pacific (developing only): 1961558757
Euro area: 331766000
--snip--

原始數(shù)據(jù)的格式常常不統(tǒng)一,因此經(jīng)常會(huì)出現(xiàn)錯(cuò)誤。導(dǎo)致上述錯(cuò)誤的原因是,Python不能直接將包含小數(shù)點(diǎn)的字符串'1127437398.85751' 轉(zhuǎn)換為整數(shù)(這個(gè)小數(shù)值可能是人口數(shù)據(jù)缺失時(shí)通過插值得到的)。為消除這種錯(cuò)誤,我們先將字符串轉(zhuǎn)換為浮點(diǎn)數(shù),再將浮點(diǎn)數(shù)轉(zhuǎn)換為整數(shù)。
代碼:

import json

# 將數(shù)據(jù)加載到一個(gè)列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 打印每個(gè)國家2010年的人口數(shù)量
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country_name = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        print(country_name + ": " + str(population))

每個(gè)字符串都成功地轉(zhuǎn)換成了浮點(diǎn)數(shù),再轉(zhuǎn)換為整數(shù)。以數(shù)字格式存儲(chǔ)人口數(shù)量值后,就可以使用它們來制作世界人口地圖了。

1.2.4 獲取兩個(gè)字母的國別碼

制作地圖前,還需要解決數(shù)據(jù)存在的最后一個(gè)問題。Pygal中的地圖制作工具要求數(shù)據(jù)為特定的格式:用國別碼表示國家,以及用數(shù)字表示人口數(shù)量。處理地理政治數(shù)據(jù)時(shí),經(jīng)常需要用到幾個(gè)標(biāo)準(zhǔn)化國別碼集。population_data.json中包含的是三個(gè)字母的國別碼,但Pygal使用兩個(gè)字母的國別碼。我們需要想辦法根據(jù)國家名獲取兩個(gè)字母的國別碼。

Pygal使用的國別碼存儲(chǔ)在模塊i18n (internationalization的縮寫)中。字典COUNTRIES 包含的鍵和值分別為兩個(gè)字母的國別碼和國家名。要查看這些國別碼,可從模塊i18n 中導(dǎo)入這個(gè)字典,并打印其鍵和值:

pygal.i18n 已經(jīng)不存在了,現(xiàn)在已經(jīng)更改成了 pygal_maps_world ,需要單獨(dú)通過pip下載

C:\>pip install pygal_maps_world
Collecting pygal_maps_world
  Downloading pygal_maps_world-1.0.2.tar.gz (270 kB)
     |████████████████████████████████| 270 kB 598 kB/s
Requirement already satisfied: pygal>=1.9.9 in c:\users\administrator\appdata\local\programs\python\python36\lib\site-packages (from pygal_maps_world) (2.4.0)
Using legacy 'setup.py install' for pygal-maps-world, since package 'wheel' is not installed.
Installing collected packages: pygal-maps-world
    Running setup.py install for pygal-maps-world ... done
Successfully installed pygal-maps-world-1.0.2

代碼:

from pygal_maps_world.i18n import COUNTRIES

for country_code in sorted(COUNTRIES.keys()):
    print(country_code, COUNTRIES[country_code])

測試記錄:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/數(shù)據(jù)可視化/countries.py
ad Andorra
ae United Arab Emirates
af Afghanistan
al Albania
--snip--
yt Mayotte
za South Africa
zm Zambia
zw Zimbabwe

Process finished with exit code 0

為獲取國別碼,我們將編寫一個(gè)函數(shù),它在COUNTRIES 中查找并返回國別碼。我們將這個(gè)函數(shù)放在一個(gè)名為country_codes 的模塊中,以便能夠在可視化程序中導(dǎo)入它
代碼:

from pygal_maps_world.i18n import COUNTRIES

def get_country_code(country_name):
    """ 根據(jù)指定的國家,返回Pygal使用的兩個(gè)字母的國別碼 """
    for code, name in COUNTRIES.items():
        if name == country_name:
            return code

print(get_country_code('Andorra'))
print(get_country_code('United Arab Emirates'))
print(get_country_code('Afghanistan'))

測試記錄:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/數(shù)據(jù)可視化/countries.py
ad
ae
af

Process finished with exit code 0

接下來,在world_population.py中導(dǎo)入get_country_code
代碼:
world_population.py

import json

from country_codes import get_country_code

# 將數(shù)據(jù)加載到一個(gè)列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 打印每個(gè)國家2010年的人口數(shù)量
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:
            print(country_name + ": " + str(population))
        else:
            print('ERROR - ' + country_name)

測試記錄:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/數(shù)據(jù)可視化/world_population.py
ERROR - Arab World
ERROR - Caribbean small states
ERROR - East Asia & Pacific (all income levels)
ERROR - East Asia & Pacific (developing only)
ERROR - Euro area
ERROR - Europe & Central Asia (all income levels)
--snip--
Zambia: 12927000
Zimbabwe: 12571000

Process finished with exit code 0

1.2.5 制作世界地圖

有了國別碼后,制作世界地圖易如反掌。Pygal提供了圖表類型Worldmap ,可幫助你制作呈現(xiàn)各國數(shù)據(jù)的世界地圖。為演示如何使用Worldmap ,我們來創(chuàng)建一個(gè)突出北美、中美和南美的簡單地圖

代碼:
americas.py

import pygal_maps_world.maps

wm = pygal_maps_world.maps.World()
wm.title = 'North, Central, and South America'

wm.add('North America', ['ca', 'mx', 'us'])
wm.add('Central America', ['bz', 'cr', 'gt', 'hn', 'ni', 'pa', 'sv'])
wm.add('South America', ['ar', 'bo', 'br', 'cl', 'co', 'ec', 'gf',
                         'gy', 'pe', 'py', 'sr', 'uy', 've'])

wm.render_to_file('americas.svg')

測試記錄:

image.png

1.2.6 在世界地圖上呈現(xiàn)數(shù)字?jǐn)?shù)據(jù)

為練習(xí)在地圖上呈現(xiàn)數(shù)字?jǐn)?shù)據(jù),我們來創(chuàng)建一幅地圖,顯示三個(gè)北美國家的人口數(shù)量

代碼:
na_populations.py

import pygal_maps_world.maps

wm = pygal_maps_world.maps.World()
wm.title = 'Populations of Countries in North America'
wm.add('North America',{ 'ca': 34126000, 'us':309349000, 'mx':113423000})

wm.render_to_file('na_populations.svg')

測試記錄:

image.png

1.2.7 繪制完整的世界人口地圖

要呈現(xiàn)其他國家的人口數(shù)量,需要將前面處理的數(shù)據(jù)轉(zhuǎn)換為Pygal要求的字典格式:鍵為兩個(gè)字母的國別碼,值為人口數(shù)量。為此,在world_population.py中添加如下代碼
代碼:
world_population.py

import json
import pygal_maps_world.maps
from country_codes import get_country_code

# 將數(shù)據(jù)加載到一個(gè)列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 創(chuàng)建一個(gè)包含人口數(shù)量的字典
cc_pupulations = {}
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        code = get_country_code(country)
        if code:
            cc_pupulations[code] = population

wm = pygal_maps_world.maps.World()
wm.title = 'World Population in 2010, by Country'
wm.add('2010', cc_pupulations)

wm.render_to_file('world_population.svg')

測試記錄:

image.png

1.2.8 根據(jù)人口數(shù)量將國家分組

印度和中國的人口比其他國家多得多,但在當(dāng)前的地圖中,它們的顏色與其他國家差別較小。中國和印度的人口都超過了10億,接下來人口最多的國家是美國,但只有大約3億。下面不將所有國家都作為一個(gè)編組,而是根據(jù)人口數(shù)量分成三組——少于1000萬的、介于1000萬和10億之間的以及超過10億的

代碼:
world_population.py

import json
import pygal_maps_world.maps
from country_codes import get_country_code

# 將數(shù)據(jù)加載到一個(gè)列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 創(chuàng)建一個(gè)包含人口數(shù)量的字典
cc_pupulations = {}
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        code = get_country_code(country)
        if code:
            cc_pupulations[code] = population

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

# 看看每組分別包含多少個(gè)國家
print(len(cc_pops_1), len(cc_pops_2), len(cc_pops_3))

wm = pygal_maps_world.maps.World()
wm.title = 'World Population in 2010, by Country'
wm.add('0-10m', cc_pops_1)
wm.add('10m-1bm', cc_pops_2)
wm.add('>1bn', cc_pops_3)


wm.render_to_file('world_population.svg')

測試記錄:

E:\python\learn_python1\venv\Scripts\python.exe E:/python/learn_python1/數(shù)據(jù)可視化/world_population.py
85 69 2

Process finished with exit code 0

image.png

1.2.9 使用Pygal設(shè)置世界地圖的樣式

在這個(gè)地圖中,根據(jù)人口將國家分組雖然很有效,但默認(rèn)的顏色設(shè)置很難看。例如,在這里,Pygal選擇了鮮艷的粉色和綠色基色。下面使用Pygal樣式設(shè)置指令來調(diào)整顏色。

我們也讓Pygal使用一種基色,但將指定該基色,并讓三個(gè)分組的顏色差別更大.

代碼:
world_population.py

import json
import pygal_maps_world.maps
from pygal.style import RotateStyle
from country_codes import get_country_code

# 將數(shù)據(jù)加載到一個(gè)列表中
filename = 'population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 創(chuàng)建一個(gè)包含人口數(shù)量的字典
cc_pupulations = {}
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        code = get_country_code(country)
        if code:
            cc_pupulations[code] = population

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

# 看看每組分別包含多少個(gè)國家
print(len(cc_pops_1), len(cc_pops_2), len(cc_pops_3))

wm_style = RotateStyle('#336699')
wm = pygal_maps_world.maps.World(style=wm_style)
wm.title = 'World Population in 2010, by Country'
wm.add('0-10m', cc_pops_1)
wm.add('10m-1bm', cc_pops_2)
wm.add('>1bn', cc_pops_3)


wm.render_to_file('world_population.svg')

測試記錄:

image.png

1.2.10 加亮顏色主題

Pygal通常默認(rèn)使用較暗的顏色主題。為方便印刷,我使用LightColorizedStyle 加亮了地圖的顏色。這個(gè)類修改整個(gè)圖表的主題,包括背景色、標(biāo)簽以及各個(gè)國家的顏色。要使用這個(gè)樣式,先導(dǎo)入它.

from pygal.style import LightColorizedStyle

然后就可獨(dú)立地使用LightColorizedStyle 了,例如:

wm_style = LightColorizedStyle

然而使用這個(gè)類時(shí),你不能直接控制使用的顏色,Pygal將選擇默認(rèn)的基色。要設(shè)置顏色,可使用RotateStyle ,并將LightColorizedStyle 作為基本樣式。為此,導(dǎo)入LightColorizedStyle 和RotateStyle

from pygal.style import LightColorizedStyle, RotateStyle

再使用RotateStyle 創(chuàng)建一種樣式,并傳入另一個(gè)實(shí)參base_style:

wm_style = RotateStyle('#336699', base_style=LightColorizedStyle)

這設(shè)置了較亮的主題,同時(shí)根據(jù)通過實(shí)參傳遞的顏色給各個(gè)國家著色。使用這種樣式時(shí),生成的圖表與本書的屏幕截圖更一致。
嘗試為不同的可視化選擇合適的樣式設(shè)置指令時(shí),在import 語句中指定別名會(huì)有所幫助:

from pygal.style import LightColorizedStyle as LCS, RotateStyle as RS

這樣,樣式定義將更短:

wm_style = RS('#336699', base_style=LCS)

通過使用幾個(gè)樣式設(shè)置指令,就能很好地控制圖表和地圖的外觀。

參考

1.Python編程:從入門到實(shí)踐
2.https://github.com/ehmatthes/pcc/tree/master/chapter_16

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容