无法播放?请 点击这里 跳转到Youtube
切换视频源:

结合之前的章节,我们已经掌握了常用的操作DataFrame的方法和数据分析相关的内容,这一章我们来补充学习一下如何合并DataFrame。

DataFrame合并的方法我们可以简单分为两种,一种是横向的合并,另一种是纵向的合并。那Pandas有4个方法来实现这两种合并:merge,join,concat和append,其中merge和join适用于横向的合并(axis = 1),concat和append则更适用于纵向的合并(axis = 0)。

merge可以做join的所有操作,还更灵活,concat也能完成append所有操作,同样也更灵活,所以只要熟练掌握merge和concat,就能保证你能完成对DataFrame大部分的操作了。接下来我们会重点学习merge和concat,然后简单了解一下join和append即可。

4种横向合并的方式(Types of joins)

横向合并比较复杂,没学习过SQL join相关内容的朋友可能比较费解,那首先简单介绍一下横向jon相关概念。横向合并总共有四种:Inner Join,Outer Join,Left Join和Right Join。进行Join操作的时候,要区分好左DataFrame和右DataFrame,不同的排序也会导致结果不同。

(大家不要被join这个词所困惑,这里提到的join是横向合并的方式,而pandas中merge和join是用来实现横向合并两个不同的函数,所以这里提到的join和pandas中的join函数并不是一个意思。)

Inner Join:返回一个DataFrame,只包含左右两个DataFrame共有的数据。

Outer Join (full join):返回左右DataFrame中包含的所有数据。

Left Join:返回一个DataFrame,保证其包含左DataFrame中所有的数据。

Right Join:返回一个DataFrame,保证其包含右DataFrame中所有的数据。

以上是对Join最基本的解释,接下来我们会通过具体的例子帮助大家更好地理解。

横向合并1:Merge

首先创建好用来学习的DataFrames:

import numpy as np
import pandas as pd

movies = pd.DataFrame({
    'movie_id': [1, 2, 3, 5, 7],
    'title': ['t1', 't2', 't3', 't5', 't7'],
    'description': ['d1', 'd2', 'd3', 'd5', 'd7']
})

ratings = pd.DataFrame({
    'user_id': [1, 2, 7, 9, 11, 15],
    'movie_id': [1, 2, 4, 5, 6, 7],
    'title': ['t1', 't2', 't3', 't4', 't5', 't6'],
    'rating': [5, 4, 3, 2, 3, 1],
    'time': ['t1', 't2', 't4', 't4', 't1', 't3']
})

然后使用merge函数进行最基础的inner merge:

pd.merge(movies, ratings) # 自动基于相同名字的列(movie_id和title)进行inner join,只有同名列上数值相同的行才会被结合。

pd.merge(movies, ratings, on=['movie_id', 'title']) # 和上面的结果相同,通过on参数显性定义要join的列

pd.merge(movies, ratings, on=['movie_id']) # 只基于 movie_id 列进行结合

pd.merge(movies, ratings, left_on='movie_id', right_on='user_id') # 使用左dataframe的movie_id和右dataframe的user_id进行合并

pd.merge(movies, ratings, left_index=True, right_index=True) # 基于两边的index进行merge

如果要解决列名字重合(overlapping)的问题,可以使用suffixes参数来解决: 

pd.merge(movies, ratings, on=['movie_id'], suffixes=['_left', '_right'])
   movie_id title_left description  user_id title_right  rating time
0         1         t1          d1        1          t1       5   t1
1         2         t2          d2        2          t2       4   t2
2         5         t5          d5        9          t4       2   t4
3         7         t7          d7       15          t6       1   t3

使用 how 参数来定义你要 merge 的方式,其中有四个选项(inner, outer, left, right):

pd.merge(movies, ratings, on=['movie_id', 'title'], how='inner') # 拿到基于movie_id和title两列数值相同的行
   movie_id title description  user_id  rating time
0         1    t1          d1        1       5   t1
1         2    t2          d2        2       4   t2

pd.merge(movies, ratings, on=['movie_id', 'title'], how='outer') # 拿到两边所有的行,没有数据会被拉下,无法merge的列会被填上空值
   movie_id title description  user_id  rating time
