從 pandas 開始 Python 與資料科學之旅

仿效 Tidyverse 的學習模式

Yao-Jen Kuo
數聚點文摘
21 min readDec 12, 2017

--

我們介紹過從 Tidyverse 中的 dplyr 與 ggplot2 套件開始學習 R 語言而非傳統 Base R First 的方式;這樣的學習模式假使套用在 Python 中的話,不從變數類型、資料結構、流程控制、迴圈、自訂函數與類別談起,又該如何開始學習 Python 與資料科學?

Photo on Visual hunt

我很快就聯想到 pandas 套件,pandas 取名自 pan(el)-da(ta)-s,也與套件主要提供的三個資料結構:PanelDataFrameSeries 相呼應,她的 GitHub repository 是如此介紹:

Flexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more.

有別於 dplyr、ggplot2 等 Tidyverse 套件各司其職的分工,pandas 自己就能處理載入、整理與視覺化等常見的資料應用。讓我們同樣以 gapminder 作為範例資料集(若您對 gapminder 資料集不熟悉,可以參考這兩篇文章的其中一篇 Tidyverse:R 語言學習之旅的新起點R 語言動態視覺化的 Hello World),來對照練習 dplyr 與 ggplot2 的功能。

這篇文章所使用的程式與圖形都可以在這個 Notebook 找到。

成為 DataInPoint 的贊助者

資料載入

pandas 可以支援多種文字、二進位檔案與資料庫的資料載入,常見的 txt、csv、excel 試算表、MySQL 或 PostgreSQL 都難不倒,如果對詳細的清單有興趣,可以參考 pandas 0.21.0 documentation

我們已經將 R 語言的 gapminder 資料匯出成為常見的 csv 文字檔與 excel 試算表儲存在雲端,打開 Jupyter Notebook 先將 csv 文字檔載入:

import pandas as pd# 讀入 csv 文字檔
csv_file = "https://storage.googleapis.com/learn_pd_like_tidyverse/gapminder.csv"
gapminder = pd.read_csv(csv_file)
print(type(gapminder))
gapminder.head()
讀入 csv 文字檔

再試一下將 excel 試算表載入:

# 讀入 excel 試算表
xlsx_file = "https://storage.googleapis.com/learn_pd_like_tidyverse/gapminder.xlsx"
gapminder = pd.read_excel(xlsx_file)
print(type(gapminder))
gapminder.head()
讀入 excel 試算表

pandas 有一些好用的屬性與方法可以快速暸解一個 DataFrame 的外觀與內容:

  • df.shape:這個 DataFrame 有幾列有幾欄
  • df.columns:這個 DataFrame 的變數資訊
  • df.index:這個 DataFrame 的列索引資訊
  • df.info():關於 DataFrame 的詳細資訊
  • df.describe():關於 DataFrame 各數值變數的描述統計
好用的屬性
好用的方法

資料整理

dplyr 的基本功能是六個能與 SQL 查詢語法相互呼應的函數:

  • filter() 函數:SQL 查詢中的 where 描述
  • select() 函數:SQL 查詢中的 select 描述
  • mutate() 函數:SQL 查詢中的衍生欄位描述
  • arrange() 函數:SQL 查詢中的 order by 描述
  • summarise() 函數:SQL 查詢中的聚合函數描述
  • group_by() 函數:SQL 查詢中的 group by 描述

撰寫布林判斷條件將符合條件的觀測值從資料框中篩選出,實踐 filter() 函數的功能,例如選出臺灣:

gapminder[gapminder['country'] == 'Taiwan']
選出臺灣

如果有多個條件,可以使用 |& 符號連結,例如選出 2007 年的亞洲國家:

gapminder[gapminder[(gapminder['year'] == 2007) & (gapminder['continent'] == 'Asia')]]
選出 2007 年的亞洲國家

list 標註變數名稱可以將變數從資料框中選出,實踐 select() 函數的功能,例如選出 country 與 continent 變數:

gapminder[['country', 'continent']]
選出 country 與 continent 變數

如果只選一個變數且沒有以 list 標註,同樣能選出變數,但是型別會變為 Series

country = gapminder['country']
print(type(country))
型別為 Series

直接撰寫衍生公式並為變數命名即可實踐 mutate() 函數的功能,搭配 apply() 與 lambda 函數將公式應用到每一個觀測值,例如新增一個 country_abb 變數擷取原本 country 變數的前三個英文字母:

gapminder['country_abb'] = gapminder['country'].apply(lambda x: x[:3])
gapminder
擷取原本 country 變數的前三個英文字母

呼叫 DataFrame 不同的聚合函數針對欄位計算,實踐 summarise() 函數的功能,例如計算 2007 年全球人口總數:

gapminder[gapminder['year'] == 2007][['pop']].sum()
2007 年全球人口總數

或者計算 2007 年全球的平均壽命、平均財富:

gapminder[gapminder['year'] == 2007][['lifeExp', 'gdpPercap']].mean()
2007 年全球的平均壽命、平均財富

