老齐教室

合并Pandas的DataFrame方法汇总

Pandas是数据分析、机器学习等常用的工具,其中的DataFrame又是最常用的数据类型,对它的操作,不得不数量。在《跟老齐学Python:数据分析》一书中,对DataFrame对象的各种常用操作都有详细介绍。本文根据书中介绍的内容,并参考其他文献,专门汇总了合并操作的各种方法。

Pandas提供好几种方法和函数来实现合并DataFrame的操作,一般的操作结果是创建一个新的DataFrame,而对原始数据没有任何影响。

方法1:merge()

先创建一个DataFrame对象,后面也会用到它。如下所示,df1包括姓名、电子邮件和用户id。

1
2
3
4
5
6
7
8
import pandas as pd

df1 = pd.DataFrame({'user_id': ['id001', 'id002', 'id003', 'id004', 'id005', 'id006', 'id007'],
'first_name': ['Rivi', 'Wynnie', 'Kristos', 'Madalyn', 'Tobe', 'Regan', 'Kristin'],
'last_name': ['Valti', 'McMurty', 'Ivanets', 'Max', 'Riddich', 'Huyghe', 'Illis'],
'email': ['rvalti0@example.com', 'wmcmurty1@example.com', 'kivanets2@example.com',
'mmax3@example.com', 'triddich4@example.com', 'rhuyghe@example.com', 'killis4@example.com']
})

为了能够进行合并的操作,还需要再创建一个df2,如下所示。

1
2
3
4
5
df2 = pd.DataFrame({'user_id': ['id001', 'id002', 'id003', 'id004', 'id005'],
'image_url': ['http://example.com/img/id001.png', 'http://example.com/img/id002.jpg',
'http://example.com/img/id003.bmp', 'http://example.com/img/id004.jpg',
'http://example.com/img/id005.png']
})

所创建的两个DataFrame数据如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# df1
user_id first_name last_name email
0 id001 Rivi Valti rvalti0@example.com
1 id002 Wynnie McMurty wmcmurty1@example.com
2 id003 Kristos Ivanets kivanets2@example.com
3 id004 Madalyn Max mmax3@example.com
4 id005 Tobe Riddich triddich4@example.com
5 id006 Regan Huyghe rhuyghe@example.com
6 id007 Kristin Illis killis4@example.com

#df2
user_id image_url
0 id001 http://example.com/img/id001.png
1 id002 http://example.com/img/id002.jpg
2 id003 http://example.com/img/id003.bmp
3 id004 http://example.com/img/id004.jpg
4 id005 http://example.com/img/id005.png

merge()函数将df1df2合并。首先,看一下这个函数可以接受的参数:

1
2
3
4
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None,
left_index=False, right_index=False, sort=True,
suffixes=('_x', '_y'), copy=True, indicator=False,
validate=None)

leftright外,大多数参数都有默认值,这两个参数是我们要合并的DataFrames的名称。函数本身将返回一个新的DataFrame,用变量df3_merged引用。

1
df3_merged = pd.merge(df1, df2)

两个DataFrames都有一个同名的列user_id,所以 merge()函数会自动根据此列合并两个对象——此种情景可以称为在键user_id上合并。

如果有两个DataFrame没有相同名称的列,可以使用left_on='left_column_name'right_on='right_column_name'显式地指定两个DataFrames上的键。

打印df3_merged ,看看它的内容:

1
2
3
4
5
6
  user_id first_name last_name                  email                         image_url
0 id001 Rivi Valti rvalti0@example.com http://example.com/img/id001.png
1 id002 Wynnie McMurty wmcmurty1@example.com http://example.com/img/id002.jpg
2 id003 Kristos Ivanets kivanets2@example.com http://example.com/img/id003.bmp
3 id004 Madalyn Max mmax3@example.com http://example.com/img/id004.jpg
4 id005 Tobe Riddich triddich4@example.com http://example.com/img/id005.png

你会注意到, df3_merged只有5行,而原来的df1有7行。为什么会这样?

