当前位置: 首页 > news >正文

Pandas 合并数据集:merge 和 join

文章目录

  • Pandas 合并数据集:merge 和 join
    • 关系代数
    • 连接的类别
      • 一对一连接
      • 多对一连接
      • 多对多连接
    • 指定合并键
      • on 关键字
      • left_on 和 right_on 关键字
      • left_index 和 right_index 关键字
    • 为连接指定集合运算方式
    • 列名重叠:suffixes 关键字
    • 示例:美国各州数据

Pandas 合并数据集:merge 和 join

cover

Pandas 提供了一个重要的功能,即高性能的内存中连接(join)和合并(merge)操作,如果你曾经使用过数据库,可能已经很熟悉这些操作。
主要的接口是 pd.merge 函数,接下来我们将通过一些示例来了解它的实际用法。

为了方便起见,我们会在常规导入之后再次定义上一章中的 display 函数:

import pandas as pd
import numpy as npclass display(object):"""Display HTML representation of multiple objects"""template = """<div style="float: left; padding: 10px;"><p style='font-family:"Courier New", Courier, monospace'>{0}</p>{1}</div>"""def __init__(self, *args):self.args = argsdef _repr_html_(self):return '\n'.join(self.template.format(a, eval(a)._repr_html_())for a in self.args)def __repr__(self):return '\n\n'.join(a + '\n' + repr(eval(a))for a in self.args)

关系代数

pd.merge 实现的行为是关系代数的一部分。关系代数是一套用于操作关系型数据的正式规则,是大多数数据库操作的概念基础。
关系代数方法的优势在于它提出了若干基本操作,这些操作成为对任何数据集进行更复杂操作的构建模块。
只要在数据库或其他程序中高效实现了这些基本操作,就可以执行各种相当复杂的复合操作。

Pandas 在 pd.merge 函数以及 SeriesDataFrame 对象的相关 join 方法中实现了这些基本构建模块中的几个。
正如你将看到的,这些方法可以高效地将来自不同来源的数据关联起来。

连接的类别

pd.merge 函数实现了多种类型的连接:一对一多对一多对多
这三种连接类型都可以通过相同的 pd.merge 接口调用实现;实际执行哪种连接,取决于输入数据的形式。
我们将从这三种合并类型的简单示例开始,稍后再讨论更详细的选项。

一对一连接

也许最简单的合并类型就是一对一连接,这在很多方面类似于你在合并数据集:concat 和 append中看到的按列拼接。
举个具体的例子,考虑下面两个 DataFrame 对象,它们包含了公司中几位员工的信息:

df1 = pd.DataFrame({'employee': ['Bob', 'Jake', 'Lisa', 'Sue'],'group': ['Accounting', 'Engineering','Engineering', 'HR']})
df2 = pd.DataFrame({'employee': ['Lisa', 'Bob', 'Jake', 'Sue'],'hire_date': [2004, 2008, 2012, 2014]})
display('df1', 'df2')

df1

employeegroup
0BobAccounting
1JakeEngineering
2LisaEngineering
3SueHR

df2

employeehire_date
0Lisa2004
1Bob2008
2Jake2012
3Sue2014

要将这些信息合并到一个 DataFrame 中,我们可以使用 pd.merge 函数:

df3 = pd.merge(df1, df2)
df3
employeegrouphire_date
0BobAccounting2008
1JakeEngineering2012
2LisaEngineering2004
3SueHR2014

pd.merge 函数会自动识别每个 DataFrame 中的 employee 列,并以此作为键进行连接。
合并的结果是一个新的 DataFrame,它将两个输入的数据整合在一起。
请注意,每一列中的条目顺序不一定会被保留:在本例中,employee 列在 df1df2 中的顺序不同,而 pd.merge 函数能够正确处理这一点。
另外需要注意的是,合并操作通常会丢弃索引,除非使用按索引合并(稍后会讨论 left_indexright_index 关键字)。

多对一连接

多对一连接是指两个键列中的一个包含重复项的连接。
对于多对一连接,结果 DataFrame 会适当地保留这些重复项。
请看下面关于多对一连接的示例:

