Pandas多源數(shù)據(jù)整合之三大數(shù)據(jù)合并方法(Merge,Join與Concat?)詳解
本節(jié)學(xué)習(xí)目標(biāo)
- 理解數(shù)據(jù)庫連接的各種類型(inner、outer、left、right)
- 掌握
merge函數(shù)的全部核心參數(shù) - 學(xué)會使用
join進(jìn)行基于索引的關(guān)聯(lián) - 掌握
concat進(jìn)行軸向合并 - 能夠處理多表關(guān)聯(lián)和數(shù)據(jù)對齊問題
為什么學(xué)這個?
在真實工作環(huán)境中,數(shù)據(jù)很少只存在于一張表中。以電商系統(tǒng)為例:
- 用戶表:存儲用戶的姓名、郵箱、注冊日期
- 訂單表:存儲訂單號、用戶 ID、下單時間、總金額
- 產(chǎn)品表:存儲產(chǎn)品 ID、名稱、分類、單價
當(dāng)你想回答"2024年購買電子產(chǎn)品最多的用戶是誰"這個問題時,你需要同時關(guān)聯(lián)這三張表。
這正是本節(jié)的重點:多源數(shù)據(jù)整合。
Pandas 提供了三種主要的數(shù)據(jù)合并方式:
merge—— 基于列值的關(guān)聯(lián)(類似 SQL 的 JOIN)join—— 基于索引的關(guān)聯(lián)concat—— 按軸方向拼接(上下或左右)
打個比方:如果說 groupby 是把數(shù)據(jù)"拆分再匯總",那么 merge/join/concat 就是把散落各處的數(shù)據(jù)"拼起來"。前者是拆解分析,后者是整合重組,兩者同等重要。
核心知識點講解
merge:基于列值的關(guān)聯(lián)
merge 是 Pandas 中最常用的數(shù)據(jù)關(guān)聯(lián)方法,它與 SQL 數(shù)據(jù)庫中的 JOIN 操作非常類似。
1. 準(zhǔn)備示例數(shù)據(jù)
import pandas as pd
import numpy as np
# 訂單表
orders = pd.DataFrame({
'訂單ID': [1001, 1002, 1003, 1004, 1005],
'用戶ID': [1, 2, 3, 1, 4],
'金額': [500, 300, 800, 200, 1500],
'日期': ['2024-01-15', '2024-02-20', '2024-03-10', '2024-04-05', '2024-05-18']
})
# 用戶表
users = pd.DataFrame({
'用戶ID': [1, 2, 3, 5],
'姓名': ['張三', '李四', '王五', '趙六'],
'城市': ['北京', '上海', '廣州', '深圳']
})
# 產(chǎn)品表
products = pd.DataFrame({
'訂單ID': [1001, 1002, 1003, 1004, 1005],
'產(chǎn)品名': ['手機(jī)', '耳機(jī)', '電腦', '充電器', '手表'],
'品類': ['電子產(chǎn)品', '配件', '電子產(chǎn)品', '配件', '飾品']
})
print("訂單表:")
print(orders)
print("\n用戶表:")
print(users)
print("\n產(chǎn)品表:")
print(products)
2. 內(nèi)連接(inner join)
內(nèi)連接只保留兩個表中都有的匹配行。
# 內(nèi)連接:只保留有用戶信息的訂單
result = pd.merge(orders, users, on='用戶ID', how='inner')
print("內(nèi)連接(只保留有用戶的訂單):")
print(result)
# 注意:用戶ID=4 的訂單被丟棄了(因為用戶表中沒有ID=4的用戶)
# 用戶ID=5 的趙六也被丟棄了(因為訂單表中沒有他的訂單)
Venn 圖理解:inner join 就像兩個圓圈的交集部分。
3. 左連接(left join)
左連接保留左表的所有行,右表中沒有匹配的填充 NaN。
# 左連接:保留所有訂單,匹配不到的用戶信息為 NaN
result = pd.merge(orders, users, on='用戶ID', how='left')
print("左連接(保留所有訂單):")
print(result)
# 訂單ID=1005 的訂單保留了,但用戶信息(姓名、城市)為 NaN
# 因為用戶表中沒有用戶ID=4的記錄
左連接是最常用的連接方式,因為它保證了主表(左表)的數(shù)據(jù)完整性。
4. 右連接(right join)
右連接保留右表的所有行,左表中沒有匹配的填充 NaN。
# 右連接:保留所有用戶,沒有訂單的用戶訂單信息為 NaN
result = pd.merge(orders, users, on='用戶ID', how='right')
print("右連接(保留所有用戶):")
print(result)
# 趙六(用戶ID=5)保留了,但訂單信息為 NaN
# 因為他沒有下過訂單
5. 外連接(outer join)
外連接保留兩個表的所有行,沒有匹配的填充 NaN。
# 外連接:保留所有訂單和所有用戶
result = pd.merge(orders, users, on='用戶ID', how='outer')
print("外連接(保留所有):")
print(result)
6. merge 參數(shù)詳解
# ===== on / left_on / right_on =====
# on=:兩個表的關(guān)聯(lián)列名相同
result = pd.merge(orders, users, on='用戶ID')
# left_on / right_on:關(guān)聯(lián)列名不同
users2 = pd.DataFrame({
'ID': [1, 2, 3, 5], # 列名不同
'姓名': ['張三', '李四', '王五', '趙六'],
'城市': ['北京', '上海', '廣州', '深圳']
})
result = pd.merge(
orders, users2,
left_on='用戶ID', # 左表的關(guān)聯(lián)列
right_on='ID' # 右表的關(guān)聯(lián)列
)
print("不同列名關(guān)聯(lián):")
print(result)
# ===== suffixes:處理重名列 =====
orders2 = pd.DataFrame({
'訂單ID': [1001, 1002],
'金額': [500, 300],
'更新時間': ['2024-01-15', '2024-02-20'] # 與右表有重名列
})
users3 = pd.DataFrame({
'訂單ID': [1001, 1002],
'金額': [550, 320], # 重名列
'更新時間': ['2024-01-16', '2024-02-21'] # 重名列
})
result = pd.merge(
orders2, users3,
on='訂單ID',
how='left',
suffixes=('_訂單', '_用戶') # 自定義后綴
)
print("重名列處理:")
print(result)
# ===== indicator:顯示匹配來源 =====
result = pd.merge(orders, users, on='用戶ID', how='outer', indicator=True)
print("匹配來源:")
print(result)
# _merge 列會顯示 "both"、"left_only"、"right_only"
7. 多表關(guān)聯(lián)
# 三表關(guān)聯(lián):先關(guān)聯(lián) orders + users,再關(guān)聯(lián) products
result = pd.merge(orders, users, on='用戶ID', how='left')
result = pd.merge(result, products, on='訂單ID', how='left')
print("三表關(guān)聯(lián)結(jié)果:")
print(result)
# 鏈?zhǔn)綄懛?
result = (orders
.merge(users, on='用戶ID', how='left')
.merge(products, on='訂單ID', how='left'))
print("\n鏈?zhǔn)綄懛ǎ?)
print(result)
join:基于索引的關(guān)聯(lián)
join 是基于索引進(jìn)行關(guān)聯(lián)的方法。它本質(zhì)上是 merge 的便捷版本,當(dāng)你的關(guān)聯(lián)鍵是索引時,使用 join 更簡潔。
# 設(shè)置索引
orders_idx = orders.set_index('用戶ID')
users_idx = users.set_index('用戶ID')
print("orders 索引:")
print(orders_idx)
print("\nusers 索引:")
print(users_idx)
# ===== 左連接(join 默認(rèn)就是左連接)=====
result = orders_idx.join(users_idx, lsuffix='_訂單', rsuffix='_用戶')
print("join 左連接:")
print(result)
# ===== 指定連接方式 =====
result = orders_idx.join(users_idx, how='inner', lsuffix='_訂單', rsuffix='_用戶')
print("\njoin 內(nèi)連接:")
print(result)
# join 的等價 merge 寫法:
# result = pd.merge(orders, users, left_index=True, right_index=True, how='left')
concat:軸向拼接
concat 用于將多個 DataFrame 按軸方向拼接——可以是上下拼接(縱向),也可以是左右拼接(橫向)。
1. 縱向拼接(axis=0,默認(rèn))
# 兩個結(jié)構(gòu)相同的 DataFrame
df1 = pd.DataFrame({
'姓名': ['張三', '李四'],
'年齡': [25, 30],
'城市': ['北京', '上海']
})
df2 = pd.DataFrame({
'姓名': ['王五', '趙六'],
'年齡': [28, 35],
'城市': ['廣州', '深圳']
})
# 上下拼接
result = pd.concat([df1, df2])
print("上下拼接:")
print(result)
# 忽略原索引
result = pd.concat([df1, df2], ignore_index=True)
print("\n忽略原索引:")
print(result)
# 添加來源標(biāo)記
result = pd.concat([df1, df2], keys=['表1', '表2'])
print("\n帶來源標(biāo)記:")
print(result)
2. 橫向拼接(axis=1)
# 兩個結(jié)構(gòu)不同的 DataFrame
df_a = pd.DataFrame({
'A': [1, 2, 3],
'B': [4, 5, 6]
})
df_b = pd.DataFrame({
'C': [7, 8, 9],
'D': [10, 11, 12]
})
# 左右拼接
result = pd.concat([df_a, df_b], axis=1)
print("左右拼接:")
print(result)
3. concat 的數(shù)據(jù)對齊行為
# concat 會按索引對齊
df_x = pd.DataFrame({'A': [1, 2]}, index=[0, 1])
df_y = pd.DataFrame({'B': [3, 4]}, index=[1, 2])
result = pd.concat([df_x, df_y], axis=1)
print("索引對齊拼接:")
print(result)
# 輸出:
# A B
# 0 1.0 NaN
# 1 2.0 3.0
# 2 NaN 4.0
# inner:只保留共有的索引
result = pd.concat([df_x, df_y], axis=1, join='inner')
print("\ninner 對齊:")
print(result)
# 只保留索引 1
對比與選擇指南
| 方法 | 適用場景 | 關(guān)聯(lián)依據(jù) | 示例 |
|---|---|---|---|
merge | 表關(guān)聯(lián)(類似 SQL JOIN) | 列值 | merge(df1, df2, on='id') |
join | 基于索引的關(guān)聯(lián) | 索引 | df1.join(df2) |
concat | 數(shù)據(jù)追加或并排拼接 | 軸方向 | concat([df1, df2]) |
快速決策:
- 有共同的列名做關(guān)聯(lián)?→ 用
merge - 索引就是關(guān)聯(lián)鍵?→ 用
join(或merge(left_index=True, right_index=True)) - 只是把兩個表上下或左右拼起來?→ 用
concat
數(shù)據(jù)對齊與常見問題
1. 重復(fù)列名處理
# 關(guān)聯(lián)后可能出現(xiàn)重復(fù)列
df1 = pd.DataFrame({'ID': [1, 2], 'name': ['A', 'B'], 'score': [90, 85]})
df2 = pd.DataFrame({'ID': [1, 2], 'name': ['A', 'B'], 'grade': ['A', 'B']})
result = pd.merge(df1, df2, on='ID', suffixes=('_1', '_2'))
print(result)
2. 一對多關(guān)聯(lián)
# 一個用戶對應(yīng)多個訂單(一對多)
users = pd.DataFrame({
'用戶ID': [1, 2, 3],
'姓名': ['張三', '李四', '王五']
})
orders = pd.DataFrame({
'訂單ID': [101, 102, 103, 104],
'用戶ID': [1, 1, 2, 3], # 用戶1有兩個訂單
'金額': [500, 300, 800, 200]
})
result = pd.merge(users, orders, on='用戶ID', how='left')
print("一對多關(guān)聯(lián):")
print(result)
# 張三會出現(xiàn)兩行,分別對應(yīng)兩個訂單
3. 多對多關(guān)聯(lián)
# 多對多關(guān)聯(lián)會產(chǎn)生笛卡爾積
df1 = pd.DataFrame({'key': ['A', 'A', 'B'], 'val1': [1, 2, 3]})
df2 = pd.DataFrame({'key': ['A', 'A', 'B'], 'val2': [4, 5, 6]})
result = pd.merge(df1, df2, on='key')
print("多對多關(guān)聯(lián)(笛卡爾積):")
print(result)
# A-A 組合產(chǎn)生 2×2=4 行
# 注意:Pandas 3.x 中多對多關(guān)聯(lián)需要顯式指定 validate 參數(shù)
實戰(zhàn)練習(xí)
練習(xí) 1:三表關(guān)聯(lián)查詢
題目:關(guān)聯(lián)學(xué)生、課程、成績?nèi)龔埍怼?/p>
# 參考答案
import pandas as pd
students = pd.DataFrame({
'學(xué)號': [101, 102, 103, 104],
'姓名': ['小明', '小紅', '小剛', '小麗'],
'班級': ['一班', '二班', '一班', '三班']
})
courses = pd.DataFrame({
'課程號': ['C01', 'C02', 'C03'],
'課程名': ['語文', '數(shù)學(xué)', '英語'],
'學(xué)分': [4, 5, 4]
})
grades = pd.DataFrame({
'學(xué)號': [101, 101, 102, 102, 103, 103, 104, 104],
'課程號': ['C01', 'C02', 'C01', 'C03', 'C02', 'C03', 'C01', 'C02'],
'成績': [85, 90, 92, 88, 78, 82, 96, 85]
})
# 1. 關(guān)聯(lián)學(xué)生 + 成績
result = students.merge(grades, on='學(xué)號', how='left')
result = result.merge(courses, on='課程號', how='left')
print("學(xué)生-課程-成績關(guān)聯(lián):")
print(result)
# 2. 計算每個學(xué)生的平均分
avg = result.groupby(['學(xué)號', '姓名'])['成績'].mean().reset_index()
avg.columns = ['學(xué)號', '姓名', '平均分']
print("\n學(xué)生平均分:")
print(avg.round(1))
練習(xí) 2:數(shù)據(jù)拼接實踐
題目:將多個季度的數(shù)據(jù)文件合并。
# 參考答案
import pandas as pd
import numpy as np
# 模擬四個季度的數(shù)據(jù)
q1 = pd.DataFrame({
'季度': ['Q1'] * 3,
'產(chǎn)品': ['A', 'B', 'C'],
'銷售額': [1000, 2000, 1500]
})
q2 = pd.DataFrame({
'季度': ['Q2'] * 3,
'產(chǎn)品': ['A', 'B', 'C'],
'銷售額': [1200, 2200, 1600]
})
q3 = pd.DataFrame({
'季度': ['Q3'] * 3,
'產(chǎn)品': ['A', 'B', 'C'],
'銷售額': [1100, 2100, 1700]
})
q4 = pd.DataFrame({
'季度': ['Q4'] * 3,
'產(chǎn)品': ['A', 'B', 'C'],
'銷售額': [1300, 2300, 1800]
})
# 縱向拼接
df = pd.concat([q1, q2, q3, q4], ignore_index=True)
print("年度合并數(shù)據(jù):")
print(df)
# 計算年度匯總
summary = df.groupby('產(chǎn)品')['銷售額'].agg(['sum', 'mean']).reset_index()
summary.columns = ['產(chǎn)品', '年銷售額', '季均銷售額']
print("\n年度匯總:")
print(summary)
本節(jié)總結(jié)
本節(jié)我們學(xué)習(xí)了 Pandas 的三種數(shù)據(jù)整合方法:
merge —— 基于列值的表關(guān)聯(lián):
how參數(shù):'inner'(交集)、'left'(保留左表)、'right'(保留右表)、'outer'(并集)on/left_on/right_on指定關(guān)聯(lián)列suffixes處理重名列
join —— 基于索引的關(guān)聯(lián):
- 默認(rèn)左連接,簡潔方便
- 適合關(guān)聯(lián)鍵為索引的場景
concat —— 軸向拼接:
axis=0(默認(rèn)):上下拼接axis=1:左右拼接keys添加來源標(biāo)記- 按索引自動對齊
到此這篇關(guān)于Pandas多源數(shù)據(jù)整合之三大數(shù)據(jù)合并方法(Merge,Join與Concat )詳解的文章就介紹到這了,更多相關(guān)Pandas數(shù)據(jù)合并內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python中playwright結(jié)合pytest執(zhí)行用例的實現(xiàn)
本文主要介紹了python中playwright結(jié)合pytest執(zhí)行用例的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12
詳解如何使用Python和正則表達(dá)式處理XML表單數(shù)據(jù)
在日常的Web開發(fā)中,處理表單數(shù)據(jù)是一個常見的任務(wù),而XML是一種常用的數(shù)據(jù)格式,用于在不同的系統(tǒng)之間傳遞和存儲數(shù)據(jù),本文通過闡述一個技術(shù)問題并給出解答的方式,介紹如何使用Python和正則表達(dá)式處理XML表單數(shù)據(jù),需要的朋友可以參考下2023-09-09
Fiddler如何抓取手機(jī)APP數(shù)據(jù)包
Fiddler,這個是所有軟件開發(fā)者必備神器!這款工具不僅可以抓取PC上開發(fā)web時候的數(shù)據(jù)包,而且可以抓取移動端,通過本文給大家介紹Fiddler如何抓取手機(jī)APP數(shù)據(jù)包,感興趣的朋友一起學(xué)習(xí)吧2016-01-01
Python基于多線程實現(xiàn)抓取數(shù)據(jù)存入數(shù)據(jù)庫的方法
這篇文章主要介紹了Python基于多線程實現(xiàn)抓取數(shù)據(jù)存入數(shù)據(jù)庫的方法,結(jié)合實例形式分析了Python使用數(shù)據(jù)庫類與多線程類進(jìn)行數(shù)據(jù)抓取與寫入數(shù)據(jù)庫操作的具體使用技巧,需要的朋友可以參考下2018-06-06
Python使用asyncio.Queue進(jìn)行任務(wù)調(diào)度的實現(xiàn)
本文主要介紹了Python使用asyncio.Queue進(jìn)行任務(wù)調(diào)度的實現(xiàn),它可以用于任務(wù)調(diào)度和數(shù)據(jù)交換,文中通過示例代碼介紹的非常詳細(xì),感興趣的可以了解一下2024-02-02

