2020年9月26日星期六

016 Python编程 從入門到實踐, 第二部分 專案2 資料視覺化 第 16 章 下載數據

申明本站飛宇網 https://feiyetopro.blogspot.com/自網路收集整理之書籍文章影音僅供預覽交流學習研究,其[書籍、文章、影音]情節內容, 評論屬其個人行為, 與本網站無關。版權歸原作者和出版社所有,請在下載 24 小時內刪除,不得用作商業用途;如果您喜歡其作品,請支持訂閱購買[正版]謝謝!



  16 章 下載數據

在本章中,你將從網上下載資料,並對這些資料進行視覺化。網上的資料多得難以置信,且大多未經過仔細檢查。如果能夠對這些資料進行分析,你就能發現別人沒有發現的規律和關聯。

我們將訪問並視覺化以兩種常見格式存儲的資料:CSV JSON。我們將使用Python模組csv 來處理以CSV(逗號分隔的值)格式存儲的天氣資料,找出兩個不同地區在一段時間內的最高溫度和最低溫度。然後,我們將使用matplotlib根據下載的資料創建一個圖表,展示兩個不同地區的氣溫變化:阿拉斯加錫特卡和加利福尼亞死亡穀。在本章的後面,我們將使用模組json 來訪問以JSON格式存儲的人口資料,並使用Pygal繪製一幅按國別劃分的人口地圖。

閱讀本章後,你將能夠處理各種類型和格式的資料集,並對如何創建複雜的圖表有更深入的認識。要處理各種真實世界的資料集,必須能夠訪問並視覺化各種類型和格式的線上資料。

16.1 CSV檔案格式

要在文字檔中存儲資料,最簡單的方式是將資料作為一系列以逗號分隔的值 CSV)寫入文件。這樣的檔稱為CSV檔。例如,下面是一行CSV格式的天氣資料:

 

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

 

 

 

 

 

 

 

 

這是阿拉斯加錫特卡201415日的天氣資料,其中包含當天的最高氣溫和最低氣溫,還有眾多其他資料。CSV檔對人來說閱讀起來比較麻煩,但程式可輕鬆地提取並處理其中的值,這有助於加快資料分析過程。

我們將首先處理少量錫特卡的CSV格式的天氣資料,這些資料可在本書的配套資源(https://www.nostarch.com/pythoncrashcourse/ )中找到。請將文件sitka_weather_07-2014.csv複製到存儲本章程式的資料夾中(下載本書的配套資源後,你就有了這個項目所需的所有檔)。

注意  這個專案使用的天氣資料是從http://www.wunderground.com/history/ 下載而來的。

16.1.1 分析CSV檔頭

csv 模組包含在Python標準庫中,可用於分析CSV檔中的資料行,讓我們能夠快速提取感興趣的值。下面先來查看這個文件的第一行,其中包含一系列有關資料的描述:

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)

 

 

 

 

 

 

 

 

導入模組csv 後,我們將要使用的檔的名稱存儲在filename 中。接下來,我們打開這個檔,並將結果檔物件存儲在f 中(見)。然後,我們調用csv.reader() ,並將前面存儲的檔物件作為實參傳遞給它,從而創建一個與該檔相關聯的閱讀器(reader )物件(見)。我們將這個閱讀器物件存儲在reader 中。

模組csv 包含函數next() ,調用它並將閱讀器物件傳遞給它時,它將返回檔中的下一行。在前面的代碼中,我們只調用了next() 一次,因此得到的是檔的第一行,其中包含檔頭(見)。我們將返回的資料存儲在header_row 中。正如你看到的,header_row 包含與天氣相關的檔頭,指出了每行都包含哪些資料:

 

['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']

 

 

 

 

 

 

 

 

reader處理檔中以逗號分隔的第一行資料,並將每項資料都作為一個元素存儲在清單中。檔頭AKDT 表示阿拉斯加時間(Alaska Daylight Time),其位置表明每行的第一個值都是日期或時間。檔頭Max TemperatureF 指出每行的第二個值都是當天的最高華氏溫度。可通過閱讀其他的檔頭來確定檔包含的資訊類型。

注意  檔頭的格式並非總是一致的,空格和單位可能出現在奇怪的地方。這在原始資料檔案中很常見,但不會帶來任何問題。

16.1.2 列印檔案頭及其位置

為讓檔頭資料更容易理解,將列表中的每個檔頭及其位置列印出來:

highs_lows.py

 

  --snip--

  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)

 

 

 

 

 

 

 

 

我們對列表調用了enumerate() (見)來獲取每個元素的索引及其值。(請注意,我們刪除了代碼行print(header_row) ,轉而顯示這個更詳細的版本。)

輸出如下,其中指出了每個檔頭的索引:

 

