Pandas连接合并

将两张表通过连接、拼接等操作合并成一张表的操作是对表格数据最常用的操作。

关系型连接

把两张相关的表按照某一个或某一组键连接起来是一种常见操作,例如学生期末考试各个科目的成绩表按照姓名和班级连接成总的成绩表。在 Pandas 中的关系型连接函数有 merge()join(),它们最重要的参数有两个,一个是 “on” 参数,用来指定连接的键;另一个是 “how” 参数,用来指定连接形式,分为左连接 left 、右连接 right 、内连接 inner 、外连接 outer,所谓左连接即以左边的键为准,如果右边表中的键于左边存在,那么就添加到左边,否则处理为缺失值,右连接的处理类似。内连接只负责合并两边同时出现的键,而外连接则会在内连接的基础上包含只在左边出现以及只在右边出现的值,因此外连接又叫全连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import pandas as pd

# DataFrame.merge()又称作“值连接”
df1 = pd.DataFrame({'Name':['San Zhang','Si Li'], 'Age':[20,30]})
df2 = pd.DataFrame({'Name':['Si Li','Wu Wang'], 'Gender':['F','M']})
print(df1.merge(df2, on='Name', how='left'))
'''
Name Age Gender
0 San Zhang 20 NaN
1 Si Li 30 F

'''

# 如果两个表中想要连接的列不具备相同的列名,可以通过left_on和right_on指定
df1 = pd.DataFrame({'df1_name':['San Zhang','Si Li'], 'Age':[20,30]})
df2 = pd.DataFrame({'df2_name':['Si Li','Wu Wang'], 'Gender':['F','M']})
print(df1.merge(df2, left_on='df1_name', right_on='df2_name', how='left'))
'''
df1_name Age df2_name Gender
0 San Zhang 20 NaN NaN
1 Si Li 30 Si Li F
'''

# 如果两个表中的列出现了重复的列名,那么可以通过suffixes参数指定
df1 = pd.DataFrame({'Name':['San Zhang'],'Grade':[70]})
df2 = pd.DataFrame({'Name':['San Zhang'],'Grade':[80]})
print(df1.merge(df2, on='Name', how='left', suffixes=['_Chinese','_Math']))
'''
Name Grade_Chinese Grade_Math
0 San Zhang 70 80
'''

# 根据多个列的值进行连接,on参数为多个列名组成的列表
df1 = pd.DataFrame({'Name':['San Zhang', 'San Zhang'], 'Age':[20, 21], 'Class':['one', 'two']})
df2 = pd.DataFrame({'Name':['San Zhang', 'San Zhang'], 'Gender':['F', 'M'], 'Class':['two', 'one']})
print(df1.merge(df2, on=['Name', 'Class'], how='left'))
'''
Name Age Class Gender
0 San Zhang 20 one M
1 San Zhang 21 two F
'''


# DataFrame.join()又称作“索引连接”
df1 = pd.DataFrame({'Age':[20,30]}, index=pd.Series(['San Zhang','Si Li'],name='Name'))
df2 = pd.DataFrame({'Gender':['F','M']}, index=pd.Series(['Si Li','Wu Wang'],name='Name'))
print(df1.join(df2, how='left'))
'''
Age Gender
Name
San Zhang 20 NaN
Si Li 30 F
'''

# 对重复的列指定左右后缀lsuffix和rsuffix
df1 = pd.DataFrame({'Name':['San Zhang'],'Grade':[70]})
df2 = pd.DataFrame({'Name':['San Zhang'],'Grade':[80]})
print(df1.join(df2, how='left', lsuffix='_Chinese', rsuffix='_Math'))
'''
Name_Chinese Grade_Chinese Name_Math Grade_Math
0 San Zhang 70 San Zhang 80
'''

# 如果想要进行类似于merge中以多列为键的操作的时候,join需要使用多级索引
df1 = pd.DataFrame({'Age':[20,21]}, index=pd.MultiIndex.from_arrays(
[['San Zhang', 'San Zhang'],['one', 'two']], names=('Name','Class')))
df2 = pd.DataFrame({'Gender':['F', 'M']}, index=pd.MultiIndex.from_arrays(
[['San Zhang', 'San Zhang'],['two', 'one']], names=('Name','Class')))
print(df1.join(df2))
'''
Age Gender
Name Class
San Zhang one 20 M
two 21 F
'''

对于一张表中出现重复的键,只需把握一个原则,即只要两边同时出现的值,就以笛卡尔积的方式加入。

方向性拼接

方向拼接为我们提供将两个或多个表按照纵向横向的方向进行直接拼接的操作,对应的函数是concat(),其最常用三个参数是 axis(拼接方向), join(连接方式), keys(在新表中指示来自于哪一张旧表的名字)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 纵向拼接各表中人的信息
df1 = pd.DataFrame({'Name':['San Zhang','Si Li'], 'Age':[20,30]})
df2 = pd.DataFrame({'Name':['Wu Wang'], 'Age':[40]})
print(pd.concat([df1, df2]))
'''
Name Age
0 San Zhang 20
1 Si Li 30
0 Wu Wang 40
'''