0         1    t1          d1      1.0     5.0   t1
1         2    t2          d2      2.0     4.0   t2
2         3    t3          d3      NaN     NaN  NaN
3         5    t5          d5      NaN     NaN  NaN
4         7    t7          d7      NaN     NaN  NaN
5         4    t3         NaN      7.0     3.0   t4
6         5    t4         NaN      9.0     2.0   t4
7         6    t5         NaN     11.0     3.0   t1
8         7    t6         NaN     15.0     1.0   t3

pd.merge(movies, ratings, on=['movie_id', 'title'], how='left') # 存下左Dataframe所有数值,扔掉右DataFrame中不和左DataFrame重合的行(基于movie_id和title列)
   movie_id title description  user_id  rating time
0         1    t1          d1      1.0     5.0   t1
1         2    t2          d2      2.0     4.0   t2
2         3    t3          d3      NaN     NaN  NaN
3         5    t5          d5      NaN     NaN  NaN
4         7    t7          d7      NaN     NaN  NaN

pd.merge(movies, ratings, on=['movie_id', 'title'], how='right') # 和left join相反,存下右DataFrame所有的行,左DataFrame中不和右DataFrame重合的数据会被扔掉
   movie_id title description  user_id  rating time
0         1    t1          d1        1       5   t1
1         2    t2          d2        2       4   t2
2         4    t3         NaN        7       3   t4
3         5    t4         NaN        9       2   t4
4         6    t5         NaN       11       3   t1
5         7    t6         NaN       15       1   t3

在做join的时候,我们也可以使用indicator来提示我们数据的情况:

pd.merge(movies, ratings, on=['movie_id', 'title'], how='outer', indicator=True) # 结果中会有一个额外的列_merge显性提示数据的情况
   movie_id title description  user_id  rating time      _merge
0         1    t1          d1      1.0     5.0   t1        both
1         2    t2          d2      2.0     4.0   t2        both
2         3    t3          d3      NaN     NaN  NaN   left_only
3         5    t5          d5      NaN     NaN  NaN   left_only
4         7    t7          d7      NaN     NaN  NaN   left_only
5         4    t3         NaN      7.0     3.0   t4  right_only
6         5    t4         NaN      9.0     2.0   t4  right_only
7         6    t5         NaN     11.0     3.0   t1  right_only
8         7    t6         NaN     15.0     1.0   t3  right_only

# 自定义indicator column的名称
pd.merge(movies, ratings, on=['movie_id', 'title'], how='outer', indicator='indicator_column')

   movie_id title description  user_id  rating time indicator_column
0         1    t1          d1      1.0     5.0   t1             both
1         2    t2          d2      2.0     4.0   t2             both
2         3    t3          d3      NaN     NaN  NaN        left_only
3         5    t5          d5      NaN     NaN  NaN        left_only
4         7    t7          d7      NaN     NaN  NaN        left_only
5         4    t3         NaN      7.0     3.0   t4       right_only
6         5    t4         NaN      9.0     2.0   t4       right_only
7         6    t5         NaN     11.0     3.0   t1       right_only
8         7    t6         NaN     15.0     1.0   t3       right_only

横向合并2:Join

虽然merge可以完成join所有的操作,还更灵活,但是这边也简单介绍一下join,帮助大家未来也能理解相关代码。

join没有merge那么方便,此函数是基于index进行结合的:

movies.join(ratings, on='movie_id', lsuffix='_left', rsuffix='_right') # 使用左DataFrame的movie_id和右DataFrame的index进行结合,默认是left join
pd.merge(movies, ratings, left_on='movie_id', right_index=True, how='left') # 执行结果和上面语句一样

如果想要基于特定的列进行结合,可以改变index后再进行操作:

movies.set_index('movie_id').join(ratings.set_index('movie_id'), lsuffix='_left', rsuffix='_right') # 基于movie_id进行left join
movies.merge(ratings, on='movie_id', how='left') # 执行结果和上面一样

因为merge比join更容易理解,而且操作,推荐大家直接使用merge代替join进行横向合并。

纵向合并1:Concat

上面提到的Merge和join用于实现横向合并,而接下来学习的Concat和Append主要是用来实现纵向合并。而其中Concat比Append功能更多,所以大家重点掌握Concat就好。

