四、数据组合和缺失值处理



前言

  • 前面我们学习的都是只处理加载出来的一份数据,当我们需要处理的数据来自多个文件的时候,我们需要将这几份数据连接到一起,在对其进行操作,那么我们就需要学习数据组合的知识,并学会对缺失值进行处理。
  • 以下操作均在 jupyter notebook 中运行

一、数据组合

  • 数据组合是指将多个数据元素或数据集通过某种方式(如连接、合并、分组等)组合成一个新的数据集或数据元素的过程。这个过程有助于揭示数据之间的内在联系和潜在规律,为后续的数据分析和决策提供支持。

1.1 concat连接

1.1.1 df对象与 df对象拼接

行拼接参考: 列名, 列拼接参考: 行号

加载并查看数据

import pandas as pd


# 1. 加载数据
df1 = pd.read_csv('data/concat_1.csv')
df2 = pd.read_csv('data/concat_2.csv')
df3 = pd.read_csv('data/concat_3.csv')

# 2. 查看数据
df1
df2
df3

运行结果
在这里插入图片描述

  • 通过concat()函数, 拼接上述的三个df对象
  • 默认是行拼接
# 3. 通过concat()函数, 拼接上述的3个df对象.
row_concat = pd.concat([df1, df2, df3])                 # 默认是纵向拼接(行拼接), 结果为: 12行4列
row_concat = pd.concat([df1, df2, df3], axis='rows')    # 效果同上
row_concat = pd.concat([df1, df2, df3], axis=0)         # 效果同上, 0: 表示行, 1表示列. 
row_concat

运行结果
在这里插入图片描述

  • 按列拼接
row_concat = pd.concat([df1, df2, df3], axis='columns') # 按列拼接,  4行12列
row_concat = pd.concat([df1, df2, df3], axis=1)         # 效果同上, 0: 表示行, 1表示列. 
row_concat

运行结果

在这里插入图片描述

  • 行拼接,重置索引
# 通过设置 ignore_case参数, 可以实现: 重置索引
# 行拼接, 重置索引, 结果为: 行索引变为 0 ~ n
pd.concat([df1, df2, df3], ignore_index=True)	

运行结果

在这里插入图片描述

  • 列拼接,重置索引
# 列拼接, 重置索引, 结果为: 列名变为 0 ~ n
pd.concat([df1, df2, df3], axis='columns', ignore_index=True)	# 

运行结果

在这里插入图片描述

1.1.2 df对象与 Serise 对象拼接

代码如下(示例):

# 4. 使用concat连接 df 和 Series
new_series = pd.Series(['n1', 'n2', 'n3', 'n4'])
# 由于Series是列数据(没有行索引), concat()默认是添加行, 所以 它们拼接会新增一列. 缺值用NaN填充
pd.concat([df1, new_series], axis='rows')   # 按行拼接    
pd.concat([df1, new_series], axis=0)        # 0: 行,  1: 列

运行结果

在这里插入图片描述

pd.concat([df1, new_series], axis='columns')    # 0: 行,  1: 列
pd.concat([df1, new_series], axis=1)            # 0: 行,  1: 列

运行结果

在这里插入图片描述

1.2 添加新的行跟列

1.2.1 添加行

  • 如果想将[‘n1’,‘n2’,‘n3’,‘n4’]作为行连接到df1后,可以创建DataFrame并指定列名.

代码如下(示例):

# 传入二维数组, 即: 1行4列数据.
df5 = pd.DataFrame([['n1', 'n2', 'n3', 'n4']], columns=['A', 'B', 'C', 'D'])

pd.concat([df1, df5])

运行结果

在这里插入图片描述

1.2.2 添加列

方式1: 通过 df [列名] = [列值1, 列值2…] 的方式, 可以给 df 添加列

df1['new_col'] = ['n1', 'n2', 'n3', 'n4']         # 正确
# df1['new_col'] = ['n1', 'n2', 'n3', 'n4', 'n5'] # 报错, 值的个数 和 行数(4行)不匹配
# df1['new_col'] = ['n1', 'n2', 'n3']             # 报错, 值的个数 和 行数(4行)不匹配
df1

运行结果

在这里插入图片描述

方式2: 通过 df [列名] = Series对象 的方式, 添加1列. # 值的个数和列的个数, 匹不匹配均可

df1['new_col2'] = pd.Series(['n1', 'n2', 'n3'])
df1['new_col2'] = pd.Series(['n1', 'n2', 'n3', 'n4'])
df1['new_col2'] = pd.Series(['n1', 'n2', 'n3', 'n4', 'n5'])
df1