how参数的默认值设置为inner时,将从左DataFrame和右DataFrame的交集生成一个新的DataFrame。因此,如果其中一个表中缺少user_id ,它就不会在合并的DataFrame中。

即使交换了左右行的位置,结果仍然如此。

解决方法,就是在使用merge()时,将参数 how的值设置为left

1
2
3
df_left_merge = pd.merge(df1, df2, how='left')

print(df_left_merge)

这就是所谓的“左联接”,这样得到了包含左DataFrame (df1) 和右DataFrame (df2)的所有元素的DataFrame。运行上述代码显示以下内容:

1
2
3
4
5
6
7
8
  user_id first_name last_name                  email                         image_url
0 id001 Rivi Valti rvalti0@example.com http://example.com/img/id001.png
1 id002 Wynnie McMurty wmcmurty1@example.com http://example.com/img/id002.jpg
2 id003 Kristos Ivanets kivanets2@example.com http://example.com/img/id003.bmp
3 id004 Madalyn Max mmax3@example.com http://example.com/img/id004.jpg
4 id005 Tobe Riddich triddich4@example.com http://example.com/img/id005.png
5 id006 Regan Huyghe rhuyghe@example.com NaN
6 id007 Kristin Illis killis4@example.com NaN

与左DataFrame没有任何匹配值的单元被填充为NaN

再试试“右联接”,创建以下的合并DataFrame:

1
2
3
df_right_merge = pd.merge(df1, df2, how='right')

print(df_right_merge)

如你所料,“右联接”将返回左DataFrame中与右DataFrame匹配的所有值:

1
2
3
4
5
6
  user_id first_name last_name                  email                         image_url
0 id001 Rivi Valti rvalti0@example.com http://example.com/img/id001.png
1 id002 Wynnie McMurty wmcmurty1@example.com http://example.com/img/id002.jpg
2 id003 Kristos Ivanets kivanets2@example.com http://example.com/img/id003.bmp
3 id004 Madalyn Max mmax3@example.com http://example.com/img/id004.jpg
4 id005 Tobe Riddich triddich4@example.com http://example.com/img/id005.png

由于df2 中的每一行在df1中都有一个值,所以在本例中,right联接类似于inner联接。

让我们看一下 outer 联接。为了更好地说明它们是如何工作的,需要交换DataFrames的位置,并为“左联接”和“外联接”创建两个新变量:

1
2
3
4
5
df_left = pd.merge(df2, df1, how='left', indicator=True)
df_outer = pd.merge(df2, df1, how='outer', indicator=True)

print(df_left)
print(df_outer)

请记住,左边的DataFrame是df2,右边的DataFrame是df1。使用how='outer' 合并在键上匹配的DataFrames,但也包括丢失或不匹配的值。

在上面的示例中,还设置了参数 indicatorTrue,以便Pandas在DataFrame的末尾添加一个额外的_merge 列。此列告诉我们是否在左、右DataFrame或两个DataFrames中都找到相应的那一行。

df_left 如下所示:

1
2
3
4
5
6
  user_id                         image_url first_name last_name                  email _merge
0 id001 http://example.com/img/id001.png Rivi Valti rvalti0@example.com both
1 id002 http://example.com/img/id002.jpg Wynnie McMurty wmcmurty1@example.com both
2 id003 http://example.com/img/id003.bmp Kristos Ivanets kivanets2@example.com both
3 id004 http://example.com/img/id004.jpg Madalyn Max mmax3@example.com both
4 id005 http://example.com/img/id005.png Tobe Riddich triddich4@example.com both

然而, df_outer 有这些数据:

1
2
3
4
5
6
7
8
  user_id                         image_url first_name last_name                  email      _merge
0 id001 http://example.com/img/id001.png Rivi Valti rvalti0@example.com both
1 id002 http://example.com/img/id002.jpg Wynnie McMurty wmcmurty1@example.com both
2 id003 http://example.com/img/id003.bmp Kristos Ivanets kivanets2@example.com both
3 id004 http://example.com/img/id004.jpg Madalyn Max mmax3@example.com both
4 id005 http://example.com/img/id005.png Tobe Riddich triddich4@example.com both
5 id006 NaN Regan Huyghe rhuyghe@example.com right_only
6 id007 NaN Kristin Illis killis4@example.com right_only

