Pandas是数据分析、机器学习等常用的工具,其中的DataFrame又是最常用的数据类型,对它的操作,不得不数量。在《跟老齐学Python:数据分析》一书中,对DataFrame对象的各种常用操作都有详细介绍。本文根据书中介绍的内容,并参考其他文献,专门汇总了合并操作的各种方法。
Pandas提供好几种方法和函数来实现合并DataFrame的操作,一般的操作结果是创建一个新的DataFrame,而对原始数据没有任何影响。
方法1:merge() 先创建一个DataFrame对象,后面也会用到它。如下所示,df1
包括姓名、电子邮件和用户id。
1 2 3 4 5 6 7 8 import pandas as pddf1 = 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 user_id first_name last_name email 0 id001 Rivi Valti rvalti0@example.com1 id002 Wynnie McMurty wmcmurty1@example.com2 id003 Kristos Ivanets kivanets2@example.com3 id004 Madalyn Max mmax3@example.com4 id005 Tobe Riddich triddich4@example.com5 id006 Regan Huyghe rhuyghe@example.com6 id007 Kristin Illis killis4@example.com user_id image_url 0 id001 http://example.com/img/id001.png1 id002 http://example.com/img/id002.jpg2 id003 http://example.com/img/id003.bmp3 id004 http://example.com/img/id004.jpg4 id005 http://example.com/img/id005.png
用merge()
函数将df1
和df2
合并。首先,看一下这个函数可以接受的参数:
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 )
除 left
和 right
外,大多数参数都有默认值,这两个参数是我们要合并的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.png1 id002 Wynnie McMurty wmcmurty1@example.com http://example.com/img/id002.jpg2 id003 Kristos Ivanets kivanets2@example.com http://example.com/img/id003.bmp3 id004 Madalyn Max mmax3@example.com http://example.com/img/id004.jpg4 id005 Tobe Riddich triddich4@example.com http://example.com/img/id005.png5 id006 Regan Huyghe rhuyghe@example.com NaN6 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,但也 包括丢失或不匹配的值。
在上面的示例中,还设置了参数 indicator
为True
,以便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 both1 id002 http://example.com/img/id002.jpg Wynnie McMurty wmcmurty1@example.com both2 id003 http://example.com/img/id003.bmp Kristos Ivanets kivanets2@example.com both3 id004 http://example.com/img/id004.jpg Madalyn Max mmax3@example.com both4 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 both1 id002 http://example.com/img/id002.jpg Wynnie McMurty wmcmurty1@example.com both2 id003 http://example.com/img/id003.bmp Kristos Ivanets kivanets2@example.com both3 id004 http://example.com/img/id004.jpg Madalyn Max mmax3@example.com both4 id005 http://example.com/img/id005.png Tobe Riddich triddich4@example.com both5 id006 NaN Regan Huyghe rhuyghe@example.com right_only6 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
参数的值是 left
,right
,outer
,inner
等,默认为 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.png1 id002 Wynnie McMurty wmcmurty1@example.com id002 http://example.com/img/id002.jpg2 id003 Kristos Ivanets kivanets2@example.com id003 http://example.com/img/id003.bmp3 id004 Madalyn Max mmax3@example.com id004 http://example.com/img/id004.jpg4 id005 Tobe Riddich triddich4@example.com id005 http://example.com/img/id005.png5 id006 Regan Huyghe rhuyghe@example.com NaN NaN6 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 NaN1 id002 Wynnie McMurty wmcmurty1@example.com NaN2 id003 Kristos Ivanets kivanets2@example.com NaN3 id004 Madalyn Max mmax3@example.com NaN4 id005 Tobe Riddich triddich4@example.com NaN5 id006 Regan Huyghe rhuyghe@example.com NaN6 id007 Kristin Illis killis4@example.com NaN7 id001 NaN NaN NaN http://example.com/img/id001.png8 id002 NaN NaN NaN http://example.com/img/id002.jpg9 id003 NaN NaN NaN http://example.com/img/id003.bmp10 id004 NaN NaN NaN http://example.com/img/id004.jpg11 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包含id006
和id007
的image_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' ] })
为了按行联接df2
和df2_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 npdf_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_tictactoe
DataFrame的最终状态:
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:数据分析. 齐伟. 北京:电子工业出版社