Pandas 筆記

週六幫小學妹整理程式碼,順手複習了一下 Pandas 的操作,筆記如下。主要的參考資料是 Pandas 自帶的 User Guide,有很完整的範例。另外也記錄一些靈光一現的小技巧。

Series 與 DataFrame

Series 可以直觀看成是行(column)。不同的 Series、同一個 index 組合起來則是列(row)。預設的 index 是從 0 開始的。當然,index 可以通過 index 參數重新指定為數字或符號,而長度必須跟內容物一致。

series_list = pd.Series([1,2,3,4,5,6])
series_np = pd.Series(np.array([1,2,3,4,5,6]))

loc 的運作模式

本次目標是選取某一欄位符合某些條件的列,參考自 How do I select rows from a DataFrame based on column values?。寫法如下:

單一比較對象與多個比較對象分別為:

df.loc[df['column_name'] == some_value]
df.loc[df['column_name'].isin(some_values)]

要有兩個比較式的時候則是

df.loc[(df['column_name'] < 0) & (df['column_name'] > 1)]

之所以要用括號是因為 & 的計算優先級比較高,如果不用括號會被解析成 0 & df['column_name'] 而報錯。

inplace 預設行為

inplace 是所有 pandas 表格操作的行為設定。假如為 True 則改寫呼叫的物件,若是預設的 False 則回傳一個新的物件。後者的設計比較符合函數式的概念,即操作不產生副作用(side effect),原本的表格維持不變。

在 serial application(及需要多個動作對表格進行變換),使用不可變(immutable)的方式更好。之前處理的時候意外發現某些去除 outlier 的動作是「不可對調的」,以及先 A 再 B 和先 B 再 A 會有完全不一樣的結果。底下複製 Functional programming and python pandas dataframes in pipelines 這篇底下的範例。

>>> table
     Name  Year of Birth      City
0    Mike           1970  New York
1   Chris           1981     Miami
2  Janine           1975   Seattle

假設我們要取年齡,和居住地是否為 Miami:

def f(s)
    return pd.Series([2022 - s['Year of Birth'],
                     s['City'] == 'Miami'])

對表操作的模式如下(注意,這是把產出的資料寫回去原本的表格):

table[['Age', 'Lives in Miami']] = table.apply(f, axis=1)

concat 的方式來合併資料,就是不可變的操作。缺點是新行沒有名稱需要額外指定。用 deep copy 複製一份再寫入也可以,但是這很不「functional」。

new_table = pd.concat([table, table.apply(f, axis=1)], axis=1)

註記:我不喜歡 Python 的一個原因就是它的引用設計(referencing system)。它不是複製物件,而是引用,很容易造成副作用 - 莫名其妙就改動了物件。

預設 axis

drop 操作預設的 axis 是 0,亦即它們丟棄列,操作對象是行(column)。若要對列操作(我大部分的 pd.DataFrame.apply() 都是針對列),則需要設 axis 為 1;也可以指定參數名稱 columns 來避免歧義。

df = pd.DataFrame()
df.drop(columns=["column_A", "column_B"])
# 等同於
df.drop(["column_A", "column_B"], axis=1)

為函數增加彈性

大部分的讀表一開始就會先進行一波清理。不過,部分的表格是運算好再清理(例如:dropna)。可以用預設參數(default arguments)的方式進行;增加了彈性不需要為了常見的需求寫過多的參數。

import pandas as pd

def read_table(file_path, dropna=True):
    df = pd.read_csv(file_path)
    if dropna:
        df = df.dropna(...)
    return df

table_1 = read_table("table_1.csv")
table_2 = read_table("table_2.csv")
table_3 = read_table("table_3.csv", dropna=False)

Intersection

要怎麼快速判定列表 A 中的元素(們)是否在列表 B 中呢?是用 Set (集合)快速搞定這個問題,而不需要寫一個冗長的 for if

a = [...]
b = [...]
c = set(a).intersection(set(b))

如上操作,可獲得 c,是 a 與 b 兩個列表的交集,型別是 Set