请注意,在 df_outer中,“id006”和“id007”只存在于右DataFrame中(在本例中是df1)。如果在不交换位置的情况下比较左联接和外联接,最终会得到两个相同的结果。

方法2:join()

与Pandas函数merge() 不同,join()是DataFrame本身的方法,即:DataFrame.join(other, on=None, how='left', lsuffix='', rsuffix='', sort=False)

用来调用join() 方法的DataFrame是左DataFrame。other参数中的DataFrame是右DataFrame。

参数 on 参数的值可以用 ['key1', 'key2' ...] 来定义匹配的键;how 参数的值是 leftrightouterinner 等,默认为 left

下面将 df2 并入 df1

1
2
3
df_join = df1.join(df2, rsuffix='_right')

print(df_join)

merge()函数一样,join() 方法自动尝试匹配具有相同名称的键(列)。在上述示例中,它是user_id键。

上面的代码执行结果是:

1
2
3
4
5
6
7
8
  user_id first_name last_name                  email user_id_right                         image_url
0 id001 Rivi Valti rvalti0@example.com id001 http://example.com/img/id001.png
1 id002 Wynnie McMurty wmcmurty1@example.com id002 http://example.com/img/id002.jpg
2 id003 Kristos Ivanets kivanets2@example.com id003 http://example.com/img/id003.bmp
3 id004 Madalyn Max mmax3@example.com id004 http://example.com/img/id004.jpg
4 id005 Tobe Riddich triddich4@example.com id005 http://example.com/img/id005.png
5 id006 Regan Huyghe rhuyghe@example.com NaN NaN
6 id007 Kristin Illis killis4@example.com NaN NaN

你可能注意到一个名为user_id_right的“复制列”。如果不想显示该列,可以将user_id 列设置为两列上的索引,以便在联接时不带后缀:

1
2
3
df_join_no_duplicates = df1.set_index('user_id').join(df2.set_index('user_id'))

print(df_join_no_duplicates)

这样做可以让我们摆脱user_id列,并将其设置为索引列,从而产生了一个更清晰的DataFrame:

1
2
3
4
5
6
7
8
9
        first_name last_name                  email                         image_url
user_id
id001 Rivi Valti rvalti0@example.com http://example.com/img/id001.png
id002 Wynnie McMurty wmcmurty1@example.com http://example.com/img/id002.jpg
id003 Kristos Ivanets kivanets2@example.com http://example.com/img/id003.bmp
id004 Madalyn Max mmax3@example.com http://example.com/img/id004.jpg
id005 Tobe Riddich triddich4@example.com http://example.com/img/id005.png
id006 Regan Huyghe rhuyghe@example.com NaN
id007 Kristin Illis killis4@example.com NaN

方法3:append()

正如Pandas官方文档所指出的,由于concat()append() 方法返回DataFrames的新副本,过度使用它可能会影响程序的性能。

这种追加的操作,比较适合于将一个DataFrame的每行合并到另外一个DataFrame的尾部,即得到一个新的DataFrame,它包含2个DataFrames的所有的行,而不是在它们的列上匹配数据。

df2 追加到 df1 并打印结果:

1
2
3
df_append = df1.append(df2, ignore_index=True)

print(df_append)

使用append()将不匹配任何键上的DataFrames ,它只将另一个DataFrame添加到第一个DataFrame并返回它的副本。如果这两个DataFrames 的形状不匹配,Pandas将用NaN替换任何不匹配的单元格。

1
2
3
4
5
6
7
8
9
10
11
12
13
   user_id first_name last_name                  email                         image_url
