数据处理与统计分析——07-Pandas的concat连接、merge()合并、多表查询、内/外/自连接查询操作
pandas数据拼接
(1) DataFrame数据组合-concat连接
-
概述
- 连接是指把某行或某列追加到数据中, 数据被分成了多份可以使用连接把数据拼接起来
- 把计算的结果追加到现有数据集,也可以使用连接
-
df对象与df对象拼接
行拼接参考: 列名, 列拼接参考: 行号
# todo 记忆: concat()函数既能实现行拼接(默认), 也能实现列拼接. 行拼接参考: 列名, 列拼接参考: 索引列(行索引) # 1. 演示行拼接 pd.concat([df1, df2, df3]) # 默认是: 行拼接. pd.concat([df1, df2, df3], axis='rows') # 效果同上 pd.concat([df1, df2, df3], axis=0) # 效果同上, 0 => rows, 行, 1 => columns, 列 # 2. 演示列拼接 pd.concat([df1, df2, df3], axis='columns') # 列拼接 pd.concat([df1, df2, df3], axis=1) # 效果同上 # 3. 演示 行, 列拼接时, 重置: 索引 和 列名 # todo 细节: 无论是行, 列拼接时, 只要忽略索引了, 都会默认用 0 ~ n来填充. pd.concat([df1, df2, df3], axis='rows', ignore_index=True) # 行拼接 pd.concat([df1, df2, df3], axis='columns', ignore_index=True) # 列拼接 # 4. 验证 行拼接时, 参考: 列名 df4 = pd.DataFrame(['n1', 'n2', 'n3'], columns=['B'], index=['a', 1, 'c']) df4 # 5. 拼接 df1 和 df4 # 行拼接, 参考: 列名 pd.concat([df1, df4], axis='rows') # 未匹配, 用NAN填充. # 列拼接, 参考: 索引列 pd.concat([df1, df4], axis='columns') # 未匹配, 用NAN填充.
-
df对象 和 Series对象拼接
# 1. 创建Series对象. s1 = pd.Series(['n1', 'n2', 'n3']) s1 # todo Series对象代表一列数据, 他没有类似于df的列名, 所以 行拼接时, 类似于: 新增了1列 #%% # 2. 使用concat拼接df和series对象. pd.concat([df1, s1], axis='rows') # 默认是: 行拼接. pd.concat([df1, s1], axis='columns') # 默认是: 列拼接. #%% # 3. 细节, 关于append()函数, 旧版本的Anaconda(例如: Anaconda2020)支持, 新版本中已经被移除掉了, 它能实现的事儿, 用concat()都能做. # df1.append(df2) # 报错 pd.concat([df1, df2]) # 可以平替上述的功能
-
df对象新增列
# 方式1: df对象[列名] = 列表, 要求: 列表的长度 要和 df的行数一致. # df1['new_col1'] = [10, 20, 30] # 报错, 值的个数 和 行数(4行)不匹配 df1['new_col1'] = [10, 20, 30, 40] # 正确 df1 # 上述的代码, 加入 concat() df5 = pd.concat([df1, df2], axis=1) # 列拼接 df5['new_col1'] = [10, 20, 30, 40] df5 # 方式2: df对象[列名] = Series对象, Series对象值的个数无要求. df1['new_col2'] = pd.Series([1, 2, 3]) df1['new_col3'] = pd.Series([1, 2, 3, 4]) df1['new_col4'] = pd.Series([1, 2, 3, 4, 5]) df1
-
append函数演示
append()函数已过时, 它作用和concat()类似, 在新版的pandas中这个方法已经被删除了.
# concat可以连接多个对象, 例如: df1, df2, df3... # 但如果只需要像现有的df对象, 添加1个对象, 可以使用 append()函数实现. # 演示 append函数, 实现: 追加1个df对象 到 另1个df对象中. df1.append(df2) # 只能行拼接, 且没有axis参数 # ignore_index: 忽略索引, 即: 索引会重置. df1.append(df2, ignore_index=True) # df对象 使用append追加一个字典时, 必须传入 ignore_index=True 参数 data_dict = {'A': 'n1', 'B': 'n2', 'C': 'n3'} df1.append(data_dict, ignore_index=True)
(2) merge()函数
-
概述
-
在使用concat连接数据时,涉及到了参数join(join = ‘inner’,join = ‘outer’)
-
数据库中可以依据共有数据把两个或者多个数据表组合起来,即join操作
-
DataFrame 也可以实现类似数据库的join操作
-
Pandas可以通过pd.join命令组合数据,
-
也可以通过pd.merge命令组合数据
merge更灵活,如果想依据行索引来合并DataFrame可以考虑使用join函数
-
-
多表查询分类
>连接查询
内连接:相当于A、B交集部分数据(无null值)
外链接:有(null)值
左外连接:查询左表所有数据,
以及两张表交集部分数据
右外连接:查询右表所有数据,
以及两张表交集部分数据
自连接:当前表与自身的连接查询,自连接必须使用表别名
-
-
准备数据
# 1. 创建链接对象 关联 db文件 import sqlite3 conn = sqlite3.connect('data/chinook.db') # 2. 从上述的文件中, 读取 歌曲表的信息 # 参1: 要执行的 sql语句 # 参2: 连接对象 tracks_df = pd.read_sql_query('select * from tracks', conn) tracks_df.head() # 3. 从上述文件中, 读取 歌曲分类表的信息 genres_df = pd.read_sql_query('select * from genres', conn) genres_df.head()
-
merge()合并数据, 一对一
# 1. 查看 歌曲风格表的信息 genres_df # 2. 查看 歌曲表的信息, 并从中找到 不同的音乐风格的数据. tracks_subset_df = tracks_df.loc[[0, 62, 76, 98, 110, 193, 204, 281]] tracks_subset_df tracks_subset_df[['TrackId', 'GenreId', 'Milliseconds']] # 歌曲id, 风格id, 歌曲时长(毫秒) # 3. 合并上述两个表, 以 风格 为标准, 合并. """ # 场景1: 内连接 # 参1: 要被合并的df对象. # 参2: on表示两个df合并的 关联字段, 如果一样可以直接写 on, 如果不一样, 则要写 left_on='左表字段名', right_on='右表字段名' # 参3: how表示合并方式, 内连接: inner, 左连接: left, 右连接: right, 全(满)连接: outer """ genres_df.merge(tracks_subset_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='inner') # 场景2: 左外连接 genres_df.merge(tracks_subset_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='left') # 场景3: 右外连接 genres_df.merge(tracks_subset_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='right') # 场景4: 满外连接, 也叫: 全连接, 即: 它的查询结果 = 左连接 + 右连接, 即: 左表全集 + 右表全集 + 交集. genres_df.merge(tracks_subset_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='outer') # 场景5: 查看默认是哪种连接. genres_df.merge(tracks_subset_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId') # 默认是: 内连接 => inner # 场景6: 如果关联的多个df有重名的列, 则默认会加上 _x, _y这样的后缀, 来源于: suffixes字段. genres_df.merge(tracks_subset_df[['TrackId', 'Name', 'GenreId', 'Milliseconds']], on='GenreId') # 默认后缀: _x, _y genres_df.merge(tracks_subset_df[['TrackId', 'Name', 'GenreId', 'Milliseconds']], on='GenreId', suffixes=('_left', '_right')) # 默认后缀: _x, _y
细节:
-
on 连接的字段, 如果左右两张表 连接的字段名字相同直接使用 on=‘关联字段名’
-
如果名字不同, left_on 写左表字段, right_on 写右表字段.
-
连接之后, 两张表中如果有相同名字的字段, 默认会加上后缀 默认值 _x, y
suffixes:(" x", “_ y”)
-
-
merge()合并数据, 一对多
# todo merge()格式: df1.merge(df2, on='关联字段', how='连接方式') # todo 参数: 1. how默认是 inner 2. 关联字段不一致时, 用 left_on 和 right_on 3. 两个df的字段有重名, 可以通过 suffixes 指定后缀 # 需求2: 计算每种类型音乐的 平均时长 # todo 1. 合并 genres(风格表) 和 tracks(歌曲表). 交集 # genres_tracks = genres_df.merge(tracks_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='inner') # 左外连接 # 风格表.merge(歌曲表[['歌曲id', '风格id', '歌曲时长毫秒']]) genres_tracks = genres_df.merge(tracks_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='left') genres_tracks # todo 2. 根据 风格id分组, 计算 歌曲时长的平均值 (分组聚合) genre_time = genres_tracks.groupby(['GenreId', 'Name']).Milliseconds.mean() genre_time # 3. 扩展: 把上述的 genre_time(毫秒) -> 秒 pd.to_timedelta(genre_time, unit='ms').dt.floor('s') pd.to_timedelta(genre_time, unit='ms').dt.floor('s').sort_values() # pd.to_timedelta(genre_time, unit='ms') 把genre_time的毫秒数, 转换成 pandas.Timedelta 类型 # dt.floor('s') : 取整, 取秒
(3) DataFrame数据组合-join()
概述
- 使用join合并,可以是依据两个DataFrame的行索引,
- 或者一个DataFrame的行索引另一个DataFrame的列索引进行数据合并
# 1. 加载数据, 获取df对象.
stock_2016 = pd.read_csv('data/stocks_2016.csv')
stock_2017 = pd.read_csv('data/stocks_2017.csv')
stock_2018 = pd.read_csv('data/stocks_2018.csv')
stock_2016
stock_2017
stock_2018
# 2. 默认情况下, join会参考 两个df的 索引列 进行合并连接.
stock_2016.join(stock_2017, lsuffix='_2016', rsuffix='_2017') # 默认: 左外连接
stock_2016.join(stock_2017, lsuffix='_2016', rsuffix='_2017', how='left') # 效果同上
# 3. 设置两个df对象的 Symbol列为索引列, 再次关联.
stock_2016.set_index('Symbol')
stock_2017.set_index('Symbol')
# 设置索引列, 并关联.
stock_2016.set_index('Symbol').join(stock_2017.set_index('Symbol'), lsuffix='_2016', rsuffix='_2017', how='left') # 左外连接
stock_2016.set_index('Symbol').join(stock_2017.set_index('Symbol'), lsuffix='_2016', rsuffix='_2017', how='right') # 右外连接
stock_2016.set_index('Symbol').join(stock_2017.set_index('Symbol'), lsuffix='_2016', rsuffix='_2017', how='outer') # 满外连接
stock_2016.set_index('Symbol').join(stock_2017.set_index('Symbol'), lsuffix='_2016', rsuffix='_2017', how='inner') # 内连接
# 4. 设置stock_2016的索引为: Symbol 和 stock_2018做关联.
stock_2016
stock_2018.set_index('Symbol')
# 拿着 stock_2016的 指定列(普通列) 和 stock_2018的 索引列 进行关联.
# 细节: on参数设定的是 函数外 df对象的 普通列
stock_2016.join(stock_2018.set_index('Symbol'), lsuffix='_left', rsuffix='_right', on='Symbol', how='outer')
# 总结: join() => 1. 默认是 左外连接. 2.如果两个df有重名字段, 需要手动设置后缀名. 3.默认是根据两个df的 索引列来合并的, 如果想要关联普通列, 需要通过 on 参数实现.