DataFrame 的级联与合并操作
有的时候,我们的数据可能会来自不同的文件。读取每个文件时,会产生一个 DataFrame。如果我们想要综合这些文件中的数据,就需要将多个表格整合成一个大表格。就类似于我们使用 MySQL 数据库,对于不同表进行的联表操作一样。
要将多个 DataFrame 整合成一个大的综合的 DataFrame,就需要使用到 DataFrame 的级联与合并操作。
级联操作
在 NumPy 的学习中,我们已经见识过了级联操作。当时使用的方法是 concatenate。级联操作,就是将两个 DataFrame 按照行/列的索引合并到一起。如果有共同的索引,则将共同索引的数据放在同一行/列。对于非共同索引,则会补空。
DataFrame 提供了两种级联操作的方法:
- pd.concat
- pd.append
首先,创建两个 DataFrame 数据。注意,两个列的索引是不一致的:
df1 = DataFrame(data=np.random.randint(0, 100, (5, 3)), columns=['A', 'B', 'C'])
df2 = DataFrame(data=np.random.randint(0, 100, (5, 3)), columns=['A', 'D', 'C'])得到的 df1 为:
	A	B	C
0	27	60	52
1	14	38	13
2	30	74	29
3	17	97	99
4	76	32	63df2 为:
	A	D	C
0	68	15	47
1	40	78	93
2	67	12	13
3	62	99	59
4	32	34	49pd.concate
pandas使用pd.concat函数,与np.concatenate函数类似,只是多了一些参数:
- objs,用于指定要级联的 DataFrame
- axis=0,0 为纵向级联,1 为横向级联
- keys
- join='outer' / 'inner':表示的是级联的方式,outer 会将所有的项进行级联(忽略匹配和不匹配),而 inner 只会将匹配的项级联到一起,不匹配的不级联
- ignore_index=False
匹配级联
匹配级联,也就是级联方向的索引是完全一致的,级联操作只需简单地按照索引合并匹配即可。比如自己与自己合并,一定是级联匹配的咯:
pd.concat((df1, df1, df1), axis=1)结果就是三个 df1 横向排布的新 DataFrame:
	A	B	C	A	B	C	A	B	C
0	27	60	52	27	60	52	27	60	52
1	14	38	13	14	38	13	14	38	13
2	30	74	29	30	74	29	30	74	29
3	17	97	99	17	97	99	17	97	99
4	76	32	63	76	32	63	76	32	63同样,也可以进行纵向级联:
pd.concat((df2, df2), axis=0)得到了两个 df2 纵向合并的 DataFrame:
	A	D	C
0	68	15	47
1	40	78	93
2	67	12	13
3	62	99	59
4	32	34	49
0	68	15	47
1	40	78	93
2	67	12	13
3	62	99	59
4	32	34	49不匹配级联
不匹配指的是级联的维度的索引不一致。例如纵向级联时列索引不一致,横向级联时行索引不一致
DataFrame 的不匹配级联有 2 种连接方式:
- 外连接 join=outer:补 NaN(默认模式)
- 内连接 join=inner:只连接匹配的项
如果想要保留数据的完整性必须使用 outer(外连接)。
刚刚已经提到了 df1 和 df2 的列索引是不一致的,如果将他们纵向级联,就会涉及到不匹配级联:
pd.concat((df1, df2), axis=0, join='outer')join 为 outer,是外连接,即不匹配的索引会补 Nan:
	A	B	C	D
0	27	60.0	52	NaN
1	14	38.0	13	NaN
2	30	74.0	29	NaN
3	17	97.0	99	NaN
4	76	32.0	63	NaN
0	68	NaN 	47	15.0
1	40	NaN 	93	78.0
2	67	NaN 	13	12.0
3	62	NaN 	59	99.0
4	32	NaN 	49	34.0如果把 join 设置为 inner:
pd.concat((df1, df2), axis=0, join='inner')不匹配的索引将会被舍弃掉:
	A	C
