3. DataFrame合并操作

DataFrame合并操作是高性能的内存中连接操作,它将两个DataFrame合并在一起,merge函数类似于SQL中join的用法,可以将不同数据集依照某些字段(属性)进行合并操作,得到一个新的数据集merge定义如下:

def merge(left, right, how='inner', on=None, left_on=None, right_on=None,
          left_index=False, right_index=False, sort=False,
          suffixes=('_x', '_y'), copy=True, indicator=False,
          validate=None):

merge方法的参数非常多,本文讲解其中比较重要的几个

编号 参数 含义
1 how 连接方式, 有left, right, inner, outer四个可选值, 默认是inner
2 on 列名或者索引标签列表
3 left_index 如果为 True,则 使用左侧DataFrame中的索引(行标签)作为其连接键
4 right_index 则 使用右侧DataFrame中的索引(行标签)作为其连接键
5 left_on 左侧DataFrame中的合并作用列
6 right_on 右侧DataFrame中的合并作用列
7 sort 按照字典顺序通过连接键对结果DataFrame进行排序

1. on

1.1 on参数作用

两个DataFrame合并,总要有所依据,比如下面这两分数据

import pandas as pd

data1 = [
    {'语文': 90, '数学': 92, 'id': 1},
    {'语文': 98, '数学': 87, 'id': 2},
    {'语文': 87, '数学': 90, 'id': 3},
    {'语文': 90, '数学': 98, 'id': 4},
]

left_df = pd.DataFrame(data1, index=['小明', '小红', '小刚', '小丽'])
left_df.index.name = '姓名'
left_df.columns.name = '科目'

data2 = [
    {'英语': 89, '物理': 98, 'id': 1},
    {'英语': 90, '物理': 90, 'id': 2},
    {'英语': 90, '物理': 97, 'id': 3},
    {'英语': 98, '物理': 94, 'id': 5},
]

right_df = pd.DataFrame(data2, index=['小明', '小红', '小刚', '小王'])
right_df.index.name = '姓名'
right_df.columns.name = '科目'
  1. 两份数据都是学生的考试成绩
  2. 有3个学生两份数据中都有,小丽和小王都各自只出现一次

现在,我们考虑合并两份数据,以常理推断,合并后,小明,小红,小刚他们3个有4个学科的成绩, 小丽只有语文,数学的成绩, 小王只有英语和物理的成绩,那么问题来了,我们需要告诉程序以哪一列做连接的依据,通过观察数据,我们可以用id这一列做连接合并的依据,id相同的数据被放到同一行上

df = pd.merge(left_df, right_df, on='id')
print(df)

程序输出结果

科目  id  数学  语文  物理  英语
0    1  92  90  98  89
1    2  87  98  90  90
2    3  90  87  97  90

看着最终的结果,有两处我很不满意,一是合并后,学生的姓名都不见了,二是少了两个学生的信息,为了解决这两个问题,需要其他参数配合

1.2 使用外连接

之所以缺少两个学生的信息,是因为参数how默认等于inner,做的是内连接,以id列为连接合并依据时,内连接只会保留两个数据集都有数据,想要保留两个数据集全部的数据,需要使用外连接

df = pd.merge(left_df, right_df, how='outer', on='id')
print(df)

合并后的数据

科目  id    数学    语文    物理    英语
0    1  92.0  90.0  98.0  89.0
1    2  87.0  98.0  90.0  90.0
2    3  90.0  87.0  97.0  90.0
3    4  98.0  90.0   NaN   NaN
4    5   NaN   NaN  94.0  98.0

1.3 保留行索引

经过1.2小节的处理,已经得到所有学生的考试成绩,但是学生的姓名都丢失了,这种情况,需要设置left_index 和 right_index

df = pd.merge(left_df, right_df, how='outer', on='id', left_index=True, right_index=True)
print(df)

设置left_index,将使用左侧DataFrame中的索引(行标签)作为其连接键, 这样就保留了左侧DataFrame的索引,最终的合并结果是

科目  id    数学    语文    物理    英语
姓名                            
小丽   4  98.0  90.0   NaN   NaN
小刚   3  90.0  87.0  97.0  90.0
小明   1  92.0  90.0  98.0  89.0
小王   5   NaN   NaN  94.0  98.0
小红   2  87.0  98.0  90.0  90.0

1.4 相同列名如何处理

现在,我使用姓名列作为连接合并的依据,两个数据集中都有id列,这两个id列会如何处理呢?

df = pd.merge(left_df, right_df, how='inner', on='姓名')
print(df)

输出结果

科目  id_x  数学  语文  id_y  物理  英语
姓名                            
小明     1  92  90     1  98  89
小红     2  87  98     2  90  90
小刚     3  90  87     3  97  90

两个数据集的id列并没有合并,而是都保留到了新的DataFrame中并有了新的列名

2. how

how有4个可选值,inner,left, right, outer,代表了4种不同的合并方式,下面用4个例子逐一解释他们的作用

2.1 inner

how参数默认是inner

df = pd.merge(left_df, right_df, how='inner', on='id', left_index=True, right_index=True)
print(df)

以id列连接合并,how参数值是inner, 两个数据集都存在的id是1, 2, 3, 因此最终的结果只有3个学生的数据

科目  id  数学  语文  物理  英语
姓名                    
小明   1  92  90  98  89
小红   2  87  98  90  90
小刚   3  90  87  97  90

