本文旨在向读者介绍SQL风格的与熊猫的融合,如何使用它,以及何时不使用它。
特别是,这篇文章将介绍以下内容:
-
基础-连接类型(左、右、外、内)
-
不同条件下与索引合并
-
有效地使用命名索引
-
合并键作为一个和另一个列的索引
-
列和索引上的多路合并(唯一和非唯一)
-
值得注意的替代方案
merge
和
join
这篇文章不会涉及的内容:
-
绩效相关讨论和时间安排(目前)。最值得注意的是,在适当的情况下,提到了更好的替代方案。
-
处理后缀、删除额外的列、重命名输出和其他特定用例。还有其他(阅读:更好)的帖子可以处理这个问题,所以要想清楚!
注释
除非另有规定,大多数示例在演示各种特性时默认为内部联接操作。
此外,这里的所有数据帧都可以复制,因此
你可以和他们一起玩。此外,参见
this
post
关于如何从剪贴板中读取数据帧。
最后,所有连接操作的可视化表示都借用了
谢谢这篇文章
https://www.codeproject.com/Articles/33052/Visual-Representation-of-SQL-Joins
.
说够了,给我演示一下怎么用
合并
你说什么?
安装程序
np.random.seed(0)
left = pd.DataFrame({'key': ['A', 'B', 'C', 'D'], 'value': np.random.randn(4)})
right = pd.DataFrame({'key': ['B', 'D', 'E', 'F'], 'value': np.random.randn(4)})
left
key value
0 A 1.494079
1 B -0.205158
2 C 0.313068
3 D -0.854096
right
key value
0 B -2.552990
1 D 0.653619
2 E 0.864436
3 F -0.742165
为了简单起见,键列(目前)具有相同的名称。
一个
内连接
代表为
注释
A
这里引用的键来自
left
DataFrame
B
引用中联接列的键
right
数据帧和交叉点
表示两个键的公共键
左边
和
正确的
.
阴影区域表示联接结果中存在的键。这项公约将贯穿始终。请记住,venn图并不是连接操作的100%准确表示,因此请用少量的盐来表示它们。
要执行内部联接,请调用
pd.merge
指定左数据帧、右数据帧和联接键。
pd.merge(left, right, on='key')
key value_x value_y
0 B 0.400157 1.867558
1 D 2.240893 -0.977278
这只返回来自
左边
和
正确的
共享一个公共密钥(在本例中为“b”和“d”)。
在熊猫的最新版本中(v0.21左右),
合并
现在是一阶函数,因此可以调用
DataFrame.merge
.
left.merge(right, on='key')
# Or, if you want to be explicit
# left.merge(right, on='key', how='inner')
key value_x value_y
0 B 0.400157 1.867558
1 D 2.240893 -0.977278
一
左外连接
或左联接由表示
这可以通过指定
how='left'
.
left.merge(right, on='key', how='left')
key value_x value_y
0 A 1.764052 NaN
1 B 0.400157 1.867558
2 C 0.978738 NaN
3 D 2.240893 -0.977278
请注意此处的nan位置。如果您指定
如何“左”
,然后只有来自的键
左边
已使用,并且缺少来自
正确的
被NaN取代。
同样地,对于
右外部联接
或右联接…
…指定
how='right'
:
left.merge(right, on='key', how='right')
key value_x value_y
0 B 0.400157 1.867558
1 D 2.240893 -0.977278
2 E NaN 0.950088
3 F NaN -0.151357
这里,钥匙来自
正确的
已使用,并且缺少来自
左边
被NaN取代。
最后,为了
全外部连接
,由
指定
how='outer'
.
left.merge(right, on='key', how='outer')
key value_x value_y
0 A 1.764052 NaN
1 B 0.400157 1.867558
2 C 0.978738 NaN
3 D 2.240893 -0.977278
4 E NaN 0.950088
5 F NaN -0.151357
这将使用来自两个帧的键,并且为两个帧中丢失的行插入nan。
文档很好地总结了这些不同的合并:
其他联接-左不包括、右不包括和完全不包括/反联接
如果你需要
左侧不包括连接
和
不包括连接的权限
分两步进行。
对于左侧,不包括连接,表示为
首先执行左外部联接,然后进行筛选(不包括!)行来自
左边
只有
(left.merge(right, on='key', how='left', indicator=True)
.query('_merge == "left_only"')
.drop('_merge', 1))
key value_x value_y
0 A 1.764052 NaN
2 C 0.978738 NaN
在哪里?
left.merge(right, on='key', how='left', indicator=True)
key value_x value_y _merge
0 A 1.764052 NaN left_only
1 B 0.400157 1.867558 both
2 C 0.978738 NaN left_only
3 D 2.240893 -0.977278 both
同样地,对于排除连接的权利,
(left.merge(right, on='key', how='right', indicator=True)
.query('_merge == "right_only"')
.drop('_merge', 1))
key value_x value_y
2 E NaN 0.950088
3 F NaN -0.151357
最后,如果需要进行合并,只保留来自左侧或右侧的键,而不同时保留这两个键(iow,执行
抗连接
)
你也可以用类似的方式来做。
(left.merge(right, on='key', how='outer', indicator=True)
.query('_merge != "both"')
.drop('_merge', 1))
key value_x value_y
0 A 1.764052 NaN
2 C 0.978738 NaN
4 E NaN 0.950088
5 F NaN -0.151357
键列的名称不同
例如,如果键列的名称不同,
左边
有
keyLeft
和
正确的
有
keyRight
而不是
key
_
left_on
和
right_on
作为参数而不是
on
:
left2 = left.rename({'key':'keyLeft'}, axis=1)
right2 = right.rename({'key':'keyRight'}, axis=1)
left2
keyLeft value
0 A 0.706573
1 B 0.010500
2 C 1.785870
3 D 0.126912
right2
keyRight value
0 B 0.401989
1 D 1.883151
2 E -1.347759
3 F -1.270485
left2.merge(right2, left_on='keyLeft', right_on='keyRight', how='inner')
keyLeft value_x keyRight value_y
0 B 0.010500 B 0.401989
1 D 0.126912 D 1.883151
避免输出中出现重复的键列
合并时
左键
从
左边
和
右键
从
正确的
,如果您只需要
左键
或
右键
(但不是两者)在输出中,可以将索引设置为一个初步步骤。
left3 = left2.set_index('keyLeft')
left3.merge(right2, left_index=True, right_on='keyRight')
value_x keyRight value_y
0 0.010500 B 0.401989
1 0.126912 D 1.883151
将其与之前的命令输出(thst是
left2.merge(right2, left_on='keyLeft', right_on='keyRight', how='inner')
)你会注意到
左键
丢失。您可以根据将哪个帧的索引设置为键来确定要保留的列。例如,当执行一些外部联接操作时,这可能很重要。
仅合并其中一列
DataFrames
例如,考虑
right3 = right.assign(newcol=np.arange(len(right)))
right3
key value newcol
0 B 1.867558 0
1 D -0.977278 1
2 E 0.950088 2
3 F -0.151357 3
如果只需要合并“新值”(不包括任何其他列),则通常可以在合并前只对列进行子集:
left.merge(right3[['key', 'newcol']], on='key')
key value newcol
0 B 0.400157 0
1 D 2.240893 1
如果您正在执行左外部联接,则更高性能的解决方案将涉及
map
:
# left['newcol'] = left['key'].map(right3.set_index('key')['newcol']))
left.assign(newcol=left['key'].map(right3.set_index('key')['newcol']))
key value newcol
0 A 1.764052 NaN
1 B 0.400157 0.0
2 C 0.978738 NaN
3 D 2.240893 1.0
如前所述,这类似于,但比
left.merge(right3[['key', 'newcol']], on='key', how='left')
key value newcol
0 A 1.764052 NaN
1 B 0.400157 0.0
2 C 0.978738 NaN
3 D 2.240893 1.0
合并多列
若要在多个列上联接,请为指定一个列表
在
(或)
左撇子
和
右上
,视情况而定)。
left.merge(right, on=['key1', 'key2'] ...)
或者,如果名称不同,
left.merge(right, left_on=['lkey1', 'lkey2'], right_on=['rkey1', 'rkey2'])
其他有用
merge*
操作和功能
这一部分只介绍了最基本的内容,并且是为满足你的胃口而设计的。有关更多示例和案例,请参见
documentation on
merge
,
join
, and
concat
以及到功能规范的链接。
基于索引的*-联接(+索引列)
合并
s)
安装程序
left = pd.DataFrame({'value': np.random.randn(4)}, index=['A', 'B', 'C', 'D'])
right = pd.DataFrame({'value': np.random.randn(4)}, index=['B', 'D', 'E', 'F'])
left.index.name = right.index.name = 'idxkey'
left
value
idxkey
A 2.269755
B -1.454366
C 0.045759
D -0.187184
right
value
idxkey
B 1.532779
D 1.469359
E 0.154947
F 0.378163
通常,索引上的合并如下所示:
left.merge(right, left_index=True, right_index=True)
value_x value_y
idxkey
B 0.410599 0.761038
D 1.454274 0.121675
支持索引名称
如果索引是命名的,那么v0.23用户还可以将级别名称指定为
在
(或)
左撇子
和
右上
必要时)。
left.merge(right, on='idxkey')
value_x value_y
idxkey
B 0.410599 0.761038
D 1.454274 0.121675
合并一个的索引,另一个的列
使用一个索引和另一个列的索引来执行合并是可能的(而且非常简单)。例如,
left.merge(right, left_on='key1', right_index=True)
反之亦然(反之亦然)
right_on=...
和
left_index=True
)
right2 = right.reset_index().rename({'idxkey' : 'colkey'}, axis=1)
right2
colkey value
0 B 1.222445
1 D 0.208275
2 E 0.976639
3 F 0.356366
left.merge(right2, left_index=True, right_on='colkey')
value_x colkey value_y
0 -1.070753 B 1.222445
1 -0.403177 D 0.208275
在这种特殊情况下,
左边
是命名的,因此您也可以将索引名用于
左撇子
,像这样:
left.merge(right2, left_on='idxkey', right_on='colkey')
value_x colkey value_y
0 -1.070753 B 1.222445
1 -0.403177 D 0.208275
DataFrame.join
除此之外,还有一个简洁的选择。你可以使用
数据流连接
默认为索引上的联接。
数据帧.join
默认情况下是左外部联接吗,因此
how='inner'
在这里是必要的。
left.join(right, how='inner', lsuffix='_x', rsuffix='_y')
value_x value_y
idxkey
B 0.410599 0.761038
D 1.454274 0.121675
请注意,我需要指定
lsuffix
和
rsuffix
自辩
参加
否则会出错:
left.join(right)
ValueError: columns overlap but no suffix specified: Index(['value'], dtype='object')
因为列名是相同的。如果他们的名字不同,这不会是问题。
left.rename(columns={'value':'leftvalue'}).join(right, how='inner')
leftvalue value
idxkey
B -1.454366 1.532779
D -0.187184 1.469359
pd.concat
最后,作为基于索引的联接的替代方法,可以使用
顺铂
:
pd.concat([left, right], axis=1, sort=False, join='inner')
value value
idxkey
B -1.980796 1.230291
D 0.156349 1.202380
省略
join='inner'
如果需要完整的外部联接(默认):
pd.concat([left, right], axis=1, sort=False)
value value
A -0.887786 NaN
B -1.980796 1.230291
C -0.347912 NaN
D 0.156349 1.202380
E NaN -0.387327
F NaN -0.302303
有关详细信息,请参阅
this canonical post on
pd.concat
by @piRSquared
.
概括:
合并
ing多个数据帧
安装程序
A = pd.DataFrame({'key': ['A', 'B', 'C', 'D'], 'valueA': np.random.randn(4)})
B = pd.DataFrame({'key': ['B', 'D', 'E', 'F'], 'valueB': np.random.randn(4)})
C = pd.DataFrame({'key': ['D', 'E', 'J', 'C'], 'valueC': np.ones(4)})
dfs = [A, B, C]
通常,当多个数据帧合并在一起时会出现这种情况。天真地说,这可以通过链接来实现。
合并
电话:
A.merge(B, on='key').merge(C, on='key')
key valueA valueB valueC
0 D 0.922207 -1.099401 1.0
然而,对于许多数据帧来说,这很快就失去了控制。此外,可能需要对未知数量的数据帧进行概括。要做到这一点,一个常用的简单技巧是
functools.reduce
,您可以使用它来实现内部联接,如下所示:
from functools import reduce
reduce(pd.merge, dfs)
key valueA valueB valueC
0 D 0.465662 1.488252 1.0
请注意,除了“键”列之外的每一列都应该以不同的名称进行命名,这样就可以开箱即用了。否则,您可能需要使用
lambda
.
对于完全外部联接,可以
curry
PD.合并
使用
functools.partial
:
from functools import partial
outer_merge = partial(pd.merge, how='outer')
reduce(outer_merge, dfs)
key valueA valueB valueC
0 A 0.056165 NaN NaN
1 B -1.165150 -1.536244 NaN
2 C 0.900826 NaN 1.0
3 D 0.465662 1.488252 1.0
4 E NaN 1.895889 1.0
5 F NaN 1.178780 NaN
6 J NaN NaN 1.0
正如您可能注意到的,这是非常强大的,您也可以使用它来控制合并期间的列名。只需根据需要添加更多关键字参数:
partial(pd.merge, how='outer', left_index=True, right_on=...)
备选方案:
顺铂
如果列值是唯一的,那么使用它是有意义的
顺铂
这比一次两次多路径合并更快。
pd.concat([
df.set_index('key') for df in dfs], axis=1, join='inner'
).reset_index()
key valueA valueB valueC
0 D 0.465662 1.488252 1.0
唯一索引上的多路合并
如果要在唯一索引上合并多个数据帧,则应再次选择
局部混凝土
以获得更好的性能。
# Note, the "key" column values are unique, so the index is unique.
A2 = A.set_index('key')
B2 = B.set_index('key')
C2 = C.set_index('key')
dfs2 = [A2, B2, C2]
pd.concat(dfs2, axis=1, sort=False, join='inner')
valueA valueB valueC
key
D 0.922207 -1.099401 1.0
一如既往,省略
加入=“内部”
完全外部连接。
具有重复项的索引的多路合并
海螺
速度快,但也有缺点。它不能处理重复项。
A3 = pd.DataFrame({'key': ['A', 'B', 'C', 'D', 'D'], 'valueA': np.random.randn(5)})
pd.concat([df.set_index('key') for df in [A3, B, C]], axis=1, join='inner')
ValueError: Shape of passed values is (3, 4), indices imply (3, 2)
在这种情况下,
参加
是最佳选项,因为它可以处理非唯一索引(
参加
电话
合并
在引擎盖下面)。
# For inner join. For left join, pass `pd.DataFrame.join` directly to `reduce`.
inner_join = partial(pd.DataFrame.join, how='inner')
reduce(inner_join, [A3.set_index('key'), B2, C2])
valueA valueB valueC
key
D -0.674333 -1.099401 1.0
D 0.031831 -1.099401 1.0