Python 資料處理:groupby、merge 與 pivot 完全指南
簡介
在日常的資料分析與商業應用中,資料整理往往是最耗時、也是最關鍵的工作。
即使有了乾淨的資料集,若無法快速彙總、合併或重新排列表格結構,我們仍無法從中抽取有價值的資訊。
pandas 作為 Python 生態系最常用的資料處理套件,提供了三個核心工具:
groupby– 依某些欄位分組並執行聚合運算merge– 依鍵值把多個 DataFrame 合併在一起,類似資料庫的 JOINpivot/pivot_table– 重新塑形,將長條資料轉成寬表或產生交叉表
掌握這三個技巧,您就能在 資料清洗、探索性分析、報表製作 等階段大幅提升效率。本文將以實務為導向,從概念說明到常見陷阱,一步步帶您玩轉 groupby、merge 與 pivot。
核心概念
1. groupby – 先分組再聚合
groupby 的基本流程可以想像成三個步驟:
- 分組 (Split):根據一或多個欄位將資料切分成多個子集合。
- 套用函式 (Apply):對每個子集合執行聚合、過濾或轉換。
- 合併結果 (Combine):將每個子集合的結果彙總成新的 DataFrame。
1.1 基本範例
import pandas as pd
# 建立測試資料
df = pd.DataFrame({
'日期': pd.date_range('2024-01-01', periods=6, freq='D'),
'產品': ['A', 'B', 'A', 'B', 'A', 'B'],
'銷售量': [10, 20, 15, 25, 12, 18],
'收入': [100, 200, 150, 250, 120, 180]
})
# 依「產品」分組,計算銷售量總和與收入平均值
result = df.groupby('產品').agg({
'銷售量': 'sum',
'收入': 'mean'
}).reset_index()
print(result)
輸出
產品 銷售量 收入
0 A 37 123.33
1 B 63 210.00
重點:
agg()可以同時對不同欄位指定不同的聚合函式,讓結果一次呈現。
1.2 多重分組與自訂聚合
# 依「產品」與「日期」的月份分組,計算每日銷售量的最大值與最小值
df['月份'] = df['日期'].dt.month
multi_grp = df.groupby(['產品', '月份']).agg({
'銷售量': ['max', 'min'],
'收入': 'sum'
})
# 為欄位重新命名
multi_grp.columns = ['銷售量_最大', '銷售量_最小', '收入總和']
multi_grp = multi_grp.reset_index()
print(multi_grp)
說明
groupby(['產品', '月份'])會產生二層索引。- 使用 列表 方式指定多個聚合函式(
['max', 'min']),最後再透過columns重新命名以提升可讀性。
1.3 transform – 保留原始形狀的分組計算
有時候我們想把分組後的統計值 回填 到原始 DataFrame,transform 正好符合需求:
# 計算每筆資料在其所屬「產品」的銷售量佔比
df['銷售量_佔比'] = df.groupby('產品')['銷售量'].transform(lambda x: x / x.sum())
print(df)
2. merge – 資料庫式的 JOIN
在實務上,我們常常需要把 不同來源(例如銷售紀錄、商品資訊、客戶資料)合併成一張完整的表格。pandas.merge() 提供了四種最常見的 JOIN 類型:
| JOIN 類型 | 說明 |
|---|---|
inner |
只保留兩表都有的鍵值(預設) |
left |
以左表為基礎,右表找不到則填 NaN |
right |
以右表為基礎,左表找不到則填 NaN |
outer |
兩表的鍵值全部保留,缺失的欄位以 NaN 填補 |
2.1 基本合併
# 商品資訊表
product = pd.DataFrame({
'產品編號': [101, 102, 103],
'產品名稱': ['手機', '筆電', '平板'],
'類別': ['電子', '電子', '電子']
})
# 銷售紀錄表
sales = pd.DataFrame({
'訂單編號': [1001, 1002, 1003],
'產品編號': [101, 102, 104],
'數量': [2, 1, 5]
})
# 內部合併:只保留兩表都有的「產品編號」
merged_inner = pd.merge(sales, product, on='產品編號', how='inner')
print(merged_inner)
輸出
訂單編號 產品編號 數量 產品名稱 類別
0 1001 101 2 手機 電子
1 1002 102 1 筆電 電子
技巧:若欄位名稱不同,可分別使用
left_on與right_on指定。
2.2 多鍵合併與避免重複欄位
# 客戶表(含城市資訊)
customer = pd.DataFrame({
'客戶編號': [1, 2, 3],
'城市': ['台北', '新北', '台中']
})
# 訂單表(同時有客戶編號與城市,作為測試)
order = pd.DataFrame({
'訂單編號': [2001, 2002, 2003],
'客戶編號': [1, 2, 4],
'城市': ['台北', '新北', '高雄'],
'金額': [5000, 3000, 7000]
})
# 以「客戶編號」與「城市」同時作為鍵值合併(左合併)
merged_multi = pd.merge(order, customer, on=['客戶編號', '城市'], how='left')
print(merged_multi)
說明
- 多鍵合併可確保 同一客戶在不同城市的紀錄 不會誤合。
- 若兩表都有同名欄位但不作為鍵值,
merge會自動加上_x、_y後綴,避免衝突。
2.3 indicator 參數 – 追蹤合併來源
# 加上 indicator,檢視每筆資料的合併狀態
merged_check = pd.merge(sales, product, on='產品編號', how='outer', indicator=True)
print(merged_check[['產品編號', '_merge']])
輸出
產品編號 _merge
0 101 both
1 102 both
2 103 left_only
3 104 right_only
此功能在 資料清理 時特別有用,可快速找出缺失或多餘的鍵值。
3. pivot / pivot_table – 重新塑形與交叉表
pivot 能把「長條」資料(每筆紀錄只包含單一變數)轉成「寬表」形式,讓每個類別成為欄位。
若需要同時處理 重複索引 或 缺失值,則使用功能更強大的 pivot_table。
3.1 pivot 的簡單應用
# 銷售明細(長條格式)
sales_long = pd.DataFrame({
'日期': pd.date_range('2024-01-01', periods=4, freq='D').repeat(3),
'產品': ['A', 'B', 'C'] * 4,
'銷售量': [5, 3, 2, 6, 4, 3, 7, 5, 4, 8, 6, 5]
})
# 以「日期」為索引,「產品」為欄位,聚合方式為 sum
sales_wide = sales_long.pivot(index='日期', columns='產品', values='銷售量')
print(sales_wide)
輸出
產品 A B C
日期
2024-01-01 5 3 2
2024-01-02 6 4 3
2024-01-03 7 5 4
2024-01-04 8 6 5
注意:若同一
index+columns組合出現多筆資料,pivot會拋出ValueError。
3.2 pivot_table 解決重複問題
# 加入重複資料
sales_long.loc[2, '銷售量'] = 10 # 同一天、同產品 A 出現兩筆
# 使用 pivot_table,指定聚合函式為 sum
sales_pt = sales_long.pivot_table(
index='日期',
columns='產品',
values='銷售量',
aggfunc='sum',
fill_value=0 # 缺失值填 0
)
print(sales_pt)
說明
aggfunc可以是'sum'、'mean'、'max',甚至自訂函式。fill_value讓結果表格更易於後續計算(例如直接相加)。
3.3 多層索引與多變數
# 多變數範例:同時計算銷售量與收入的平均值
sales_detail = pd.DataFrame({
'日期': pd.date_range('2024-01-01', periods=6, freq='D'),
'產品': ['A', 'A', 'B', 'B', 'C', 'C'],
'銷售量': [5, 6, 3, 4, 2, 3],
'收入': [500, 600, 300, 400, 200, 300]
})
pt_multi = sales_detail.pivot_table(
index='日期',
columns='產品',
values=['銷售量', '收入'],
aggfunc='mean'
)
print(pt_multi)
結果 為 MultiIndex(欄位同時有變數與產品),可使用 pt_multi['銷售量']['A'] 直接取出特定子表。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方法 |
|---|---|---|
groupby 產生的索引不易閱讀 |
groupby 後的結果會把分組欄位變成索引。 |
使用 .reset_index() 或在 agg 時直接指定 as_index=False。 |
merge 產生重複欄位 |
兩表同名欄位且未指定為鍵值,會自動加上 _x/_y。 |
事先 rename 欄位,或在 merge 後使用 df.filter/df.drop 清理。 |
pivot 無法處理重複索引 |
若同一 index+columns 出現多筆,拋錯。 |
改用 pivot_table,或先使用 groupby 先聚合。 |
| 缺失值的傳遞 | groupby、merge、pivot 都會把 NaN 帶入結果,可能影響後續運算。 |
使用 fillna()、dropna() 或在 agg 時指定 fill_value。 |
| 效能問題 | 大型資料(>10^6 筆)頻繁 groupby+merge 會變慢。 |
- 先 篩選 必要欄位 - 使用 categorical 類型降低記憶體 - 若可行,改用 Dask 或 Polars 等分散式框架。 |
最佳實踐小結
- 先思考資料流:先
groupby聚合再merge,往往比一次性合併後再聚合更有效率。 - 明確指定鍵值與欄位類型:
astype('category')可顯著提升groupby、merge的速度。 - 善用
agg與pivot_table的多函式特性:一次完成多個統計指標,避免重複計算。 - 保留原始資料:在重要步驟前
df.copy(),以免不小心改變原始 DataFrame。
實際應用場景
| 場景 | 需求 | 使用工具 | 範例流程 |
|---|---|---|---|
| 銷售報表 | 按月份、產品類別統計銷售額與毛利 | groupby + pivot_table |
1. 讀入交易資料 2. groupby(['月份','類別']).agg({'銷售額':'sum','毛利':'mean'}) 3. pivot_table 產生交叉表供 Excel 匯出 |
| 客戶分群 | 合併客戶基本資訊與最近 6 個月的購買行為 | merge(left join)+ groupby |
1. 讀入 customer、order 2. merge(customer, order, on='客戶編號', how='left') 3. groupby('客戶編號').agg({'金額':'sum','訂單編號':'nunique'}) |
| 異常偵測 | 找出同一天、同產品的銷售量突增 | groupby + transform |
1. df['每日均值'] = df.groupby(['日期','產品'])['銷售量'].transform('mean') 2. df['異常'] = df['銷售量'] > df['每日均值'] * 3 |
| 市場調查 | 交叉分析不同地區、不同年齡層的產品偏好 | pivot_table(多維度) |
pivot_table(index='地區', columns='年齡層', values='購買次數', aggfunc='sum', fill_value=0) |
| 資料庫遷移 | 從多個 CSV 合併成單一事實表(Fact Table) | 多次 merge(inner)+ groupby 去重 |
1. 讀入 sales.csv, product.csv, store.csv 2. 依主鍵 product_id, store_id 依序 merge 3. groupby(['order_id']).first() 產生唯一事實表 |
總結
groupby是彙總與統計的核心,配合agg、transform能同時產出多種指標。merge為資料庫式的 JOIN,掌握how、indicator、多鍵合併等參數,可安全且高效地整合多源資料。pivot/pivot_table讓長條資料瞬間變寬,提供交叉表與多維度分析的利器;遇到重複索引時,pivot_table是最好的備胎。
透過上述概念與實作範例,您已具備在 資料清理、探索性分析以及報表自動化 中運用 groupby、merge、pivot 的能力。建議在日常專案中,先以 小樣本驗證 再擴展至全量資料,並隨時檢查 記憶體與效能,才能真正把 Python 的資料處理力量發揮到極致。祝您在資料分析的路上玩得開心、寫程式更順!