9.7.拼接
from toolkit import H
import numpy as np
import pandas as pd
Linux 5.4.0-74-generic
Python 3.9.5 @ GCC 7.3.0
Latest build date 2021.06.09
pandas version: 1.2.4
numpy version: 1.20.3
Pandas有两种拼接DataFrame的函数,一种是concat
函数,另一种是SQL风格的拼接函数,例如merge
函数。concat
函数只能按轴索引进行拼接,而merge
函数则更加灵活,不仅可以根据列索引进行拼接,也可以按照指定的列进行拼接。但merge
函数不能根据行标签进行拼接。
concat
函数
在深入研究concat
函数的细节及其作用之前,这里有一个简单的示例:
df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']},
index=[0, 1, 2, 3])
df2 = pd.DataFrame({'A': ['A4', 'A5', 'A6', 'A7'],
'B': ['B4', 'B5', 'B6', 'B7'],
'C': ['C4', 'C5', 'C6', 'C7'],
'D': ['D4', 'D5', 'D6', 'D7']},
index=[4, 5, 6, 7])
df3 = pd.DataFrame({'A': ['A8', 'A9', 'A10', 'A11'],
'B': ['B8', 'B9', 'B10', 'B11'],
'C': ['C8', 'C9', 'C10', 'C11'],
'D': ['D8', 'D9', 'D10', 'D11']},
index=[8, 9, 10, 11])
frames = [df1, df2, df3]
result = pd.concat(frames)
与numpy.concatenate
一样,pandas.concat
获取包含同类型对象的列表或字典,将它们沿着串联轴拼接起来,并处理非串联轴的逻辑。
pd.concat(objs, axis=0, join='outer', ignore_index=False, keys=None,
levels=None, names=None, verify_integrity=False, sort=False,
copy=True)
objs
:包含Series或DataFrame对象的序列或字典。如果传递了dict
,dict
的排序后的key
值将作为keys
参数,除非显式传入keys
参数。序列或字典中可以包含None
,None
会被静默删除,但所有对象都为None
将引发ValueError
。axis
:{0,1,…}
. 沿着该轴进行拼接。join
:{'inner','outer'}
. 处理其他轴的索引的逻辑,'outer'
为并集,'inner'
为交集。ignore_index
:bool
. 如果为True
,则用0, …, n-1
代替串联轴的原索引。注意,拼接时仍会考虑其他轴上的索引值。keys
:list of str
,list of tuple
. 用于构造多层级索引。如果要传入多层级索引,则应传入list of tuple
。levels
:序列列表,默认为无。用于构造MultiIndex的特定级别(唯一值)。否则,将从按键推断出它们。names
:list
. 指定多层级索引各层级的名称。verify_integrity
:bool
. 检查新的串联轴是否包含重复项。性能代价可能非常昂贵。copy
:bool
. 如果为False
,在非必要的情况下不会复制数据。
没有一个例子,讨论这些参数的作用没有多大意义。回顾一下上面的例子。假设我们想将特定的键与各个待拼接的DataFrame相关联。我们可以使用keys
参数来做到这一点 :
result = pd.concat(frames, keys=['x', 'y', 'z'])
返回对象的索引具有层次结构索引。这意味着现在可以通过键值选择每个块:
result.loc['y']
A B C D
4 A4 B4 C4 D4
5 A5 B5 C5 D5
6 A6 B6 C6 D6
7 A7 B7 C7 D7
意识到这个功能是非常有用的并不是一件容易的事,有关此功能的详细信息,参见下文。
Attention
值得注意的是concat()
会完整复制数据(append()
方法也是如此),不断重复使用此函数会严重影响性能。如果需要对多个数据集使用该操作,请使用列表推导式。
frames = [process_your_file(f) for f in files]
result = pd.concat(frames)
处理其他轴的逻辑:join参数
拼接多个DataFrame时,有如下两种方式可用于处理非串联轴:
join='outer'
:并集逻辑,保留非串联轴上的所有索引值。这是默认选项,不会丢失信息。join='inner'
:交集逻辑,只保留所有DataFrame的非串联轴上都存在的索引值。
默认join='outer'
:
df4 = pd.DataFrame({'B': ['B2', 'B3', 'B6', 'B7'],
'D': ['D2', 'D3', 'D6', 'D7'],
'F': ['F2', 'F3', 'F6', 'F7']},
index=[2, 3, 7, 6])
result = pd.concat([df1, df4], axis=1, sort=True, join="outer")
只保留交集join='inner'
:
result = pd.concat([df1, df4], axis=1, join='inner')
如果只想用其中一个DataFrame的索引:
result = pd.concat([df1, df4], axis=1).reindex(df1.index)
也可以在连接之前建立索引:
pd.concat([df1, df4.reindex(df1.index)], axis=1)
A B C D B D F
0 A0 B0 C0 D0 NaN NaN NaN
1 A1 B1 C1 D1 NaN NaN NaN
2 A2 B2 C2 D2 B2 D2 F2
3 A3 B3 C3 D3 B3 D3 F3
使用append
方法进行拼接
append()
是Series和DataFrame的实例方法,它是concat()
的一个快捷方式,但实际上,append
比concat
更早出现。append
方法让Series和DataFrame沿着行(axis=0
)拼接在一起:
df.append(self, other, ignore_index=False, verify_integrity=False, sort=False)
append
不要求两个DataFrame的行索引或列索引交集为空:
result = df1.append(df4, sort=False, ignore_index=True)
append
可以拼接多个对象:
result = df1.append([df2, df3])
与list对象的append()
方法不同,DataFrame对象的append()
方法会返回副本,不会修改 df1
。
忽略串联轴索引:ignore_index
如果DataFrame
的索引没有意义,可以使用ignore_index
参数:
result = pd.concat([df1, df4], ignore_index=True, sort=False)
DataFrame.append()
方法也有这个参数:
result = df1.append(df4, ignore_index=True, sort=False)
混合串联Series
和DataFrame
可以拼接Series
和DataFrame
。该 Series
会转化为DataFrame
的一列,列名是Series
的name
。
s1 = pd.Series(['X0', 'X1', 'X2', 'X3'], name='X')
result = pd.concat([df1, s1], axis=1)
如果Series
未命名,则将连续编号。
s2 = pd.Series(['_0', '_1', '_2', '_3'])
result = pd.concat([df1, s2, s2, s2], axis=1)
通过ignore_index=True
删除原有索引标签。
result = pd.concat([df1, s1], axis=1, ignore_index=True)
key
参数
keys
参数的一个相当普遍的用法是重新指定原DataFrame或Series的名字,可以作为拼接后的DataFrame的列索引:
s3 = pd.Series([0, 1, 2, 3], name='foo')
s4 = pd.Series([0, 1, 2, 3])
s5 = pd.Series([0, 1, 4, 5])
pd.concat([s3, s4, s5], axis=1)
foo 0 1
0 0 0 0
1 1 1 1
2 2 2 4
3 3 3 5
通过keys
参数,可以覆盖现有的列名。
pd.concat([s3, s4, s5], axis=1, keys=['red', 'blue', 'yellow'])
red blue yellow
0 0 0 0
1 1 1 1
2 2 2 4
3 3 3 5
让我们考虑第一个示例的变体:
result = pd.concat(frames, keys=['x', 'y', 'z'])
还可以将包含DataFrame对象的字典传递给concat
函数,在这种情况下,字典的键将用作keys
参数(除非指定了其他键):
pieces = {'x': df1, 'y': df2, 'z': df3}
result = pd.concat(pieces)
result = pd.concat(pieces, keys=['z', 'y'])
创建的MultiIndex由传递的键和DataFrame
行索引构造:
result.index.levels
FrozenList([['z', 'y'], [4, 5, 6, 7, 8, 9, 10, 11]])
如果想指定其他级别(偶尔会这样),则可以使用以下levels
参数:
result = pd.concat(pieces, keys=['x', 'y', 'z'],
levels=[['z', 'y', 'x', 'w']],
names=['group_key'])
result.index.levels
FrozenList([['z', 'y', 'x', 'w'], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11]])
将行追加到 DataFrame
尽管效率不是很高(因为必须创建一个新的对象),但是可以向append
传递一个Series
或dict
来将一行附加到DataFrame
:
s2 = pd.Series(['X0', 'X1', 'X2', 'X3'], index=['A', 'B', 'C', 'D'])
result = df1.append(s2, ignore_index=True)
还可以传递字典或系列的列表:
dicts = [{'A': 1, 'B': 2, 'C': 3, 'X': 4},
{'A': 5, 'B': 6, 'C': 7, 'Y': 8}]
result = df1.append(dicts, ignore_index=True, sort=False)
数据库形式的拼接
pandas具有全功能、高性能的内存连接操作,与SQL等关系数据库非常相似。这些方法的性能比其他开源实现(如R中的base::merge.data.frame
)要好得多(在某些情况下甚至超过一个数量级)。原因是仔细的算法设计和DataFrame的数据的内部布局。
pandas提供函数merge()
,作为DataFrame或Series对象之间所有标准数据库拼接操作的入口点:
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None,
left_index=False, right_index=False, sort=False, suffixes=('_x', '_y'),
copy=True, indicator=False, validate=None)
left
:DataFrame
,Series
.right
:DataFrame
,Series
.on
:str
,list of str
. 它指定用作连接键的列的label
。并且必须在两个DataFrame
中这些label
都存在。如果它为None
,则默认使用两个DataFrame
的列label
的交集。你可以通过left_on/right_on
分别指定两侧DataFrame
对齐的连接键。left_on
:str
,list of str
. 指定left
对象用作连接键的列,参考on
。right_on
:str
,list of str
. 指定right
对象用作连接键的列,参考on
。left_index
:bool
. 如果为True
,则使用left
对象的行索引来作为连接键来合并。right_index
:bool
. 如果为True
,则使用right
对象的行索引来作为连接键来合并。how
:{'left', 'right', 'outer', 'inner'}
。默认为inner
。有关每种方法的详细说明,请参见下文。sort
:bool
. 如果为True
,则在结果中,对合并采用的连接键进行排序。suffixes
:一个二元序列。对于结果中同名的列,添加前缀来指示它们来自哪个DataFrame
。copy
:bool
. 如果为True
,则拷贝基础数据。否则不拷贝数据。indicator
:str
,bool
.- 如果为
True
,结果中会多出一个名为_merge
的列,用于指示每一行来自于哪个DataFrame
。 - 如果为字符串,结果中会多出一个名为
indicator
的列,用于指示每一行来自于哪个DataFrame
。
- 如果为
validate
:str
,None
. 如果指定,则检查合并是否为指定的类型。'one_to_one'
或'1:1'
:检查合并键在left
和right
中是否唯一。'one_to_many'
或'1:m'
:检查合并键在left
中是否唯一。'many_to_one'
或'm:1'
:检查合并键在right
中是否唯一。'many_to_many'
或'm:m'
:允许合并键在left
和right
中存在重复。
新功能
0.23.0版本中添加了指定索引级别的on
、left_on
、right_on
参数的支持。在0.24.0版中添加了对被命名的Series合并的支持。
merge
是pandas命名空间中的一个函数,它也可以作为DataFrame实例方法merge()
使用,调用 DataFrame.merge
时,DataFrame
被视为拼接中的左侧对象。
相关join()
方法在merge
内部用于索引索引连接(默认情况下)和索引列连接。如果仅加入索引,则可能希望使用DataFrame.join
来节省一些输入。
合并方法简介(关系代数)
有经验的关系数据库用户熟悉用于描述两个SQL类表结构(DataFrame对象)之间的连接操作的术语。有几个案例需要考虑,这些案例非常重要:
- one-to-one连接:两个DataFrame对象的组合键都是唯一值。
- many-to-one连接:其中一个DataFrame对象的组合键是唯一值,另一个有重复值。
- many-to-many连接:两个DataFrame对象的组合键都有重复值。
Caution
在列上连接列时(可能是多对多连接),传递的DataFrame
对象上的所有索引都将被丢弃。
值得花些时间了解多对多连接案例的结果。在SQL/标准关系代数中,如果键组合在两个表中出现不止一次,则生成的表将具有关联数据的笛卡尔积。
下面是一个非常基本的例子,使用一个组合键,并且组合键的值都是唯一的:
left = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})
right = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']})
result = pd.merge(left, right, on='key')
多个组合键的示例(默认how='inner'
):
left = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'],
'key2': ['K0', 'K1', 'K0', 'K1'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})
right = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'],
'key2': ['K0', 'K0', 'K0', 'K0'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']})
result = pd.merge(left, right, on=['key1', 'key2'])
当左右两边的组合键不一致时,处理行为由how
参数指定。以下是how
选项及其SQL等效名称的摘要:
合并方式 | SQL连接名称 | 描述 |
---|---|---|
left |
LEFT OUTER JOIN |
仅保留left出现的组合键 |
right |
RIGHT OUTER JOIN |
仅保留right出现的组合键 |
outer |
FULL OUTER JOIN |
保留left和right组合键的并集 |
inner |
INNER JOIN |
保留left和right组合键的交集 |
result = pd.merge(left, right, how='left', on=['key1', 'key2'])
result = pd.merge(left, right, how='right', on=['key1', 'key2'])
result = pd.merge(left, right, how='outer', on=['key1', 'key2'])
result = pd.merge(left, right, how='inner', on=['key1', 'key2'])
这是组合键具有重复值的示例:
left = pd.DataFrame({'A': [1, 2], 'B': [2, 2]})
right = pd.DataFrame({'A': [4, 5, 6], 'B': [2, 2, 2]})
result = pd.merge(left, right, on='B', how='outer')
Caution
在重复键上进行拼接,会导致返回的DataFrame的函数是原来两个DataFrame行数的乘积,这可能导致内存溢出。如果DataFrame很大,应该对重复键进行管理。
检查重复键:validate
pandas 0.21.0 版本添加了此参数,用于自动检查组合键中是否有意外的重复项。在合并操作之前检查键的唯一性,可以防止内存溢出。检查组合键的唯一性也是确保数据结构符合预期的一种好方法。
在以下示例中,right中的B
存在重复的值。由于这不是validate
参数中指定的一对一合并,因此将引发异常。
left = pd.DataFrame({'A': [1, 2], 'B': [1, 2]})
right = pd.DataFrame({'A': [4, 5, 6], 'B': [2, 2, 2]})
try:
result = pd.merge(left, right, on='B', how='outer', validate="one_to_one")
except Exception as e:
print("MergeError:", e)
MergeError: Merge keys are not unique in right dataset; not a one-to-
one merge
如果用户知道右边的重复项,DataFrame
但要确保左边的DataFrame中没有重复项,则可以改用该validate='one_to_many'
参数,这不会引发异常。
pd.merge(left, right, on='B', how='outer', validate="one_to_many")
A_x B A_y
0 1 1 NaN
1 2 2 4.0
2 2 2 5.0
3 2 2 6.0
指示符:indicator
merge()
接受指示符参数indicator
。如果indicator=True
,则将一个名为_merge
,dtype为Categorical的列添加到输出DataFrame的末尾:
indicator | _merge 值 |
---|---|
仅出现在'left' 组合键 |
left_only |
仅出现在'right' 组合键 |
right_only |
同时出现在'left' 和'right' 组合键 |
both |
df1 = pd.DataFrame({'col1': [0, 1], 'col_left': ['a', 'b']})
df2 = pd.DataFrame({'col1': [1, 2, 2], 'col_right': [2, 2, 2]})
pd.merge(df1, df2, on='col1', how='outer', indicator=True)
col1 col_left col_right _merge
0 0 a NaN left_only
1 1 b 2.0 both
2 2 NaN 2.0 right_only
3 2 NaN 2.0 right_only
indicator
参数还可以接受字符串参数,在这种情况下,指标函数将使用传递的字符串的值作为指标列的名称。
pd.merge(df1, df2, on='col1', how='outer', indicator='indicator_column')
col1 col_left col_right indicator_column
0 0 a NaN left_only
1 1 b 2.0 both
2 2 NaN 2.0 right_only
3 2 NaN 2.0 right_only
合并时数据类型的转换
合并将保留组合键的数据类型
left = pd.DataFrame({'key': [1], 'v1': [10]})
print(left, "\n")
right = pd.DataFrame({'key': [1, 2], 'v1': [20, 30]})
print(right)
key v1
0 1 10
key v1
0 1 20
1 2 30
我们可以保留组合键:
print(pd.merge(left, right, how='outer'), "\n")
print(pd.merge(left, right, how='outer').dtypes)
key v1
0 1 10
1 1 20
2 2 30
key int64
v1 int64
dtype: object
如果缺少引入的值,则生成的dtype将被转换:
print(pd.merge(left, right, how='outer', on='key'), "\n")
print(pd.merge(left, right, how='outer', on='key').dtypes)
key v1_x v1_y
0 1 10.0 20
1 2 NaN 30
key int64
v1_x float64
v1_y int64
dtype: object
合并将保留mergands的category类型。另请参阅关于categoricals的部分。
left:
from pandas.api.types import CategoricalDtype
X = pd.Series(np.random.choice(['foo', 'bar'], size=(10,)))
X = X.astype(CategoricalDtype(categories=['foo', 'bar']))
left = pd.DataFrame({'X': X,
'Y': np.random.choice(['one', 'two', 'three'],
size=(10,))})
print(left, "\n")
print(left.dtypes)
X Y
0 foo one
1 foo one
2 foo one
3 foo one
4 foo one
5 foo two
6 bar one
7 bar three
8 bar three
9 bar one
X category
Y object
dtype: object
right:
right = pd.DataFrame({'X': pd.Series(['foo', 'bar'],
dtype=CategoricalDtype(['foo', 'bar'])),
'Z': [1, 2]})
print(right, "\n")
print(right.dtypes)
X Z
0 foo 1
1 bar 2
X category
Z int64
dtype: object
合并结果:
result = pd.merge(left, right, how='outer')
print(result, "\n")
print(result.dtypes)
X Y Z
0 foo one 1
1 foo one 1
2 foo one 1
3 foo one 1
4 foo one 1
5 foo two 1
6 bar one 2
7 bar three 2
8 bar three 2
9 bar one 2
X category
Y object
Z int64
dtype: object
注意
category 类型必须完全相同,这意味着category对象有相同的类别和有序属性。否则,category类型将被转换成object类型。
注意
与object数据类型合并相比,在相同的category数据类型上进行合并可以获得更好的性能。
join
方法
这是一个非常基本的示例:
left = pd.DataFrame({'A': ['A0', 'A1', 'A2'],
'B': ['B0', 'B1', 'B2']},
index=['K0', 'K1', 'K2'])
right = pd.DataFrame({'C': ['C0', 'C2', 'C3'],
'D': ['D0', 'D2', 'D3']},
index=['K0', 'K2', 'K3'])
result = left.join(right)
result = left.join(right, how='outer')
与上述相同,但带有how='inner'
:
result = left.join(right, how='inner')
此处的数据对齐在索引(行标签)上。使用merge
指示其使用索引的附加参数可以实现相同的行为:
result = pd.merge(left, right, left_index=True, right_index=True, how='outer')
result = pd.merge(left, right, left_index=True, right_index=True, how='inner')
拼接列和索引
join()
接受一个可选的on
参数,该参数可以是一个列或多个列名,它指定right
的行索引将在left
中的该列上对齐。即以下两个函数调用完全等价:
left.join(right, on=key_or_keys, how='left', sort=False)
pd.merge(left, right, left_on=key_or_keys, right_index=True,
how='left', sort=False)
您可以选择任何一种更方便的形式。对于many-to-one拼接(其中一个DataFrame的索引是组合键),使用join
可能会更方便。这是一个简单的示例:
left = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3'],
'key': ['K0', 'K1', 'K0', 'K1']})
right = pd.DataFrame({'C': ['C0', 'C1'],
'D': ['D0', 'D1']},
index=['K0', 'K1'])
result = left.join(right, on='key')
result = pd.merge(left, right, left_on='key', right_index=True,
how='left', sort=False)
如果组合键有多个,传递的DataFrame必须具有MultiIndex
:
left = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3'],
'key1': ['K0', 'K0', 'K1', 'K2'],
'key2': ['K0', 'K1', 'K0', 'K1']})
index = pd.MultiIndex.from_tuples([('K0', 'K0'),
('K1', 'K0'),
('K2', 'K0'),
('K2', 'K1')])
right = pd.DataFrame({'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']},
index=index)
现在,可以通过传递两个组合键来进行拼接:
result = left.join(right, on=['key1', 'key2'])
默认的DataFrame.join
是执行左联接(对于Excel用户,本质上是“ VLOOKUP”操作),该联接仅使用在调用DataFrame中找到的键。其他联接类型(例如内部联接)也可以轻松执行:
result = left.join(right, on=['key1', 'key2'], how='inner')
如您所见,这将删除所有不匹配的行。
拼接单层索引和多层索引
可以将单层索引DataFrame与多层索引DataFrame的一个级别连接起来。该级别将与单索引DataFrame的索引名称和多索引DataFrame的级别名称相匹配
left = pd.DataFrame({'A': ['A0', 'A1', 'A2'],
'B': ['B0', 'B1', 'B2']},
index=pd.Index(['K0', 'K1', 'K2'], name='key'))
index = pd.MultiIndex.from_tuples([('K0', 'Y0'),
('K1', 'Y1'),
('K2', 'Y2'),
('K2', 'Y3')],
names=['key', 'Y'])
right = pd.DataFrame({'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']},
index=index)
result = left.join(right, how='inner')
下面的函数和上面的方法是等效的,但pd.merge
的信息更详细,存储效率更高/更快。
result = pd.merge(left.reset_index(), right.reset_index(),
on=['key'], how='inner').set_index(['key', 'Y'])
拼接MultiIndexes
只要在连接中完全使用了右参数的索引,并且该参数是左参数中索引的子集,就可以用有限的方式来支持它,如下例所示:
leftindex = pd.MultiIndex.from_product([list('abc'), list('xy'), [1, 2]],
names=['abc', 'xy', 'num'])
left = pd.DataFrame({'v1': range(12)}, index=leftindex)
print(left, "\n")
rightindex = pd.MultiIndex.from_product([list('abc'), list('xy')],
names=['abc', 'xy'])
right = pd.DataFrame({'v2': [100 * i for i in range(1, 7)]}, index=rightindex)
print(right, "\n")
left.join(right, on=['abc', 'xy'], how='inner')
v1
abc xy num
a x 1 0
2 1
y 1 2
2 3
b x 1 4
2 5
y 1 6
2 7
c x 1 8
2 9
y 1 10
2 11
v2
abc xy
a x 100
y 200
b x 300
y 400
c x 500
y 600
v1 v2
abc xy num
a x 1 0 100
2 1 100
y 1 2 200
2 3 200
b x 1 4 300
2 5 300
y 1 6 400
2 7 400
c x 1 8 500
2 9 500
y 1 10 600
2 11 600
如果不满足该条件,则可以使用以下代码完成具有两个多索引的联接。
leftindex = pd.MultiIndex.from_tuples([('K0', 'X0'), ('K0', 'X1'),
('K1', 'X2')],
names=['key', 'X'])
left = pd.DataFrame({'A': ['A0', 'A1', 'A2'],
'B': ['B0', 'B1', 'B2']},
index=leftindex)
rightindex = pd.MultiIndex.from_tuples([('K0', 'Y0'), ('K1', 'Y1'),
('K2', 'Y2'), ('K2', 'Y3')],
names=['key', 'Y'])
right = pd.DataFrame({'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']},
index=rightindex)
result = pd.merge(left.reset_index(), right.reset_index(),
on=['key'], how='inner').set_index(['key', 'X', 'Y'])
按照列和索引合并
这是 0.23 版中添加的功能。
on
、left_on
和right_on
参数可以引用列名或索引级名称。这允许在索引和列的组合上合并DataFrame实例,而不必重置索引:
left_index = pd.Index(['K0', 'K0', 'K1', 'K2'], name='key1')
left = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3'],
'key2': ['K0', 'K1', 'K0', 'K1']},
index=left_index)
right_index = pd.Index(['K0', 'K1', 'K2', 'K2'], name='key1')
right = pd.DataFrame({'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3'],
'key2': ['K0', 'K0', 'K0', 'K1']},
index=right_index)
result = left.merge(right, on=['key1', 'key2'])
名字重复的组合键
merge
函数的suffixes
参数接受字符串列表的元组,以附加到输入DataFrame中重叠的列名以消除结果列的歧义:
left = pd.DataFrame({'k': ['K0', 'K1', 'K2'], 'v': [1, 2, 3]})
right = pd.DataFrame({'k': ['K0', 'K0', 'K3'], 'v': [4, 5, 6]})
result = pd.merge(left, right, on='k')
result = pd.merge(left, right, on='k', suffixes=['_l', '_r'])
DataFrame.join()
具有lsuffix
和rsuffix
行为类似的参数。
left = left.set_index('k')
right = right.set_index('k')
result = left.join(right, lsuffix='_l', rsuffix='_r')
拼接多个DataFrames
DataFrames
也可以传递的列表或元组以join()
将它们连接到它们的索引上。
right2 = pd.DataFrame({'v': [7, 8, 9]}, index=['K1', 'K1', 'K2'])
result = left.join([right, right2])
按索引填补缺失值
另一个相当常见的情况是有两个索引存在交集的对象,其中一个存在缺失值,想通过另一个对象填补缺失值。这是一个例子:
df1 = pd.DataFrame([[np.nan, 3., 5.], [-4.6, np.nan, np.nan],
[np.nan, 7., np.nan]])
df2 = pd.DataFrame([[-42.6, np.nan, -8.2], [-5., 1.6, 4]],
index=[1, 2])
combine_first()
方法可以达到这个效果:
result = df1.combine_first(df2)
请注意,combine_first
返回副本,而update()
则会修改原对象:
df1.update(df2)
拼接时间序列数据
拼接有序数据
merge_ordered()
函数允许拼接时间序列和其他有序数据。
pd.merge_ordered(left, right, on=None, left_on=None, right_on=None,
left_by=None, right_by=None, fill_method=None,
suffixes=('_x', '_y'), how='outer')
left_by
: Group left DataFrame by group columns and merge piece by piece with right DataFrame
left = pd.DataFrame({'k': ['K0', 'K1', 'K1', 'K2'],
'lv': [1, 2, 3, 4],
's': ['a', 'b', 'c', 'd']})
right = pd.DataFrame({'k': ['K1', 'K2', 'K4'],
'rv': [1, 2, 3]})
print(left, "\n")
print(right)
pd.merge_ordered(left, right, fill_method=None, left_by='s')
k lv s
0 K0 1 a
1 K1 2 b
2 K1 3 c
3 K2 4 d
k rv
0 K1 1
1 K2 2
2 K4 3
k lv s rv
0 K0 1.0 a NaN
1 K1 NaN a 1.0
2 K2 NaN a 2.0
3 K4 NaN a 3.0
4 K1 2.0 b 1.0
5 K2 NaN b 2.0
6 K4 NaN b 3.0
7 K1 3.0 c 1.0
8 K2 NaN c 2.0
9 K4 NaN c 3.0
10 K1 NaN d 1.0
11 K2 4.0 d 2.0
12 K4 NaN d 3.0
它具有一个可选fill_method
关键字来填充丢失的数据:
# 前向填充:后面的缺失值用前面的数字填充
pd.merge_ordered(left, right, fill_method="ffill", left_by='s')
k lv s rv
0 K0 1.0 a NaN
1 K1 1.0 a 1.0
2 K2 1.0 a 2.0
3 K4 1.0 a 3.0
4 K1 2.0 b 1.0
5 K2 2.0 b 2.0
6 K4 2.0 b 3.0
7 K1 3.0 c 1.0
8 K2 3.0 c 2.0
9 K4 3.0 c 3.0
10 K1 NaN d 1.0
11 K2 4.0 d 2.0
12 K4 4.0 d 3.0
拼接ASOF
merge_asof()
类似于有序左联接,只是我们匹配最近的键而不是相等的键。对于左数据框中的每一行,我们选择右数据框中的最后一行,该行的on键小于lefs键。两个数据帧都必须按键排序。
可选地,asof合并可以执行逐组合并。除了on键上最接近的匹配之外,它与by键的匹配相同
pd.merge_asof(left, right, on=None, left_on=None, right_on=None,
left_index=False, right_index=False, by=None, left_by=None,
right_by=None, suffixes=('_x', '_y'), tolerance=None,
allow_exact_matches=True, direction='backward')
例如,我们可能有trades
和quotes
,我们想合并它们
trades = pd.DataFrame({
'time': pd.to_datetime(['20160525 13:30:00.023',
'20160525 13:30:00.038',
'20160525 13:30:00.048',
'20160525 13:30:00.048',
'20160525 13:30:00.048']),
'ticker': ['MSFT', 'MSFT',
'GOOG', 'GOOG', 'AAPL'],
'price': [51.95, 51.95,
720.77, 720.92, 98.00],
'quantity': [75, 155,
100, 100, 100]},
columns=['time', 'ticker', 'price', 'quantity'])
quotes = pd.DataFrame({
'time': pd.to_datetime(['20160525 13:30:00.023',
'20160525 13:30:00.023',
'20160525 13:30:00.030',
'20160525 13:30:00.041',
'20160525 13:30:00.048',
'20160525 13:30:00.049',
'20160525 13:30:00.072',
'20160525 13:30:00.075']),
'ticker': ['GOOG', 'MSFT', 'MSFT',
'MSFT', 'GOOG', 'AAPL', 'GOOG',
'MSFT'],
'bid': [720.50, 51.95, 51.97, 51.99,
720.50, 97.99, 720.50, 52.01],
'ask': [720.93, 51.96, 51.98, 52.00,
720.93, 98.01, 720.88, 52.03]},
columns=['time', 'ticker', 'bid', 'ask'])
print(trades, "\n")
print(quotes)
time ticker price quantity
0 2016-05-25 13:30:00.023 MSFT 51.95 75
1 2016-05-25 13:30:00.038 MSFT 51.95 155
2 2016-05-25 13:30:00.048 GOOG 720.77 100
3 2016-05-25 13:30:00.048 GOOG 720.92 100
4 2016-05-25 13:30:00.048 AAPL 98.00 100
time ticker bid ask
0 2016-05-25 13:30:00.023 GOOG 720.50 720.93
1 2016-05-25 13:30:00.023 MSFT 51.95 51.96
2 2016-05-25 13:30:00.030 MSFT 51.97 51.98
3 2016-05-25 13:30:00.041 MSFT 51.99 52.00
4 2016-05-25 13:30:00.048 GOOG 720.50 720.93
5 2016-05-25 13:30:00.049 AAPL 97.99 98.01
6 2016-05-25 13:30:00.072 GOOG 720.50 720.88
7 2016-05-25 13:30:00.075 MSFT 52.01 52.03
默认情况下,我们采用的是quotes。
pd.merge_asof(trades, quotes,
on='time',
by='ticker')
time ticker price quantity bid ask
0 2016-05-25 13:30:00.023 MSFT 51.95 75 51.95 51.96
1 2016-05-25 13:30:00.038 MSFT 51.95 155 51.97 51.98
2 2016-05-25 13:30:00.048 GOOG 720.77 100 720.50 720.93
3 2016-05-25 13:30:00.048 GOOG 720.92 100 720.50 720.93
4 2016-05-25 13:30:00.048 AAPL 98.00 100 NaN NaN
我们只在quote 时间和trade 时间之间的2毫秒内合并:
pd.merge_asof(trades, quotes,
on='time',
by='ticker',
tolerance=pd.Timedelta('2ms'))
time ticker price quantity bid ask
0 2016-05-25 13:30:00.023 MSFT 51.95 75 51.95 51.96
1 2016-05-25 13:30:00.038 MSFT 51.95 155 NaN NaN
2 2016-05-25 13:30:00.048 GOOG 720.77 100 720.50 720.93
3 2016-05-25 13:30:00.048 GOOG 720.92 100 720.50 720.93
4 2016-05-25 13:30:00.048 AAPL 98.00 100 NaN NaN
我们只在quote 时间和trade 时间之间的10毫秒内合并,并且不包括精确匹配。注意,尽管我们排除了(quotes的)完全匹配项,但以前的quotes确实会传播到那个时间点。
pd.merge_asof(trades, quotes,
on='time',
by='ticker',
tolerance=pd.Timedelta('10ms'),
allow_exact_matches=False)
time ticker price quantity bid ask
0 2016-05-25 13:30:00.023 MSFT 51.95 75 NaN NaN
1 2016-05-25 13:30:00.038 MSFT 51.95 155 51.97 51.98
2 2016-05-25 13:30:00.048 GOOG 720.77 100 NaN NaN
3 2016-05-25 13:30:00.048 GOOG 720.92 100 NaN NaN
4 2016-05-25 13:30:00.048 AAPL 98.00 100 NaN NaN
比较对象
df.compare(self, other, align_axis=1, keep_shape=False,
keep_equal=False)
pandas v1.1.0 版本中添加了compare
方法,用于比较两个DataFrame或Series的差异。例如,你可能想要比较两个DataFrame
并并排堆叠它们的差异。
df = pd.DataFrame({"col1": ["a", "a", "b", "b", "a"],
"col2": [1.0, 2.0, 3.0, np.nan, 5.0],
"col3": [1.0, 2.0, 3.0, 4.0, 5.0]},
columns=["col1", "col2", "col3"])
print(df)
df2 = df.copy()
df2.loc[0, "col1"] = "c"
df2.loc[2, "col3"] = 4.0
print(df2)
df.compare(df2)
col1 col2 col3
0 a 1.0 1.0
1 a 2.0 2.0
2 b 3.0 3.0
3 b NaN 4.0
4 a 5.0 5.0
col1 col2 col3
0 c 1.0 1.0
1 a 2.0 2.0
2 b 3.0 4.0
3 b NaN 4.0
4 a 5.0 5.0
col1 col3
self other self other
0 a c NaN NaN
2 NaN NaN 3.0 4.0
默认情况下,如果两个对应的值相等,它们将显示为NaN
。此外,如果整个行/列中的值都没有差异,则结果将省略该行/列。其余差异将在列上对齐。
如果愿意,可以选择将差异按行堆叠。如果你希望保留所有原始行和列,请将keep_shape
参数设置为True
。你也可以保留所有原始值,即使它们相等。