0 id001 Rivi Valti rvalti0@example.com NaN
1 id002 Wynnie McMurty wmcmurty1@example.com NaN
2 id003 Kristos Ivanets kivanets2@example.com NaN
3 id004 Madalyn Max mmax3@example.com NaN
4 id005 Tobe Riddich triddich4@example.com NaN
5 id006 Regan Huyghe rhuyghe@example.com NaN
6 id007 Kristin Illis killis4@example.com NaN
7 id001 NaN NaN NaN http://example.com/img/id001.png
8 id002 NaN NaN NaN http://example.com/img/id002.jpg
9 id003 NaN NaN NaN http://example.com/img/id003.bmp
10 id004 NaN NaN NaN http://example.com/img/id004.jpg
11 id005 NaN NaN NaN http://example.com/img/id005.png

方法4:concat()

concat()merge()join()相比,更灵活,因为它允许按行或按列组合DataFrames 。

以下是带参数的完整函数:

1
2
pandas.concat(objs, axis=0, join='outer', ignore_index=False, keys=None,
levels=None, names=None, verify_integrity=False, sort=False, copy=True)

下面是 concat()函数最常用的参数:

  • objs:将要连接的DataFrame 对象([df1,df2,…])的列表

  • axis: 定义连接的方向,0 表示0轴方向,即以行为单位链接;1 1轴方向,即以列为单位连接

  • join 的值可以是 inner (交集)或 outer(并集)

  • ignore_index: 默认设置为 False ,即索引值为原有DataFrames中的状态,这可能会导致索引值重复。如果设置为 True ,它将忽略原始值并按顺序重新创建索引值

  • keys:用于设置多级索引,可以将它看作附加在DataFrame左外侧的索引的另一个层级的索引,它可以帮助我们在值不唯一时区分索引

用与 df2 相同的列类型创建一个新的DataFrame,但这个DataFrame包含id006id007image_url

1
2
3
4
df2_addition = pd.DataFrame({'user_id': ['id006', 'id007'],
'image_url': ['http://example.com/img/id006.png',
'http://example.com/img/id007.jpg']
})

为了按行联接df2df2_addition,可以将它们作为objs参数传递到一个列表中,并将结果DataFrame赋给一个新变量:

1
2
3
df_row_concat = pd.concat([df2, df2_addition])

print(df_row_concat)

成功地填充了缺少的值:

1
2
3
4
5
6
7
8
  user_id                         image_url
0 id001 http://example.com/img/id001.png
1 id002 http://example.com/img/id002.jpg
2 id003 http://example.com/img/id003.bmp
3 id004 http://example.com/img/id004.jpg
4 id005 http://example.com/img/id005.png
0 id006 http://example.com/img/id006.png
1 id007 http://example.com/img/id007.jpg

不过,请看最左边一栏中的索引,存在索引“0”和“1”的重复。为了获得全新的唯一索引值,将True传给ignore_index参数:

1
df_row_concat = pd.concat([df2, df2_addition], ignore_index=True)

现在,df_row_concat具有唯一的索引值:

1
2
3
4
5
6
7
8
  user_id                         image_url
0 id001 http://example.com/img/id001.png
1 id002 http://example.com/img/id002.jpg
2 id003 http://example.com/img/id003.bmp
3 id004 http://example.com/img/id004.jpg
4 id005 http://example.com/img/id005.png
5 id006 http://example.com/img/id006.png
6 id007 http://example.com/img/id007.jpg

正如前面提到的,concat()可以在水平和竖直(0轴和1轴)方向上合并,要按列(即在1轴方向上合并)将两个DataFrames连接在一起,要将axis值从默认值0更改为1

1
2
3
df_column_concat = pd.concat([df1, df_row_concat], axis=1)

print(df_column_concat)

你会注意到,它的工作方式与merge不同,在一个键上匹配两个表:

1
2
3
4
5
6
7
8
  user_id first_name last_name                  email user_id                         image_url