df4 = pd.DataFrame({'group': ['Accounting', 'Engineering', 'HR'],'supervisor': ['Carly', 'Guido', 'Steve']})
display('df3', 'df4', 'pd.merge(df3, df4)')

df3

employeegrouphire_date
0BobAccounting2008
1JakeEngineering2012
2LisaEngineering2004
3SueHR2014

df4

groupsupervisor
0AccountingCarly
1EngineeringGuido
2HRSteve

pd.merge(df3, df4)

employeegrouphire_datesupervisor
0BobAccounting2008Carly
1JakeEngineering2012Guido
2LisaEngineering2004Guido
3SueHR2014Steve

结果的 DataFrame 会多出一列 “supervisor”(主管)信息,其中的信息会根据输入数据的需要在一个或多个位置重复出现。

多对多连接

多对多连接在概念上可能有些令人困惑,但它们依然有明确的定义。
如果左右数组中的键列都包含重复项,那么结果就是多对多合并。
通过具体的例子可以更清楚地理解这一点。
请看下面的例子,我们有一个 DataFrame,显示了每个小组关联的一项或多项技能。
通过执行多对多连接,我们可以获得与每个个人相关的技能信息:

df5 = pd.DataFrame({'group': ['Accounting', 'Accounting','Engineering', 'Engineering', 'HR', 'HR'],'skills': ['math', 'spreadsheets', 'software', 'math','spreadsheets', 'organization']})
display('df1', 'df5', "pd.merge(df1, df5)")

df1

employeegroup
0BobAccounting
1JakeEngineering
2LisaEngineering
3SueHR

df5

groupskills
0Accountingmath
1Accountingspreadsheets
2Engineeringsoftware
3Engineeringmath
4HRspreadsheets
5HRorganization

pd.merge(df1, df5)

employeegroupskills
0BobAccountingmath
1BobAccountingspreadsheets
2JakeEngineeringsoftware
3JakeEngineeringmath
4LisaEngineeringsoftware
5LisaEngineeringmath
6SueHRspreadsheets
7SueHRorganization

这三种连接类型可以与其他 Pandas 工具结合使用,实现各种功能。
但在实际应用中,数据集很少像我们这里演示的那样干净整齐。
在接下来的部分,我们将介绍 pd.merge 提供的一些选项,这些选项可以帮助你调整连接操作的具体方式。

指定合并键

我们已经看到了 pd.merge 的默认行为:它会在两个输入数据中查找一个或多个匹配的列名,并将其作为连接键。
然而,很多时候列名并不会如此完美地匹配,pd.merge 提供了多种选项来处理这种情况。

on 关键字

最简单的方式是使用 on 关键字显式指定连接键的列名。on 可以接受一个列名或列名列表:

display('df1', 'df2', "pd.merge(df1, df2, on='employee')")

df1

employeegroup
0BobAccounting
1JakeEngineering
2LisaEngineering
3SueHR

df2

employeehire_date
0Lisa2004
1Bob2008
2Jake2012
3Sue2014

pd.merge(df1, df2, on='employee')

employeegrouphire_date
0BobAccounting2008
1JakeEngineering2012
2LisaEngineering2004
3SueHR2014

此选项仅在左右两个 DataFrame 都包含指定的列名时有效。

left_on 和 right_on 关键字

有时你可能希望合并两个具有不同列名的数据集;例如,我们可能有一个数据集,其中员工姓名的列名是 “name” 而不是 “employee”。
在这种情况下,我们可以使用 left_onright_on 关键字来分别指定两个列名:

df3 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],'salary': [70000, 80000, 120000, 90000]})
display('df1', 'df3', 'pd.merge(df1, df3, left_on="employee", right_on="name")')

df1

employeegroup
0BobAccounting
1JakeEngineering
2LisaEngineering
3SueHR

df3

namesalary
0Bob70000
1Jake80000
2Lisa120000
3Sue90000

pd.merge(df1, df3, left_on="employee", right_on="name")