最後是呼叫 DataFrame 的 groupby 方法實踐 group_by() 函數的功能,例如計算 2007 年各洲人口總數:

gapminder[gapminder['year'] == 2007].groupby(by = 'continent')['pop'].sum()
2007 年各洲人口總數

或者計算 2007 年各洲平均壽命、平均財富:

gapminder[gapminder['year'] == 2007].groupby(by = 'continent')[['lifeExp', 'gdpPercap']].mean()
2007 年各洲平均壽命、平均財富

資料視覺化

Python 視覺化的基石是 Matplotlib 套件的 pyplot,她的繪圖哲學是將圖形的元素,例如座標軸、線、點或者文字用不同的方法一一拼湊起來,優點是繪圖的彈性非常高,缺點則是對於初學者的門檻略高。為了解決這個問題,pandas 套件將 matplotlib.pyplot 的基礎圖形包裝起來成為一個方法,讓使用者只要呼叫 df.plot() 就能夠便利地繪圖,可以選擇的圖形種類相當豐富,只要指定 kind = 參數即可:

  • ‘line’ : 線圖(預設)
  • ‘bar’ : 垂直長條圖
  • ‘barh’ : 水平長條圖
  • ‘hist’ : 直方圖
  • ‘box’ : 盒鬚圖
  • ‘scatter’ : 散佈圖
  • ‘hexbin’ : hexbin plot
  • …etc.

在作圖之前我們載入 matplotlib.pyplot 與 seaborn,前者是繪圖的基礎套件,後者是讓圖形的樣式美觀:

import matplotlib.pyplot as plt
import seaborn as sns

視覺化時間與數值:線圖

將臺灣資料篩選出來並繪製從 1952 年至 2007 年的人口變化:

gapminder_twn = gapminder[gapminder['country'] == 'Taiwan']
gapminder_twn[['year', 'pop']].plot(kind = 'line', x = 'year', y = 'pop', title = 'Pop vs. Year in Taiwan', legend = False)
plt.show()
臺灣從 1952 年至 2007 年的人口變化

或者將中國、日本、南韓與臺灣資料篩選出來並繪製從 1952 年至 2007 年的平均壽命變化:

gapminder_northasia = gapminder.loc[gapminder['country'].isin(['China', 'Japan', 'Korea, Rep.', 'Taiwan'])]
gapminder_northasia_pivot = gapminder_northasia.pivot_table(values = 'lifeExp', columns = 'country', index = 'year')
gapminder_northasia_pivot.plot(title = 'Life Expectancies in North Asia')
plt.show()
中國、日本、南韓與臺灣從 1952 年至 2007 年的平均壽命變化

視覺化數值的分佈:直方圖、盒鬚圖

將 2007 年資料篩選出來並以三個子圖(subplots)繪製人口數、平均壽命與人均所得的直方圖:

gapminder_2007 = gapminder[gapminder['year'] == 2007]
gapminder_2007[['pop', 'gdpPercap', 'lifeExp']].hist(bins = 15)
plt.show()
人口數、平均壽命與人均所得的直方圖

或者繪製人均所得的直方圖:

gapminder_2007[['gdpPercap']].plot(kind = 'hist', title = 'GDP Per Capita in 2007', legend = False, bins = 15)
plt.show()
人均所得的直方圖

或者將人均所得直方圖依照不同洲別以不同顏色繪製:

gapminder_continent_pivot = gapminder_2007.pivot_table(values = 'gdpPercap', columns = 'continent', index = 'country')
gapminder_continent_pivot.plot(kind = 'hist', alpha=0.5, bins = 20, title = 'GDP Per Capita by Continent')
plt.show()
人均所得直方圖依照不同洲別

或者依照不同洲別,將人均所得以盒鬚圖繪製:

gapminder_continent_pivot.plot(kind = 'box', title = 'GDP Per Capita by Continent')
plt.show()
人均所得盒鬚圖依照不同洲別

視覺化相關性:散佈圖、hexbin plot

繪製 2007 年各國人均所得與平均壽命的散佈圖:

gapminder_2007.plot(kind = 'scatter', x = 'gdpPercap', y = 'lifeExp', title = 'Wealth vs. Health in 2007')
plt.show()
2007 年各國人均所得與平均壽命(散佈圖)

或改以 hexbin plot 呈現:

gapminder_2007.plot(kind = 'hexbin', x = 'gdpPercap', y = 'lifeExp', title = 'Wealth vs. Health in 2007', gridsize = 20)
plt.show()
2007 年各國人均所得與平均壽命(hexbin plot)

視覺化排名:長條圖

繪製 2007 年各洲的人口總數:

summarized_df = gapminder[gapminder['year'] == 2007].groupby(by = 'continent')['pop'].sum()
summarized_df.plot(kind = 'bar', rot = 0)
plt.show()
2007 年各洲的人口總數

或者繪製 2007 年各洲平均壽命、平均財富:

