💡一句話定義
為什麼要有它?因為原生 Python 的 list/dict 處理十萬列資料又慢又難讀;NumPy 很快但只有數字、沒有欄名。Pandas 把 NumPy 的速度包上「欄名+標籤」,讓你用「金額 > 1000」這種人話來操作資料,而不是記第幾欄。
🧱三個零件:Series、DataFrame、Index
先把關係弄對,之後看任何程式碼都不會迷路:DataFrame = 好幾條 Series 並排,共用同一個 Index。
一維、有標籤的陣列。底層是 NumPy 陣列+一個 Index。
df["金額"] 就是一條。二維表。可以想成「一個 dict,key 是欄名、value 是 Series」,所有 Series 共用同一個 Index。
每一列的「名牌」。預設是 0,1,2…,但篩選後不會自動重排,這是很多題目的陷阱。
🔄八步工作流:讀→看→挑→清→轉→算→拼→存
任何 Pandas 程式幾乎都在這條線上,考題給一段程式問輸出時,先問自己「這段在做哪一步」。
- 讀檔(load):
pd.read_csv("orders.csv")、read_excel、read_sql。超大檔加chunksize=一塊一塊讀(見下方記憶體那節)。 - 檢視(inspect):
df.head()看前 5 列、df.info()看每欄型別與非空數量、df.describe()看數值欄的統計摘要、df.shape看幾列幾欄。info 看型別與缺值,describe 看分布——考題常拿這兩個互換來考。 - 篩選(select/filter):布林遮罩
df[df["金額"] > 1000];df.loc[列標籤, 欄名]用名字;df.iloc[列位置, 欄位置]用數字位置;df.query("金額 > 1000 and 城市 == '台北'")用字串寫條件。 - 清理(clean):缺失值
isna().sum()數、dropna()丟、fillna(中位數)補;型別astype("int64")、pd.to_datetime();重複drop_duplicates()。補值與整數型別的坑看 fillna 中位數補值與 Int64 逐行解剖。 - 轉換(transform):
Series.map(字典或函式)逐值對應、Series.apply(函式)逐值套用、df.apply(函式, axis=1)逐列套用、df.assign(新欄=...)。能用向量化(直接df["金額"] * 1.05)就不要 apply。 - 彙總(aggregate):
df.groupby("城市")["金額"].sum()=「分組 → 套用 → 合併」(split-apply-combine);agg(["sum","mean"])一次多個;pivot_table是「groupby 後再攤成寬表」。範例題看 groupby 加總+nlargest 前三名。 - 合併(combine):
pd.merge(左, 右, on="鍵", how="inner")像 SQL JOIN,橫向按鍵對;pd.concat([a, b])像疊積木,縱向(或橫向)直接接。範例題看 query 篩選+inner merge 留下誰。 - 輸出(export):
df.to_csv("out.csv", index=False)、to_excel、to_parquet(欄式格式,大資料更省空間)。index=False不然 Index 會變成多一欄。
🎮互動:DataFrame 操作模擬器
下面內建一張 10 列的訂單表(客戶、城市、金額、日期)和一張 5 列的「城市→區域」對照表。點操作按鈕,右邊立刻顯示結果表與對應的 Pandas 程式碼。注意看每次操作後的形狀與Index 怎麼變。
📜 程式碼
📋 結果
🔀寬表 vs 長表:melt 與 pivot
同一份資料可以有兩種長相。寬表(wide):一列一個主體、每個月各一欄,人看最舒服;長表(long/tidy):一列一筆觀測(城市、月份、金額三欄),程式與繪圖最好處理——seaborn、groupby 都喜歡長表。
# 寬 → 長:id_vars 留著當身分,其餘欄名跑進 var_name、值跑進 value_name
long = wide.melt(id_vars="城市", var_name="月份", value_name="金額")
# 長 → 寬:index 當列、columns 當欄、values 填格子;重複鍵要用 pivot_table 指定 aggfunc
wide = long.pivot_table(index="城市", columns="月份", values="金額", aggfunc="sum")
完整題目走一遍:melt 加 barplot 畫出什麼?六根條=groupby 一字不差。
⚡向量化 vs for 迴圈;記憶體與 chunksize
向量化(vectorization):一次對整欄動手
# ❌ 慢:Python 迴圈一格一格算
total = []
for i in range(len(df)):
total.append(df.loc[i, "金額"] * 1.05)
df["含稅"] = total
# ✅ 快:整欄一起算(底層是 C 的 NumPy)
df["含稅"] = df["金額"] * 1.05
# 條件也向量化
df["大單"] = df["金額"] > 1000
df["等級"] = np.where(df["金額"] > 1000, "A", "B")
迴圈每一格都要經過 Python 直譯器;向量化把整欄丟給底層 C 程式一口氣算完,十萬列的差距常是數十到數百倍。apply 本質上還是逐格呼叫 Python 函式,比迴圈好讀、但不會比向量化快。優先順序:原生運算子/內建方法 > np.where/map > apply > for 迴圈。
記憶體:DataFrame 是整張載進 RAM 的
一個 10 GB 的 CSV 讀進來不會只佔 10 GB——字串欄用 object 型別時每格都是一個 Python 物件,常膨脹 2–5 倍。三招:
- 只讀需要的欄:
usecols=[...]。 - 縮小型別:
dtype={"城市": "category", "金額": "int32"};重複值多的字串改category能省 90% 以上。 - 分塊串流:
pd.read_csv(..., chunksize=100_000)回傳的是TextFileReader(迭代器),for chunk in reader:一塊一塊處理、邊讀邊累加,記憶體只需裝下一塊。
sum 與 count,最後才相除——只有能拆成「可累加的部分和」的統計量才能串流。逐行解剖:chunksize 與 TextFileReader、chunksize 串流平均的陷阱。
時間序列的 rolling/shift 也屬於轉換這一步,且很容易「偷看未來」造成資料洩漏:rolling 移動平均與 shift 落後特徵。
🤔最容易混淆的六組配對
| 配對 | 前者 | 後者 | 一句記法 |
|---|---|---|---|
| loc vs iloc | loc 用標籤(列名、欄名),切片含尾端 | iloc 用整數位置,切片不含尾端(像 Python list) | l=label,i=integer |
| merge vs concat | merge 按鍵橫向對齊,像 SQL JOIN,有 inner/left/right/outer | concat 直接疊,預設縱向(axis=0),不看鍵 | merge 要有共同鍵,concat 只要形狀合 |
| map vs apply | Series.map 逐值對應,可吃 dict/Series/函式 | apply 可用在 Series(逐值)或 DataFrame(逐列/逐欄,用 axis 控制) | 查表用 map,複雜邏輯用 apply |
| groupby vs pivot_table | groupby 結果是「長」的(分組鍵當 Index) | pivot_table 是 groupby 後再攤成「寬」的二維交叉表 | pivot_table = groupby + unstack |
| melt vs pivot | melt 寬→長,欄名融進一欄 | pivot/pivot_table 長→寬,一欄攤成多欄 | 互為反操作;有重複鍵用 pivot_table |
| info vs describe | info() 看型別、非空數、記憶體 | describe() 看count/mean/std/min/四分位/max | info 管結構,describe 管分布 |
⚠️常見誤區與陷阱
df[df["金額"]>1000] 的 Index 還是 0, 2, 4…;之後 iloc[0] 與 loc[0] 可能指向不同列。要重排加 .reset_index(drop=True)。NaN 是浮點數,所以
[1, 2, NaN] 的 dtype 是 float64,印出來是 1.0、2.0。要保留整數用可空型別 "Int64"(大寫 I)。df[df.a>1]["b"] = 0 是「鏈式索引」,改的可能是副本、原表沒變。正確寫法:df.loc[df.a>1, "b"] = 0。df.dropna() 不會改 df 本身,要 df = df.dropna() 或 inplace=True(官方不建議,難以串接)。df.groupby("城市")["金額"].sum() 回傳 Series、城市是 Index;要當普通欄加 .reset_index() 或一開始 as_index=False。head 是「前 3 個位置」,沒排序就不是名次;要名次先 sort_values 或直接 nlargest(3, "金額")。✅自我檢測
Q1. df.iloc[0:2] 會取出幾列?
Q2. 想得到「每個城市的總金額」,哪個方法最直接?
Q3. 左表 10 列、右表 5 列,用 how="inner" 以「城市」合併;左表有 1 列的城市右表沒有,右表有 1 個城市左表沒有。結果幾列?
Q4. 一張「城市 × 1月、2月、3月」的寬表要變成「城市、月份、金額」三欄的長表,用哪個?
Q5. 用 chunksize 分塊讀 CSV 要算整欄平均,正確做法是?
🎯重點整理
- 心智模型:DataFrame = 共用 Index 的多條 Series;對齊看標籤不看位置。
- 工作流:讀 → 看(head/info/describe)→ 挑(遮罩/loc/iloc)→ 清(缺值/型別)→ 轉(map/apply)→ 算(groupby/agg/pivot)→ 拼(merge/concat)→ 存。
- 形狀:melt 寬→長、pivot 長→寬;groupby 出長表、pivot_table 出寬表。
- 效能:向量化 > map/np.where > apply > for;大檔用 usecols、category、chunksize。
- 陷阱:篩選後 Index 不重排、NaN 讓整數變 float、鏈式索引改不到原表、head 不是名次。