跳转至

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)

../_images/merging_concat_basic.png

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对象的序列或字典。如果传递了dictdict的排序后的key值将作为keys参数,除非显式传入keys参数。序列或字典中可以包含NoneNone会被静默删除,但所有对象都为None将引发ValueError
  • axis{0,1,…}. 沿着该轴进行拼接。
  • join{'inner','outer'}. 处理其他轴的索引的逻辑,'outer'为并集,'inner'为交集。
  • ignore_indexbool. 如果为True,则用0, …, n-1代替串联轴的原索引。注意,拼接时仍会考虑其他轴上的索引值。
  • keyslist of str, list of tuple. 用于构造多层级索引。如果要传入多层级索引,则应传入list of tuple
  • levels:序列列表,默认为无。用于构造MultiIndex的特定级别(唯一值)。否则,将从按键推断出它们。
  • nameslist. 指定多层级索引各层级的名称。
  • verify_integritybool. 检查新的串联轴是否包含重复项。性能代价可能非常昂贵。
  • copybool. 如果为False,在非必要的情况下不会复制数据。

没有一个例子,讨论这些参数的作用没有多大意义。回顾一下上面的例子。假设我们想将特定的键与各个待拼接的DataFrame相关联。我们可以使用keys参数来做到这一点 :

result = pd.concat(frames, keys=['x', 'y', 'z'])

../_images/merging_concat_keys.png

返回对象的索引具有层次结构索引。这意味着现在可以通过键值选择每个块:

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时,有如下两种方式可用于处理非串联轴:

  1. join='outer':并集逻辑,保留非串联轴上的所有索引值。这是默认选项,不会丢失信息。
  2. 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")

../_images/merging_concat_axis1.png

只保留交集join='inner'

result = pd.concat([df1, df4], axis=1, join='inner')

../_images/merging_concat_axis1_inner.png

如果只想用其中一个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

../_images/merging_concat_axis1_join_axes.png

使用append方法进行拼接

append()是Series和DataFrame的实例方法,它是concat()的一个快捷方式,但实际上,appendconcat更早出现。append方法让Series和DataFrame沿着行(axis=0)拼接在一起:

df.append(self, other, ignore_index=False, verify_integrity=False, sort=False)

../_images/merging_append1.png

append不要求两个DataFrame的行索引或列索引交集为空:

result = df1.append(df4, sort=False, ignore_index=True)

../_images/merging_append2.png

append 可以拼接多个对象:

result = df1.append([df2, df3])

../_images/merging_append3.png

与list对象的append()方法不同,DataFrame对象的append()方法会返回副本,不会修改 df1

忽略串联轴索引:ignore_index

如果DataFrame的索引没有意义,可以使用ignore_index参数:

result = pd.concat([df1, df4], ignore_index=True, sort=False)

../_images/merging_concat_ignore_index.png

DataFrame.append()方法也有这个参数:

result = df1.append(df4, ignore_index=True, sort=False)

../_images/merging_append_ignore_index.png

混合串联SeriesDataFrame

可以拼接SeriesDataFrame。该 Series会转化为DataFrame的一列,列名是Seriesname

s1 = pd.Series(['X0', 'X1', 'X2', 'X3'], name='X')
result = pd.concat([df1, s1], axis=1)

../_images/merging_concat_mixed_ndim.png

如果Series未命名,则将连续编号。

s2 = pd.Series(['_0', '_1', '_2', '_3'])
result = pd.concat([df1, s2, s2, s2], axis=1)

../_images/merging_concat_unnamed_series.png

通过ignore_index=True删除原有索引标签。

result = pd.concat([df1, s1], axis=1, ignore_index=True)

../_images/merging_concat_series_ignore_index.png

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'])

../_images/merging_concat_group_keys2.png

还可以将包含DataFrame对象的字典传递给concat函数,在这种情况下,字典的键将用作keys参数(除非指定了其他键):

pieces = {'x': df1, 'y': df2, 'z': df3}
result = pd.concat(pieces)

../_images/merging_concat_dict.png

result = pd.concat(pieces, keys=['z', 'y'])

../_images/merging_concat_dict_keys.png

创建的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'])

../_images/merging_concat_dict_keys_names.png

result.index.levels
FrozenList([['z', 'y', 'x', 'w'], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11]])

将行追加到 DataFrame

尽管效率不是很高(因为必须创建一个新的对象),但是可以向append传递一个Seriesdict来将一行附加到DataFrame

s2 = pd.Series(['X0', 'X1', 'X2', 'X3'], index=['A', 'B', 'C', 'D'])
result = df1.append(s2, ignore_index=True)

../_images/merging_append_series_as_row.png

还可以传递字典或系列的列表:

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)

../_images/merging_append_dits.png