# 横向拼接各表中的字段
df2 = pd.DataFrame({'Grade':[80, 90]})
df3 = pd.DataFrame({'Gender':['M', 'F']})
print(pd.concat([df1, df2, df3], 1))
'''
Name Age Grade Gender
0 San Zhang 20 80 M
1 Si Li 30 90 F
'''

# 通过keys参数产生多级索引进行标记
df1 = pd.DataFrame({'Name':['San Zhang','Si Li'], 'Age':[20,21]})
df2 = pd.DataFrame({'Name':['Wu Wang'],'Age':[21]})
print(pd.concat([df1, df2], keys=['one', 'two']))
'''
Name Age
one 0 San Zhang 20
1 Si Li 21
two 0 Wu Wang 21
'''

在默认状态下的 axis=0 ,表示纵向拼接多个表,常常用于多个样本的拼接;而 axis=1 表示横向拼接多个表,常用于多个字段或特征的拼接。纵向拼接会根据列索引对齐,默认状态下 join=outer ,表示保留所有的列,并将不存在的值设为缺失;join=inner 表示保留两个表都出现过的列。横向拼接则根据行索引对齐。

表追加序列

利用 concat 可以实现多个表之间的方向拼接,如果想要把一个序列追加到表的行末或者列末,则可以分别使用 append()assign() 方法。

1
2
3
4
5
6
7
8
9
s = pd.Series(['Wu Wang', 21], index = df1.columns)
print(df1.append(s, ignore_index=True))

df_test = pd.DataFrame()
s_test= pd.Series({"x":1,"y":2}, name="a")
print(df_test.append(s_test))

s = pd.Series([80, 90])
print(df1.assign(Grade=s))

在 append 中,如果原表是默认整数序列的索引,那么可以使用 ignore_index=True 对新序列对应索引的自动标号,否则必须对 Series 指定 name 属性。使用 [] 添加新的列会直接在原表上进行改动,而 assign 返回的是一个临时副本。

类连接操作

我们可以把 Pandas 中对两个表同时进行某些操作的函数也看做是类连接的操作,这里重点介绍 compare() 比较函数和 combine() 组合函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 比较两个表或者序列的不同处并将其汇总展示
df1 = pd.DataFrame({'Name':['San Zhang', 'Si Li', 'Wu Wang'], 'Age':[20, 21 ,21], 'Class':['one', 'two', 'three']})
df2 = pd.DataFrame({'Name':['San Zhang', 'Li Si', 'Wu Wang'], 'Age':[20, 21 ,21], 'Class':['one', 'two', 'Three']})
print(df1.compare(df2))
'''
Name Class
self other self other
1 Si Li Li Si NaN NaN
2 NaN NaN three Three
'''

# 选出对应索引位置较小的元素
def choose_min(s1, s2):
s2 = s2.reindex_like(s1)
res = s1.where(s1<s2, s2)
res = res.mask(s1.isna())
return res

df1 = pd.DataFrame({'A':[1,2], 'B':[3,4], 'C':[5,6]})
df2 = pd.DataFrame({'B':[5,6], 'C':[7,8], 'D':[9,10]}, index=[1,2])
print(df1.combine(df2, choose_min))
# 设置overtwrite=False可以保留被调用表中未出现在传入的参数表中的列,而不会设置未缺失值
print(df1.combine(df2, choose_min, overwrite=False))
'''
A B C D
0 NaN NaN NaN NaN
1 NaN 4.0 6.0 NaN
2 NaN NaN NaN NaN
A B C D
0 1.0 NaN NaN NaN
1 2.0 4.0 6.0 NaN
2 NaN NaN NaN NaN
'''

#combine_first方法,其功能是在对两张表组合时,若第二张表中的值在第一张表中对应索引位置的值不是缺失状态,那么就使用第一张表的值填充
import numpy as np
df1 = pd.DataFrame({'A':[1,2], 'B':[3,np.nan]})
df2 = pd.DataFrame({'A':[5,6], 'B':[7,8]}, index=[1,2])
print(df1.combine_first(df2))
'''
A B
0 1.0 3.0
1 2.0 7.0
2 6.0 8.0
'''

compare 函数展示的结果中返回了不同值所在的行列,如果相同则会被填充为缺失值 NaN ,其中 other 和 self 分别指代传入的参数表和被调用的表自身,如果想要完整显示表中所有元素的比较情况,可以设置 keep_shape=True。combine 函数能够让两张表按照一定的规则进行组合,在进行规则比较时会自动进行列索引的对齐。对于传入的函数而言,每一次操作中输入的参数是来自两个表的同名 Series ,依次传入的列是两个表列名的并集。