0 AKDT

1 Max TemperatureF

2 Mean TemperatureF

3 Min TemperatureF

--snip--

20  CloudCover

21  Events

22  WindDirDegrees

 

 

 

 

 

 

 

 

從中可知,日期和最高氣溫分別存儲在第0列和第1列。為研究這些資料,我們將處理sitka_weather_07-2014.csv中的每行資料,並提取其中索引為01的值。

16.1.3 提取並讀取資料

知道需要哪些列中的資料後,我們來讀取一些資料。首先讀取每天的最高氣溫:

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)

 

 

 

 

 

 

 

 

我們創建了一個名為highs 的空列表(見),再遍歷文件中餘下的各行(見)。閱讀器物件從其停留的地方繼續往下讀取CSV檔,每次都自動返回當前所處位置的下一行。由於我們已經讀取了檔頭行,這個迴圈將從第二行開始——從這行開始包含的是實際資料。每次執行該迴圈時,我們都將索引1處(第2列)的資料附加到highs 末尾(見

下面顯示了highs 現在存儲的資料:

 

['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']

 

 

 

 

 

 

 

 

我們提取了每天的最高氣溫,並將它們作為字串整潔地存儲在一個清單中。

下面使用int() 將這些字串轉換為數位,讓matplotlib能夠讀取它們:

highs_lows.py

 

  --snip--

      highs = []

      for row in reader:

         high = int(row[1])

          highs.append(high)

 

      print(highs)

 

 

 

 

 

 

 

 

處,我們將表示氣溫的字串轉換成了數位,再將其附加到列表末尾。這樣,最終的清單將包含以數字表示的每日最高氣溫:

 

[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]

 

 

 

 

 

 

 

 

下面來對這些資料進行視覺化。

16.1.4 繪製氣溫圖表

為視覺化這些氣溫資料,我們首先使用matplotlib創建一個顯示每日最高氣溫的簡單圖形,如下所示:

highs_lows.py

 

  import csv

 

  from matplotlib import pyplot as plt

 

  # 從文件中獲取最高氣溫

  --snip--

 

  # 根據資料繪製圖形

  fig = plt.figure(dpi=128, figsize=(10, 6))

 plt.plot(highs, c='red')

 

  # 設置圖形的格式

 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()

 

 

 

 

 

 

 

 

我們將最高氣溫列表傳給plot() (見),並傳遞c='red' 以便將資料點繪製為紅色(紅色顯示最高氣溫,藍色顯示最低氣溫)。接下來,我們設置了一些其他的格式,如字體大小和標籤(見),這些都在第15章介紹過。鑒於我們還沒有添加日期,因此沒有給x 軸添加標籤,但plt.xlabel() 確實修改了字體大小,讓預設標籤更容易看清。圖16-1顯示了繪製的圖表:一個簡單的折線圖,顯示了阿拉斯加錫特卡20147月每天的最高氣溫。


16-1 阿拉斯加錫特卡20147月每日最高氣溫折線圖

16.1.5 模組datetime

下麵在圖表中添加日期,使其更有用。在天氣資料檔案中,第一個日期在第二行:

 

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

 

 

 

 

 

 

 

 

讀取該資料時,獲得的是一個字串,因為我們需要想辦法將字串'2014-7-1' 轉換為一個表示相應日期的物件。為創建一個表示201471日的物件,可使用模組datetime 中的方法strptime() 。我們在終端會話中看看strptime() 的工作原理:

 

>>> from datetime import datetime

>>> first_date = datetime.strptime('2014-7-1', '%Y-%m-%d')

>>> print(first_date)

2014-07-01 00:00:00

 

 

 

 

 

 

 

 

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

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

16-1 模組datetime中設置日期和時間格式的實參

實參

含義

%A

星期的名稱,如Monday

%B

月份名,如January

%m

用數字表示的月份(01~12

%d

用數字表示月份中的一天(01~31

%Y

四位的年份,如2015

%y

兩位的年份,如15

%H

24小時制的小時數(00~23

%I

12小時制的小時數(01~12

%p

ampm

%M

分鐘數(00~59

%S

秒數(00~61

16.1.6 在圖表中添加日期

知道如何處理CSV檔中的日期後,就可對氣溫圖形進行改進了,即提取日期和最高氣溫,並將它們傳遞給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)

 

  # 根據資料繪製圖形

  fig = plt.figure(dpi=128, figsize=(10, 6))

 plt.plot(dates, highs, c='red')

 

  # 設置圖形的格式

  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()

 

 

 

 

 

 

 

 

我們創建了兩個空清單,用於存儲從檔中提取的日期和最高氣溫(見)。然後,我們將包含日期資訊的資料(row[0] )轉換為datetime 對象(見),並將其附加到列表dates 末尾。在處,我們將日期和最高氣溫值傳遞給plot() 。在處,我們調用了fig.autofmt_xdate() 來繪製斜的日期標籤,以免它們彼此重疊。圖16-2顯示了改進後的圖表。


16-2 現在圖表的 x 軸上有日期,含義更豐富

16.1.7 涵蓋更長的時間

設置好圖表後,我們來添加更多的資料,以成一幅更複雜的錫特卡天氣圖。請將文件sitka_weather_2014.csv複製到存儲本章程式的資料夾中,該檔包含Weather Underground提供的整年的錫特卡天氣資料。

現在可以創建覆蓋整年的天氣圖了:

highs_lows.py

 

  --snip--

  # 從檔中獲取日期和最高氣溫

 filename = 'sitka_weather_2014.csv'

  with open(filename) as f:

  --snip--

  # 設置圖形的格式

 plt.title("Daily high temperatures - 2014", fontsize=24)

  plt.xlabel('', fontsize=16)

  --snip--

 

 

 

 

 

 

 

 

我們修改了檔案名,以使用新的資料檔案sitka_weather_2014.csv(見);我們還修改了圖表的標題,以反映其內容的變化(見)。16-3顯示了生成的圖形。


16-3 一年的天氣資料

16.1.8 再繪製一個資料數列

16-3所示的改進後的圖表顯示了大量意義深遠的資料,但我們可以在其中再添加最低氣溫資料,使其更有用。為此,需要從資料檔案中提取最低氣溫,並將它們添加到圖表中,如下所示:

highs_lows.py

 

  --snip--

  # 從檔中獲取日期、最高氣溫和最低氣溫

  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)

  # 根據資料繪製圖形

  fig = plt.figure(dpi=128, figsize=(10, 6))

  plt.plot(dates, highs, c='red')

 plt.plot(dates, lows, c='blue')

 

  # 設置圖形的格式

 plt.title("Daily high and low temperatures - 2014", fontsize=24)

  --snip--

 

 

 

 

 

 

 

 

處,我們添加了空清單lows ,用於存儲最低氣溫。接下來,我們從每行的第4列(row[3] )提取每天的最低氣溫,並存儲它們(見)。在處,我們添加了一個對plot() 的調用,以使用藍色繪製最低氣溫。最後,我們修改了標題(見)。16-4顯示了這樣繪製出來的圖表。


16-4 在一個圖表中包含兩個資料數列

16.1.9 給圖表區域著色

添加兩個資料數列後,我們就可以瞭解每天的氣溫範圍了。下面來給這個圖表做最後的修飾,通過著色來呈現每天的氣溫範圍。為此,我們將使用方法fill_between() ,它接受一個 x 值系列和兩個 y 值系列,並填充兩個 y 值系列之間的空間:

highs_lows.py

 

  --snip--

  # 根據資料繪製圖形

  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)

  --snip--

 

 

 

 

 

 

 

 

處的實參alpha 指定顏色的透明度。Alpha 值為0表示完全透明,1(默認設置)表示完全不透明。通過將alpha 設置為0.5,可讓紅色和藍色折線的顏色看起來更淺。

處,我們向fill_between() 傳遞了一個 x 值系列:清單dates ,還傳遞了兩個 y 值系列:highs lows 。實參facecolor 指定了填充區域的顏色,我們還將alpha 設置成了較小的值0.1,讓填充區域將兩個資料數列連接起來的同時不分散觀察者的注意力。圖16-5顯示了最高氣溫和最低氣溫之間的區域被填充的圖表。


16-5 給兩個資料集之間的區域著色

通過著色,讓兩個資料集之間的區域顯而易見。

16.1.10 錯誤檢查

我們應該能夠使用有關任何地方的天氣資料來運行highs_lows.py中的代碼,但有些氣象站會偶爾出現故障,未能收集部分或全部其應該收集的資料。缺失資料可能會引發異常,如果不妥善地處理,還可能導致程式崩潰。

例如,我們來看看生成加利福尼亞死亡穀的氣溫圖時出現的情況。將文件death_valley_2014.csv複製到本章程式所在的資料夾,再修改highs_lows.py,使其生成死亡穀的氣溫圖:

highs_lows.py

 

--snip--

從檔中獲取日期、最高氣溫和最低氣溫

filename = 'death_valley_2014.csv'

with open(filename) as f:

--snip--

 

 

 

 

 

 

 

 

運行這個程式時,出現了一個錯誤,如下述輸出的最後一行所示:

 

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無法處理其中一天的最高氣溫,因為它無法將空字串(' ' )轉換為整數。只要看一下death_valley_2014.csv,就能發現其中的問題:

 

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

 

 

 

 

 

 

 

 

其中好像沒有記錄2014216日的資料,表示最高溫度的字串為空。為解決這種問題,我們在從CSV檔中讀取值時執行錯誤檢查代碼,對分析資料集時可能出現的異常進行處理,如下所示:

highs_lows.py

 

  --snip--

  # 從檔中獲取日期、最高氣溫和最低氣溫

  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)

 

   #根據資料繪製圖形

   --snip--

 

   #設置圖形的格式

 title = "Daily high and low temperatures - 2014\nDeath Valley, CA"

   plt.title(title, fontsize=20)

   --snip--

 

 

 

 

 

 

 

 

對於每一行,我們都嘗試從中提取日期、最高氣溫和最低氣溫(見)。只要缺失其中一項資料,Python就會引發ValueError 異常,而我們可這樣處理:列印一條錯誤消息,指出缺失資料的日期(見)。列印錯誤消息後,迴圈將接著處理下一行。如果獲取特定日期的所有資料時沒有發生錯誤,將運行else 代碼塊,並將資料附加到相應清單的末尾(見)。鑒於我們繪圖時使用的是有關另一個地方的資訊,我們修改了標題,在圖表中指出了這個地方(見

如果你現在運行highs_lows.py ,將發現缺失資料的日期只有一個:

 

2014-02-16 missing data

 

 

 

 

 

 

 

 

16-6顯示了繪製出的圖形。


16-6 死亡谷每日高氣溫和最低氣溫

將這個圖表與錫特卡的圖表對比可知,總體而言,死亡穀比阿拉斯加東南部暖和,這可能符合預期,但這個沙漠中每天的溫差也更大,從著色區域的高度可以明顯看出這一點。

使用的很多資料集都可能缺失資料、資料格式不正確或資料本身不正確。對於這樣的情形,可使用本書前半部分介紹的工具來處理。在這裡,我們使用了一個try-except-else 代碼塊來處理資料缺失的問題。在有些情況下,需要使用continue 來跳過一些資料,或者使用remove() del 將已提取的資料刪除。可採用任何管用的方法,只要能進行精確而有意義的視覺化就好。

動手試一試

16-1 三藩市 :三藩市的氣溫更接近於錫特卡還是死亡穀呢?請繪製一個顯示三藩市最高氣溫和最低氣溫的圖表,並進行比較。可從http://www.wunderground.com/history/ 下載幾乎任何地方的天氣資料。為此,請輸入相應的地方和日期範圍,滾動到頁面底部,找到名為Comma-Delimited File的連結,再按一下該連結,將資料存儲為CSV檔。

16-2 比較錫特卡和死亡穀的氣溫 :在有關錫特卡和死亡穀的圖表中,氣溫刻度反映了資料範圍的不同。為準確地比較錫特卡和死亡穀的氣溫範圍,需要在y 軸上使用相同的刻度。為此,請修改圖16-5和圖16-6所示圖表的y 軸設置,對錫特卡和死亡穀的氣溫範圍進行直接比較(你也可以對任何兩個地方的氣溫範圍進行比較)。你還可以嘗試在一個圖表中呈現這兩個資料集。

16-3 降雨量 :選擇你感興趣的任何地方,通過視覺化將其降雨量呈現出來。為此,可先只涵蓋一個月的資料,確定代碼正確無誤後,再使用一整年的資料來運行它。

16-4 探索 :生成一些圖表,對你好奇的任何地方的其他天氣資料進行研究。

16.2 製作世界人口地圖:JSON格式

在本節中,你將下載JSON格式的人口資料,並使用json 模組來處理它們。Pygal提供了一個適合初學者使用的地圖創建工具,你將使用它來對人口資料進行視覺化,以探索全球人口的分佈情況。

16.2.1 下載世界人口資料

將文件population_data.json複製到本章程式所在的資料夾中,這個檔包含全球大部分國家1960~2010年的人口資料。Open Knowledge Foundationhttp://data.okfn.org/ )提供了大量可以免費使用的資料集,這些資料就來自其中一個資料集。

16.2.2 提取相關的資料

我們來研究一下population_data.json,看看如何著手處理這個檔中的資料:

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--

]

 

 

 

 

 

 

 

 

這個檔實際上就是一個很長的Python清單,其中每個元素都是一個包含四個鍵的字典:國家名、國別碼、年份以及表示人口數量的值。我們只關心每個國家2010年的人口數量,因此我們首先編寫一個列印這些資訊的程式:

world_population.py

 

  import json

 

  # 將資料載入到一個清單中

  filename = 'population_data.json'

  with open(filename) as f:

     pop_data = json.load(f)

 

  # 列印每個國家2010年的人口數量

 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)

 

 

 

 

 

 

 

 

我們首先導入了模組json ,以便能夠正確地載入檔中的資料,然後,我們將資料存儲在pop_data 中(見)。函數json.load() 將資料轉換為Python能夠處理的格式,這裡是一個列表。在處,我們遍歷pop_data 中的每個元素。每個元素都是一個字典,包含四個鍵值對,我們將每個字典依次存儲在pop_dict 中。

處,我們檢查字典的'Year' 鍵對應的值是否是2010(由於population_data.json中的值都是用引號括起的,因此我們執行的是字串比較)。如果年份為2010,我們就將與'Country Name' 相關聯的值存儲到country_name 中,並將與'Value' 相關聯的值存儲在population 中(見)。接下來,我們列印每個國家的名稱和人口數量。

輸出為一系列國家的名稱和人口數量:

 

Arab World: 357868000

Caribbean small states: 6880000

East Asia & Pacific (all income levels): 2201536674

--snip--

Zimbabwe: 12571000

 

 

 

 

 

 

 

 

我們捕獲的資料並非都包含準確的國家名,但這開了一個好頭。現在,我們需要將資料轉換為Pygal能夠處理的格式。

16.2.3 將字串轉換為數位值

population_data.json中的每個鍵和值都是字串。為處理這些人口資料,我們需要將表示人口數量的字串轉換為數位值,為此我們使用函數int() 

world_population.py

 

  --snip--

  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))

 

 

 

 

 

 

 

 

