第5章 合并
本次跟随Datawhale组队学习Pandas基础,希望能有所收获。Datawhale是一个很好的开源组织,会组织很多免费的知识学习。
教程:地址
文章目录
- 第5章 合并
- 一、思维导图
- 二、问题与练习
- 1. 问题
- 2. 练习
- 【练习一】有2张公司的员工信息表,每个公司共有16名员工,共有五个公司,请解决如下问题:
- (a) 每个公司有多少员工满足如下条件:既出现第一张表,又出现在第二张表。
- (b) 将所有不符合(a)中条件的行筛选出来,合并为一张新表,列名与原表一致。
- (c) 现在需要编制所有80位员工的信息表,对于(b)中的员工要求不变,对于满足(a)条件员工,它们在某个指标的数值,取偏离它所属公司中满足(b)员工的均值数较小的哪一个,例如:P公司在两张表的交集为{p1},并集扣除交集为{p2,p3,p4},那么如果后者集合的工资均值为1万元,且p1在表1的工资为13000元,在表2的工资为9000元,那么应该最后取9000元作为p1的工资,最后对于没有信息的员工,利用缺失值填充。
- 【练习二】有2张课程的分数表(分数随机生成),但专业课(学科基础课、专业必修课、专业选修课)与其他课程混在一起,请解决如下问题:
- (a) 将两张表分别拆分为专业课与非专业课(结果为四张表)。
- (b) 将两张专业课的分数表和两张非专业课的分数表分别合并。
- (c) 不使用(a)中的步骤,请直接读取两张表合并后拆分。
- (d) 专业课程中有缺失值吗,如果有的话请在完成(3)的同时,用组内(3种类型的专业课)均值填充缺失值后拆分。
一、思维导图
二、问题与练习
1. 问题
【问题一】 请思考什么是append/assign/combine/update/concat/merge/join各自最适合使用的场景,并举出相应的例子。
- append: 通过dict Series或DataFrame对象向DataFrame对象添加新的行 不会修改原来的DataFrame 而是创建一个新的副本 是concat 在行添加方向的快捷方法
- assign:通过Series 或者DataFrame中的可调用的列(lambda方法) 添加到DataFrame 返回一个DataFrame
- combine: 与另一个DataFrame按列组合,这里是按照规则进行逐列组合,自动索引对齐,缺失值补充为NaN
- update:使用来自另一个DataFrame的非NaN值就地进行修改,在索引上对齐,没有返回值
- concat:通过dict Series或DataFrame在一个维度拼接另一个维度取并或交集 根据join方式 比较使用于添加任意数量的DataFrame 不过最好需要使用列表表达式,因为concat()制作了数据的完整副本
- merge:将两个pandas对象横向合并,遇到重复的索引项时会使用笛卡尔积。与concat在axis=1的差别在于on参数可以指定在key值下连接
- join:这是将两个可能具有不同索引的列DataFrames合并为单个结果的便捷方法
示例
## append 添加行
df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']},
index=[0, 1, 2, 3])
df2 = pd.DataFrame({'A': ['A4', 'A5', 'A6', 'A7'],
'B': ['B4', 'B5', 'B6', 'B7'],
'C': ['C4', 'C5', 'C6', 'C7'],
'D': ['D4', 'D5', 'D6', 'D7']},
index=[4, 5, 6, 7])
result = df1.append(df2, sort=False)
result
## assign 添加temp_f列
df3 = pd.DataFrame({'temp_c': [17.0, 25.0]},
index=['Portland', 'Berkeley'])
df3.assign(temp_f=df3['temp_c'] * 9 / 5 + 32)
## combine 进行df4与df5合并 按照take_smaller规则从df4和df5取列
df4 = pd.DataFrame({'A': [0, 0], 'B': [4, 4]})
take_smaller = lambda s1, s2: s1 if s1.sum() < s2.sum() else s2
df5 = pd.DataFrame({'B': [3, 3], 'C': [-10, 1]}, index=[1, 2])
## 第一种由于overwrite为False 第一列不填充 使得df4原来符合条件的值不会被覆盖
df4.combine(df5, take_smaller,overwrite=False)
## 第二种由于overwrite为True 第一列的取值看df5
df4.combine(df5, take_smaller)
## update 更新B列值
new_column = pd.Series(['d', 'e','f','g'], name='B', index=[0,1,2,3])
df1.update(new_column)
df1
## concat 列轴连接df1 df2
result = pd.concat([df1, df4], axis=1).reindex(df1.index)
result
## merge
df6 = pd.DataFrame({'A': ['A4', 'A5', 'A6', 'A7'],
'E': ['B4', 'B5', 'B6', 'B7'],
'F': ['C4', 'C5', 'C6', 'C7'],
'G': ['D4', 'D5', 'D6', 'D7']},
index=[4, 5, 6, 7])
pd.merge(df1, df6, how='outer', on='A')
## join
df7 = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3', 'K4', 'K5'],
'A': ['A0', 'A1', 'A2', 'A3', 'A4', 'A5']})
other = pd.DataFrame({'key': ['K0', 'K1', 'K2'],
'B': ['B0', 'B1', 'B2']})
df7.join(other, lsuffix='_caller', rsuffix='_other')
key_caller | A | key_other | B | |
---|---|---|---|---|
0 | K0 | A0 | K0 | B0 |
1 | K1 | A1 | K1 | B1 |
2 | K2 | A2 | K2 | B2 |
3 | K3 | A3 | NaN | NaN |
4 | K4 | A4 | NaN | NaN |
5 | K5 | A5 | NaN | NaN |
【问题二】 merge_ordered和merge_asof的作用是什么?和merge是什么关系?
- merge_ordered:函数允许组合时间序列和其他有序数据。 特别是它有一个可选的fill_method关键字来填充/插入缺失的数据。
- merge_asof:除了我们匹配最近的键而不是相等的键之外,其他的都类似于有序的left-join 。 对于左侧DataFrame中的每一行,我们选择右侧DataFrame中on键对应的值小于left的键对应的值的最后一行。 两个DataFrame必须按键排序。
【问题三】 请构造一个多级索引与多级索引合并的例子,尝试使用不同的合并函数。
leftindex = pd.MultiIndex.from_product([list('abc'), list('xy'), [1, 2]],names=['abc', 'xy', 'num'])
left = pd.DataFrame({'v1': range(12)}, index=leftindex)
rightindex = pd.MultiIndex.from_product([list('abc'), list('xy')],names=['abc', 'xy'])
right = pd.DataFrame({'v2': [100 * i for i in range(1, 7)]}, index=rightindex)
left.head()
right.head()
#left.append(right).head()
#pd.concat([left,right],sort=True).head()
pd.merge(left,right,on=['abc', 'xy'],how='inner').head()
left.join(right, on=['abc', 'xy'], how='inner').head()
v1 | v2 | |||
---|---|---|---|---|
abc | xy | num | ||
a | x | 1 | 0 | 100 |
2 | 1 | 100 | ||
y | 1 | 2 | 200 | |
2 | 3 | 200 | ||
b | x | 1 | 4 | 300 |
【问题四】 上文提到了连接的笛卡尔积,那么当连接方式变化时(inner/outer/left/right),这种笛卡尔积规则会相应变化吗?请构造相应例子。
- inner:保留两对象都拥有的笛卡尔积
- outer:保留全部的笛卡尔积
- left:保留left对象所拥有的全部的笛卡尔积(默认缺失值用nan填充)
- rifht:保留right对象所拥有的全部的笛卡尔积(默认缺失值用nan填充)
left = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'],
'key2': ['K0', 'K1', 'K0', 'K1'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})
right = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'],
'key2': ['K0', 'K0', 'K0', 'K0'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']})
pd.merge(left, right, how='left', on=['key1','key2'])
#pd.merge(left, right, how='right', on=['key1','key2'])
#pd.merge(left, right, how='outer', on=['key1','key2'])
#pd.merge(left, right, how='inner', on=['key1','key2'])
key1 | key2 | A | B | C | D | |
---|---|---|---|---|---|---|
0 | K0 | K0 | A0 | B0 | C0 | D0 |
1 | K1 | K0 | A2 | B2 | C1 | D1 |
2 | K1 | K0 | A2 | B2 | C2 | D2 |
3 | K2 | K0 | NaN | NaN | C3 | D3 |
2. 练习
【练习一】有2张公司的员工信息表,每个公司共有16名员工,共有五个公司,请解决如下问题:
df1 = pd.read_csv('data/Employee1.csv')
df1.head()
Company | Name | Age | Height | Weight | Salary | |
---|---|---|---|---|---|---|
0 | A | a1 | 47 | 188 | 63.7 | 25819 |
1 | A | a3 | 39 | 172 | 55.9 | 21983 |
2 | A | a4 | 43 | 158 | 62.5 | 21755 |
3 | A | a6 | 42 | 182 | 76.9 | 17354 |
4 | A | a7 | 49 | 171 | 94.6 | 6177 |
df2 = pd.read_csv('data/Employee2.csv')
df2.head()
Company | Name | Age | Height | Weight | Salary | |
---|---|---|---|---|---|---|
0 | A | a1 | 30 | 156 | 91.2 | 28133 |
1 | A | a2 | 50 | 190 | 83.4 | 6673 |
2 | A | a3 | 34 | 168 | 96.6 | 16503 |
3 | A | a5 | 51 | 176 | 97.2 | 23294 |
4 | A | a6 | 37 | 183 | 93.2 | 19256 |
(a) 每个公司有多少员工满足如下条件:既出现第一张表,又出现在第二张表。
(b) 将所有不符合(a)中条件的行筛选出来,合并为一张新表,列名与原表一致。
© 现在需要编制所有80位员工的信息表,对于(b)中的员工要求不变,对于满足(a)条件员工,它们在某个指标的数值,取偏离它所属公司中满足(b)员工的均值数较小的哪一个,例如:P公司在两张表的交集为{p1},并集扣除交集为{p2,p3,p4},那么如果后者集合的工资均值为1万元,且p1在表1的工资为13000元,在表2的工资为9000元,那么应该最后取9000元作为p1的工资,最后对于没有信息的员工,利用缺失值填充。
a题做法
name1 = list(set(df1['Name']))
name1
name2 = list(set(df2['Name']))
name2
name = list(set(name1) & set(name2))
name
['a6',
'e10',
'b3',
'b7',
'e8',
'a1',
'a3',
'b1',
'd5',
'c3',
'c13',
'c10',
'c12',
'e11',
'b15',
'd10']
b题做法
df_b1 = df1[~df1['Name'].isin(name)]
df_b2 = df2[~df2['Name'].isin(name)]
df_result = df_b1.append(df_b2).set_index('Name')
df_result.head()
Company | Age | Height | Weight | Salary | |
---|---|---|---|---|---|
Name | |||||
a4 | A | 43 | 158 | 62.5 | 21755 |
a7 | A | 49 | 171 | 94.6 | 6177 |
a8 | A | 51 | 168 | 89.5 | 3246 |
a9 | A | 36 | 186 | 62.8 | 3569 |
a13 | A | 58 | 190 | 75.9 | 21854 |
c题这里我没有看懂直接看答案吧
## 读取数据
df1 = pd.read_csv('data/Employee1.csv')
df2 = pd.read_csv('data/Employee2.csv')
## 标记df1 与 df2中在两个表出现的记录为Y_1 Y_2
df1['重复'] = ['Y_1' if df1.loc[i,'Name'] in name else 'N' for i in range(df1.shape[0])]
df2['重复'] = ['Y_2' if df2.loc[i,'Name'] in name else 'N' for i in range(df2.shape[0])]
## 合成总表
df1 = df1.set_index(['Name','重复'])
df2 = df2.set_index(['Name','重复'])
df_c = pd.concat([df1,df2])
result = pd.DataFrame({'Company':[],'Name':[],'Age':[],'Height':[],'Weight':[],'Salary':[]})
group = df_c.groupby(['Company','重复'])
for i in name:
first = group.get_group((i[0].upper(),'Y_1')).reset_index(level=1).loc[i,:][-4:]
second = group.get_group((i[0].upper(),'Y_2')).reset_index(level=1).loc[i,:][-4:]
mean = group.get_group((i[0].upper(),'N')).reset_index(level=1).mean()
final = [i[0].upper(),i]
for j in range(4):
final.append(first[j] if abs(first[j]-mean[j])<abs(second[j]-mean[j]) else second[j])
result = pd.concat([result,pd.DataFrame({result.columns.tolist()[k]:[final[k]] for k in range(6)})])
result = pd.concat([result.set_index('Name'),df_result])
for i in list('abcde'):
for j in range(1,17):
item = i+str(j)
if item not in result.index:
result = pd.concat([result,pd.DataFrame({'Company':[i.upper()],'Name':[item]
,'Age':[np.nan],'Height':[np.nan],'Weight':[np.nan],'Salary':[np.nan]}).set_index('Name')])
result['Number'] = [int(i[1:]) for i in result.index]
result.reset_index().drop(columns='Name').set_index(['Company','Number']).sort_index().head()
Age | Height | Weight | Salary | ||
---|---|---|---|---|---|
Company | Number | ||||
A | 1 | 47.0 | 188.0 | 91.2 | 25819.0 |
2 | 50.0 | 190.0 | 83.4 | 6673.0 | |
3 | 39.0 | 172.0 | 96.6 | 16503.0 | |
4 | 43.0 | 158.0 | 62.5 | 21755.0 | |
5 | 51.0 | 176.0 | 97.2 | 23294.0 |
【练习二】有2张课程的分数表(分数随机生成),但专业课(学科基础课、专业必修课、专业选修课)与其他课程混在一起,请解决如下问题:
df1 = pd.read_csv('data/Course1.csv')
df1.head(10)
课程名字 | 课程类别 | 学分 | 分数 | |
---|---|---|---|---|
0 | 思想道德修养与法律基础 | 思政类 | 3 | 89.0 |
1 | 云计算应用与开发 | 专业选修课 | 3 | 96.0 |
2 | 社会计算 | 专业选修课 | 3 | 78.0 |
3 | 深度学习 | 专业选修课 | 3 | 75.0 |
4 | 人工智能导论 | 专业必修课 | 3 | 84.0 |
5 | 中国近代史纲要 | 思政类 | 3 | 97.0 |
6 | 数据结构与算法 | 学科基础课 | 5 | 82.0 |
7 | 开源软件设计与开发 | 专业选修课 | 3 | 81.0 |
8 | 网球(初) | 体育类 | 1 | 81.0 |
9 | 大数据应用案例 | 专业必修课 | 3 | 91.0 |
df2 = pd.read_csv('data/Course2.csv')
df2.head(10)
课程名字 | 课程类别 | 学分 | 分数 | |
---|---|---|---|---|
0 | 高等数学(一) | 学科基础课 | 4 | 99.0 |
1 | 数据科学与工程导论 | 学科基础课 | 3 | NaN |
2 | 专业英语 | 学科基础课 | 2 | 100.0 |
3 | 概率论 | 学科基础课 | 3 | 99.0 |
4 | 计算机系统 | 专业必修课 | 4 | 80.0 |
5 | 数据科学与工程算法 | 专业必修课 | 3 | 99.0 |
6 | 操作系统 | 专业必修课 | 4 | 89.0 |
7 | 分布式模型与编程 | 专业必修课 | 4 | 81.0 |
8 | 数据管理系统 | 专业必修课 | 6 | 70.0 |
9 | 统计与数据分析基础 | 专业必修课 | 4 | 82.0 |
(a) 将两张表分别拆分为专业课与非专业课(结果为四张表)。
(b) 将两张专业课的分数表和两张非专业课的分数表分别合并。
© 不使用(a)中的步骤,请直接读取两张表合并后拆分。
(d) 专业课程中有缺失值吗,如果有的话请在完成(3)的同时,用组内(3种类型的专业课)均值填充缺失值后拆分。
a题答案
df_profess1 = df1[df1['课程类别'].isin(["学科基础课","专业必修课","专业选修课"])]
df_profess1
df_notprofess1 = df1[~df1['课程类别'].isin(["学科基础课","专业必修课","专业选修课"])]
df_notprofess1
df_profess2 = df2[df2['课程类别'].isin(["学科基础课","专业必修课","专业选修课"])]
df_profess2
df_notprofess2 = df2[~df2['课程类别'].isin(["学科基础课","专业必修课","专业选修课"])]
df_notprofess2
课程名字 | 课程类别 | 学分 | 分数 | |
---|---|---|---|---|
25 | 学术英语听说(二) | 英语类 | 2 | 92.0 |
26 | 学术英语阅读 | 英语类 | 2 | 72.0 |
27 | 学术英语写作 | 英语类 | 2 | 98.0 |
28 | 美国社会与文化 | 英语类 | 2 | 77.0 |
29 | 马克思主义基本原理概论 | 思政类 | 3 | 95.0 |
30 | 军事理论 | 思政类 | 2 | 87.0 |
31 | 大学语文 | 文化传承类 | 2 | 100.0 |
32 | 现代交通地理学 | 公共任意选修类 | 2 | 88.0 |
33 | 植物王国 | 公共任意选修类 | 2 | 92.0 |
b题做法
df_profess = pd.concat([df_profess1,df_profess2])
df_common = pd.concat([df_notprofess1,df_notprofess2])
c题做法
df_all = pd.concat([df1,df2])
df_profess_all = df_all[df_all['课程类别'].isin(["学科基础课","专业必修课","专业选修课"])]
df_common_all = df_all[~df_all['课程类别'].isin(["学科基础课","专业必修课","专业选修课"])]
(df_profess.equals(df_profess_all),df_common.equals(df_common_all))
d题做法
df_all = pd.concat([df1,df2])
df_all['分数'] = df_all.groupby('课程类别').transform(lambda x: x.fillna(x.mean()))['分数']
df_profess_all = df_all[df_all['课程类别'].isin(["学科基础课","专业必修课","专业选修课"])]
df_common_all = df_all[~df_all['课程类别'].isin(["学科基础课","专业必修课","专业选修课"])]