运行结果

在这里插入图片描述

1.3 merge方式

加载数据:

# 导包
import sqlite3
# 1. 创建连接对象, 关联: *.db文件.
conn = sqlite3.connect('data/chinook.db')
# 2. 从文件中, 加载 歌曲分类表的信息.
genres_df = pd.read_sql_query('select * from genres;', conn)
genres_df

执行结果

在这里插入图片描述

1.3.1 merge()合并数据, 一对一关系

1、加载数据:

# 1. 查看 歌曲表的信息, 并从中找到 不同的音乐风格的数据.
tracks_subset_df = tracks_df.loc[[0, 62, 76, 98, 110, 193, 204, 281]]
tracks_subset_df

执行结果

在这里插入图片描述

2、展示其中的三列

tracks_subset_df[['TrackId', 'GenreId', 'Milliseconds']]        # 歌曲id, 风格id, 歌曲时长(毫秒)

执行结果

在这里插入图片描述

3、合并上述两个表,以风格为标准进行合并:

场景1: 内连接

# 场景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

执行结果
在这里插入图片描述

1.3.2 merge()合并数据, 多对一关系

需求: 计算每种类型音乐的 平均时长.

# 1. 合并 genres(风格表) 和 tracks(歌曲表)
genres_df.merge(tracks_df[['TrackId', 'Name', 'GenreId', 'Milliseconds']], on='GenreId') 
# 需求2: 计算每种类型音乐的平均时长. 
# 1. 合并 genres(风格表) 和 tracks(歌曲表).  交集.
genre_track =  genres_df.merge(tracks_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='inner')    # 风格表.merge(歌曲表['歌曲id', '风格id', '歌曲时长毫秒']) 
# 左外连接.
genre_track =  genres_df.merge(tracks_df[['TrackId', 'GenreId', 'Milliseconds']], on='GenreId', how='left')    # 风格表.merge(歌曲表['歌曲id', '风格id', '歌曲时长毫秒']) 
genre_track

执行结果
在这里插入图片描述

# 2. 根据 风格id分组, 计算 时长的平均值.
genre_time = genre_track.groupby(['GenreId', 'Name']).Milliseconds.mean()
genre_time

执行结果
在这里插入图片描述

1.4 join方式

代码如下(示例):

# 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

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 参数实现.

执行结果
在这里插入图片描述

二、缺失值简介与处理

  • 1、好多数据集都会确实数据,缺失数据有多重表现形式
    • 数据库中,缺失数据表示为NULL
    • 在某些编程语言中用NA表示
    • 缺失值也可能是空字符串(’’)或数值
    • 在Pandas中使用NaN表示缺失值
  • 2、Pandas中的NaN值来自NumPy库,NumPy中缺失值有几种表示形式:
    • NaN,NAN,nan,他们都一样
    • 缺失值和其它类型的数据不同,它毫无意义,NaN不等于0,也不等于空串

2.1. 缺失值演示

代码如下(示例):

# 导包
import numpy as np

# 1. 缺失值不是 True, False, 空字符串, 0等, 它"毫无意义"
print(np.NaN == False)
print(np.NaN == True)
print(np.NaN == 0)
print(np.NaN == '')

# 2. np.nan np.NAN np.NaN 都是缺失值, 这个类型比较特殊, 不同通过 == 方式判断, 只能通过API
print(np.NaN == np.nan)
print(np.NaN == np.NAN)
print(np.nan == np.NAN)

# 3. Pandas 提供了 isnull() / isna()方法, 用于测试某个值是否为缺失值
import pandas as pd

print(pd.isnull(np.NaN))    # True
print(pd.isnull(np.nan))    # True
print(pd.isnull(np.NAN))    # True

print(pd.isna(np.NaN))  # True
print(pd.isna(np.nan))  # True
print(pd.isna(np.NAN))  # True

# isnull() / isna()方法 还可以判断数据.
print(pd.isnull(20))    # False
print(pd.isnull('abc'))    # False

# 4. Pandas的notnull() / notna() 方法可以用于判断某个值是否为缺失值
print(pd.notnull(np.NaN))   # False
print(pd.notnull('abc'))   # True

2.2. 加载缺失值的处理

# 加载数据时可以通过keep_default_na 与 na_values 指定加载数据时的缺失值
pd.read_csv('data/survey_visited.csv')

执行结果
在这里插入图片描述