0	27	52
1	14	13
2	30	29
3	17	99
4	76	63
0	68	47
1	40	93
2	67	13
3	62	59
4	32	49我们看到,使用内连接会丢失很多数据。多数时候,为了保证数据的完整性,我们会使用外连接,也就是把 join 的值设置为 outer。
append
如果是纵向的级联,更方便的级联方式是使用 append。注意,append 只能将两个 DataFrame 纵向级联起来。
对于非匹配级联,默认会对不一致的索引位置补空。
使用 append 进行纵向级联的操作十分简单:
df1.append(df1)得到的结果是两个 df1 纵向级联的数据:
	A	B	C
0	27	60	52
1	14	38	13
2	30	74	29
3	17	97	99
4	76	32	63
0	27	60	52
1	14	38	13
2	30	74	29
3	17	97	99
4	76	32	63对于非匹配级联的情况:
df1.append(df2)索引不一致的列会默认补空:
	A	B	C	D
0	27	60.0	52	NaN
1	14	38.0	13	NaN
2	30	74.0	29	NaN
3	17	97.0	99	NaN
4	76	32.0	63	NaN
0	68	NaN 	47	15.0
1	40	NaN 	93	78.0
2	67	NaN 	13	12.0
3	62	NaN 	59	99.0
4	32	NaN 	49	34.0合并操作
merge 与 concat 的区别在于,merge 需要依据某一共同列来进行合并。
使用 pd.merge() 合并时,会自动根据两者相同 column 名称的那一列,作为 key 来进行合并。
注意每一列元素的顺序不要求一致。
一对一合并
首先,构造数据 df1:
df1 = DataFrame(data={
    'employee': ['Bob', 'Jake', 'Lisa'],
    'group': ['Accounting', 'Engineering', 'Engineering'],
})
df1df1 的数据为:
	employee	group
0	Bob		Accounting
1	Jake	Engineering
2	Lisa	Engineering然后,构造数据 df2:
df2 = DataFrame(data={
    'employee': ['Lisa', 'Bob', 'Jake'],
    'hire_date': [2004, 2008, 2012],
})
df2df2 的数据为:
	employee	hire_date
0	Lisa	2004
1	Bob 	2008
2	Jake	2012我们看到,df1 和 df2 有共同的列,employee。不过在两个 DataFrame 中的每一行数据顺序并不一致。如果使用级联操作,那么这两组数据回直接合并在一块,出现两个 employee 不说,每个数据并不能一一对应。我们希望的是,Lisa 的 hire_data 和她的 group 出现在同一行。
对于这种需求,我们可以使用合并操作来实现:
pd.merge(df1, df2, on='employee')on 参数用来指定合并操作所参照的列。合并好的数据为:
	employee	group	hire_date
0	Bob 	Accounting	2008
1	Jake	Engineering	2012
2	Lisa	Engineering	2004我们看到,合并好的数据中,只有一个 employee 列,而且每个人的 group 和 hire_data 都出现在自己名字的那一行。这和 MySQL 的多表的联表查询十分相似。
一对多合并
在一对一合并中,共同列中的元素个数是相同且一一对应的。但是真实的数据可能未必这样。有的时候,两个 DataFrame 虽然有共同的列,列中的元素也确实差不多,但是有的 DataFrame 中,每个元素都只有一个,没有重复的,而在另一个 DataFrame 中,会出现重复元素,且这些重复元素在前一个 DataFram 中都存在。这样这个列中就不是一一对应的关系。还是可以参照 MySQL 多表操作的一对多联表操作。
这时,操作上和一对一合并没有什么不同。只是结果上,没有重复元素的那个 DataFrame 中的行会复制多个,和有重复元素的 DataFrame 对应上。
接下来,我们演示一下一对多合并的操作。
首先,还是创建两组数据。df3 中,没有重复元素:
df3 = DataFrame({
    'Employee': ['Lisa', 'Jake'],
    'group': ['Accounting', 'Engineering'],
    'hire_date': [2004, 2016],
})
df3df3 的数据为:
	Employee	group	hire_date