0 id001 Rivi Valti rvalti0@example.com id001 http://example.com/img/id001.png
1 id002 Wynnie McMurty wmcmurty1@example.com id002 http://example.com/img/id002.jpg
2 id003 Kristos Ivanets kivanets2@example.com id003 http://example.com/img/id003.bmp
3 id004 Madalyn Max mmax3@example.com id004 http://example.com/img/id004.jpg
4 id005 Tobe Riddich triddich4@example.com id005 http://example.com/img/id005.png
5 id006 Regan Huyghe rhuyghe@example.com id006 http://example.com/img/id006.png
6 id007 Kristin Illis killis4@example.com id007 http://example.com/img/id007.jpg

甚至于右边的DataFrame可以没有user_id列,也会得到类似上面的相同结果。函数concat()将两个DataFrames粘在一起,同时考虑DataFrames索引值和表格形状。它不会像merge()join()那样按键匹配。有兴趣的话,可以通过更改join参数的值尝试不同形式的组合,从而了解其差异!

方法5:combine_first()和update()

假设有一个DataFrame,但是它存在缺失数据,希望能够从另一个DataFrame中讲丢失的数据填充进来。这样,就要保留第一个DataFrame中的所有非缺失值,同时用第二个DataFrame可用的非缺失值(如果有这样的非缺失值)替换第一个DataFrame中的所有NaN

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np

df_first = pd.DataFrame({'COL 1': ['X', 'X', np.nan],
'COL 2': ['X', np.nan, 'X'],
'COL 3': [np.nan, 'X', 'X']},
index=range(0, 3))

df_second = pd.DataFrame({'COL 1': [np.nan, 'O', 'O'],
'COL 2': ['O', 'O', 'O']},
index=range(0, 3))

print(df_first)
print(df_second)

df_first 有3列,每列中有1个缺失值:

1
2
3
4
  COL 1 COL 2 COL 3
0 X X NaN
1 X NaN X
2 NaN X X

df_second只有2列,第一列中缺少一个值:

1
2
3
4
  COL 1 COL 2
0 NaN O
1 O O
2 O O

下面用df_second中所有对应的值来填充df_first` 中缺失值:

1
2
3
df_tictactoe = df_first.combine_first(df_second)

print(df_tictactoe)

combine_first() 方法只会按索引顺序替换NaN值,并且会保留第一个DataFrame中所有非缺失的值:

1
2
3
4
  COL 1 COL 2 COL 3
0 X X NaN
1 X O X
2 O X X

另一方面,如果想用 df_second中相应的值(不管它们是否为NaN)覆盖df_first中的值,可以使用 update()方法。

再创建另一个DataFrame:

1
2
3
df_third = pd.DataFrame({'COL 1': ['O'], 'COL 2': ['O'], 'COL 3': ['O']})

print(df_third)

输出:

1
2
  COL 1 COL 2 COL 3
0 O O O

现在用df_third中的值更新df_first

1
2
3
df_first.update(df_third)

print(df_first)

请记住,与combine_first()不同,update()不会返回新的DataFrame,它原地修改df_first,更改相应的值:

1
2
3
4
  COL 1 COL 2 COL 3
0 O O O
1 X NaN X
2 NaN X X

update() 函数的 overwrite参数默认设置为True,这就是为什么它会更改所有相应的值,而不是只更改NaN值。如果将其更改为False,就仅替换NaN

1
2
3
df_tictactoe.update(df_first, overwrite=False)

print(df_tictactoe)

以下是df_tictactoeDataFrame的最终状态:

1
2
3
4
  COL 1 COL 2 COL 3
0 X X O
1 X O X
2 O X X

结论

Pandas为合并DataFrames提供了强大的工具,但很难确定死板的条条框框,来决定什么时候用什么函数。虽然大多数情况下,merge() 已经足够了,但在某些情况下,可能需要使用concat()来按行合并,或者使用join(),或者使用combine_first()update()来填充缺失值。甚至可以使用append()添加数据行。

总之,具体问题具体分析。

参考文献

[1]. https://stackabuse.com/how-to-merge-dataframes-in-pandas/

[2]. 跟老齐学Python:数据分析. 齐伟. 北京:电子工业出版社

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

关注微信公众号,读文章、听课程,提升技能