處,我們將每個人口數量詞都存儲為數位格式。列印人口數量詞時,需要將其轉換為字串(見

然而,對於有些值,這種轉換會導致錯誤,如下所示:

 

  Arab World: 357868000

  Caribbean small states: 6880000

  East Asia & Pacific (all income levels): 2201536674

  --snip--

  Traceback (most recent call last):

    File "print_populations.py", line 12, in <module>

      population = int(pop_dict['Value'])

 ValueError: invalid literal for int() with base 10: '1127437398.85751'

 

 

 

 

 

 

 

 

原始資料的格式常常不統一,因此經常會出現錯誤。導致上述錯誤的原因是,Python不能直接將包含小數點的字串'1127437398.85751' 轉換為整數(這個小數值可能是人口資料缺失時通過插值得到的)。為消除這種錯誤,我們先將字串轉換為浮點數,再將浮點數轉換為整數:

world_population.py

 

--snip--

for pop_dict in pop_data:

    if pop_dict['Year'] == '2010':

        country = pop_dict['Country Name']

        population = int(float(pop_dict['Value']))

        print(country + ": " + str(population))

 

 

 

 

 

 

 

 

函數float() 將字串轉換為小數,而函數int() 丟棄小數部分,返回一個整數。現在,我們可以列印2010年的完整人口資料,不會導致錯誤了:

 

Arab World: 357868000

Caribbean small states: 6880000

East Asia & Pacific (all income levels): 2201536674

--snip--

Zimbabwe: 12571000

 

 

 

 

 

 

 

 

每個字串都成功地轉換成了浮點數,再轉換為整數。以數位格式存儲人口數量詞後,就可以使用它們來製作世界人口地圖了。

16.2.4 獲取兩個字母的國別碼

製作地圖前,還需要解決資料存在的最後一個問題。Pygal中的地圖製作工具要求資料為特定的格式:用國別碼表示國家,以及用數字表示人口數量。處理地理政治資料時,經常需要用到幾個標準化國別碼集。population_data.json中包含的是三個字母的國別碼,但Pygal使用兩個字母的國別碼。我們需要想辦法根據國家名獲取兩個字母的國別碼。

Pygal使用的國別碼存儲在模組i18n internationalization的縮寫)中。字典COUNTRIES 包含的鍵和值分別為兩個字母的國別碼和國家名。要查看這些國別碼,可從模組i18n 中導入這個字典,並列印其鍵和值:

