使用 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 的學習筆記;也可以點選下方按鈕,分享給對正在精進資料科學的朋友們。