# 加载数据,不包含默认缺失值,  
# 参数解释: keep_default_na = False  表示加载数据时, 不加载缺失值.
pd.read_csv('data/survey_visited.csv', keep_default_na=False)

执行结果
在这里插入图片描述

# 加载数据,手动指定缺失值, 例如: 指定619, 734为缺失值
# 参数解释: na_values=[值1, 值2...]	表示加载数据时, 设定哪些值为缺失值.
pd.read_csv('data/survey_visited.csv', na_values=['619', '734'], keep_default_na=False)

执行结果
在这里插入图片描述

2.3. 删除缺失值

  • dropna()函数, 参数介绍如下:

    • subset=None 默认是: 删除有缺失值的行, 可以通过这个参数来指定, 哪些列有缺失值才会被删除
    • 例如: subset = [‘Age’] 只有当年龄有缺失才会被删除
    • inplace=False 通用参数, 是否修改原始数据默认False
    • axis=0 通用参数 按行按列删除 默认行
    • how=‘any’ 只要有缺失就会删除 还可以传入’all’ 全部都是缺失值才会被删除

代码如下(示例):

train = pd.read_csv('data/titanic_train.csv')
train.shape     # 原始数据, 891行, 12列

# 方式1: 删除缺失值
# 删除缺失值会损失信息,并不推荐删除,当缺失数据占比较低的时候,可以尝试使用删除缺失值
# 按行删除: 删除包含缺失值的记录
# train.dropna().shape        # 默认按行删(该行只要有空值, 就删除该行), 结果为: 183行, 12列
train.loc[:10].dropna()       # 获取前11行数据, 删除包含空值的行. 

# any: 只要有空值就删除该行|列, all: 该行|列 全为空才删除  subset: 参考哪些列的空值.  inplace=True 在原表修改  
train.dropna(subset=['Age'], how='any')

# 该列值只要有空, 就删除该列值.
train.dropna(how='any', axis=1)  # 0(默认): 行,  1: 列 

train.isnull().sum() # 快速计算是否包含缺失值

2.4. 填充缺失值

2.4.1 非时序数据填充

代码如下(示例):

# 方式2: 填充缺失值, 填充缺失值是指用一个估算的值来去替代缺失数
# 场景1: 非时间序列数据, 可以使用常量来替换(默认值)

# 用 0 来填充 空值.
train.fillna(0) 
# 查看填充后, 每列缺失值 情况.
train.fillna(0).isnull().sum()

# 需求: 用平均年龄, 来替换 年龄列的空值.
train['Age'].fillna(train['Age'].mean())

2.4.2 时序数据填充

代码如下(示例):

# 1. 加载时间序列数据,数据集为印度城市空气质量数据(2015-2020)
# parse_dates: 把某些列转成时间列. 
# index_col:   设置指定列为 索引列
city_day = pd.read_csv('data/city_day.csv', parse_dates=['Date'], index_col='Date')

# 2. 查看缺失值情况.
city_day.isnull().sum()

# 3. 数据中有很多缺失值,比如 Xylene(二甲苯)和 PM10 有超过50%的缺失值
# 3.1 查看包含缺失数据的部分
city_day['Xylene'][50:64]

# 3.2 用固定值填充, 例如: 该列的平均值.
# 查看平均值.
city_day['Xylene'].mean()  # 3.0701278234985114
# 用平均值来填充.
city_day.fillna(city_day['Xylene'].mean())[50:64]['Xylene']

# 3.3 使用ffill 填充,用时间序列中空值的上一个非空值填充
# NaN值的前一个非空值是0.81,可以看到所有的NaN都被填充为0.81
city_day.fillna(method='ffill')[50:64]['Xylene']

# 3.4 使用bfill填充,用时间序列中空值的下一个非空值填充
# NaN值的后一个非空值是209,可以看到所有的NaN都被填充为209
city_day.fillna(method='bfill')[50:64]['Xylene']

# 3.5 线性插值方法填充缺失值
# 时间序列数据,数据随着时间的变化可能会较大。使用bfill和ffill进行插补并不是解决缺失值问题的最优方案。
# 线性插值法是一种插补缺失值技术,它假定数据点之间存在线性关系,利用相邻数据点中的非缺失值来计算缺失数据点的值。
# 参数limit_direction: 表示线性填充时, 参考哪些值(forward: 向前, backward:向后, both:前后均参考)
city_day.interpolate(limit_direction="both")[50:64]['Xylene']

总结

  • 文章介绍了数据拼接和缺失值处理的相关知识。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值