countries.py

 

  from pygal.i18n import COUNTRIES

 

 for country_code in sorted(COUNTRIES.keys()):

      print(country_code, COUNTRIES[country_code])

 

 

 

 

 

 

 

 

在上面的for 迴圈中,我們讓Python將鍵按字母順序排序(見),然後列印每個國別碼及其對應的國家:

 

ad Andorra

ae United Arab Emirates

af Afghanistan

--snip--

zw Zimbabwe

 

 

 

 

 

 

 

 

為獲取國別碼,我們將編寫一個函數,它在COUNTRIES 中查找並返回國別碼。我們將這個函數放在一個名為country_codes 的模組中,以便能夠在視覺化程式中導入它:

country_codes.py

 

  from pygal.i18n import COUNTRIES

 

 def get_country_code(country_name):

      """根據指定的國家,返回Pygal使用的兩個字母的國別碼"""

     for code, name in COUNTRIES.items():

         if name == country_name:

              return code

      # 如果沒有找到指定的國家,就返回None

     return None

 

  print(get_country_code('Andorra'))

  print(get_country_code('United Arab Emirates'))

  print(get_country_code('Afghanistan'))

 

 

 

 

 

 

 

 

get_country_code() 接受國家名,並將其存儲在形參country_name 中(見)。接下來,我們遍歷COUNTRIES 中的國家名國別碼對(見);如果找到指定的國家名,就返回相應的國別碼(見)。在迴圈後面,我們在沒有找到指定的國家名時返回None (見)。最後,我們使用了三個國家名來調用這個函數,以核實它能否正確地工作。與預期的一樣,這個程式輸出了三個由兩個字母組成的國別碼:

 

