Pandas是数据分析、机器学习等常用的工具,其中的DataFrame又是最常用的数据类型,对它的操作,不得不熟练。在《跟老齐学Python:数据分析》一书中,对DataFrame对象的各种常用操作都有详细介绍。本文根据书中介绍的内容,并参考其他文献,专门汇总了合并操作的各种方法。
Pandas提供好几种方法和函数来实现合并DataFrame的操作,一般的操作结果是创建一个新的DataFrame,而对原始数据没有任何影响。
先创建一个DataFrame对象,后面也会用到它。如下所示,df1
包括姓名、电子邮件和用户id。
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
,如下所示。
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数据如下:
# 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()
函数将df1
和df2
合并。首先,看一下这个函数可以接受的参数:
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)
除 left
和 right
外,大多数参数都有默认值,这两个参数是我们要合并的DataFrames的名称。函数本身将返回一个新的DataFrame,用变量df3_merged
引用。
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
,看看它的内容:
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
:
df_left_merge = pd.merge(df1, df2, how='left')
print(df_left_merge)
这就是所谓的“左联接”,这样得到了包含左DataFrame (df1
) 和右DataFrame (df2
)的所有元素的DataFrame。运行上述代码显示以下内容:
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:
df_right_merge = pd.merge(df1, df2, how='right')
print(df_right_merge)
如你所料,“右联接”将返回左DataFrame中与右DataFrame匹配的所有值:
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的位置,并为“左联接”和“外联接”创建两个新变量:
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,但也包括丢失或不匹配的值。
在上面的示例中,还设置了参数 indicator
为True
,以便Pandas在DataFrame的末尾添加一个额外的_merge
列。此列告诉我们是否在左、右DataFrame或两个DataFrames中都找到相应的那一行。
df_left
如下所示:
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
有这些数据:
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
)。如果在不交换位置的情况下比较左联接和外联接,最终会得到两个相同的结果。
与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
参数的值是 left
,right
,outer
,inner
等,默认为 left
。
下面将 df2
并入 df1
:
df_join = df1.join(df2, rsuffix='_right')
print(df_join)
像 merge()
函数一样,join()
方法自动尝试匹配具有相同名称的键(列)。在上述示例中,它是user_id
键。
上面的代码执行结果是:
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
列设置为两列上的索引,以便在联接时不带后缀:
df_join_no_duplicates = df1.set_index('user_id').join(df2.set_index('user_id'))
print(df_join_no_duplicates)
这样做可以让我们摆脱user_id
列,并将其设置为索引列,从而产生了一个更清晰的DataFrame:
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
正如Pandas官方文档所指出的,由于concat()
和append()
方法返回DataFrames的新副本,过度使用它可能会影响程序的性能。
这种追加的操作,比较适合于将一个DataFrame的每行合并到另外一个DataFrame的尾部,即得到一个新的DataFrame,它包含2个DataFrames的所有的行,而不是在它们的列上匹配数据。
将df2
追加到 df1
并打印结果:
df_append = df1.append(df2, ignore_index=True)
print(df_append)
使用append()
将不匹配任何键上的DataFrames ,它只将另一个DataFrame添加到第一个DataFrame并返回它的副本。如果这两个DataFrames 的形状不匹配,Pandas将用NaN替换任何不匹配的单元格。
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
concat()
与 merge()
和join()
相比,更灵活,因为它允许按行或按列组合DataFrames 。
以下是带参数的完整函数:
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包含id006
和id007
的image_url
:
df2_addition = pd.DataFrame({'user_id': ['id006', 'id007'],
'image_url': ['http://example.com/img/id006.png',
'http://example.com/img/id007.jpg']
})
为了按行联接df2
和df2_addition
,可以将它们作为objs
参数传递到一个列表中,并将结果DataFrame赋给一个新变量:
df_row_concat = pd.concat([df2, df2_addition])
print(df_row_concat)
成功地填充了缺少的值:
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
参数:
df_row_concat = pd.concat([df2, df2_addition], ignore_index=True)
现在,df_row_concat
具有唯一的索引值:
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
:
df_column_concat = pd.concat([df1, df_row_concat], axis=1)
print(df_column_concat)
你会注意到,它的工作方式与merge不同,在一个键上匹配两个表:
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
参数的值尝试不同形式的组合,从而了解其差异!
假设有一个DataFrame,但是它存在缺失数据,希望能够从另一个DataFrame中讲丢失的数据填充进来。这样,就要保留第一个DataFrame中的所有非缺失值,同时用第二个DataFrame可用的非缺失值(如果有这样的非缺失值)替换第一个DataFrame中的所有NaN
。
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个缺失值:
COL 1 COL 2 COL 3
0 X X NaN
1 X NaN X
2 NaN X X
df_second
只有2列,第一列中缺少一个值:
COL 1 COL 2
0 NaN O
1 O O
2 O O
下面用df_second
中所有对应的值来填充df_first` 中缺失值:
df_tictactoe = df_first.combine_first(df_second)
print(df_tictactoe)
combine_first()
方法只会按索引顺序替换NaN
值,并且会保留第一个DataFrame中所有非缺失的值:
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:
df_third = pd.DataFrame({'COL 1': ['O'], 'COL 2': ['O'], 'COL 3': ['O']})
print(df_third)
输出:
COL 1 COL 2 COL 3
0 O O O
现在用df_third
中的值更新df_first
:
df_first.update(df_third)
print(df_first)
请记住,与combine_first()
不同,update()
不会返回新的DataFrame,它原地修改df_first
,更改相应的值:
COL 1 COL 2 COL 3
0 O O O
1 X NaN X
2 NaN X X
update()
函数的 overwrite
参数默认设置为True
,这就是为什么它会更改所有相应的值,而不是只更改NaN
值。如果将其更改为False
,就仅替换NaN
:
df_tictactoe.update(df_first, overwrite=False)
print(df_tictactoe)
以下是df_tictactoe
DataFrame的最终状态:
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:数据分析. 齐伟. 北京:电子工业出版社(与本书相关的资料:http://www.itdiffer.com/data.html)
点击“阅读原文”,查看有关资料