2.2 outer

outer是外连接,最终的结果会包含左右两个数据集的全部数据,以id列作为连接合并的依据,相同id的数据会融合到一起,不同id缺失的数据使用np.nan填充

df = pd.merge(left_df, right_df, how='outer', on='id', left_index=True, right_index=True)
print(df)

程序输出结果

科目  id    数学    语文    物理    英语
姓名                            
小丽   4  98.0  90.0   NaN   NaN
小刚   3  90.0  87.0  97.0  90.0
小明   1  92.0  90.0  98.0  89.0
小王   5   NaN   NaN  94.0  98.0
小红   2  87.0  98.0  90.0  90.0

2.3 left, right

仍然以id列作为连接合并的依据,how参数值为left时,将保留全部左侧的数据,右侧DataFrame的数据只保留那些与左侧DataFrame有相同id的数据, 因此最终只有小明, 小红, 小刚, 小丽这4个人的数据,而没有小王的数据,如果how设置为right,那么最终结果将没有小丽的数据

df = pd.merge(left_df, right_df, how='left', on='id', left_index=True, right_index=True)
print(df)

print('*'*20)

df = pd.merge(left_df, right_df, how='right', on='id', left_index=True, right_index=True)
print(df)

程序输出结果

科目  id  数学  语文    物理    英语
姓名                        
小明   1  92  90  98.0  89.0
小红   2  87  98  90.0  90.0
小刚   3  90  87  97.0  90.0
小丽   4  98  90   NaN   NaN
********************
科目   id    数学    语文  物理  英语
姓名                         
小明  1.0  92.0  90.0  98  89
小红  2.0  87.0  98.0  90  90
小刚  3.0  90.0  87.0  97  90
小王  NaN   NaN   NaN  94  98

3. left_on, right_on

left_on和right_on需要一起使用,是一种替代on参数的方法,前面的例子中,或使用id或使用姓名作为连接合并的依据列,这样做有一个前提条件,两个数据集必须同时有相同名称的列,否则无法使用on参数,比如下面的数据

import pandas as pd

data1 = [
    {'语文': 90, '数学': 92, 'id': 1},
    {'语文': 98, '数学': 87, 'id': 2},
    {'语文': 87, '数学': 90, 'id': 3},
    {'语文': 90, '数学': 98, 'id': 4},
]

left_df = pd.DataFrame(data1, index=['小明', '小红', '小刚', '小丽'])
left_df.index.name = '姓名'
left_df.columns.name = '科目'

data2 = [
    {'英语': 89, '物理': 98, 'stu_id': 1},
    {'英语': 90, '物理': 90, 'stu_id': 2},
    {'英语': 90, '物理': 97, 'stu_id': 3},
    {'英语': 98, '物理': 94, 'stu_id': 5},
]

right_df = pd.DataFrame(data2, index=['小明', '小红', '小刚', '小王'])
right_df.index.name = 'name'
right_df.columns.name = '科目'

唯一可以标识一个学生身份的学号id,在不同的数据集中有不同的列名,而且index.name也不相同了,这时,就无法使用on这个参数来指定合并时所依据的列,对于这种情况,则可以使用left_on, right_on设置两个数据集分别起作用的列名

df = pd.merge(left_df, right_df, left_on='id', right_on='stu_id', left_index=True)
print(df)

程序最终输出结果

科目    id  数学  语文  stu_id  物理  英语
name                            
小明     1  92  90       1  98  89
小红     2  87  98       2  90  90
小刚     3  90  87       3  97  90

4. sort

按照字典顺序通过连接键对结果DataFrame进行排序

为了演示效果,修改左侧数据集

import pandas as pd

data1 = [
    {'语文': 87, '数学': 90, 'id': 3},
    {'语文': 98, '数学': 87, 'id': 2},
    {'语文': 90, '数学': 92, 'id': 1},
    {'语文': 90, '数学': 98, 'id': 4},
]

left_df = pd.DataFrame(data1, index=['小明', '小红', '小刚', '小丽'])
left_df.index.name = '姓名'
left_df.columns.name = '科目'

data2 = [
    {'英语': 89, '物理': 98, 'stu_id': 1},
    {'英语': 90, '物理': 90, 'stu_id': 2},
    {'英语': 90, '物理': 97, 'stu_id': 3},
    {'英语': 98, '物理': 94, 'stu_id': 5},
]

right_df = pd.DataFrame(data2, index=['小明', '小红', '小刚', '小王'])
right_df.index.name = 'name'
right_df.columns.name = '科目'

df = pd.merge(left_df, right_df, left_on='id', right_on='stu_id', left_index=True)
print(df)

学生的信息没有发生变化,我只修改了学生信息在列表中的顺序,程序输出结果

科目    id  数学  语文  stu_id  物理  英语
name                            
小刚     3  90  87       3  97  90
小红     2  87  98       2  90  90
小明     1  92  90       1  98  89

最终的结果里,id列是从到小排序的,如果我们想让id列从小到大排序,需要设置sort参数为True

df = pd.merge(left_df, right_df, left_on='id', right_on='stu_id', left_index=True, sort=True)
print(df)

程序最终输出结果

科目    id  数学  语文  stu_id  物理  英语
name                            
小刚     3  90  87       3  97  90
小红     2  87  98       2  90  90
小明     1  92  90       1  98  89

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案