ad

ae

af

 

 

 

 

 

 

 

 

使用這個函數前,先將country_codes.py中的print 語句刪除。

接下來,在world_population.py中導入get_country_code 

world_population.py

 

  import json

 

  from country_codes import get_country_code

  --snip--

 

  # 列印每個國家2010年的人口數量

  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(code + ": "+ str(population))

         else:

              print('ERROR - ' + country_name)

 

 

 

 

 

 

 

 

提取國家名和人口數量後,我們將國別碼存儲在code 中,如果沒有國別碼,就在其中存儲None (見)。如果返回了國別碼,就列印國別碼和相應國家的人口數量(見)。如果沒有找到國別碼,就顯示一條錯誤消息,其中包含無法找到國別碼的國家的名稱(見)。如果你運行這個程式,將看到一些國別碼和相應國家的人口數量,還有一些錯誤消息:

 

ERROR - Arab World

ERROR - Caribbean small states

ERROR - East Asia & Pacific (all income levels)

--snip--

af: 34385000

al: 3205000

dz: 35468000

--snip--

ERROR - Yemen, Rep.

zm: 12927000

zw: 12571000

 

 

 

 

 

 

 

 

導致顯示錯誤消息的原因有兩個。首先,並非所有人口數量對應的都是國家,有些人口數量對應的是地區(阿拉伯世界)和經濟類群(所有收入水準)。其次,有些統計資料使用了不同的完整國家名(如Yemen, Rep.,而不是Yemen)。當前,我們將忽略導致錯誤的資料,看看根據成功恢復了的資料製作出的地圖是什麼樣的。

