第六章 连接
将两张相关的表按照某一个或某一组键连接起来,关键要素是键和连接形式,其中连接形式包含:
left
:以左边的键为准,若右表中的键 出现于左表,则将改键添加到左表。否则处理为缺失值。right
:类似处理inner
:合并两边同时出现的键out
:又叫全连接,在内连接基础上也包含仅在单侧表中出现的键。 根据某一列连接
连接不具备相同列名的表
连接两个出现相同列名的表
指定多个列作为on参数
validate 参数
练一练
上面以多列为键的例子中,错误写法显然是一种多对多连接,而正确写法是一对一连接,请修改原表,使得以多列为键的正确写法能够通过 validate=‘1:m’ 的检验,但不能通过 validate=‘m:1’ 的检验。
df2 = pd.DataFrame({
'Name':['San Zhang', 'San Zhang'],'Gender':['F', 'M'],'Class':['two', 'two']})
df1.merge(df2, on=['Name', 'Class'], how='left',validate="1:m")
>>>
Name Age Class Gender
0 San Zhang 20 one NaN
1 San Zhang 21 two F
2 San Zhang 21 two M
df1.merge(df2, on=['Name', 'Class'], how='left',validate="m:1")
>>> MergeError: Merge keys are not unique in right dataset; not a many-to-one merge
要实现类似merge
的 多列为键 的操作,join
需要使用 多级索引 。
df1 = pd.DataFrame({
'Age':[20,21]},index=pd.MultiIndex.from_arrays([['San Zhang', 'San Zhang'],['one', 'two']],names=('Name','Class')))
df1
df2 = pd.DataFrame({
'Gender':['F', 'M']},index=pd.MultiIndex.from_arrays([['San Zhang', 'San Zhang'],['two', 'one']],names=('Name','Class')))
df2
df1.join(df2)
axis
拼接方向,0-纵向拼接多表(常用于多样本拼接),1-横向拼接多表(常用于多字段/特征拼接)join
连接形式,默认状态下join=outer
表示保留所有的列,将不存在的值设为缺失,join=inner
表示 两个表都出现过的列key
在新表中指示来自于哪一张旧表的名字# 纵向拼接:根据列索引对齐
df1 = pd.DataFrame({
'Name':['San Zhang','Si Li'],'Age':[20,30]})
df1
Name Age
0 San Zhang 20
1 Si Li 30
======
df2 = pd.DataFrame({
'Name':['Wu Wang'], 'Age':[40],'Gender':['F']})
df2
Name Age Gender
0 Wu Wang 40 F
======
pd.concat([df1, df2])
Name Age Gender
0 San Zhang 20 NaN
1 Si Li 30 NaN
0 Wu Wang 40 F
# 横向拼接:根据行索引对齐
df2 = pd.DataFrame({
'Grade':[80, 90]}, index=[1, 2])
pd.concat([df1, df2], 1)
>>>
Name Age Grade
0 San Zhang 20.0 NaN
1 Si Li 30.0 80.0
2 NaN NaN 90.0
当使用多表直接方向合并时,尤其是横向合并,先用reset_index
方法恢复默认整数索引再进行合并,防止出现由索引误对齐和重复索引的笛卡尔积带来的错误结果。
# keys参数 能够表示新表中的数据来自于哪个原表
df1 = pd.DataFrame({
'Name':['San Zhang','Si Li'],'Age':[20,21]})
df1
Name Age
0 San Zhang 20
1 Si Li 21
======
df2 = pd.DataFrame({
'Name':['Wu Wang'],'Age':[21]})
df2
Name Age
0 Wu Wang 21
======
pd.concat([df1, df2], keys=['one', 'two'])
append
将序列追加到行末assign
将序列追加到列末s = pd.Series(['Wu Wang', 21], index = df1.columns)
df1.append(s, ignore_index=True)
s = pd.Series([80, 90])
df1.assign(Grade=s)
NaN
other
指代传入的参数表self
指代被调用的表自身# 选出对应索引位置较小的元素
def choose_min(s1, s2):
s2 = s2.reindex_like(s1)
res = s1.where(s1<s2, s2) # 注意where逻辑,若df1
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])
df1.combine(df2, choose_min)
>>>
A B C D
0 NaN NaN NaN NaN
1 NaN 4.0 6.0 NaN
2 NaN NaN NaN NaN
练一练
请在上述代码的基础上修改,保留 df2 中4个未被 df1 替换的相应位置原始值。
def choose_min(s1, s2):
s2 = s2.reindex_like(s1)
res = s1.where(s1<s2, s2) # 注意where逻辑,若df1
# res = res.mask(s1.isna())
return res
# overwrite=False 可以保留 被调用表 中未出现在传入的参数表中的列
df1.combine(df2, choose_min, overwrite=False)
>>>
A B C D
0 1.0 NaN NaN NaN
1 2.0 4.0 6.0 NaN
2 NaN NaN NaN NaN
练一练
除了 combine 之外, pandas 中还有一个 combine_first 方法,其功能是在对两张表组合时,若第二张表中的值在第一张表中对应索引位置的值不是缺失状态,那么就使用第一张表的值填充。下面给出一个例子,请用 combine 函数完成相同的功能。
df1.combine(df2, lambda x,y : x.mask(x.isna(), y))
# zfill() 方法返回指定长度的字符串,原字符串右对齐,前面填充0。
date = date.dt.month.astype('string').str.zfill(2) + '-' + date.dt.day.astype('string').str.zfill(2) + '-' + '2020'
date = date.tolist()
result = pd.DataFrame(columns=['Confirmed', 'Deaths', 'Recovered', 'Active'])
for date_item in date:
data = pd.read_csv('./data/us_report/%s.csv'%(date_item))
data = data.set_index('Province_State')
res = data.loc['New York',['Confirmed', 'Deaths', 'Recovered', 'Active']]
result = result.append(res,ignore_index=True)
result.index = date
# 答案中的for循环部分
L.append(data.to_frame().T)
# 拼接使用的是 concat,两个表之间的方向连接
res = pd.concat(L)
# 想了一下下,然后直接看了答案
def join(df1, df2, how='left'):
res_col = df1.columns.tolist() + df2.columns.tolist()
dup = df1.index.unique().intersection(df2.index.unique()) # 取交集
res_df = pd.DataFrame(columns = res_col) # 定义结果的df
for label in dup:
cartesian = [list(i)+list(j) for i in df1.loc[label].values for j in df2.loc[label].values] # 两表查询结果拼接
dup_df = pd.DataFrame(cartesian, index = [label]*len(cartesian), columns = res_col) # 放入df中
res_df = pd.concat([res_df,dup_df]) # 两个df拼接,公共部分的拼接结果
# 左连接
if how in ['left', 'outer']:
for label in df1.index.unique().difference(dup): # 取差集,据df1与公共部分的差集作为查询依据
if isinstance(df1.loc[label], pd.DataFrame): # 如果是df
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):
if isinstance(df2.loc[label], pd.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