summarized_df = gapminder[gapminder['year'] == 2007].groupby(by = 'continent')[['lifeExp', 'gdpPercap']].mean()
summarized_df.plot(kind = 'barh', subplots = True, layout = (1, 2), sharex = False, sharey = True, legend = False)
plt.show()
2007 年各洲平均壽命、平均財富

Series 與 Panel

pandas 除了提供 DataFrame 這個資料結構,尚有 SeriesPanel 兩種資料結構;只要單選 DataFrame 之中的單一變數且不要以 list 標註就可以獲得 Series

country = gapminder['country']
type(country)
從 DataFrame 中拆分出 Series

Series 還可以再拆分為 indexvalues 兩個部分,其中 values 就是一個 Numpy 的 ndarray

print(country.values)
print(type(country.values))
從 Series 中拆分出 ndarray

如此一來就順利掌握住這些資料結構的關係;一個 DataFrame 可以解構為多個 Series,一個 Series 可以再解構為 ndarrayndarray 可以再解構取得之中的數字、布林或文字。

Panel 則是能儲存多個 DataFrame 資料結構,例如可以將原本的 gapminder 依照年份拆開,一個年份的資料用一個 DataFrame 儲存,然後將 12 個 DataFrame 都儲存到一個 Panel 物件之中:

df_grouped = gapminder.groupby(['year'])
df_dict = {}
for i in range(1952, 2011, 5):
df_dict[i] = df_grouped.get_group(i).reset_index(drop = True)
gapminder_panel = pd.Panel(df_dict)
gapminder_panel
將 12 個 DataFrame 都儲存到一個 Panel 物件

練習:整理 gapminder.org 的資料

接著我們利用 gapminder.org 提供的資料來練習使用 pandas 做資料載入與整理合併,最後輸出更貼近 Hans Rosling(1948–2017) 所展示的資料;首先從網站將人口數、平均壽命與平均財富三個 excel 試算表下載,先觀察一下資料外觀:

三個 excel 試算表的外觀皆為寬表格

接著我們會將三個 excel 試算表載入、轉換為長表格、合併、然後寫出。

載入

gapminder.org 提供的 excel 試算表都有多個工作表,我們只需要名稱為 Data 的工作表,載入成 DataFrame 並存入一個 list

def get_data(url_list):
df_list = []
for url in url_list:
df_list.append(pd.read_excel(url, sheetname = 'Data'))
return df_list
url_list = ['https://storage.googleapis.com/learn_pd_like_tidyverse/indicator_gapminder_population.xlsx', 'https://storage.googleapis.com/learn_pd_like_tidyverse/indicator_gapminder_gdp_per_capita_ppp.xlsx', 'https://storage.googleapis.com/learn_pd_like_tidyverse/indicator_life_expectancy_at_birth.xlsx']
wide_df_list = get_data(url_list)
wide_df_list[0].head()
wide_df_list[1].head()
wide_df_list[2].head()
資料載入(一)
資料載入(二)
資料載入(三)

轉換為長表格

接著利用 pd.melt() 方法將 list 中的寬表格轉換為長表格:

def get_long_df(wide_df_list):
long_df_list = []
source_var = ['Total population', 'GDP per capita', 'Life expectancy']
renamed_var = ['pop', 'gdpPercap', 'lifeExp']
for (i, old_var, new_var) in zip(range(3), source_var, renamed_var):
df = pd.melt(wide_df_list[i], id_vars = [old_var])
df.columns = ['country', 'year', new_var]
long_df_list.append(df)
return long_df_list
long_df_list = get_long_df(wide_df_list)
long_df_list[0].head()
long_df_list[1].head()
long_df_list[2].head()
轉為長表格(一)
轉為長表格(二)
轉為長表格(三)

合併並移除遺漏值

最後利用 pd.merge() 方法將 list 中三個長表格合併、用 drop.na() 移除有遺漏的觀測值、依年份與國名排序(sort_values)最後重設索引值(reset_index)。

merged_df = pd.merge(long_df_list[0], long_df_list[1], on = ['country', 'year'])
merged_df = pd.merge(merged_df, long_df_list[2], on = ['country', 'year'])
merged_df = merged_df.dropna()
merged_df = merged_df.sort_values(['year', 'country'])
merged_df = merged_df.reset_index(drop = True)
merged_df.head()
最終的 DataFrame

寫出

與資料載入相互呼應,pandas 可以支援多種文字、二進位檔案與資料庫的資料寫出,常見的 txt、csv、excel 試算表、MySQL 或 PostgreSQL 都難不倒,如果對詳細的清單有興趣,可以參考 pandas 0.21.0 documentation;我們最後將整理合併完成的資料寫出為 csv 與 excel 試算表,通常寫出時都會將索引值移除。

merged_df.to_csv('gapminder.csv', index = False)
merged_df.to_excel('gapminder.xlsx', index = False)
寫出為 csv
寫出為 excel 試算表
如果您喜歡這篇文章,請多按下方的「拍手」圖像幾次、分享到社群網站、成為我們的贊助者以及訂閱 DataInPoint 的新文章!

--

--