使用 Python 的 Pandas 套件做資料分析時,如果你要對一個資料表格連續做很多個動作,一般的寫法會像是以下這樣的:
df['foo'] = np.random.random(len(df))
df = df[['cut', 'price', 'x', 'y', 'z', 'foo']]
df['price_42x'] = df['price']*42
df = df.sort_values('price')
df = df.rename(columns={'x': 'length', 'y': 'width', 'z': 'height'})
df.head()
你第一眼看到這堆程式碼時,是否會覺得有些眼花撩亂呢?
看完這篇文章後,你將學會如何讓上述程式碼變成以下這樣:
(df
.assign(
foo = lambda x: np.random.random(len(x)),
price_42x = lambda x: x['price']*42
)
[['cut', 'price', 'x', 'y', 'z', 'foo']]
.rename(columns={'x': 'length', 'y': 'width', 'z': 'height'})
.sort_values('price')
.head()
)
更加簡潔好讀了,對吧!這就是使用 Pandas 的 Method Chaining 可以達成的效果。
這則筆記將告訴你 Pandas 的 Method Chaining 是什麼意思、使用這個方法有什麼好處,並且與你分享筆者在資料分析中超常用的幾項 Method Chaining 運用技巧,強化你的 Pandas 實力。
目錄
Method Chaining 介紹
pipe:寫程式的「水管」風格工作流
pipe 的意思是將資料重新導向,把某一段程式碼產生的結果直接導向另一段程式碼。程式碼會像水管一樣輸出輸入前後相連,後一段程式碼的輸入資料,就是上一段程式碼的輸出資料。
例如,在 Linux 作業系統的 pipe 會用 |
符號來連接程式,以下範例程式要做三個動作,先在終端機列印出所有 log.txt
內容、篩選出有 “error” 關鍵字的資料、最後計算總共有幾行:
cat log.txt | grep "error" | wc -l
使用 pipe,三個動作就不需要分成三行程式,只需要在同一行將三支程式的輸入輸出串起來就好了。
在 R 語言也有相當熱門的 %>%
符號實現 pipe 功能,讓資料處理的過程變得清楚又簡潔。例如以下 範例,四個資料處理動作原本分成四行程式、還需要設定四個變數來存中介資料,使用 pipe 之後,一口氣全串起來即可:
library(magrittr)
library(dplyr)
## 沒使用 pipe
df1 <- filter(mtcars, carb > 1)
df2 <- group_by(a, cyl)
df3 <- summarise(b, Avg_mpg = mean(mpg))
df4 <- arrange(c, desc(Avg_mpg))
## 使用 pipe
mtcars %>%
filter(carb > 1) %>%
group_by(cyl) %>%
summarise(Avg_mpg = mean(mpg)) %>%
arrange(desc(Avg_mpg))
既然 R 語言跟 Linux 都有 pipe,那 Python 的 Pandas 資料分析也有嗎?有!就是我們以下介紹的 Method Chaining 方法。
什麼是 Python 的 Method Chaining?
Python 中,所有變數(Variable)都指向一個物件(Object),物件可以定義出只屬於該物件的方法(Method),以 object.method()
的方式呼叫,這種呼叫方式與函數(Function)不同:
- Function 使用:
function_3( function_2( function_1( data ) ) )
- Method 使用:
object.function_1().function_2().function_3()
Method 的呼叫語法看起來就像鏈子一樣,會把物件經過 Method 運算完的結果由左而右串連、傳遞下去,所以稱為 Method Chaining。
Method Chaining 有什麼好處?
使用函數呼叫 function_3( function_2( function_1( data ) ) )
這種寫法的話,運算結果的閱讀順序是由右到左,違反人類閱讀的直覺。
如果不喜歡由右到左閱讀,也可以將每個動作分開來寫,變成:
data1 = function_1( data )
data2 = function_2( data1 )
data3 = function_3( data2 )
但是這樣就要花腦筋想額外的變數名稱,存下來這些中介變數、之後也不見得會用到。
使用 object.function_1().function_2().function_3()
的 Method Chaining 寫法,就把以上兩個問題都解決了,做到一行之內串連多個動作、不產生多餘變數、並且很直覺地看到 Method 執行順序是由左到右。
接下來,我將分享在 Pandas 使用 Method Chaining 的技巧,讓你的 Pandas 寫程式效率提升。
Pandas 的 Method Chaining 技巧教學
前面提到,要使用 Method 呼叫之前要先定義只屬於該物件的 Method,好消息是,Pandas 為 DataFrame
物件定義出超多好用的 Methods,也就可以發揮 Method Chaining 的優勢:
- 專注在「動作」、去除了為變數取名字的雜事
- 順序由上而下符合直覺
- 程式碼展現一致性、更好讀
為了達成這些程式可讀性優勢,以下介紹的幾項超常用技巧一定要學會!
## 本文的程式碼範例會用到以下三個套件
import seaborn as sns
import pandas as pd
import numpy as np
小括號 ()
啟用換行
一次有多個動作要串連的話,寫出來可能會超長、看不到該行尾端:
df = sns.load_dataset('diamonds')
df.rename(columns={'x': 'length', 'y': 'width', 'z': 'height'}).sort_values('price').head()
只要加上括號,就可以把同一行的程式切成多行,維持簡潔好讀、程式也不會出現錯誤:
(df
.rename(columns={'x': 'length', 'y': 'width', 'z': 'height'})
.sort_values('price')
.head()
)
使用括號切成多行的寫法,讓 Pandas 程式碼維持「一行一個動作」的原則,提升可讀性。
常用方法
要發揮 Pandas 的 Method Chaining 威力,筆者認為有幾個方法是大家必備的:
query()
:篩選資料assign()
:進行欄位計算sort_values()
:資料排序rename()
:更改欄位名稱drop()
:移除特定行或列
活用這些方法,複雜的資料處理就可以在一個括號的範圍內,流暢地寫完:
df = sns.load_dataset('diamonds')
(df
.assign(
foo = lambda x: np.random.random(len(x)),
price_42x = lambda x: x['price']*42
)
.rename(columns={'x': 'length', 'y': 'width', 'z': 'height'})
.sort_values('price')
.query('380 <= price <= 400')
.drop(['clarity', 'depth'], axis=1)
.head()
)
嘿!你不確定為什麼上方的範例程式碼 .assign()
裡面會用到 lambda
這個東東嗎?在此推薦你閱讀好豪的 Pandas + Lambda 使用教學文章,看完這篇就會理解箇中奧妙囉!
中括號 []
也可以串連
筆者發現很多人會忽略、因此值得一提的是,篩選資料的中括號 []
也可以 Chaining 串連。
df = sns.load_dataset('diamonds')
(df
.query('380 <= price <= 400')
[['cut', 'price', 'x', 'y', 'z']] # 用中括號選欄位
.head()
)
進階技巧:pipe()
Pandas 內的 pipe()
讓 Method Chaining 使用彈性更大,pipe()
會將你傳入引數的函式,用在整個資料表物件(DataFrame
)上,為 pipe()
傳入自己定義的函式的話,什麼資料處理都做得到、毫不受限。
需要注意的是,傳入 pipe()
的函式,輸入跟輸出都必須是 DataFrame
物件才行,否則就不符合 pipe 的工作流。
例如,我們可以自己定義一個離群值移除函式,以 pipe()
串連:
def remove_price_outlier(df):
q1 = df['price'].quantile(0.25)
q3 = df['price'].quantile(0.75)
iqr = q3 - q1 # Interquartile Range
thr_low = q1-1.5*iqr
thr_high = q3+1.5*iqr
result = df.loc[(df['price'] > thr_low) & (df['price'] < thr_high)]
return result
df = sns.load_dataset('diamonds')
# 離群值移除前的平均價格
(df
['price']
.mean()
)
## 3932.799721913237
# 移除離群值之後的平均價格
(df
.pipe(remove_price_outlier) # 用 pipe() 串連自定義函式
['price']
.mean()
)
## 3159.4608333333335
用註解 debug
使用 Method Chaining 之後,的確少了很多中介變數,但是偶爾 Pandas 資料分析出現問題的時候,也反而不能用中介資料來 debug 了。
要 debug 的時候,因為 Method Chaining 寫法「一行一個動作」的特性,用註解的方式來 debug 也是個方便的辦法。
df = sns.load_dataset('diamonds')
(df
.query('38000 <= price <= 40000')
[['cut', 'price', 'x', 'y', 'z']]
.sort_values('price')
.head()
)
# 程式顯示出 0 列資料
# 不知道哪裡寫錯了?!
(df
.query('38000 <= price <= 40000')
# [['cut', 'price', 'x', 'y', 'z']]
# .sort_values('price')
# .head()
)
# 由後往前把可疑的動作註解掉
# 就可以找出「出問題的第一個動作」是誰
註:在大多數的 Python IDE,用 Ctrl + /
或者 MAC 用 cmd + /
快捷鍵可以快速註解
結語
這則筆記用多個程式碼片段示範如何使用 Pandas 的 Method Chaining,資料科學家們只要維持「一行只有一個資料處理動作」的程式碼風格,就能為程式碼提升可讀性,更好看懂的程式碼、會更好調整也更好除錯、也就代表寫程式效率提升。請各位讀者務必練習使用 Method Chaining 這個好上手又實用的技巧。
感謝閱讀到這裡的你,筆者自己認為,要啟動 Method Chaining 的全部威力,Lambda 匿名函式是一定要會的技巧,這項技巧筆者在這篇文章寫不完,因此強力推薦你進一步閱讀好豪的下一篇教學文章:《Python Pandas 的 Lambda 匿名函式:五個實用技巧》。
這篇筆記來自於我在 《Pandas 資料清理、重塑、過濾、視覺化》 書中所學,本書作者從頭到尾都用 Method Chaining 的方法來分享 Pandas 程式碼,看起來十分清爽,也看得出作者是實務經驗豐富的 Pandas 資料分析專家。我在這本書學會許多進階操作,因此推薦這本書給任何常用 Pandas 的人閱讀。
如果這篇文章有幫助到你,歡迎追蹤好豪的 Facebook 粉絲專頁,我會持續分享 pandas 與 Python 的學習筆記;也可以點選下方按鈕,分享給對正在精進資料科學的朋友們。