employeegroupnamesalary
0BobAccountingBob70000
1JakeEngineeringJake80000
2LisaEngineeringLisa120000
3SueHRSue90000

结果中有一个多余的列,如果需要,可以通过 DataFrame.drop() 方法将其删除:

pd.merge(df1, df3, left_on="employee", right_on="name").drop('name', axis=1)
employeegroupsalary
0BobAccounting70000
1JakeEngineering80000
2LisaEngineering120000
3SueHR90000

left_index 和 right_index 关键字

有时候,你可能希望根据索引而不是某一列来进行合并。
例如,你的数据可能如下所示:

df1a = df1.set_index('employee')
df2a = df2.set_index('employee')
display('df1a', 'df2a')

df1a

group
employee
BobAccounting
JakeEngineering
LisaEngineering
SueHR

df2a

hire_date
employee
Lisa2004
Bob2008
Jake2012
Sue2014

你可以通过在 pd.merge() 中指定 left_index 和/或 right_index 参数,将索引作为合并的键来使用:

display('df1a', 'df2a',"pd.merge(df1a, df2a, left_index=True, right_index=True)")

df1a

group
employee
BobAccounting
JakeEngineering
LisaEngineering
SueHR

df2a

hire_date
employee
Lisa2004
Bob2008
Jake2012
Sue2014

pd.merge(df1a, df2a, left_index=True, right_index=True)

grouphire_date
employee
BobAccounting2008
JakeEngineering2012
LisaEngineering2004
SueHR2014

为方便起见,Pandas 提供了 DataFrame.join() 方法,它可以在不需要额外关键字的情况下基于索引进行合并:

df1a.join(df2a)
grouphire_date
employee
BobAccounting2008
JakeEngineering2012
LisaEngineering2004
SueHR2014

如果你希望混合使用索引和列进行合并,可以结合使用 left_indexright_on,或 left_onright_index,以实现所需的行为:

display('df1a', 'df3', "pd.merge(df1a, df3, left_index=True, right_on='name')")

df1a

group
employee
BobAccounting
JakeEngineering
LisaEngineering
SueHR

df3

namesalary
0Bob70000
1Jake80000
2Lisa120000
3Sue90000

pd.merge(df1a, df3, left_index=True, right_on='name')

groupnamesalary
0AccountingBob70000
1EngineeringJake80000
2EngineeringLisa120000
3HRSue90000

所有这些选项同样适用于多个索引和/或多列;其接口设计非常直观易用。
更多相关内容,请参阅 Pandas 文档中的“合并、连接与拼接”部分。

为连接指定集合运算方式

在之前的所有示例中,我们都忽略了执行连接时一个重要的考虑因素:连接中所使用的集合运算类型。
当某个值出现在一个键列中但未出现在另一个键列时,这个问题就会出现。请看下面的例子:

df6 = pd.DataFrame({'name': ['Peter', 'Paul', 'Mary'],'food': ['fish', 'beans', 'bread']},columns=['name', 'food'])
df7 = pd.DataFrame({'name': ['Mary', 'Joseph'],'drink': ['wine', 'beer']},columns=['name', 'drink'])
display('df6', 'df7', 'pd.merge(df6, df7)')

df6

namefood
0Peterfish
1Paulbeans
2Marybread

df7

namedrink
0Marywine
1Josephbeer

pd.merge(df6, df7)

namefooddrink
0Marybreadwine

这里我们合并了两个数据集,它们只有一个共同的 “name” 条目:Mary。
默认情况下,结果只包含两个输入集合的交集;这就是所谓的内连接(inner join)。
我们可以通过 how 关键字显式指定这一点,how 的默认值为 "inner"

pd.merge(df6, df7, how='inner')
namefooddrink
0Marybreadwine

how 关键字的其他选项包括 'outer''left''right'
外连接(outer join)会返回输入列的并集,并用 NA 填充所有缺失值:

display('df6', 'df7', "pd.merge(df6, df7, how='outer')")

df6

namefood
0Peterfish
1Paulbeans
2Marybread

