目录
一、关系型连接
1. 连接的基本概念
2. 值连接 merge函数
df1 = pd.DataFrame({'Name':['San Zhang','Si Li'], 'Age':[20,30]})
df2 = pd.DataFrame({'Name':['Si Li','Wu Wang'], 'Gender':['F','M']})
df1.merge(df2, on='Name', how='left')
#如果两个表中想要连接的列不具备相同的列名,可以通过`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']})
df1.merge(df2, left_on='df1_name', right_on='df2_name', how='left')
#出现了重复的列名,可以通过suffixes参数指定
df1 = pd.DataFrame({'Name':['San Zhang','lili'],'Grade':[70,90]})
df2 = pd.DataFrame({'Name':['San Zhang','lili'],'Grade':[80,85]})
#出现重复元素的时候指定on参数为多个列使得正确连接
df1.merge(df2, on='Name', how='left', suffixes=['_Chinese','_Math'])
#错误的结果
df1.merge(df2, on=['Name','Class'],how='left')
#正确的结果 必须确保唯一性 通过姓名和班级可以确定一个人
检查重复:duplicated函数
merge提供了validate参数来检查连接的唯一性
- 一对一 1:1
- 多对一 m:1
- 一对多 1:m
第一个是指左右表的键都是唯一的,后面两个分别指右表键唯一和左表键唯一。
【练一练】
上面以多列为键的例子中,错误写法显然是一种多对多连接,而正确写法是一对一连接,请修改原表,使得以多列为键的正确写法能够通过validate='1:m'
的检验,但不能通过validate='m:1'
的检验。
#可以通过1:m的检验 但不能通过 m:1的检验
df1=pd.DataFrame({'Name':['San Zhang','Si Li'],'Age':[20,21],'Class':['one','two']})
#出现在左表中的内容不出现在右表
df2=pd.DataFrame({'Name':['San Zhang','San Zhang'],'Gender':['F','M'],'Class':['one','one']})
#除了Gender之外都是相等的,连接的根据是 Name和Class,validate的时候可以检测时1:m和M:1
Err_Msg='不存在错误'
try:
df1.merge(df2,on=['Name','Class'],how='left',validate='1:m')
except Exception as e:
Err_Msg=e
Err_Msg
出现重复列的时候会报错
3. 索引连接
所谓索引连接,就是把索引当作键,因此这和值连接本质上没有区别,
pandas
中利用join
函数来处理索引连接,它的参数选择要少于merge
,
除了必须的on
和how
之外,可以对重复的列指定左右后缀lsuffix
和rsuffix
。
其中,on
参数指索引名,单层索引时省略参数表示按照当前索引连接。
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')
df1.join(df2,how='left')
df1=pd.DataFrame({'Grade':[70]},index=pd.Series(['San']))
df1.join(df2, how='left', lsuffix='_Chinese', rsuffix='_Math')
#使用多级索引进行连接
df1=pd.DataFrame({'Age':[20,21]},index=pd.MultiIndex.from_arrays([['San Zhang','San Zhang'],['one','two']],names=('Names','Class')))
df2=pd.DataFrame({'Gender':['F','M']},index=pd.MultiIndex.from_arrays([['San Zhang','San Zhang'],['two','one']],names=('Names','Class')))
df1.join(df2)
二、方向连接
1. concat
只是希望把两个表或者多个表按照纵向或者横向拼接,为这种需求,pandas
中提供了concat
函数来实现
concat是关于索引进行连接的
-
axis 拼接方向 (0:纵向 1:横向)
-
join 连接形式 (默认outer保留所有的列,将不存在的列设置为缺失)
inner 表示保留两个表都出现过的列 -
keys 在新表中指示来自于哪张旧表中的名字
使用于多个表合并后,用户仍然想要知道新表中的数据来源于哪个原表,可以通过keys参数产生多级索引进行标记。
纵向合并表中人的信息 (axis=0)
df1=pd.DataFrame({'Name':['San Zhang','Si Li'],'Age':[20,30]})
df2=pd.DataFrame({'Name':['Wu Wang'],'Age':[40]})
pd.concat([df1,df2])
#注意concat传入的是一个列表的形式
横向合并表中人的信息 第二个参数传入 1(axis=1)
横向拼接根据行索引对齐
df1=pd.DataFrame({'Name':['San Zhang','Si Li'],'Age':[20,30]})
df2=pd.DataFrame({'Grade':[80,90]})
df3=pd.DataFrame({'Gender':['M','F']})
pd.concat([df1,df2,df3],1)
#部分交叉横向合并
pd.concat([df1,df2],axis=1,join='inner')
#内部交集横向合并
pd.concat([df1,df2],keys=['one','two'])
#合并后标注行或列的来源
keys
df1=pd.DataFrame({'Name':['San Zhang','Si Li'],'Age':[20,21]})
df2=pd.DataFrame({'Name':['Wu Wang'],'Age':[21]})
pd.concat([df1,df2],keys=['one','two'])
TIPS:
当确认使用多表连接的方向进行合并的时候,尤其是横向合并,可以先用reset_index方法恢复默认整数索引再进行合并,防止出现由索引的误对齐和重复索引的笛卡儿积带来的错误
2. Series序列与DataFrame表的合并 append,assign
利用 concat可以实现多个表之间的方向拼接,如果想要把一个序列追加到表的行末或者列末,可以分别使用append和assign方法
(1) 在append中,如果原表是默认整数序列的索引,可以使用ignore_index=True
属性,对新序列对应的索引自动编号,否则必须对Series
指定name
属性
s=pd.Series(['Wu Wang',21],index=df1.columns)
df1.append(s,ignore_index=True)
(3)对于assign而言,虽然可以利用其添加新的列,但一般通过df['new_col']=...
的形式就可以等价地添加新列
#特性
使用[]修改的缺点是它会直接在原表上进行改动
assign返回的是一个临时副本
#检测特性
s=pd.Series([80,90])
df1.assign(Grade=s)
df['Grade']=s
df1
(3)
- to_frame()方法将Series转化成DataFrame,使用concat方法进行合并
三、类连接操作
可以对两个表进行某些操作(类连接操作)
1. 比较
compare
是在1.1.0
后引入的函
作用:比较两个表或者序列的不同之处,并将其汇总展示
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']})
df1.compare(df2)
如果想要完整展示表中所有元素的比较情况,
可以设置`keep_shape=True`
df1.compare(df2,keep_shape=True)
结果返回了不同值所在的行列,结果相同会被填充缺失值NAN
,其中的self和other代表被调用表自身和传入的参数表,如果一列全部都相同则不显示
2. 组合
combine
函数能够让两张表按照一定的规则进行组合
进行规则比较的时候会自动进行列索引的对齐
对于传入的函数而言,每次操作中输入的参数是来自两个表的同名Series
,一次传入的列是两个表列名的并集
def choose_min(s1,s2):
s2=s2.reindex_like(s1)
res=s1.where(s1<s2,s2)
print(res)
res=res.mask(s1.isna())
print('mask之后',res)
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])
df1.combine(df2,choose_min)
第一个参数是要比较的表,第二个参数是比较的函数
每列相比的结果结合起来
【练一练】
请在上述代码的基础上修改,保留df2
中4个未被df1
替换的相应位置原始值。
res=res.mask(s1.isna())
作用是和一个值比较,而且在s1列不存在的时候
df1.combine(df2,lambda s1,s2:s1.where(s1<s2,s2))
#res=res.mask(s1.isna())和一个值进行比较,且s1列不存在时,不展示s2列
【END】
此外,设置overtwrite
参数为False
可以保留
被调用表
\color{red}{被调用表}
被调用表中未出现在传入的参数表中的列,而不会设置未缺失值:
df1.combine(df2,choose_min,overwrite=False)
【练一练】
除了combine
之外,pandas
中还有一个combine_first
方法,其功能是在对两张表组合时,若第二张表中的值在第一张表中对应索引位置的值不是缺失状态,那么就使用第一张表的值填充。下面给出一个例子,请用combine
函数完成相同的功能。
【END】
df1=pd.DataFrame({'A':[1,2],'B':[3,np.nan]})
df2=pd.DataFrame({'A':[5,6],'B':[7,8]},index=[1,2])
df1.combine_first(df2)
#combine_first
df2填充df1缺失的地方
四、练习
Ex1:美国疫情数据集
现有美国4月12日至11月16日的疫情报表(在/data/us_report
文件夹下),请将New York
的Confirmed, Deaths, Recovered, Active
合并为一张表,索引为按如下方法生成的日期字符串序列:
date=pd.date_range('20200412','20201116').to_series()
date=date.dt.month.astype('string').str.zfill(2)+'-'+date.dt.day.astype('string').str.zfill(2)+'-'+'2020'
date=date.tolist()
#转换为list类型
date[:5]
#取出前5个数据
Ex2:实现join函数
join函数处理索引连接
- lsuffix rsuffix
- on ,how参数
左连接,右连接,外连接
def join(df1,df2,how='left'):
res_col=df1.columns.tolist()+df2.columns.tolist()
print('df1columns+df2columns',res_col)
dup=df1.index.unique().intersection(df2.index.unique())
#返回存在于df1和df2的行索引index
res_df=pd.DataFrame(columns=res_col)
print(res_df)
#构造一个列索引为df1和df2的拼接而成的
for label in dup:
#遍历df1和df2的行索引的交集
cartesian=[list(i)+list(j) for i in df1.loc[label
].values.reshape(-1,1) for j in df2.loc[
label].values.reshape(-1,1)]
#合并行索引相同的列
dup_df=pd.DataFrame(cartesian,index=[label]*len(
cartesian),columns=res_col)
#结果构造一个dataframe
res_df=pd.concat([res_df,dup_df])
#合并res和dup
if how in ['left','outer']:
for label in df1.index.unique().difference(dup):
if isinstance(df1.loc[label],pd.DataFrame):
cat=[list(i)+[np.nan]*df2.shape[1
] for i in df1.loc[label].values]
else: cat=[list(i)+[np.nan]*df2.shape[1
]for i in df1.loc[label].to_frame().values]
dup_df=pd.DataFrame(cat,index=[label
]*len(cat),columns=res_col)
res_df=pd.concat([res_df,dup_df])
if how in ['right','outer']:
for label in df2.index.unique().difference(dup):
#交集之外存在于df2的标签
if isinstance(df2.loc[label],pd.DataFrame):
cat=[[np.nan]+list(i)*df1.shape[1
] for i in df2.loc[label].values]
#如果df2存在label的dataframe,
# cat=[[np.nan]+list(i)*df1.shape[1
# ] for i in df2.loc[label].values]
#这个是什么意思
else: cat=[[np.nan]+list(i)*df1.shape[1
] for i in df2.loc[label].to_frame().values]
dup_df=pd.DataFrame(cat,index=[label
]*len(cat),columns=res_col)
res_df=pd.concat([res_df,dup_df])
return res_df
df1 = pd.DataFrame({'col1':list('01234')}, index=list('AABCD'))
df2 = pd.DataFrame({'col2':list('opqrst')}, index=list('ABBCEE'))
join(df1, df2, how='outer')
请实现带有how
参数的join
函数
-
假设连接的两表无公共列
-
调用方式为
join(df1, df2, how="left")
-
给出测试样例