0	Lisa	Accounting	2004
1	Jake	Engineering	2016然后是 df4:
df4 = DataFrame({
    'group': ['Accounting', 'Engineering', 'Engineering'],
    'supervisor': ['Carly', 'Guido', 'Steve']
})
df4df4 的数据为:
	group	supervisor
0	Accounting	Carly
1	Engineering	Guido
2	Engineering	Steve我们把 df3 和 df4 合并,就是:
pd.merge(df3, df4)    # on不写,默认的合并条件就是两张表中公有的列索引合并后的结果为:
	employee	group	hire_date	supervisor
0	Lisa	Accounting	2004	Carly
1	Jake	Engineering	2016	Guido
2	Jake	Engineering	2016	Steve我们看到,employee 列的 Jake 被复制了一份,分给了两个 Engineering group。
如果两个 DataFrame 中存在共同索引,则不必指定 on,会默认以共同索引合并。
多对多合并
一对一合并和一对多合并都要求,在合并的参考列中,其中一个 DataFrame 的数据在另一个 DataFrame 中都存在。但是对于实际的数据中,尤其是数据量很大的情况,很可能会出现两张表中各自都有对方没有的数据的情况。
我们先创建两个数据。我们依旧使用上面的 df1:
df1 = DataFrame(data={
    'employee': ['Bob', 'Jake', 'Lisa'],
    'group': ['Accounting', 'Engineering', 'Engineering'],
})
df1df1 的数据为:
	employee	group
0	Bob 	Accounting
1	Jake	Engineering
2	Lisa	Engineering然后再来一个 df5:
df5 = DataFrame({
    'group': ['Engineering', 'Engineering', 'HR'],
    'supervisor': ['Carly', 'Guido', 'Steve'],
})
df5df5 的数据为:
	group   	supervisor
0	Engineering	Carly
1	Engineering	Guido
2	HR      	Steve我们看到,df1 和 df5 有一个相同的列,group。但是 df1 的 group 中,有一个 Accounting,这在 df5 中是不存在的。同样,df5 中 group 列的 HR 是不存在与 df1 中的。
这时,如果我们直接使用 merge 方法,合并这两个 DataFrame 的话:
pd.merge(df1, df5)就会发现,结果是这样的:
	employee	group	supervisor
0	Jake	Engineering	Carly
1	Jake	Engineering	Guido
2	Lisa	Engineering	Carly
3	Lisa	Engineering	Guido合并后的结果仅保留了 df1 和 df5 共有的部分,它们独自的那部分数据默认全都被舍弃掉了。
而它们相同的那部分数据,以笛卡尔积的形式生成。
如果我们想要保留它们独有的行,需要指定 how 的参数为 outer:
pd.merge(df1, df5, how='outer')两组数据中的独有行全都被保留,没有的数据补空:
	employee	group	supervisor
0	Bob 	Accounting	NaN
1	Jake	Engineering	Carly
2	Jake	Engineering	Guido
3	Lisa	Engineering	Carly
4	Lisa	Engineering	Guido
5	NaN 	HR      	Steve如果我们只想保留左侧的数据,只需将 how 设置为 left:
pd.merge(df1, df5, how='left')位于左侧的 df1 中有 Accounting,被保留了,而 df5 中的 HR 没有保留:
	employee	group	supervisor
0	Bob 	Accounting	NaN
1	Jake	Engineering	Carly
2	Jake	Engineering	Guido
3	Lisa	Engineering	Carly
4	Lisa	Engineering	Guido如果只想保留右侧的数据,可以将 how 设置为 right:
pd.merge(df1, df5, how='right')这样,只有右侧的 df5 中的 HR 被保留,而 Accounting 别舍弃:
	employee	group	supervisor
0	Jake	Engineering	Carly
1	Lisa	Engineering	Carly
2	Jake	Engineering	Guido
3	Lisa	Engineering	Guido
4	NaN 	HR      	Stevekey 的规范化
多个同名列,使用 on 指定列名
当列冲突时,即有多个列名称相同时,需要使用 on 参数来指定哪一个列作为 key,配合 suffixes 指定冲突列名。
首先,创建两个数据表。数据表 df6:
df6 = DataFrame({
    'employee': ['Jack', 'Summer', 'Steve'],
    'group': ['Accounting', 'Finace', 'Marketing'],
})
df6df6 的数据为:
	employee	group
