Data Whale第20期组队学习 Pandas学习—连接
一、关系型连接
1.1 连接的基本概念
把两张相关的表按照某一个或某一组键连接起来是一种常见操作,例如学生期末考试各个科目的成绩表按照 姓名 和 班级 连接成总的成绩表,又例如对企业员工的各类信息表按照 员工ID号 进行连接汇总。由此可以看出,在关系型连接中, 键 是十分重要的,往往用 on 参数表示。
连接的形式: 在 pandas 中的关系型连接函数 merge 和 join 中提供了 how 参数来代表连接形式,分为左连接 left 、右连接 right 、内连接 inner 、外连接 outer .
pd.merge()实现的功能基于关系代数的一部分。关系代数是处理关系型数据的通用理论,巨大部分数据库的可用操作都以此为理论基础。Pandas的pd.merge()方法与Series和DataFrame的join()方法就是基于这些关系代数对应的基本操作规则。
1、merge()函数的具体参数
用法:
DataFrame1.merge(DataFrame2, how=‘inner’, on=None, left_on=None, right_on=None, left_index=False, right_index=False, sort=False, suffixes=(’_x’, ‘_y’))
2、参数说明
参数 | 说明 |
---|---|
how | 默认为inner,可设为inner/outer/left/right |
on | 根据某个字段进行连接,必须存在于两个DateFrame中(若未同时存在,则需要分别使用left_on和right_on来设置) |
left_on | 左连接,以DataFrame1中用作连接键的列 |
right_on | 右连接,以DataFrame2中用作连接键的列 |
left_index | 将DataFrame1行索引用作连接键 |
right_index | 将DataFrame2行索引用作连接键 |
sort | 根据连接键对合并后的数据进行排列,默认为True |
suffixes | 对两个数据集中出现的重复列,新数据集中加上后缀_x,_y进行区别 |
3、编程实践
import pandas as pd
import numpy as np
DF1=pd.DataFrame({'lkey':['abc','def','baz','abc'],'value':[2,3,4,5]})
DF2=pd.DataFrame({'rkey':['abc','def','qwe','abc'],'value':[6,7,8,9]})
print("DF1=",DF1)
# DF1= lkey value
# 0 abc 2
# 1 def 3
# 2 baz 4
# 3 abc 5
print("DF2=",DF2)
# DF2= rkey value
# 0 abc 6
# 1 def 7
# 2 qwe 8
# 3 abc 9
# 内连接(Inner)
DF_I=DF1.merge(DF2,left_on='lkey',right_on='rkey')
print("DF_I=",DF_I)
# DF_I= lkey value_x rkey value_y
# 0 abc 2 abc 6
# 1 abc 2 abc 9
# 2 abc 5 abc 6
# 3 abc 5 abc 9
# 4 def 3 def 7
# 右链接(Right)
DF_R=DF1.merge(DF2, left_on='lkey', right_on='rkey',how='right')
print("DF_R=",DF_R)
# DF_R= lkey value_x rkey value_y
# 0 abc 2.0 abc 6
# 1 abc 5.0 abc 6
# 2 abc 2.0 abc 9
# 3 abc 5.0 abc 9
# 4 def 3.0 def 7
# 5 NaN NaN qwe 8
# 全链接(Outer)
DF_O=DF1.merge(DF2, left_on='lkey', right_on='rkey', how='outer')
print("DF_O=",DF_O)
# DF_O= lkey value_x rkey value_y
# 0 abc 2.0 abc 6.0
# 1 abc 2.0 abc 9.0
# 2 abc 5.0 abc 6.0
# 3 abc 5.0 abc 9.0
# 4 def 3.0 def 7.0
# 5 baz 4.0 NaN NaN
# 6 NaN NaN qwe 8.0
# 两个表中的列出现了重复的列名,那么可以通过 suffixes 参数指定
df1 = pd.DataFrame({'Name':['San Liu'],'Grade':[79]})
df2 = pd.DataFrame({'Name':['San Liu'],'Grade':[85]})
print("df1.merge(df2, on='Name', how='left', suffixes=['_Chinese','_Math'])=",
df1.merge(df2, on='Name', how='left', suffixes=['_Chinese','_Math']))
# df1.merge(df2, on='Name', how='left', suffixes=['_Chinese','_Math'])= Name Grade_Chinese Grade_Math
# 0 San Liu 79 85
"""
在某些时候出现重复元素是麻烦的,
例如两位同学来自不同的班级,但是姓名相同,
这种时候就要指定 on 参数为多个列使得正确连接
"""
df3 = pd.DataFrame({'Name':['San Zhang', 'San Zhang'],'Age':[20, 21],'Class':['one', 'two']})
df4 = pd.DataFrame({'Name':['San Zhang', 'San Zhang'],'Gender':['F', 'M'],'Class':['two', 'one']})
print("df3=",df3)
# df3= Name Age Class
# 0 San Zhang 20 one
# 1 San Zhang 21 two
print("df4=",df4)
# df4= Name Gender Class
# 0 San Zhang F two
# 1 San Zhang M one
print("df3.merge(df4, on=['Name', 'Class'], how='left')=",
df3.merge(df4, on=['Name', 'Class'], how='left'))
# df3.merge(df4, on=['Name', 'Class'], how='left')= Name Age Class Gender
# 0 San Zhang 20 one M
# 1 San Zhang 21 two F
1.2 索引连接
索引连接就是把索引当作键,即和值连接本质上没有区别, pandas 中利用 join 函数来处理索引连接,它的参数选择要少于 merge ,除了必须的 on 和 how 之外,可以对重复的列指定左右后缀 lsuffix 和 rsuffix 。其中, on 参数指索引名,单层索引时省略参数表示按照当前索引连接。
join(self, other, on=None, how='left', lsuffix='', rsuffix='',sort=False):
DF3=pd.DataFrame({'G': ['G0', 'G1', 'G2', 'G3'],'H': ['H0', 'H1', 'H2', 'H3'],'key': ['K0', 'K1', 'K0', 'K1']})
DF4=pd.DataFrame({'C': ['C0', 'C1'],'D': ['D0', 'D1']},index=['K0', 'K1'])
res=DF3.join(DF4,on='key')
print("res=",res)
# res= G H key C D
# 0 G0 H0 K0 C0 D0
# 1 G1 H1 K1 C1 D1
# 2 G2 H2 K0 C0 D0
# 3 G3 H3 K1 C1 D1
"""
一次组合多个dataframe的时候可以传入元素为dataframe的列表或者tuple,一次join多个.
"""
DF5=pd.DataFrame({'v': [5, 19, 29]}, index=['K0', 'K1', 'K2'])
DF6=pd.DataFrame({'v': [17, 18, 91]}, index=['K0', 'K0', 'K3'])
DF7=pd.DataFrame({'v': [7, 8, 9]}, index=['K1', 'K1', 'K2'])
res1=DF5.join([DF6,DF7])
print("res1=",res1)
# res1= v_x v_y v
# K0 5 17.0 NaN
# K0 5 18.0 NaN
# K1 19 NaN 7.0
# K1 19 NaN 8.0
# K2 29 NaN 9.0
二、方向连接
2.1 concat
关系型连接中最重要的参数是 on 和 how , pandas 中 concat 函数可以把两个表或者多个表按照纵向或者横向拼接。
concat方法相当于数据库中的全连接(union all),它不仅可以指定连接的方式(outer join或inner join)还可以指定按照某个轴进行连接。与数据库不同的是,它不会去重,但是可以使用drop_duplicates方法达到去重的效果。
在 concat 中,最常用的参数是 axis, join, keys ,分别表示拼接方向,连接形式,以及在新表中指示来自于哪一张旧表的名字。这里需要特别注意, join 和 keys 与之前提到的 join 函数和键的概念没有任何关系。
在默认状态下的 axis=0 ,表示纵向拼接多个表,常常用于多个样本的拼接;而 axis=1 表示横向拼接多个表,常用于多个字段或特征的拼接。
# 纵向合并各表中人的信息
df5=pd.DataFrame({'Name':['San Zhang','Si Li','Wu Wang'],'Age':[20,30,25]})
df6=pd.DataFrame({'Name':['Ming Xiao','Hong Xiao','Zhang Xiao'], 'Age':[19,18,23]})
print("pd.concat([df5, df6])=",pd.concat([df5, df6]))
# pd.concat([df5, df6])= Name Age
# 0 San Zhang 20
# 1 Si Li 30
# 2 Wu Wang 25
# 0 Ming Xiao 19
# 1 Hong Xiao 18
# 2 Zhang Xiao 23
# 横向合并各表中的字段
df7=pd.DataFrame({'Grade':[80,85,96, 90]})
df8=pd.DataFrame({'Gender':['M','K','R' 'F']})
print("pd.concat([df5, df7, df8], 1)=",pd.concat([df5, df7, df8], 1))
# pd.concat([df5, df7, df8], 1)= Name Age Grade Gender
# 0 San Zhang 20.0 80 M
# 1 Si Li 30.0 85 K
# 2 Wu Wang 25.0 96 RF
# 3 NaN NaN 90 NaN
df9= pd.DataFrame({'Name':['Wu Wang','Fang Xiao'], 'Gender':['M',"L"]})
print("pd.concat([df5, df9])=",pd.concat([df5, df9]))
# pd.concat([df5, df9])= Name Age Gender
# 0 San Zhang 20.0 NaN
# 1 Si Li 30.0 NaN
# 2 Wu Wang 25.0 NaN
# 0 Wu Wang NaN M
# 1 Fang Xiao NaN L
df10=pd.DataFrame({'Grade':[80,85, 90,86]}, index=[1, 2,3,4])
print(" pd.concat([df5, df10], 1)=", pd.concat([df5, df10], 1))
# pd.concat([df5, df10], 1)= Name Age Grade
# 0 San Zhang 20.0 NaN
# 1 Si Li 30.0 80.0
# 2 Wu Wang 25.0 85.0
# 3 NaN NaN 90.0
# 4 NaN NaN 86.0
print("pd.concat([df5, df10], axis=1, join='inner')=",pd.concat([df5, df10], axis=1, join='inner'))
# pd.concat([df5, df10], axis=1, join='inner')= Name Age Grade
# 1 Si Li 30 80
# 2 Wu Wang 25 85
keys 参数的使用场景在于多个表合并后,用户仍然想要知道新表中的数据来自于哪个原表,这时可以通过 keys 参数产生多级索引进行标记
"""
第一个表中都是一班的同学,而第二个表中都是二班的同学,可以使用如下方式合并:
"""
print("pd.concat([df5, df6], keys=['one', 'two'])=", pd.concat([df5, df6], keys=['one', 'two']))
# pd.concat([df5, df6], keys=['one', 'two'])= Name Age
# one 0 San Zhang 20
# 1 Si Li 30
# 2 Wu Wang 25
# two 0 Ming Xiao 19
# 1 Hong Xiao 18
# 2 Zhang Xiao 23
2.2 序列与表的合并
利用 concat 可以实现多个表之间的方向拼接,如果想要把一个序列追加到表的行末或者列末,还可以分别使用 append 和 assign 方法。
在 append 中,如果原表是默认整数序列的索引,那么可以使用 ignore_index=True 对新序列对应索引的自动标号,否则必须对 Series 指定 name 属性。
df11=pd.Series(['Wu Qian', 24], index = df5.columns)
print("df5.append(df11, ignore_index=True)=",df5.append(df11, ignore_index=True))
# df5.append(df11, ignore_index=True)= Name Age
# 0 San Zhang 20
# 1 Si Li 30
# 2 Wu Wang 25
# 3 Wu Qian 24
"""
assign虽然可以利用其添加新的列,但一般通过 df['new_col'] = ... 的形式就可以等价地添加新列。
同时使用 [] 修改的缺点是它会直接在原表上进行改动,而 assign 返回的是一个临时副本.
"""
df12=pd.Series([80, 90,95])
print("df5.assign(Grade=df12)=",df5.assign(Grade=df12))
# df5.assign(Grade=df12)= Name Age Grade
# 0 San Zhang 20 80
# 1 Si Li 30 90
# 2 Wu Wang 25 95
df5['Grade'] = df12
print("df5=",df5)
# df5= Name Age Grade
# 0 San Zhang 20 80
# 1 Si Li 30 90
# 2 Wu Wang 25 95
三、类连接操作
3.1 比较
compare 是在 1.1.0 后引入的新函数,它能够比较两个表或者序列的不同处并将其汇总展示
df13= pd.DataFrame({'Name':['San Zhang', 'Si Li', 'Wu Wang'],'Age':[20, 21 ,21],'Class':['one', 'two', 'three']})
df14= pd.DataFrame({'Name':['San Zhang', 'Li Si', 'Wu Qian'],'Age':[20, 21 ,25],'Class':['one', 'three', 'Three']})
print("df13.compare(df14)=",df13.compare(df14))
# df13.compare(df14)= Name Age Class
# self other self other self other
# 1 Si Li Li Si NaN NaN two three
# 2 Wu Wang Wu Qian 21.0 25.0 three Three
"""
结果中返回了不同值所在的行列,如果相同则会被填充为缺失值 NaN ,其中 other 和 self 分别指代传入的参数表和被调用的表自身。
"""
# 如果想要完整显示表中所有元素的比较情况,可以设置 keep_shape=True
print("df13.compare(df14, keep_shape=True)=",df13.compare(df14, keep_shape=True))
# df13.compare(df14, keep_shape=True)= Name Age Class
# self other self other self other
# 0 NaN NaN NaN NaN NaN NaN
# 1 Si Li Li Si NaN NaN two three
# 2 Wu Wang Wu Qian 21.0 25.0 three Three
3.2 组合
combine 函数能够让两张表按照一定的规则进行组合,在进行规则比较时会自动进行列索引的对齐。对于传入的函数而言,每一次操作中输入的参数是来自两个表的同名 Series ,依次传入的列是两个表列名的并集,例如下面这个例子会依次传入 A,B,C,D 四组序列,每组为左右表的两个序列。同时,进行 A 列比较的时候, s1 指代的就是一个全空的序列,因为它在被调用的表中并不存在,并且来自第一个表的序列索引会被 reindex 成两个索引的并集。具体的过程可以通过在传入的函数中插入适当的 print 方法查看。
def choose_min(s1, s2):
s2 = s2.reindex_like(s1)
res = s1.where(s1 < s2, s2)
res = res.mask(s1.isna()) # isna表示是否为缺失值,返回布尔序列
return res
df15 = pd.DataFrame({'A':[1,2,9], 'B':[3,4,6], 'C':[5,6,8]})
df16 = pd.DataFrame({'B':[5,6,4], 'C':[7,8,2], 'D':[9,10,3]}, index=[1,2,4])
print("df15.combine(df16, choose_min)=",df15.combine(df16, choose_min))
# df15.combine(df16, choose_min)= A B C D
# 0 NaN NaN NaN NaN
# 1 NaN 4.0 6.0 NaN
# 2 NaN 6.0 8.0 NaN
# 4 NaN NaN NaN NaN
# 设置 overtwrite 参数为 False 可以保留 被调用表 中未出现在传入的参数表中的列,而不会设置未缺失值.
print("df15.combine(df16, choose_min, overwrite=False)=",
df15.combine(df16, choose_min, overwrite=False))
# df15.combine(df16, choose_min, overwrite=False)= A B C D
# 0 1.0 NaN NaN NaN
# 1 2.0 4.0 6.0 NaN
# 2 9.0 6.0 8.0 NaN
# 4 NaN NaN NaN NaN
参考文献
1、https://datawhalechina.github.io/joyful-pandas/build/html/%E7%9B%AE%E5%BD%95/ch6.html