结合之前的章节,我们已经掌握了常用的操作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)