Pandas 合并数据集merge 和 join的使用

 更新时间:2026年06月02日 10:37:55   作者:Humbunklung  
Pandas 提供了一个重要的功能,即高性能的内存中连接(join)和合并(merge)操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

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

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

import pandas as pd
import numpy as np

class 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 = args
        
    def _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 函数以及 Series 和 DataFrame 对象的相关 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 列在 df1 和 df2 中的顺序不同,而 pd.merge 函数能够正确处理这一点。
另外需要注意的是,合并操作通常会丢弃索引,除非使用按索引合并(稍后会讨论 left_index 和 right_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_on 和 right_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_index 与 right_on,或 left_on 与 right_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

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

这些后缀适用于所有可能的连接方式,并且在存在多个重叠列时同样有效。
我们将在其中更深入地探讨关系代数。

示例:美国各州数据

合并和连接操作最常见于将来自不同来源的数据组合在一起。
这里我们将以美国各州及其人口数据为例进行说明。
相关数据文件可在 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 中获得完整的州名。
我们希望基于 pop 的 state/region 列和 abbrevs 的 abbreviation 列进行合并。
我们将使用 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

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

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

到此这篇关于Pandas 合并数据集merge 和 join的使用的文章就介绍到这了,更多相关Pandas 合并数据集内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:

相关文章

  • 解读pandas交叉表与透视表pd.crosstab()和pd.pivot_table()函数

    解读pandas交叉表与透视表pd.crosstab()和pd.pivot_table()函数

    这篇文章主要介绍了pandas交叉表与透视表pd.crosstab()和pd.pivot_table()函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • Python函数式编程指南(一):函数式编程概述

    Python函数式编程指南(一):函数式编程概述

    这篇文章主要介绍了Python函数式编程指南(一):函数式编程概述,本文讲解了什么是函数式编程概述、什么是函数式编程、为什么使用函数式编程、如何辨认函数式风格等核心知识,需要的朋友可以参考下
    2015-06-06
  • 在Pytorch中计算卷积方法的区别详解(conv2d的区别)

    在Pytorch中计算卷积方法的区别详解(conv2d的区别)

    今天小编就为大家分享一篇在Pytorch中计算卷积方法的区别详解(conv2d的区别),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-01-01
  • Mac下PyCharm快捷键分享

    Mac下PyCharm快捷键分享

    这篇文章主要介绍了Mac下PyCharm快捷键,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • python列表操作使用示例分享

    python列表操作使用示例分享

    这篇文章主要介绍了python列表操作使用示例,需要的朋友可以参考下
    2014-02-02
  • Python实现图算法、堆操作和并查集代码实例

    Python实现图算法、堆操作和并查集代码实例

    这篇文章主要介绍了Python实现图算法、堆操作和并查集代码实例,图算法、堆操作和并查集是计算机科学中常用的数据结构和算法,它们在解决各种实际问题中具有重要的应用价值,需要的朋友可以参考下
    2023-08-08
  • numpy中矩阵合并的实例

    numpy中矩阵合并的实例

    今天小编就为大家分享一篇numpy中矩阵合并的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-06-06
  • 一文带你搞懂Python如何解析CSV文件

    一文带你搞懂Python如何解析CSV文件

    CSV(Comma-Separated Values)是一种以纯文本格式存储表格数据的文件格式,本文主要为大家详细介绍了如何使用Python解析CSV文件,有需要的小伙伴可以了解下
    2026-03-03
  • 最新版Anaconda安装教程

    最新版Anaconda安装教程

    本文主要介绍了最新版Anaconda安装教程,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-03-03
  • python 全文检索引擎详解

    python 全文检索引擎详解

    这篇文章主要介绍了python 全文检索引擎详解的相关资料,需要的朋友可以参考下
    2017-04-04

最新评论