df7

namedrink
0Marywine
1Josephbeer

pd.merge(df6, df7, how='outer')

namefooddrink
0JosephNaNbeer
1Marybreadwine
2PaulbeansNaN
3PeterfishNaN

左连接(left join)和右连接(right join)分别返回以左侧条目或右侧条目为基础的连接结果。
例如:

display('df6', 'df7', "pd.merge(df6, df7, how='left')")

df6

namefood
0Peterfish
1Paulbeans
2Marybread

df7

namedrink
0Marywine
1Josephbeer

pd.merge(df6, df7, how='left')

namefooddrink
0PeterfishNaN
1PaulbeansNaN
2Marybreadwine

输出的行现在对应于左侧输入中的条目。使用 how='right' 的方式也类似。

所有这些选项都可以直接应用于前面介绍的各种连接类型。

列名重叠:suffixes 关键字

最后,你可能会遇到两个输入的 DataFrame 存在列名冲突的情况。
请看下面的例子:

df8 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],'rank': [1, 2, 3, 4]})
df9 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],'rank': [3, 1, 4, 2]})
display('df8', 'df9', 'pd.merge(df8, df9, on="name")')

df8

namerank
0Bob1
1Jake2
2Lisa3
3Sue4

df9

namerank
0Bob3
1Jake1
2Lisa4
3Sue2

pd.merge(df8, df9, on="name")

namerank_xrank_y
0Bob13
1Jake21
2Lisa34
3Sue42

由于输出结果中会有两个冲突的列名,merge 函数会自动在输出列名后添加 _x_y 后缀,以确保列名唯一。
如果默认的后缀不合适,可以通过 suffixes 关键字自定义后缀:

pd.merge(df8, df9, on="name", suffixes=["_L", "_R"])
namerank_Lrank_R
0Bob13
1Jake21
2Lisa34
3Sue42

这些后缀适用于所有可能的连接方式,并且在存在多个重叠列时同样有效。
我们将在其中更深入地探讨关系代数。
另请参阅 Pandas 文档中的“合并、连接、拼接与比较”部分,以获取这些主题的进一步讨论。

示例:美国各州数据

合并和连接操作最常见于将来自不同来源的数据组合在一起。
这里我们将以美国各州及其人口数据为例进行说明。
相关数据文件可在 http://github.com/jakevdp/data-USstates 找到:

# Following are commands to download the data
# repo = "https://raw.githubusercontent.com/jakevdp/data-USstates/master"
# !cd data && curl -O {repo}/state-population.csv
# !cd data && curl -O {repo}/state-areas.csv
# !cd data && curl -O {repo}/state-abbrevs.csv

让我们使用 Pandas 的 read_csv 函数来查看这三个数据集:

pop = pd.read_csv('data/state-population.csv')
areas = pd.read_csv('data/state-areas.csv')
abbrevs = pd.read_csv('data/state-abbrevs.csv')display('pop.head()', 'areas.head()', 'abbrevs.head()')

pop.head()

state/regionagesyearpopulation
0ALunder1820121117489.0
1ALtotal20124817528.0
2ALunder1820101130966.0
3ALtotal20104785570.0
4ALunder1820111125763.0

areas.head()

statearea (sq. mi)
0Alabama52423
1Alaska656425
2Arizona114006
3Arkansas53182
4California163707

abbrevs.head()

stateabbreviation
0AlabamaAL
1AlaskaAK
2ArizonaAZ
3ArkansasAR
4CaliforniaCA

根据上述信息,假设我们想要计算一个相对简单的结果:按 2010 年人口密度对美国各州和领地进行排名。
我们已经拥有了实现这一目标所需的数据,但需要将这些数据集进行合并。

我们首先进行一次多对一合并,以便在人口 DataFrame 中获得完整的州名。
我们希望基于 popstate/region 列和 abbrevsabbreviation 列进行合并。
我们将使用 how='outer',以确保不会因为标签不匹配而丢弃任何数据:

