使用Python進行資料整理 – 初探Pandas
李孟諵
資料整理是資料分析和建模前的準備工作,包括資料的讀取、檢核、修改、重新編碼和合併等。市場上有不少專為量化研究所設計的商業軟體,例如:SPSS、SAS和Stata等,都能進行這項任務,但共通問題是價格所貲不菲。因此,有些人轉向使用開源軟體,其中,具資料整理功能的代表軟體就是「R」。近年來,隨著資料科學[1](Data
Science)一詞廣泛被討論,另一套追求開源精神的程式語言Python,因第三方函式庫[2](library)Pandas日漸成熟,使其在資料整理的功能上也逐漸受到關注。因此,為了使大家更容易了解Pandas在調查資料的運用,本文以一個符合調查資料結構的小資料表,藉由調查資料的整理過程,來介紹所使用的函式以及說明程式碼和輸出結果。
一、Python簡介
Python一個開源、簡潔、易讀的程式語言──由Guido
van Rossum創建,於1991年首次發布,設計哲學為「優雅」、「明確」、「簡單」,其直譯式[3]、易擴充和跨平台的特性,加上豐富多元的第三方函式庫和社群,使其在各種程式應用上都能看見它的身影,例如:網頁開發、系統管理、資料處理、科學計算,以及近年熱門的機器學習和深度學習等。在近期的TIOBE指數[4]和KDnuggets調查[5]都有不錯的熱門度。
二、Python環境安裝
由於跨平台的特性,Windows、Mac和Linux系統都能安裝。一般從Python原生環境加上所需的函式庫到整合開發環境(Integrated
Development Environment,簡稱IDE)[6],對初學者來說安裝就是一個挑戰,因此建議直接下載「Anaconda」(號稱Python資料科學懶人包)來安裝,優點是安裝簡單,只需點選「下一步」即可,缺點是有些函式庫可能用不到,會佔據太多磁碟空間。安裝步驟可參考官網說明,惟補充兩個安裝時會遇到的問題(以Windows為例),第一個是環境變數(PATH
environment variable)的選擇(如圖一),主要作用在於透過命令提示字元(cmd.exe)呼叫Python或相關程式時,是否需指引到絕對路徑。Anaconda官方建議可不勾選,而是透過Anaconda
Navigator、Anaconda
Prompt或Windows開始鈕來開啟IDE。
圖一 選擇是否添加環境變數
另一個問題則是可選擇是否安裝VSCode(Visual
Studio Code)(如圖二),這是Anaconda從5.1版本開始內含的微軟跨平台IDE;此外,也含部分函式庫需要用到的微軟套件。
圖二 選擇是否安裝VSCode
以上兩個問題都依個人使用習慣自行決定,不論是否設定環境變數或有沒有安裝VSCode都不影響後續的操作。目前Python有2.x[7]和3.x版,其中一個差異就是Python2是ASCII編碼,Python3是Unicode編碼,因此,讀取資料時要留意原資料的編碼格式,才不會出現亂碼。至於版本選擇上,除非要沿用2.x版開發的環境,不然建議可直接從3.x版開始。
三、Pandas資料結構
(一)Series
是一維標籤陣列(array),能夠保存任何資料類型(整數、字符串、浮點數等)。
(二)DataFrame
是一個二維標籤資料結構,可以具有不同類型的行(column),類似Excel的資料表,對於有使用過統計軟體的分析人員應該不陌生。
簡單來說,Series可以想像為一行多列(row)的資料,而DataFrame是多行多列的資料,藉由選擇索引(列標籤)和行(行標籤)的參數來操作資料,就像使用統計軟體透過樣本編號或變項名稱來操作資料。
四、Pandas在資料整理的運用
操作環境為Windows7
64位元之Anaconda3
5.3.1版(內含Python
3.7版和Pandas
0.23.4版),並使用Anaconda內建的Jupyter
Notebook來當IDE。相關檔案(問卷、過錄編碼簿、程式碼和資料檔)可於SRDA網站「課程講義」下載。
(一)開啟Jupyter
Notebook
圖三 用命令提示字元呼叫Jupyter
Notebook
或是Windows程式集可用點選的方式開啟(如圖四)。
圖四
Windows程式集點選
Jupyter
Notebook啟動後,點選右上角「New」再點選「Python
3」(如圖五),即可進入編輯頁面。由於本文非Jupyter
Notebook操作教學,若想瞭解更多可參考文末的網路資源「政大蔡炎龍老師線上課程」。
圖五 點選右上角Python
3
(二)載入函式庫
當IDE開起後,起手式就是利用import來載入函式庫。
# 載入函式庫
import pandas as pd pd.__version__ #查看版本 |
說明:為保持程式碼簡潔,會利用「pd」簡寫來代替pandas全名。(以下提到pd就是指pandas)
|
(三)函式(function)介紹
# 讀取csv檔案
df = pd.read_csv("C:/py_pandas/data/SRDA20181107.csv") #請自行依檔案位置調整 df
|
說明:宣告一個df變數(df為DataFrame簡寫,名稱可自行定義),透過pd的read_csv()函式指定路徑和檔案名稱,放入一個8
* 10的資料表(8個欄位,10筆樣本)物件,輸出df可看出讀取的資料樣貌。
|
【輸出】
id
|
sex
|
age
|
a1
|
a2
|
a3
|
a4
|
ka4
|
|
0
|
1001
|
1.0
|
43
|
2
|
1
|
1
|
5
|
打零工
|
1
|
1002
|
2.0
|
46
|
3
|
1
|
2
|
3
|
NaN
|
2
|
1003
|
1.0
|
28
|
1
|
2
|
3
|
5
|
NaN
|
3
|
1004
|
NaN
|
25
|
4
|
2
|
4
|
2
|
NaN
|
4
|
1005
|
2.0
|
77
|
2
|
9
|
5
|
1
|
幫家裡做家庭代工
|
5
|
1006
|
2.0
|
55
|
3
|
3
|
6
|
3
|
NaN
|
6
|
1006
|
3.0
|
60
|
2
|
3
|
7
|
5
|
育嬰假
|
7
|
1008
|
NaN
|
18
|
1
|
4
|
8
|
4
|
NaN
|
8
|
1009
|
1.0
|
15
|
2
|
4
|
9
|
4
|
NaN
|
9
|
1010
|
2.0
|
65
|
2
|
9
|
10
|
3
|
NaN
|
輸出說明:
1.
第一行0到9為列標籤(索引值),第一列id到ka4為行標籤(變項名稱)。
2.
python預設起始值是從0開始。
3.
sex變項非純數字型態,讀取後會顯示小數位。
4.
NaN(Not a
Number)為預設的遺失資料(missing
data)標示。
5.
變項說明與選項數值說明可參考問卷或過錄編碼簿。
|
就調查資料而言,通常一筆樣本會編列一組受訪者識別碼,所以,可用duplicated()來檢查id變項是否有重複的樣本編號。
# 檢查樣本編號
cd1 = df.duplicated('id')
df.id[cd1] #僅輸出id變項
|
說明:
1.
將檢查條件放入cd1變數(cd為condition簡寫)。
2. df為資料表物件,透過duplicated()檢查id變項。
3. 輸出id變項的值,並帶入cd1的條件。
|
【輸出】
6
1006
Name: id,
dtype: int64
輸出說明:從輸出結果得知,索引值6有重複值情形,為第二筆id變項為1006的數值。
|
# 補充:亦可整合成一列程式碼,差別在於有沒有把條件存成物件。(當條件存成物件,可方便隨時調用。)
df.id[df.duplicated('id')]
|
調查資料可能會出現未填答或漏建的情形,可用isna()來檢查指定變項是否出現遺漏值。本文以sex變項為例。
# 檢查遺漏值/缺失值(missing value/ data)
cd2 = df['sex'].isna()
df.sex[cd2] #僅輸出sex變項
|
說明:
1.
將檢查條件放入cd2變數。
2. df為資料表物件,指定sex變項透過isna()來檢查。
3.
輸出sex變項的值,並帶入cd2的條件。
|
【輸出】
3 NaN
7 NaN
Name: sex, dtype: float64
輸出說明:從輸出結果可以知道,sex變項有兩筆NaN,分別為索引值3和7。
|
處理調查資料時,需要透過特定值來過濾資料,這時候可以用isin()來檢查指定變項是否包含特定值。
(1)檢查特定樣本編號
經duplicated()檢查,已得知樣本編號1006出現重複情形,接著我們可以將1006帶入isin()。
# 檢查特定樣本編號 cd3 = df['id'].isin([1006]) df[cd3] #輸出整筆資料 |
說明:
1.
將檢查條件放入cd3變數。
2. df為資料表物件,指定id變項透過isin()檢查1006值。
3.
輸出df資料表,並帶入cd3的條件。
|
【輸出1】
id
|
sex
|
age
|
a1
|
a2
|
a3
|
a4
|
ka4
|
|
5
|
1006
|
2.0
|
55
|
3
|
3
|
6
|
3
|
NaN
|
6
|
1006
|
3.0
|
60
|
2
|
3
|
7
|
5
|
育嬰假
|
# 補充1:依檢查需求,也可僅輸出id變項。
df.id[cd3] #僅輸出id變項 |
【輸出2】
5 1006
6 1006
Name: id, dtype: int64
輸出說明:id變項有兩筆1006數值的樣本,其索引值分別為5和6,可從其他變項來判斷是否為同一筆樣本或是鍵入錯誤。
|
# 補充2:亦可整合成一列程式碼
df[df['id'].isin([1006])]
|
# 補充3:或是結合其他函式,如duplicated()
df[df['id'].isin([df.id[df.duplicated('id')]])]
|
補充3說明:列出此段程式碼,主要目的在於表示程式碼撰寫的彈性(可一列、可多列或存成物件),當然,撰寫時還是以易讀好維護為主。
|
(2)檢查類別變項
簡單來說就是受訪者的特徵或答案可以分門歸類,例如:受訪者性別或詢問意向等。可以透過isin()來檢查該變項中是否出現未定義的數值,例如:sex變項的選項(1)代表男、(2)代表女,所以超出這兩個值就是未定義的數值。
# 檢查類別變項
cd4 = ~df['sex'].isin(['1', '2'])
df[cd4] #輸出整筆資料
|
說明:
1.
將檢查條件放入cd4變數。
2. df為資料表物件,指定sex變項透過isin()檢查不包含1和2的值。
(1)特別注意,當在最前方加上「~」符號,即表示為「不」。
(2)只要整行不是全部數字,資料會是文字(string)格式存放,因此特定值需要加上上引號「'」表示文字型。
3.
輸出df資料表,並帶入cd4的條件。
|
【輸出】
id
|
sex
|
age
|
a1
|
a2
|
a3
|
a4
|
ka4
|
|
3
|
1004
|
NaN
|
25
|
4
|
2
|
4
|
2
|
NaN
|
6
|
1006
|
3.0
|
60
|
2
|
3
|
7
|
5
|
育嬰假
|
7
|
1008
|
NaN
|
18
|
1
|
4
|
8
|
4
|
NaN
|
輸出說明:sex變項有三筆不在1和2的範圍,分別是一筆3的數值和兩筆NaN。
|
# 補充:亦可一列程式碼表示
df[~df['sex'].isin(['1', '2'])]
|
(3)題目間關聯性
調查問卷有時會設計跳續答或開放題,檢查時,可將多個條件集合起來。
a. 檢查跳續答
依照問卷設計a1變項回答(4)無法選擇,a2變項要跳答。因此邏輯上,當a1變項回答4,a2變項卻不是9(跳答碼);而當a1變項不是回答4,a2變項卻回答9。我們可以透過這個邏輯,把錯誤的答案挑出來。
# 檢查跳續答
cd5 = ((df['a1'].isin(['4']) & ~df['a2'].isin(['9'])) |
(~df['a1'].isin(['4']) & df['a2'].isin(['9'])))
df[cd5] #輸出整筆資料
|
說明:
1.
將檢查條件放入cd5變數。
2.
布林運算子「|」符號為or的意思;「&」符號為and的意思。
3.
為了易讀性可寫成兩列,第一列程式碼表示,當a1變項為4,a2變項不為9;第二列程式碼表示,當a1變項不為4,a2變項為9。再將第一和第二列條件透過「()」和「|」放入cd5變數。
4.
輸出df資料表,並帶入cd5的條件。
|
【輸出】
id
|
sex
|
age
|
a1
|
a2
|
a3
|
a4
|
ka4
|
|
3
|
1004
|
NaN
|
25
|
4
|
2
|
4
|
2
|
NaN
|
4
|
1005
|
2.0
|
77
|
2
|
9
|
5
|
1
|
幫家裡做家庭代工
|
9
|
1010
|
2.0
|
65
|
2
|
9
|
10
|
3
|
NaN
|
輸出說明:從輸出結果可看出有3筆不符問卷跳續答設計,如1004樣本a1變項回答4,a2變項的答案卻是2不是9;再如1005樣本a1變項回答2,a2變項的答案卻是9而不是其他選項數值。
|
b. 檢查開放題
依照問卷設計a4變項回答(5)其他,ka4變項要鍵入文字。因此邏輯上,當a4變項回答5,ka4變項卻無資料(NaN);而當a4變項不是回答5,ka4變項有鍵入文字。我們可以利用這個邏輯挑出錯誤的答案。
# 檢查開放題
cd6 = (((df['a4'] == 5) & (df['ka4'].isna())) |
((df['a4'].isin([1, 2, 3, 4])) & (df['ka4'].notna())))
df[['id', 'a4', 'ka4']][cd6] #輸出id、a4和ka4變項
|
說明:
1.
將檢查條件放入cd6變數。
2.
比較運算子「==」兩個等號為相等的意思。
3.
第一列程式碼表示,當a4變項為5,ka4變項卻沒資料;第二列程式碼表示,當a4變項不為5,ka4變項卻不是沒資料。將第一和第二列條件透過「()」和「|」放入cd6變數。
4.
輸出df資料表的id、a4和ka4變項,並帶入cd6的條件。
|
【輸出】
id
|
a4
|
ka4
|
|
2
|
1003
|
5
|
NaN
|
4
|
1005
|
1
|
幫家裡做家庭代工
|
輸出說明:從輸出結果可看出有2筆不符開放題設計,如1003樣本a4變項回答5,ka4變項卻沒鍵入文字,而1005樣本a4變項回答1,ka4變項卻確有鍵入文字。
|
當變項的數值很多時,不適合用isin()來檢查,可採用between()來檢查區間值,如年齡、身高和收入等連續型變項。
# 檢查連續變項
cd7 = ~df['age'].between(18, 65)
df.age[cd7] #輸出age變項
|
說明:
1.
將檢查條件放入cd7變數。
2.
檢查age變項值域不在18到65範圍內的值。(預設是不包含設定本值)
4.
輸出df資料表的age變項,並帶入cd7的條件。
|
【輸出】
4
77
8
15
Name: age, dtype: int64
輸出說明:有兩筆年齡不在18到65歲之間,一筆是15歲,另一筆是77歲。
|
當資料檢查完成也確認後,接著進行資料的修改。
(1)利用索引值(index)
假設翻閱問卷後,確認樣本編號1007誤鍵為1006,可利用loc[]鎖定索引值並調整指定變項的內容。
# 利用索引值修改資料
df.loc[6, 'id'] = 1007
df.id #輸出id變項
|
說明:
1.
指定索引值6並將id變項資料改為1007。
2.
輸出df資料表的id變項。
|
【輸出】
0
1001
1
1002
2
1003
3
1004
4
1005
5
1006
6 1007
7
1008
8
1009
9
1010
Name: id, dtype: int64
輸出說明:索引值6的id變項資料已改為1007。
|
(2)利用樣本編號
在翻閱問卷後,得知樣本編號1007的sex變項應為2,可利用樣本編號指定特定值並調整指定變項的內容。
# 利用樣本編號修改資料
df.loc[df['id']==1007, 'sex'] = 2
df[['id', 'sex']] #輸出id和sex變項
|
說明:
1.
當樣本編號(id)為1007時,sex變項改為2。
2.
輸出df資料表的id和sex變項。
|
【輸出】
id
|
sex
|
|
0
|
1001
|
1.0
|
1
|
1002
|
2.0
|
2
|
1003
|
1.0
|
3
|
1004
|
NaN
|
4
|
1005
|
2.0
|
5
|
1006
|
2.0
|
6
|
1007
|
2.0
|
7
|
1008
|
NaN
|
8
|
1009
|
1.0
|
9
|
1010
|
2.0
|
輸出說明:可以看到樣本編號(id)1007的sex變項已改為2。
|
整理資料時,當需要調整變項名稱時,可用rename()來處理。
# 更改變項名稱
df = df.rename(columns={'ka4':'ka4_o'})
df.columns #輸出columns
|
說明:
1.
重新命名df資料表的columns,並用字典(dictionary)結構{}大括號符號框起來,冒號前為原名稱,冒號後為新名稱。
2.
輸出df資料表的columns。
|
【輸出】
Index(['id', 'sex', 'age', 'a1', 'a2', 'a3', 'a4', 'ka4_o'], dtype='object')
輸出說明:可以看出原ka4變項名稱已改為ka4_o。
|
當需要對某變項的數值重新編碼時,可使用replace()來處理。
(1)同一變項
在原變項進行重新編碼。假設要將原1和2的數值調整為1,原3和4的數值調整為2,原9數值維持為9。
# 重新編碼:同一變項 df['a2'].replace({1:1, 2:1, 3:2, 4:2, 9:9}, inplace=True) #inplace=True才會寫入 df
|
說明:
1.
指定df資料表的a2變項進行重新編碼,透過字典結構{}大括號符號框起來,冒號前為原數值,冒號後為新數值,再用逗號(,)區分各數值對應情形。
2.
由於預設不會更改資料,需補上inplace=True參數才會寫入資料表。
3.
輸出df資料表。
|
【輸出】
id
|
sex
|
age
|
a1
|
a2
|
a3
|
a4
|
ka4_o
|
|
0
|
1001
|
1.0
|
43
|
2
|
1
|
1
|
5
|
打零工
|
1
|
1002
|
2.0
|
46
|
3
|
1
|
2
|
3
|
NaN
|
2
|
1003
|
1.0
|
28
|
1
|
1
|
3
|
5
|
NaN
|
3
|
1004
|
NaN
|
25
|
4
|
1
|
4
|
2
|
NaN
|
4
|
1005
|
2.0
|
77
|
2
|
9
|
5
|
1
|
幫家裡做家庭代工
|
5
|
1006
|
2.0
|
55
|
3
|
2
|
6
|
3
|
NaN
|
6
|
1007
|
2.0
|
60
|
2
|
2
|
7
|
5
|
育嬰假
|
7
|
1008
|
NaN
|
18
|
1
|
2
|
8
|
4
|
NaN
|
8
|
1009
|
1.0
|
15
|
2
|
2
|
9
|
4
|
NaN
|
9
|
1010
|
2.0
|
65
|
2
|
9
|
10
|
3
|
NaN
|
輸出說明:經過重新編碼,可以看出a2變項已沒有3和4的數值。
|
(2)新增變項
當需要對某變項的數值重新編碼,且要保留原變項的資料時,可以先建立一個新變項,再使用replace()來處理。假設a3變項1到5的數值要調整為1,6到10的數值要調整為2,並放到new_a3變項。
# 重新編碼:新增變項
df['new_a3'] = df['a3'].replace({1:1, 2:1, 3:1, 4:1, 5:1, 6:2, 7:2, 8:2, 9:2, 10:2})
df
|
說明:
1.
指定df資料表建立一個new_a3的變項。
2.
指定df資料表的a3變項進行重新編碼,透過字典結構{}大括號符號框起來,冒號前為原數值,冒號後為新數值,再用逗號區分各數值對應情形。
3.
透過等號帶入new_a3變項。
4.
輸出df資料表。
|
【輸出】
id
|
sex
|
age
|
a1
|
a2
|
a3
|
a4
|
ka4_o
|
new_a3
|
|
0
|
1001
|
1.0
|
43
|
2
|
1
|
1
|
5
|
打零工
|
1
|
1
|
1002
|
2.0
|
46
|
3
|
1
|
2
|
3
|
NaN
|
1
|
2
|
1003
|
1.0
|
28
|
1
|
1
|
3
|
5
|
NaN
|
1
|
3
|
1004
|
NaN
|
25
|
4
|
1
|
4
|
2
|
NaN
|
1
|
4
|
1005
|
2.0
|
77
|
2
|
9
|
5
|
1
|
幫家裡做家庭代工
|
1
|
5
|
1006
|
2.0
|
55
|
3
|
2
|
6
|
3
|
NaN
|
2
|
6
|
1007
|
2.0
|
60
|
2
|
2
|
7
|
5
|
育嬰假
|
2
|
7
|
1008
|
NaN
|
18
|
1
|
2
|
8
|
4
|
NaN
|
2
|
8
|
1009
|
1.0
|
15
|
2
|
2
|
9
|
4
|
NaN
|
2
|
9
|
1010
|
2.0
|
65
|
2
|
9
|
10
|
3
|
NaN
|
2
|
輸出說明:可以看出df資料表已新增new_a3變項,其中1和2的數值是來自a3變項重新編碼而來。
|
以上藉由調查資料的基本整理原理來介紹Pandas的運用,希望能讓大家對於程式語言Python和資料分析函式庫Pandas能有進一步認識。最後,本文只是基礎概念的介紹,對於想了解更多的讀者,提供一些可能會有用的資源:
- Pandas官方說明文件pandas 0.23.4 documentation
- 政大蔡炎龍老師線上課程(成為Python數據分析達人的第一堂課)
- Python資料分析 第二版(O'Reilly)
- 程式設計領域的問答網站Stack Overflow
[1] 是一門利用資料學習知識的學科,基本上需要3個領域:Computer Science(技術能力)、Maths &
Statistics(數學能力)和Domain Knowledge(某領域知識)。(取自https://ion.icaew.com/itcounts/b/weblog/posts/theaccountinganddatascienceworldsmeet)
[4] TIOBE指數是根據網際網路上有經驗的程式人員、課程和第三方廠商的數量,並使用搜尋引擎(如Google、Bing、Yahoo!、百度)以及Wikipedia、Amazon、YouTube統計出排名。
[8] 逗號分隔值(Comma-Separated Values,簡稱CSV)有時也稱為字元分隔值,因為分隔字元也可以不是逗號。是目前常見資料交換格式之一,惟讀取時須留意編碼格式(如ASCII、UTF-8或Big5),避免造成亂碼。
感謝教學!幫助很大
回覆刪除非常感謝你!!講解的好詳細!!
回覆刪除