数据库形式的拼接

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)
  • leftDataFrame, Series.
  • rightDataFrame, Series.
  • onstr, list of str. 它指定用作连接键的列的label。并且必须在两个DataFrame中这些label都存在。如果它为None,则默认使用两个DataFrame的列label的交集。你可以通过left_on/right_on分别指定两侧DataFrame对齐的连接键。
  • left_onstr, list of str. 指定left对象用作连接键的列,参考on
  • right_onstr, list of str. 指定right对象用作连接键的列,参考on
  • left_indexbool. 如果为True,则使用left对象的行索引来作为连接键来合并。
  • right_indexbool. 如果为True,则使用right对象的行索引来作为连接键来合并。
  • how{'left', 'right', 'outer', 'inner'}。默认为inner。有关每种方法的详细说明,请参见下文。
  • sortbool. 如果为True,则在结果中,对合并采用的连接键进行排序。
  • suffixes:一个二元序列。对于结果中同名的列,添加前缀来指示它们来自哪个DataFrame
  • copybool. 如果为True,则拷贝基础数据。否则不拷贝数据。
  • indicatorstr, bool.
    1. 如果为True,结果中会多出一个名为_merge的列,用于指示每一行来自于哪个DataFrame
    2. 如果为字符串,结果中会多出一个名为indicator的列,用于指示每一行来自于哪个DataFrame
  • validatestr, None. 如果指定,则检查合并是否为指定的类型。
    1. 'one_to_one''1:1':检查合并键在leftright中是否唯一。
    2. 'one_to_many''1:m':检查合并键在left中是否唯一。
    3. 'many_to_one''m:1':检查合并键在right中是否唯一。
    4. 'many_to_many''m:m':允许合并键在leftright中存在重复。

新功能

0.23.0版本中添加了指定索引级别的onleft_onright_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')

../_images/merging_merge_on_key.png

多个组合键的示例(默认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'])

../_images/merging_merge_on_key_multiple.png

当左右两边的组合键不一致时,处理行为由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'])

../_images/merging_merge_on_key_left.png

result = pd.merge(left, right, how='right', on=['key1', 'key2'])

../_images/merging_merge_on_key_right.png

result = pd.merge(left, right, how='outer', on=['key1', 'key2'])

../_images/merging_merge_on_key_outer.png

result = pd.merge(left, right, how='inner', on=['key1', 'key2'])

../_images/merging_merge_on_key_inner.png

这是组合键具有重复值的示例:

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')

../_images/merging_merge_on_key_dup.png

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)

../_images/merging_join.png

result = left.join(right, how='outer')

../_images/merging_join_outer.png

与上述相同,但带有how='inner'

result = left.join(right, how='inner')

../_images/merging_join_inner.png

此处的数据对齐在索引(行标签)上。使用merge指示其使用索引的附加参数可以实现相同的行为:

result = pd.merge(left, right, left_index=True, right_index=True, how='outer')

../_images/merging_merge_index_outer.png

result = pd.merge(left, right, left_index=True, right_index=True, how='inner')

../_images/merging_merge_index_inner.png

拼接列和索引

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')

../_images/merging_join_key_columns.png

result = pd.merge(left, right, left_on='key', right_index=True,
                  how='left', sort=False)

../_images/merging_merge_key_columns.png

如果组合键有多个,传递的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'])

../_images/merging_join_multikeys.png

默认的DataFrame.join是执行左联接(对于Excel用户,本质上是“ VLOOKUP”操作),该联接仅使用在调用DataFrame中找到的键。其他联接类型(例如内部联接)也可以轻松执行:

result = left.join(right, on=['key1', 'key2'], how='inner')

../_images/merging_join_multikeys_inner.png

如您所见,这将删除所有不匹配的行。

拼接单层索引和多层索引

可以将单层索引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')

../_images/merging_join_multiindex_inner.png

下面的函数和上面的方法是等效的,但pd.merge的信息更详细,存储效率更高/更快。

result = pd.merge(left.reset_index(), right.reset_index(),
                  on=['key'], how='inner').set_index(['key', 'Y'])

../_images/merging_merge_multiindex_alternative.png

拼接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'])

../_images/merging_merge_two_multiindex.png

按照列和索引合并

这是 0.23 版中添加的功能。

onleft_onright_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'])

../_images/merge_on_index_and_column.png

名字重复的组合键

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')

../_images/merging_merge_overlapped.png

result = pd.merge(left, right, on='k', suffixes=['_l', '_r'])

../_images/merging_merge_overlapped_suffix.png

DataFrame.join()具有lsuffixrsuffix行为类似的参数。

left = left.set_index('k')
right = right.set_index('k')
result = left.join(right, lsuffix='_l', rsuffix='_r')

../_images/merging_merge_overlapped_multi_suffix.png

拼接多个DataFrames

DataFrames也可以传递的列表或元组以join() 将它们连接到它们的索引上。

right2 = pd.DataFrame({'v': [7, 8, 9]}, index=['K1', 'K1', 'K2'])

result = left.join([right, right2])

../_images/merging_join_multi_df.png

按索引填补缺失值

另一个相当常见的情况是有两个索引存在交集的对象,其中一个存在缺失值,想通过另一个对象填补缺失值。这是一个例子:

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)

../_images/merging_combine_first.png

请注意,combine_first返回副本,而update()则会修改原对象:

df1.update(df2)

../_images/merging_update.png

拼接时间序列数据

拼接有序数据

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')

例如,我们可能有tradesquotes,我们想合并它们

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。你也可以保留所有原始值,即使它们相等。