merged = pd.merge(pop, abbrevs, how='outer',left_on='state/region', right_on='abbreviation')
merged = merged.drop('abbreviation', axis=1) # drop duplicate info
merged.head()
state/regionagesyearpopulationstate
0AKtotal1990553290.0Alaska
1AKunder181990177502.0Alaska
2AKtotal1992588736.0Alaska
3AKunder181991182180.0Alaska
4AKunder181992184878.0Alaska

让我们仔细检查一下是否存在不匹配的情况,可以通过查找包含空值的行来实现:

merged.isnull().any()
state/region    False
ages            False
year            False
population       True
state            True
dtype: bool

有些 population(人口)值是空的;让我们找出这些值对应的是哪些数据!

merged[merged['population'].isnull()].head()
state/regionagesyearpopulationstate
1872PRunder181990NaNNaN
1873PRtotal1990NaNNaN
1874PRtotal1991NaNNaN
1875PRunder181991NaNNaN
1876PRtotal1993NaNNaN

所有人口为 null 的值似乎都来自 2000 年之前的波多黎各;这很可能是因为原始数据源中没有这些数据。

更重要的是,我们发现有些新的 state 条目也是 null,这意味着在 abbrevs 键中没有对应的条目!
让我们找出哪些地区缺少这种匹配:

merged.loc[merged['state'].isnull(), 'state/region'].unique()
array(['PR', 'USA'], dtype=object)

我们可以很快推断出问题所在:我们的人口数据中包含了波多黎各(PR)和美国整体(USA)的条目,而这些条目在州缩写键中并未出现。
我们可以通过补充合适的条目来快速修复这个问题:

merged.loc[merged['state/region'] == 'PR', 'state'] = 'Puerto Rico'
merged.loc[merged['state/region'] == 'USA', 'state'] = 'United States'
merged.isnull().any()
state/region    False
ages            False
year            False
population       True
state           False
dtype: bool

state 列中已无空值:一切准备就绪!

现在我们可以用类似的方法将结果与面积数据进行合并。
检查我们的结果后,我们会希望在两个数据集中都以 state 列作为连接键:

final = pd.merge(merged, areas, on='state', how='left')
final.head()
state/regionagesyearpopulationstatearea (sq. mi)
0AKtotal1990553290.0Alaska656425.0
1AKunder181990177502.0Alaska656425.0
2AKtotal1992588736.0Alaska656425.0
3AKunder181991182180.0Alaska656425.0
4AKunder181992184878.0Alaska656425.0

再次检查是否存在空值,以确定是否有不匹配的情况:

area 列中存在空值;我们可以查看一下哪些地区在这里被忽略了:

final['state'][final['area (sq. mi)'].isnull()].unique()
array(['United States'], dtype=object)

我们发现 areas 这个 DataFrame 并没有包含美国整体的面积数据。
我们可以插入一个合适的值(例如所有州面积的总和),但在这里我们只需删除这些空值,因为整个美国的人口密度并不是我们当前讨论的重点:

final.dropna(inplace=True)
final.head()
state/regionagesyearpopulationstatearea (sq. mi)
0AKtotal1990553290.0Alaska656425.0
1AKunder181990177502.0Alaska656425.0
2AKtotal1992588736.0Alaska656425.0
3AKunder181991182180.0Alaska656425.0
4AKunder181992184878.0Alaska656425.0