使用Concat实现纵向合并(将两个 DataFrame 按照水平方向叠起来):

pd.concat([movies, ratings]) # 纵向结合两个DataFrame,index不会被reset
pd.concat([movies, ratings], ignore_index = True)  # index会被reset 

我们也能使用 concat 进行 inner join 和 outer join(没有left join和right join,default是outer join):

pd.concat([movies, ratings], axis=1) # 将axis设为1后,可基于index进行outer join,但不是真正意义上的join,只是横向水平叠合
 movie_id title description  user_id  movie_id title  rating time
0       1.0    t1          d1        1         1    t1       5   t1
1       2.0    t2          d2        2         2    t2       4   t2
2       3.0    t3          d3        7         4    t3       3   t4
3       5.0    t5          d5        9         5    t4       2   t4
4       7.0    t7          d7       11         6    t5       3   t1
5       NaN   NaN         NaN       15         7    t6       1   t3

pd.concat([movies, ratings], join='inner', axis=1) # 基于index进行inner join

pd.concat([movies.set_index('movie_id'), ratings.set_index('movie_id')], join='inner', axis=1) # 基于movie_id进行inner join

pd.merge(movies, ratings, on='movie_id') # 执行结果和上面一样

将 axis 设为 0,则会实现纵向结合的操作:

pd.concat([movies, ratings], axis=0, join='inner') # 纵向inner结合

纵向合并2:Append

因为 concat 能实现 append 中的所有操作,所以纵向操作直接交给 concat 就好。那这边也简单来了解一下Append函数:

# 将rating合并到movies底下,并重置index
movies.append(ratings, ignore_index = True) 
pd.concat([movies, ratings], ignore_index = True) # 执行结果和上面一样
    movie_id title description  user_id  rating time
0          1    t1          d1      NaN     NaN  NaN
1          2    t2          d2      NaN     NaN  NaN
2          3    t3          d3      NaN     NaN  NaN
3          5    t5          d5      NaN     NaN  NaN
4          7    t7          d7      NaN     NaN  NaN
5          1    t1         NaN      1.0     5.0   t1
6          2    t2         NaN      2.0     4.0   t2
7          4    t3         NaN      7.0     3.0   t4
8          5    t4         NaN      9.0     2.0   t4
9          6    t5         NaN     11.0     3.0   t1
10         7    t6         NaN     15.0     1.0   t3

# 将ratings和movies自己合并到movies下面,并重置index
movies.append([ratings, movies], ignore_index = True)
    movie_id title description  user_id  rating time
0          1    t1          d1      NaN     NaN  NaN
1          2    t2          d2      NaN     NaN  NaN
2          3    t3          d3      NaN     NaN  NaN
3          5    t5          d5      NaN     NaN  NaN
4          7    t7          d7      NaN     NaN  NaN
5          1    t1         NaN      1.0     5.0   t1
6          2    t2         NaN      2.0     4.0   t2
7          4    t3         NaN      7.0     3.0   t4
8          5    t4         NaN      9.0     2.0   t4
9          6    t5         NaN     11.0     3.0   t1
10         7    t6         NaN     15.0     1.0   t3
11         1    t1          d1      NaN     NaN  NaN
12         2    t2          d2      NaN     NaN  NaN
13         3    t3          d3      NaN     NaN  NaN
14         5    t5          d5      NaN     NaN  NaN
15         7    t7          d7      NaN     NaN  NaN

pd.concat([movies, ratings, movies], ignore_index = True) # 执行结果和上面一样

如果要将结果按照列名排序,请将sort参数设为True:

movies.append(ratings, sort = True) 

以上就是merge, join, concat, append相关的内容了,如果大家想要更好地理解这章知识点,可以通过操作前几章常用的survey_df进行复习:

csv_data_path = 'https://raw.githubusercontent.com/turingplanet/pandas-intro/main/public-datasets/small_survey_results.csv'
survey_df = pd.read_csv(csv_data_path) # 此数据集包含了StackOverflow上随机的1000份用户调研(列名的具体含义请参考:https://github.com/turingplanet/pandas-intro/blob/main/public-datasets/survey_results_schema.csv)