16.2.5 製作世界地圖

有了國別碼後,製作世界地圖易如反掌。Pygal提供了圖表類型Worldmap ,可説明你製作呈現各國資料的世界地圖。為演示如何使用Worldmap ,我們來創建一個突出北美、中美和南美的簡單地圖:

americas.py

 

  import pygal

 

 wm = pygal.Worldmap()

  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')

 

 

 

 

 

 

 

 

處,我們創建了一個Worldmap 實例,並設置了該地圖的的title 屬性。在處,我們使用了方法add() ,它接受一個標籤和一個列表,其中後者包含我們要突出的國家的國別碼。每次調用add() 都將為指定的國家選擇一種新顏色,並在圖表左邊顯示該顏色和指定的標籤。我們要以同一種顏色顯示整個北美地區,因此第一次調用add() 時,在傳遞給它的列表中包含'ca' 'mx' 'us' ,以同時突出加拿大、墨西哥和美國。接下來,對中美和南美國家做同樣的處理。

處的方法render_to_file() 創建一個包含該圖表的.svg檔,你可以在流覽器中打開它。輸出是一幅以不同顏色突出北美、中美和南美的地圖,如圖16-7所示。


16-7 圖表類型Worldmap 的一個簡單實例

知道如何創建包含彩色區域、顏色標示和標籤的地圖後,我們在地圖中添加資料,以顯示有關國家的資訊。

16.2.6 在世界地圖上呈現數位資料

為練習在地圖上呈現數位資料,我們來創建一幅地圖,顯示三個北美國家的人口數量:

na_populations.py

 

  import pygal

 

  wm = pygal.Worldmap()

  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')

 

 

 

 

 

 

 

 

首先,創建了一個Worldmap 實例並設置了標題。接下來,使用了方法add() ,但這次通過第二個實參傳遞了一個字典而不是清單(見)。這個字典將兩個字母的Pygal國別碼作為鍵,將人口數量作為值。Pygal根據這些數位自動給不同國家著以深淺不一的顏色(人口最少的國家顏色最淺,人口最多的國家顏色最深),如圖16-8所示。


16-8 北美國家的人口數量

這幅地圖具有交互性:如果你將滑鼠指向某個國家,將看到其人口數量。下面在這個地圖中添加更多的資料。

16.2.7 繪製完整的世界人口地圖