0	Jack	Accounting
1	Summer	Finace
2	Steve	Marketing然后是数据表 df7:
df7 = DataFrame({
    'employee': ['Jack', 'Bob', 'Jake'],
    'hire_date': [2003, 2009, 2012],
    'group': ['Accounting', 'Sell', 'CEO'],
})
df7df7 的数据为:
	employee	group	hire_date
0	Jack	Accounting	2003
1	Bob 	Sell    	2009
2	Jake	CEO     	2012可以看到,两组数据都有 employee 和 group 列。如果我们还是使用 merge 方法,直接合并的话:
pd.merge(df6, df7, how='outer')所有同名列都会进行合并:
	employee	group	hire_date
0	Jack	Accounting	2003.0
1	Summer	Finace  	NaN
2	Steve	Marketing	NaN
3	Bob 	Sell    	2009.0
4	Jake	CEO     	2012.0可是有的时候,两张表中的列名字虽然相同,但含义是有差异的。比如,group 的确指代同一个含义,我们可以依照它来合并,但是 employee 是有不同含义的,我们需要区分开来。这时,我们就需要指定 on 参数为我们要合并的列的列名,这里就是 group:
pd.merge(df6, df7, on='group', how='outer')这样,我们合并后的新表中,两个 employee 就被区分开了:
	employee_x	group	employee_y	hire_date
0	Jack	Accounting	Jack	2003.0
1	Summer	Finace  	NaN 	NaN
2	Steve	Marketing	NaN 	NaN
3	NaN 	Sell    	Bob 	2009.0
4	NaN 	CEO     	Jake	2012.0默认情况下,两个 employee 以后缀 _x 和 _y 区分。我们可以使用 suffixes 参数指定后缀名:
pd.merge(df6, df7, on='group', how='outer', suffixes=['a', 'b'])这时,合并后的表格就成了:
	employeea	group	employeeb	hire_date
0	Jack	Accounting	Jack	2003.0
1	Summer	Finace  	NaN 	NaN
2	Steve	Marketing	NaN 	NaN
3	NaN 	Sell    	Bob 	2009.0
4	NaN 	CEO     	Jake	2012.0无同名列,使用 left_on 和 right_on
之前讨论的合并都是两组数据中,只要有一列是同名的。可是如果所有列都不同名,该如何做呢?
首先,我们还是创建两个没有任何同名列的 DataFrame。首先是 df8:
df8 = DataFrame({
    'employee': ['Bob', 'Linda', 'Bill'],
    'group': ['Accounting', 'Product', 'Marketing'],
    'hire_data': [1998, 2017, 2018],
})
df8df8 的数据为:
	employee	group	hire_data
0	Bob 	Accounting	1998
1	Linda	Product 	2017
2	Bill	Marketing	2018然后是 df9:
df9 = DataFrame({
    'name': ['Lisa', 'Bob', 'Bill'],
    'hire_dates': [1998, 2016, 2007],
})
df9df9 的数据为:
	hire_dates	name
0	1998	Lisa
1	2016	Bob
2	2007	Bill我们看见,df8 和 df9 中没有任何相同名字的列。这时,如果直接使用 merge 进行合并的话,会报错。我们需要使用 left_on 和 right_on 参数指定数据合并时需要参照的列,比如 name 和 employ 本来的含义是相同的,我们可以以这两列为基准:
pd.merge(df8, df9, left_on='employee', right_on='name', how='outer')合并后的结果为:
	employee	group	hire_data	hire_dates	name
0	Bob 	Accounting	1998.0	2016.0	Bob
1	Linda	Product 	2017.0	NaN 	NaN
2	Bill	Marketing	2018.0	2007.0	Bill
3	NaN 	NaN     	NaN 	1998.0	Lisa