7.合并数据集:合并与连接
Pandas 高性能内存数据连接的基本特征之一是(join)和合并(merge)操作。如果您有使用数据库的经验,您必须熟悉此类操作。Pandas 的主接口是pd.merge
函数,让我们通过一些例子来介绍它的用法。
7.1、关系代数
pd.merge()
基于实现的功能关系代数
(relational algebra)部分。关系代数是处理关系数据的一般理论,绝大多数数据库的可用操作都以此为基础。关系代数方法论的强大之处在于,。在数据库或程序中高效实现的基本操作规范的帮助下,您可以完成许多非常复杂的操作。
Pandas 在pd.merge()
函数与 Series 和 DataFrame 的join()
这些基本现了这些基本操作规则。以下是如何用这些简单的规则连接不同数据源的数据。
import pandas as pd import numpy as np class display(object): 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)
7.2.数据连接类型
pd.merge()
函数实现了三种数据连接类型:一对一
、多对一
和多对多
。
7.2.一对一连接
与之前介绍的按列合并非常相似,如下面的例子所示,有两个包含同一公司员工的不同信息 DataFrame:
df1 = pd.DataFrame({
'员工': ['Bob', 'Jake', 'Lisa', 'Sue']
,
'部门'
:
[
'会计'
,
'工程'
,
'工程'
,
'招聘'
]
}
) df2
= pd
.DataFrame
(
{
'员工'
:
[
'Lisa'
,
'Bob'
,
'Jake'
,
'Sue'
]
,
'入职日期'
:
[
2004
,
2008
,
2012
,
2014
]
}
) display
(
'df1'
,
'df2'
)
df1
员工 | 部门 | |
---|---|---|
0 | Bob | 会计 |
1 | Jake | 工程 |
2 | Lisa | 工程 |
3 | Sue | 招聘 |
df2
员工 | 入职日期 | |
---|---|---|
0 | Lisa | 2004 |
1 | Bob | 2008 |
2 | Jake | 2012 |
3 | Sue | 2014 |
若想将这两个 DataFrame 合并成一个 DataFrame,可以用pd.merge()
函数实现:
df3 = pd.merge(df1, df2)
df3
员工 | 部门 | 入职日期 | |
---|---|---|---|
0 | Bob | 会计 | 2008 |
1 | Jake | 工程 | 2012 |
2 | Lisa | 工程 | 2004 |
3 | Sue | 招聘 | 2014 |
pd.merge()
方法会默认丢弃原来的行索引,不过也可以自定义。
7.2.2、多对一连接
多对一连接是指,在需要连接的两个列中,有一列的值有重复。通过多对一连接获得的结果 DataFrame 将会保留重复值。请看下面的例子:
df4 = pd.DataFrame({
'部门': ['会计', '工程', '招聘'],
'领导': ['Carly', 'Guido', 'Steve']})
display('df3', 'df4', 'pd.merge(df3, df4)')
df3
员工 | 部门 | 入职日期 | |
---|---|---|---|
0 | Bob | 会计 | 2008 |
1 | Jake | 工程 | 2012 |
2 | Lisa | 工程 | 2004 |
3 | Sue | 招聘 | 2014 |
df4
部门 | 领导 | |
---|---|---|
0 | 会计 | Carly |
1 | 工程 | Guido |
2 | 招聘 | Steve |
pd.merge(df3, df4)
员工 | 部门 | 入职日期 | 领导 | |
---|---|---|---|---|
0 | Bob | 会计 | 2008 | Carly |
1 | Jake | 工程 | 2012 | Guido |
2 | Lisa | 工程 | 2004 | Guido |
3 | Sue | 招聘 | 2014 | Steve |
在结果 DataFrame 中多了一个“领导”列,里面会因为输入数据的对应关系而有所重复。
7.2.3、多对多连接
多对多连接是个有点复杂的概念,不过也可以理解。如果左右两个输入的共同列都包含重复值,那么合并的结果就是一种多对多连接。用一个例子来演示更容易理解。来看下面的例子,里面有一个 DataFrame 显示不同岗位员工的一种或多种能力。
通过多对多连接,就可以得知每位员工所具备的能力:
df5 = pd.DataFrame({
'部门': ['会计', '会计', '工程', '工程', '招聘', '招聘'],
'技能': ['数学', '制表', '编码', 'Linux', '制表', '管理']})
display('df1', 'df5', "pd.merge(df1, df5)")
df1
员工 | 部门 | |
---|---|---|
0 | Bob | 会计 |
1 | Jake | 工程 |
2 | Lisa | 工程 |
3 | Sue | 招聘 |
df5
部门 | 技能 | |
---|---|---|
0 | 会计 | 数学 |
1 | 会计 | 制表 |
2 | 工程 | 编码 |
3 | 工程 | Linux |
4 | 招聘 | 制表 |
5 | 招聘 | 管理 |
pd.merge(df1, df5)
员工 | 部门 | 技能 | |
---|---|---|---|
0 | Bob | 会计 | 数学 |
1 | Bob | 会计 | 制表 |
2 | Jake | 工程 | 编码 |
3 | Jake | 工程 | Linux |
4 | Lisa | 工程 | 编码 |
5 | Lisa | 工程 | Linux |
6 | Sue | 招聘 | 制表 |
7 | Sue | 招聘 | 管理 |
这三种数据连接可以直接与其他 Pandas 工具组合使用,从而实现各种各样的功能。但是工作中的真实数据集往往不像示例中演示的那么干净、整洁。下面就来介绍pd.merge()
的一些功能,它们可以让你更好的应对数据连接中的问题。
7.3、设置数据合并的键
我们已经知道pd.merge()
的默认行为:它会将两个输入的一个或多个共同列作为键进行合并。但是由于两个输入要合并的列通常都不是同名的,因此pd.merge()
提供了一些参数处理这个问题。
7.3.1、参数on
的用法
最简单的方法就是直接将参数on
设置为一个列名称或者一个包含多列名称的列表:
- 这个参数只有两个 DataFrame 有共同列名的时候才可以使用。
display('df1', 'df2', "pd.merge(df1, df2, on='员工')")
df1
员工 | 部门 | |
---|---|---|
0 | Bob | 会计 |
1 | Jake | 工程 |
2 | Lisa | 工程 |
3 | Sue | 招聘 |
df2
员工 | 入职日期 | |
---|---|---|
0 | Lisa | 2004 |
1 | Bob | 2008 |
2 | Jake | 2012 |
3 | Sue | 2014 |
pd.merge(df1, df2, on='员工')
员工 | 部门 | 入职日期 | |
---|---|---|---|
0 | Bob | 会计 | 2008 |
1 | Jake | 工程 | 2012 |
2 | Lisa | 工程 | 2004 |
3 | Sue | 招聘 | 2014 |
7.3.2、left_on
与right_on
参数
有时候你需要合并连个列名不同的数据集,例如前面的员工信息表中有一个字段不是“员工”
而是“姓名”
。在这种情况下,就可以用left_on
与right_on
参数来指定列名:
df3 = pd.DataFrame({
'姓名': ['Bob', 'Jake', 'Lisa', 'Sue'],
'工资': [70000, 80000, 120000, 90000]})
display('df1', 'df3', 'pd.merge(df1, df3, left_on="员工", right_on="姓名")')
df1
员工 | 部门 | |
---|---|---|
0 | Bob | 会计 |
1 | Jake | 工程 |
2 | Lisa | 工程 |
3 | Sue | 招聘 |
df3
姓名 | 工资 | |
---|---|---|
0 | Bob | 70000 |
1 | Jake | 80000 |
2 | Lisa | 120000 |
3 | Sue | 90000 |
pd.merge(df1, df3, left_on="员工", right_on="姓名")
员工 | 部门 | 姓名 | 工资 | |
---|---|---|---|---|
0 | Bob | 会计 | Bob | 70000 |
1 | Jake | 工程 | Jake | 80000 |
2 | Lisa | 工程 | Lisa | 120000 |
3 | Sue | 招聘 | Sue | 90000 |
但是获取的结果中有一个多余的列,可以通过 DataFrame 的drop()
方法将这一列去掉:
pd.merge(df1, df3, left_on="员工", right_on="姓名").drop("姓名", axis=1)
员工 | 部门 | 工资 | |
---|---|---|---|
0 | Bob | 会计 | 70000 |
1 | Jake | 工程 | 80000 |
2 | Lisa | 工程 | 120000 |
3 | Sue | 招聘 | 90000 |
7.3.3、left_index
与right_index
参数
除了合并列之外,你可能还需要合并索引:
df1a = df1.set_index('员工')
df2a = df2.set_index('员工')
display('df1a', 'df2a')
df1a
部门 | |
---|---|
员工 | |
Bob | 会计 |
Jake | 工程 |
Lisa | 工程 |
Sue | 招聘 |
df2a
入职日期 | |
---|---|
员工 | |
Lisa | 2004 |
Bob | 2008 |
Jake | 2012 |
Sue | 2014 |
# pd.merge(df1a, df2a) 这样合并会报错,报错如下(MergeError:没有要执行合并的通用列)
# MergeError: No common columns to perform merge on. Merge options: left_on=None, right_on=None, left_index=False, right_index=False
display('df1a', 'df2a', "pd.merge(df1a, df2a, left_index=True, right_index=True)")
df1a
部门 | |
---|---|
员工 | |
Bob | 会计 |
Jake | 工程 |
Lisa | 工程 |
Sue | 招聘 |
df2a
入职日期 | |
---|---|
员工 | |
Lisa | 2004 |
Bob | 2008 |
Jake | 2012 |
Sue | 2014 |
pd.merge(df1a, df2a, left_index=True, right_index=True)
部门 | 入职日期 | |
---|---|---|
员工 | ||
Bob | 会计 | 2008 |
Jake | 工程 | 2012 |
Lisa | 工程 | 2004 |
Sue | 招聘 | 2014 |
为了方便考虑,DataFrame 实现了join()
方法,它可以按照索引进行数据合并:
display('df1a', 'df2a', 'df1a.join(df2a)')
df1a
部门 | |
---|---|
员工 | |
Bob | 会计 |
Jake | 工程 |
Lisa | 工程 |
Sue | 招聘 |
df2a
入职日期 | |
---|---|
员工 | |
Lisa | 2004 |
Bob | 2008 |
Jake | 2012 |
Sue | 2014 |
df1a.join(df2a)
部门 | 入职日期 | |
---|---|---|
员工 | ||
Bob | 会计 | 2008 |
Jake | 工程 | 2012 |
Lisa | 工程 | 2004 |
Sue | 招聘 | 2014 |
如果想将索引与列混合使用,那么可以通过结合left_index
与right_on
,或者结合left_on
与right_index
来实现:
# df1a 的 索引,与 df3 的 “姓名” 列,数据相同
display('df1a', 'df3', "pd.merge(df1a, df3, left_index=True, right_on='姓名')")
df1a
部门 | |
---|---|
员工 | |
Bob | 会计 |
Jake | 工程 |
Lisa | 工程 |
Sue | 招聘 |
df3
姓名 | 工资 | |
---|---|---|
0 | Bob | 70000 |
1 | Jake | 80000 |
2 | Lisa | 120000 |
3 | Sue | 90000 |
pd.merge(df1a, df3, left_index=True, right_on='姓名')
部门 | 姓名 | 工资 | |
---|---|---|---|
0 | 会计 | Bob | 70000 |
1 | 工程 | Jake | 80000 |
2 | 工程 | Lisa | 120000 |
3 | 招聘 | Sue | 90000 |
7.4、设置数据连接的集合操作规则
通过前面的演示,我们总结出数据连接的一个重要条件:集合操作规则。
- 当一个值出现在一列,却没有出现在另一列时,就需要考虑集合操作规则了。
df6 = pd.DataFrame({
'姓名': ['Peter', 'Paul', 'Mary'],
'食物': ['鱼', '大豆', '面包']},
columns=['姓名', '食物'])
df7 = pd.DataFrame({
'姓名': ['Mary', 'Joseph'],
'饮品': ['葡萄酒', '啤酒']},
columns=['姓名', '饮品'])
display('df6', 'df7', 'pd.merge(df6, df7)')
df6
姓名 | 食物 | |
---|---|---|
0 | Peter | 鱼 |
1 | Paul | 大豆 |
2 | Mary | 面包 |
df7
姓名 | 饮品 | |
---|---|---|
0 | Mary | 葡萄酒 |
1 | Joseph | 啤酒 |
pd.merge(df6, df7)
姓名 | 食物 | 饮品 | |
---|---|---|---|
0 | Mary | 面包 | 葡萄酒 |
我们合并两个数据集,在“姓名”列中只有一个共同值:Mary。默认情况下,结果中只会包含两个输入集合的交集
,这种连接方式叫做内连接
(inner join)。我们可以用how
参数设置连接方式,默认值是'inner'
:
pd.merge(df6, df7, how='inner')
姓名 | 食物 | 饮品 | |
---|---|---|---|
0 | Mary | 面包 | 葡萄酒 |
how
参数支持的数据连接方式还有'outer'
、'left'
和'right'
。外连接
(outer join)返回两个输入列的并集,所有缺失值都用NaN
填充:
display('df6', 'df7', "pd.merge(df6, df7, how='outer')")
df6
姓名 | 食物 | |
---|---|---|
0 | Peter | 鱼 |
1 | Paul | 大豆 |
2 | Mary | 面包 |
df7
姓名 | 饮品 | |
---|---|---|
0 | Mary | 葡萄酒 |
1 | Joseph | 啤酒 |
pd.merge(df6, df7, how='outer')
姓名 | 食物 | 饮品 | |
---|---|---|---|
0 | Peter | 鱼 | NaN |
1 | Paul | 大豆 | NaN |
2 | Mary | 面包 | 葡萄酒 |
3 | Joseph | NaN | 啤酒 |
左连接
(left join)和右连接
(right join)返回的结果分别只包含左列和右列:
display('df6', 'df7', "pd.merge(df6, df7, how='left')")
df6
姓名 | 食物 | |
---|---|---|
0 | Peter | 鱼 |
1 | Paul | 大豆 |
2 | Mary | 面包 |
df7
姓名 | 饮品 | |
---|---|---|
0 | Mary | 葡萄酒 |
1 | Joseph | 啤酒 |
pd.merge(df6, df7, how='left')
姓名 | 食物 | 饮品 | |
---|---|---|---|
0 | Peter | 鱼 | NaN |
1 | Paul | 大豆 | NaN |
2 | Mary | 面包 | 葡萄酒 |
display('df6', 'df7', "pd.merge(df6, df7, how='right')")
df6
姓名 | 食物 | |
---|---|---|
0 | Peter | 鱼 |
1 | Paul | 大豆 |
2 | Mary | 面包 |
df7
姓名 | 饮品 | |
---|---|---|
0 | Mary | 葡萄酒 |
1 | Joseph | 啤酒 |
pd.merge(df6, df7, how='right')
姓名 | 食物 | 饮品 | |
---|---|---|---|
0 | Mary | 面包 | 葡萄酒 |
1 | Joseph | NaN | 啤酒 |
7.5、重复列名:suffixes
参数
df8 = pd.DataFrame({
'姓名': ['Bob', 'Jake', 'Lisa', 'Sue'],
'等级': [1, 2, 3, 4]})
df9 = pd.DataFrame({
'姓名': ['Bob', 'Jake', 'Lisa', 'Sue'],
'等级': [3, 1, 4, 2]})
display('df8', 'df9', 'pd.merge(df8, df9, on="姓名")')
df8
姓名 | 等级 | |
---|---|---|
0 | Bob | 1 |
1 | Jake | 2 |
2 | Lisa | 3 |
3 | Sue | 4 |
df9
姓名 | 等级 | |
---|---|---|
0 | Bob | 3 |
1 | Jake | 1 |
2 | Lisa | 4 |
3 | Sue | 2 |
pd.merge(df8, df9, on="姓名")
姓名 | 等级_x | 等级_y | |
---|---|---|---|
0 | Bob | 1 | 3 |
1 | Jake | 2 | 1 |
2 | Lisa | 3 | 4 |
3 | Sue | 4 | 2 |
由于输出结果中有两个重复的列名,因此pd.merge()
函数会自动为它们增加后缀_x
或_y
,当然也可以用通过suffixes
参数自定义后缀:
display('df8', 'df9', 'pd.merge(df8, df9, on="姓名", suffixes=["_L", "_R"])')
df8
姓名 | 等级 | |
---|---|---|
0 | Bob | 1 |
1 | Jake | 2 |
2 | Lisa | 3 |
3 | Sue | 4 |
df9
姓名 | 等级 | |
---|---|---|
0 | Bob | 3 |
1 | Jake | 1 |
2 | Lisa | 4 |
3 | Sue | 2 |
pd.merge(df8, df9, on="姓名", suffixes=["_L", "_R"])
姓名 | 等级_L | 等级_R | |
---|---|---|---|
0 | Bob | 1 | 3 |
1 | Jake | 2 | 1 |
2 | Lisa | 3 | 4 |
3 | Sue | 4 | 2 |
suffixes
参数同样适用于任何连接方式,即使有三个及三个以上的重复列名时也同样适用。
7.6、案例:美国各州的统计数据
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/region | ages | year | population | |
---|---|---|---|---|
0 | AL | under18 | 2012 | 1117489.0 |
1 | AL | total | 2012 | 4817528.0 |
2 | AL | under18 | 2010 | 1130966.0 |
3 | AL | total | 2010 | 4785570.0 |
4 | AL | under18 | 2011 | 1125763.0 |
areas.head()
state | area (sq. mi) | |
---|---|---|
0 | Alabama | 52423 |
1 | Alaska | 656425 |
2 | Arizona | 114006 |
3 | Arkansas | 53182 |
4 | California | 163707 |
abbrevs.head()
state | abbreviation | |
---|---|---|
0 | Alabama | AL |
1 | Alaska | AK |
2 | Arizona | AZ |
3 | Arkansas | AR |
4 | California | CA |
有了数据,我们先完成一个比较简单的小指标:美国各州的人口密度排名:
7.6.1、先合并人口和州缩写数据
merged = pd.merge(pop, abbrevs, how='outer', left_on='state/region', right_on="abbreviation" 标签:
amic单点式传感器ross高继电器e12