现在我们已经拥有了所需的全部数据。为了回答我们关心的问题,首先需要选取年份为 2010 且人口类型为总人口的数据部分。
我们将使用 query 函数来快速完成这一操作(这需要安装 NumExpr 包:

data2010 = final.query("year == 2010 & ages == 'total'")
data2010.head()
state/regionagesyearpopulationstatearea (sq. mi)
43AKtotal2010713868.0Alaska656425.0
51ALtotal20104785570.0Alabama52423.0
141ARtotal20102922280.0Arkansas53182.0
149AZtotal20106408790.0Arizona114006.0
197CAtotal201037333601.0California163707.0

现在让我们计算人口密度并按顺序显示结果。
我们将首先以州为索引重新排列数据,然后计算结果:

data2010.set_index('state', inplace=True)
density = data2010['population'] / data2010['area (sq. mi)']
density.sort_values(ascending=False, inplace=True)
density.head()
state
District of Columbia    8898.897059
Puerto Rico             1058.665149
New Jersey              1009.253268
Rhode Island             681.339159
Connecticut              645.600649
dtype: float64

结果是美国各州(包括华盛顿特区和波多黎各)按 2010 年人口密度(每平方英里居民数)排序的排名。
可以看到,在这个数据集中,人口密度最高的地区是华盛顿特区(即哥伦比亚特区);在各州中,人口密度最高的是新泽西州。

我们还可以查看排名末尾的地区:

density.tail()
state
South Dakota    10.583512
North Dakota     9.537565
Montana          6.736171
Wyoming          5.768079
Alaska           1.087509
dtype: float64

我们可以看到,人口密度最低的州远远是阿拉斯加,平均每平方英里仅有一名居民。

这种数据合并操作在使用真实世界数据源回答问题时非常常见。
希望这个例子能让你了解如何结合我们介绍过的工具,从数据中获得洞见!


文章转载自:

http://p6lsqes5.npkLq.cn
http://6JkbqQeW.npkLq.cn
http://4cYs5rBq.npkLq.cn
http://ctFdMZkZ.npkLq.cn
http://JfRiWBqO.npkLq.cn
http://JHWYXX5e.npkLq.cn
http://pDna517a.npkLq.cn
http://yp76hYYw.npkLq.cn
http://QIiXD9eS.npkLq.cn
http://Bf265rJQ.npkLq.cn
http://OHCbykrz.npkLq.cn
http://2LUPHpSj.npkLq.cn
http://PSR4jdpa.npkLq.cn
http://H2Q9Qa5j.npkLq.cn
http://iOWuj0XY.npkLq.cn
http://E5DRGIRz.npkLq.cn
http://X6rpKdNR.npkLq.cn
http://zCc6cYsa.npkLq.cn
http://VJr7U8Ql.npkLq.cn
http://DSR7qlz8.npkLq.cn
http://cFBzKsc0.npkLq.cn
http://q1xcAaqt.npkLq.cn
http://Pkwj3BUV.npkLq.cn
http://hq6OjEMB.npkLq.cn
http://V4ZBsKIp.npkLq.cn
http://V9fjrQvL.npkLq.cn
http://zEebtVBW.npkLq.cn
http://4VG95BaE.npkLq.cn
http://FOuKW5aA.npkLq.cn
http://6nrKxWdy.npkLq.cn
http://www.dtcms.com/a/372498.html

相关文章:

  • DINOv3 新颖角度解释
  • leetcode219.存在重复元素
  • 卷积神经网络CNN-part4-VGG
  • 【图像处理基石】图像处理中的边缘检测算法及应用场景
  • 项目中缓存雪崩,击穿,穿透的应对方法
  • AI推介-多模态视觉语言模型VLMs论文速览(arXiv方向):2025.06.10-2025.06.15
  • struct结构体内存对齐详解
  • 使用QLoRA 量化低秩适配微调大模型介绍篇
  • 变量与常量
  • 第7.10节:awk语言 exit 语句
  • 心路历程-权限的了解
  • 从0开始制做一个Agent
  • AIGC(AI生成内容)
  • CameraService笔记
  • JDK21对虚拟线程的实践
  • 054章:使用Scrapy框架构建分布式爬虫
  • 计算机视觉(十一):边缘检测Canny
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘wheel’问题
  • 监控系统 | 脚本案例
  • TI-92 Plus计算器:高等数学之函数特性判断
  • IDEA 配置tomcat服务器
  • HTTP中Payload的含义解析
  • docker-compose build命令及参数
  • 接入第三方升级协议OTA教程
  • IO模型多路转接
  • Python-基础语法
  • FastApi框架
  • 单片机的bin、exe、elf、hex文件差异
  • 基于ResNet50的智能垃圾分类系统
  • 大模型推理参数讲解