要呈現其他國家的人口數量,需要將前面處理的資料轉換為Pygal要求的字典格式:鍵為兩個字母的國別碼,值為人口數量。為此,在world_population.py中添加如下代碼:

world_population.py

 

  import json

 

  import pygal

 

  from country_codes import get_country_code

 

  # 將數據載入到清單中

  --snip--

 

  # 創建一個包含人口數量的字典

 cc_populations = {}

  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_populations[code] = population

 

 wm = pygal.Worldmap()

  wm.title = 'World Population in 2010, by Country'

 wm.add('2010', cc_populations)

 

  wm.render_to_file('world_population.svg')

 

 

 

 

 

 

 

 

我們首先導入了pygal 。在處,我們創建了一個空字典,用於以Pygal要求的格式存儲國別碼和人口數量。在處,如果返回了國別碼,就將國別碼和人口數量分別作為鍵和值填充字典cc_populations 。我們還刪除了所有的print 語句。

處,我們創建了一個Worldmap 實例,並設置其title 屬性。在處,我們調用了add() ,並向它傳遞由國別碼和人口數量組成的字典。圖16-9顯示了生成的地圖。


16-9 2010年的世界人口數量

有幾個國家沒有相關的資料,我們將其顯示為黑色,但對於大多數國家,都根據其人口數量進行了著色。本章後面將處理資料缺失的問題,這裡先來修改著色,以更準確地反映各國的人口數量。在當前的地圖中,很多國家都是淺色的,只有兩個國家是深色的。對大多數國家而言,顏色深淺的差別不足以反映其人口數量的差別。為修復這種問題,我們將根據人口數量將國家分組,再分別給每個組著色。

16.2.8 根據人口數量將國家分組

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

world_population.py

 

  --snip--

  # 創建一個包含人口資料的字典

  cc_populations = {}

  for pop_dict in pop_data:

      if pop_dict['Year'] == '2010':

          --snip--

          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

 

   # 看看每組分別包含多少個國家

 print(len(cc_pops_1), len(cc_pops_2), len(cc_pops_3))

 

   wm = pygal.Worldmap()

   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')

 

 

 

 

 

 

 

 

為將國家分組,我們創建了三個空字典(見)。接下來,遍cc_populations ,檢查每個國家的人口數量(見)。if-elif-else 代碼塊將每個國別碼-人口數量對加入到合適的字典(cc_pops_1 cc_pops_2 cc_pops_3 )中。

處,我們列印這些字典的長度,以獲悉每個分組的規模。繪製地圖時,我們將全部三個分組都添加到Worldmap 中(見)。如果你現在運行這個程式,首先看到的將是每個分組的規模:

 

85 69 2

 

 

 

 

 

 

 

 

上述輸出表明,人口少於1000萬的國家有85個,人口介於1000萬和10億之間的國家有69個,還有兩個國家比較特殊,其人口都超過了10億。這樣的分組看起來足夠了,讓地圖包含豐富的資訊。圖16-10顯示了生成的地圖。


16-10 分三組顯示的世界各國人口

現在使用了三種不同的顏色,讓我們能夠看出人口數量上的差別。在每組中,各個國家都按人口從少到多著以從淺到深的顏色。

16.2.9 使用Pygal設置世界地圖的樣式

在這個地圖中,根據人口將國家分組雖然很有效,但默認的顏色設置很難看。例如,在這裡,Pygal選擇了鮮豔的粉色和綠色基色。下面使用Pygal樣式設置指令來調整顏色。

我們也讓Pygal使用一種基色,但將指定該基色,並讓三個分組的顏色差別更大:

world_population.py

 

  import json

 

  import pygal

 from pygal.style import RotateStyle

  --snip--

  # 根據人口數量將所有的國家分成三組

  cc_pops_1, cc_pops_2, cc_pops_3 = {}, {}, {}

  for cc, pop in cc_populations.items():

      if pop < 10000000:

          --snip--

 

 wm_style = RotateStyle('#336699')

 wm = pygal.Worldmap(style=wm_style)

  wm.title = 'World Population in 2010, by Country'

  --snip--

 

 

 

 

 

 

 

 

Pygal樣式存儲在模組style 中,我們從這個模組中導入了樣式RotateStyle (見)。創建這個類的實例時,需要提供一個實參——十六進位的RGB顏色(見);Pygal將根據指定的顏色為每組選擇顏色。十六進位格式 RGB顏色是一個以井號(#)打頭的字串,後面跟著6個字元,其中前兩個字元表示紅色分量,接下來的兩個表示綠色分量,最後兩個表示藍色分量。每個分量的取值範圍為00 (沒有相應的顏色)~FF (包含最多的相應顏色)。如果你線上搜索hex color chooser十六進位顏色選擇器 ),可找到讓你能夠嘗試選擇不同的顏色並顯示其RGB值的工具。這裡使用的顏色值(#336699)混合了少量的紅色(33)、多一些的綠色(66)和更多一些的藍色(99),它為RotateStyle 提供了一種淡藍色基色。

RotateStyle 返回一個樣式物件,我們將其存儲在wm_style 中。為使用這個樣式物件,我們在創建Worldmap 實例時以關鍵字實參的方式傳遞它(見)。更新後的地圖如圖16-11所示。


16-11 按人口劃分的三個國家編組了統一的顏色主題

前面的樣式設置讓地圖的顏色更一致,也更容易區分不同的編組。

16.2.10 加亮顏色主題

Pygal通常預設使用較暗的顏色主題。為方便印刷,我使用LightColorizedStyle 加亮了地圖的顏色。這個類修改整個圖表的主題,包括背景色、標籤以及各個國家的顏色。要使用這個樣式,先導入它:

 

from pygal.style import LightColorizedStyle

 

 

 

 

 

 

 

 

然後就可獨立地使用LightColorizedStyle 了,例如:

 

wm_style = LightColorizedStyle

 

 

 

 

 

 

 

 

然而使用這個類時,你不能直接控制使用的顏色,Pygal將選擇默認的基色。要設置顏色,可使用RotateStyle ,並將LightColorizedStyle 作為基本樣式。為此,導入LightColorizedStyle RotateStyle 

 

from pygal.style import LightColorizedStyle, RotateStyle

 

 

 

 

 

 

 

 

再使用RotateStyle 創建一種樣式,並傳入另一個實參base_style 

 

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

 

 

 

 

 

 

 

 

這設置了較亮的主題,同時根據通過實參傳遞的顏色給各個國家著色。使用這種樣式時,生成的圖表與本書的螢幕截圖更一致。

嘗試為不同的視覺化選擇合適的樣式設置指令時,在import 語句中指定別名會有所幫助:

 

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

 

 

 

 

 

 

 

 

這樣,樣式定義將更短:

 

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

 

 

 

 

 

 

 

 

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

動手試一試

16-5 涵蓋所有國家 :本節制作人口地圖時,對於大約12個國家,程式不能自動確定其兩個字母的國別碼。請找出這些國家,在字典COUNTRIES 中找到它們的國別碼;然後,對於每個這樣的國家,都在get_country_code() 中添加一個if-elif 代碼塊,以返回其國別碼:

 

if country_name == 'Yemen, Rep.'

    return 'ye'

elif --snip--

 

 

 

 

 

 

 

 

將這些代碼放在遍歷COUNTRIES 的迴圈和語句return None 之間。完成這樣的修改後,你看到的地圖將更完整。

16-6 國內生產總值 Open Knowledge Foundation提供了一個資料集,其中包含全球各國的國內生產總值(GDP),可在http://data.okfn.org/data/core/gdp/ 找到這個資料集。請下載這個資料集的JSON版本,並繪製一個圖表,將全球各國最近一年的GDP呈現出來。

16-7 選擇你自己的資料 :世界銀行(The World Bank)提供了很多資料集,其中包含有關全球各國的資訊。請訪問http://data.worldbank.org/indicator/ ,並找到一個你感興趣的資料集。按一下該資料集,再按一下連結Download Data並選擇CSV。你將收到三個CSV檔,其中兩個包含字樣Metadata,你應使用第三個CSV檔。編寫一個程式,生成一個字典,它將兩個字母的Pygal國別碼作為鍵,並將你從這個檔中選擇的資料作為值。使用Worldmap 製作一個地圖,在其中呈現這些資料,並根據你的喜好設置這個地圖的樣式。

16-8 測試模組country_codes :我們編寫模組country_codes 時,使用了print 語句來核實get_country_code() 能否按預期那樣工作。請利用你在第11章學到的知識,為這個函數編寫合適的測試。

16.3 小結

在本章中,你學習了:如何使用網上的資料集;如何處理CSVJSON檔,以及如何提取你感興趣的資料;如何使用matplotlib來處理以往的天氣資料,包括如何使用模組datetime ,以及如何在同一個圖表中繪製多個資料數列;如何使用Pygal繪製呈現各國資料的世界地圖,以及如何設置Pygal地圖和圖表的樣式。

有了使用CSVJSON檔的經驗後,你將能夠處理幾乎任何要分析的資料。大多數線上資料集都可以以這兩種格式中的一種或兩種下載。學習使用這兩種格式為學習使用其他格式的資料做好了準備。

在下一章,你將編寫自動從網上採集資料並對其進行視覺化的程式。如果你只是將程式設計作為業餘愛好,學會這些技能可以增加樂趣;如果你有志于成為專業程式師,就必須掌握這些技能。

0 留言:

發佈留言