数据组合和缺失数据处理
今天讲了pandas的数据组合方式以及缺失数据的处理,数据组合说实话有一点点像sql的组合查询,但是要复杂的多,很难记,缺失数据处理也是听的半懂不懂,总之写点博客复习一下吧
数据组合
数据清理
1.在动手进行数据分析工作之前,需要进行数据清理工作
每个观测值成一行
每个变量成一列
每种观测单元构成一张表格
2.数据整理好之后,可能需要多张表格组合到一起才能进行某些问题的分析
如一张表保存公司名称,另一张表保存股票价格
单个数据集也可能会分割成多个,比如时间序列数据,每个日期可能在一个单独的文件中
连接数据
1.组合数据的一种方法是使用“连接”(concatenation)
连接是指把某行或某列追加到数据中
数据被分成了多份可以使用连接把数据拼接起来
把计算的结果追加到现有数据集,可以使用连接
添加行-DataFrame连接
# 1: 导包
import pandas as pd
import sqlite3
# 2:加载数据(读取文件数据)
df1 = pd.read_csv('data/concat_1.csv')
df2 = pd.read_csv('data/concat_2.csv')
df3 = pd.read_csv('data/concat_3.csv')
print("----------DataFrame与DataFrame拼接-----------------------")
# 3:concat()函数,将df对象链接组合起来,默认是:按照行拼接
# 3-1:默认按照行拼接
r1 = pd.concat([df1, df2, df3]) # 默认按照行拼接
r2 = pd.concat([df1, df2, df3], axis="rows") # 按照行拼接(同上)
r3 = pd.concat([df1, df2, df3], axis=0) # 按照行拼接(同上)
print(r3)
通过 iloc ,loc等方法取出连接后的数据的子集
r_c.iloc[3,]

r_c.loc[3,]

添加行-DataFrame和Serire连接
print("----------DataFrame与Series拼接(作为列添加)-----------------------")
# 细节:由于series对象是列数据,concat()函数默认是添加行,但是series中没有行索引,所以添加了新的列,缺失值用NaN填充
s1 = pd.Series(['n1', 'n2', 'n3'])
r6 = pd.concat([df1, s1])
print(r6)

如果想将['n1','n2','n3','n4']作为行连接到df1后,可以创建DataFrame并指定列名
print("----------DataFrame与DataFrame拼接(将['n1','n2','n3','n4']作为行连接到df1后,可以创建DataFrame并指定列名)-------")
# 如果要将['n1','n2','n3']作为行链接列df1后,如何实现
df5 = pd.DataFrame([['n1', 'n2', 'n3', 'n4']], columns=['A', 'B', 'C', 'D'])
print(df5)

r7 = pd.concat([df1, df5], ignore_index=True) # 忽略索引,重新编号,如果不写ignore_index=True,意味着索引会保留
print(r7)

添加行-append函数
concat可以连接多个对象,如果只需要向现有DataFrame追加一个对象,可以通过append函数来实现
print("----------使用append函数,追加DataFrame(一次只能追加一个df对象)---------")
dict1 = {'A': 'n1', 'B': 'n2', 'C': 'n3', 'D': 'n4'}
# 注意版本问题,使用_append函数或者append函数
r8 = df1._append(dict1, ignore_index=True)
print(r8)
添加行-重置索引
如果是两个或者多个DataFrame连接,可以通过ignore_index = True参数,忽略后面DataFrame的索引
print(pd.concat([df1, df2], ignore_index=True))

添加列-concat函数
使用concat函数添加列,与添加行的方法类似
需要多传一个axis参数 axis的默认值是index 按行添加,传入参数 axis = columns 即可按列添加
r4 = pd.concat([df1, df2, df3], axis="columns") # 按照列拼接
r5 = pd.concat([df1, df2, df3], axis=1) # 按照列拼接(同上)
print(r5)

print(cc['A'])

添加列-通过dataframe['列名'] = ['值']
向DataFrame添加一列,不需要调用函数,通过dataframe['列名'] = ['值'] 即可
# 采用赋值的方式追加新列(同时也支持concat()函数追加新列)
cc['n_c'] = ['n1', 'n2', 'n3', 'n4']
print(cc)

添加列-通过dataframe['列名'] = Series对象
cc['n_c_s'] = pd.Series(['n1', 'n2', 'n3', 'n4'])
print(cc)

添加列-重置索引
按列合并数据之后,可以重置列索引,获得有序索引
print(pd.concat([df1, df2, df3],axis='columns',ignore_index=True))

合并多个数据集
1.在使用concat连接数据时,涉及到了参数join(join = 'inner',join = 'outer')
2.数据库中可以依据共有数据把两个或者多个数据表组合起来,即join操作
3.DataFrame 也可以实现类似数据库的join操作
4.Pandas可以通过pd.join命令组合数据,也可以通过pd.merge命令组合数据
5.merge更灵活,如果想依据行索引来合并DataFrame可以考虑使用join函数
加载数据
从数据库加载数据和使用read_sql_query函数从数据库中读取表,第一个参数是表名,第二个参数是数据库连接对象
import sqlite3
con = sqlite3.connect('data/chinook.db')
t = pd.read_sql_query('SELECT * FROM tracks', con)
t.head()

一对一合并
# TODO 使用merge()函数,实现一对一的组合数据
# 为了更好的演示,连接查询,防止不相关的列干扰,从tracks(歌曲表)中提取一部分数据进行演示
# 根据行号提取部分数据进行演示
tracks_subset = tracks_df.loc[[0,62,76,98,110,193,204,281,322,359],]
print(tracks_subset)
# 通过merge()函数,实现tracks_subset(歌曲表子集)与genres_df(歌曲分类表)的连接查询
# 格式:df.merge(df, on='关键字段', how='连接方式')
# 细节:如果两个df对象关联字段一样,用on直接连接,如果不一样,使用left_on='关联字段1', right_on='关联字段2'
df1 = genres_df.merge(tracks_subset[["TrackId", "GenreId", "Milliseconds"]], on="GenreId", how="inner") # inner内连接,交集,默认连接方式
df2 = genres_df.merge(tracks_subset[["TrackId", "GenreId", "Milliseconds"]], on="GenreId", how="left") # left左连接,左表所有数据,右表匹配数据,左表全集+交集
df3 = genres_df.merge(tracks_subset[["TrackId", "GenreId", "Milliseconds"]], on="GenreId", how="right") # right右连接,右表所有数据,左表匹配数据,右表全集+交集
df4 = genres_df.merge(tracks_subset[["TrackId", "GenreId", "Milliseconds"]], on="GenreId", how="outer") # outer外连接,并集, 满外连接(左外连接+右外连接)
print(df1.head())
print(df2.head())
print(df3.head())
print(df4.head())

------------------------inner

---------------------------------left

---------------------------right

outer
多对一合并
# TODO 使用merge()函数,实现多对一的组合数据
# 需求:计算每个歌曲类型下歌曲的平均时长(需要对时间进行单位转换)
# 1: 将歌曲表tracks和歌曲类别表genres关联到一起
genres_df.merge(tracks_subset) # 一对一关联,默认内连接, 歌曲分类表是全部的数据,而歌曲表子集是部分数据
genres_df.merge(tracks_df) # 一对多,歌曲分类下会有很多的歌曲(多首歌曲)
genres_track_df = genres_df.merge(tracks_df[["TrackId", "GenreId", "Milliseconds"]], on="GenreId", how="left")
# 2: 对关联后的结果进行分组,计算每个类型下歌曲的平均时长
print(genres_track_df.head())
# print(genres_df.info())
avg_series = genres_track_df.groupby(["GenreId", "Name"])["Milliseconds"].mean()
print(type(avg_series))
print(avg_series.head())


练习1
# 基于上述的数据,转换成日期格式
# 转换函数:pd.to_timedelta( )
# 格式:pd.to_timedelta(数据, unit='单位')
# 单位:s:秒,ms:毫秒,us:微秒,ns:纳秒
# 先将毫秒转换成秒
# dt.floor() 这里是截断意思,s表示秒,将毫秒截断为秒
# 将平均时间序列转换为秒级精度的降序时间差序列
#
# 该代码行执行以下操作:
# 1. 使用pd.to_timedelta将avg_series转换为以毫秒为单位的时间差对象
# 2. 通过.dt.floor("s")将时间精度向下取整到秒级
# 3. 使用.sort_values(ascending=False)按降序排列时间差值
#
# 注意:该代码假设avg_series是一个包含毫秒数值的pandas Series对象
#
# 在这个代码中,pd.to_timedelta(avg_series, unit="ms") 不需要指定列,因为 avg_series 本身就是一个 pandas Series 对象,而不是 DataFrame。Series 是一维的数据结构,只有一个数据列,
# 所以不需要指定特定的列。
# avg_series 是通过 genres_track_df.groupby(["GenreId", "Name"])["Milliseconds"].mean() 得到的,它是一个按 "GenreId" 和 "Name" 分组后计算出的 "Milliseconds" 列的平均值,
# 结果是一个具有层级索引(MultiIndex)的 Series。
# 因此,pd.to_timedelta() 直接作用于整个 Series 即可,不需要额外指定列名。
#
rs1 = pd.to_timedelta(avg_series, unit="ms").dt.floor("s").sort_values(ascending=False)
print(rs1.head())

练习2
计算每名用户的平均消费
从三张表中获取数据,用户表获取用户id,姓名
发票表,获取发票id,用户id
发票详情表,获取发票id,单价,数量
# 计算每名用户的平均消费
# 从三张表中获取数据,用户表获取用户id,姓名
# 发票表,获取发票id,用户id
# 发票详情表,获取发票id,单价,数量
# 步骤1:从三张表中获取数据
user_df = pd.read_sql_query("select CustomerId,FirstName,LastName from customers", conn)
print(user_df.info())
invoice_df = pd.read_sql_query("select InvoiceId,CustomerId from invoices", conn)
print(invoice_df.info())
invoice_items_df = pd.read_sql_query("select InvoiceId,UnitPrice,Quantity from invoice_items", conn)
print(invoice_items_df.info())
# 步骤2: 将三张表关联到一起
user_invoice_df = user_df.merge(invoice_df, on="CustomerId").merge(invoice_items_df, on="InvoiceId")
print(user_invoice_df.head())

# 步骤3:计算每个用户每笔消费的总金额
# 写法一:增加新列采用赋值的方式增加新列
user_invoice_df["TotalPrice"] = user_invoice_df["UnitPrice"] * user_invoice_df["Quantity"]
# # 写法二:增加新列采用assign()方法增加新列
# tmp_tmp_user_invoice_df = user_invoice_df.assign(total = lambda x: x["UnitPrice"] * x["Quantity"])
# print(tmp_tmp_user_invoice_df.head())
print(user_invoice_df.head())
写法1

写法2

# 步骤4:计算每名用户的平均消费
# 按照用户id,姓名分组,分组后对总金额求和,平均值,并排序
print(user_invoice_df.groupby(["CustomerId", "FirstName", "LastName"])["TotalPrice"].agg(["sum", "mean"])
.sort_values(by="mean", ascending=False).head())
# print(user_invoice_df.groupby(["CustomerId", "FirstName", "LastName"])["TotalPrice"].sum().sort_values(ascending=False).head())

join合并
# join合并的演示
stocks_2016 = pd.read_csv(r'E:\pycharm project\pythonProject1\pandas1\data\stocks_2016.csv')
stocks_2017 = pd.read_csv(r'E:\pycharm project\pythonProject1\pandas1\data\stocks_2017.csv')
stocks_2018 = pd.read_csv(r'E:\pycharm project\pythonProject1\pandas1\data\stocks_2018.csv')
print(stocks_2016.head())
print(stocks_2017.head())
print(stocks_2018.head())
# 通过join,来合并上述的三个df对象, join:默认是按照行索引进行匹配的,可以通过on关键字设置关联字段
# 场景1:根据两个df的行索引进行关联
df1 = stocks_2016.join(stocks_2017, lsuffix="_2016", rsuffix="_2017") # 默认是左外连接,默认的索引是行索引
print(df1.head())
df2 = stocks_2016.join(stocks_2017, lsuffix="_2016", rsuffix="_2017", how="outer") # 满外连接
print(df2.head())
# 场景2:根据两个df的Symbol设置为行索引,然后关联,无需手动写on字段,默认就是根据行索引关联
df3 = stocks_2016.set_index("Symbol").join(stocks_2017.set_index("Symbol"), lsuffix="_2016", rsuffix="_2017")
print(df3.head())
# 场景3:1个df的Symbol设置为行索引,另一个df的Symbol不设置。
df4 = stocks_2016.join(stocks_2017.set_index("Symbol"), on="Symbol", lsuffix="_2016", rsuffix="_2017")
print(df4.head())
# 上面的需求:使用concat()和merge()函数能否实现?
df5 = pd.concat([stocks_2016, stocks_2017], axis="columns")
# df5 = pd.concat([stocks_2016, stocks_2017], axis=1)
print(df5.head())
df6 = stocks_2016.merge(stocks_2017, on="Symbol")
print(df6.head())


缺失数据处理
许多数据集都含缺失数据。缺失数据有多重表现形式
数据库中,缺失数据表示为NULL
在某些编程语言中用NA表示
缺失值也可能是空字符串(’’)或数值
在Pandas中使用NaN表示缺失值
Pandas中的NaN值来自NumPy库,NumPy中缺失值有几种表示形式:NaN,NAN,nan,他们都一样
缺失值和其它类型的数据不同,它毫无意义,NaN不等于0,也不等于空串,
"""
pandas中,缺失值使用NaN,或者nan,NAN,这三个表示,都是一样的,切记不是''字符串,或者false,0等,表示什么都没有
"""
# 1:缺失值不是flase,也不是空和0等。
import numpy as np
import pandas as pd
print(np.NaN == True) # False
print(np.NaN == False) # False
print(np.NaN == 0) # False
print(np.NaN == '') # False
print("-----------------------------")
# 2: 缺失值的判断,三种表现形式的判断
print(np.NaN == np.NAN) # False
print(np.NaN == np.nan) # False
print(np.nan == np.NAN) # False
# 3: 如何判断缺失值?
# np.isnan()和pd.isnull()效果是一样的,即:判断是否是缺失值
"""
在pandas中,np.isnan和np.isnull的主要区别如下:
功能范围:
np.isnan:只能检测NaN值,专门用于数值型数据
pd.isnull:可以检测NaN、None以及其他类型的缺失值,适用范围更广
处理对象:
np.isnan:只能处理数值型数据,遇到非数值型数据会报错
pd.isnull:可以处理各种数据类型,包括字符串、数值、时间等
对None的处理:
np.isnan:无法处理None值,会抛出异常
pd.isnull:可以正确识别None为缺失值
使用建议:
在pandas中通常推荐使用pd.isnull()np.isnan()(两者功能相同)
对于纯数值数据且确定只有NaN值时,可以使用np.isnan
np.isnull在pandas环境中更安全,因为它能处理更多类型的缺失值
总的来说,在pandas数据处理中,pd.isnull比np.isnan更加通用和安全。
"""
print(np.isnan(np.NAN)) # True
print(np.isnan(np.nan)) # True
print(np.isnan(np.NaN)) # True
print("-----------------------------")
# isnull()和isna()效果是一样的,即:判断是否是缺失值
print(pd.isnull(np.NAN)) # True
print(pd.isnull(np.nan)) # True
print(pd.isnull(np.NaN)) # True
print("-----------------------------")
print(pd.isna(np.NAN)) # True
print(pd.isna(np.nan)) # True
print(pd.isna(np.NaN)) # True
print("-----------------------------")
# notnull()和notna()效果是一样的,即:判断是否不是缺失值
print(pd.notnull(np.NAN)) # False
print(pd.notnull(np.nan)) # False
print(pd.notnull(np.NaN)) # False
print("-----------------------------")
print(pd.notna(np.NAN)) # False
print(pd.notna(np.nan)) # False
print(pd.notna(np.NaN)) # False
print(pd.isnull(10)) # False
print(pd.isnull('abc')) # False
print(pd.notnull(10)) # True
print(pd.notnull('abc')) # True
加载缺失值
缺失值的来源有两个
原始数据包含缺失值
数据整理过程中产生缺失值
"""
缺失值的来源:
原始数据包含缺失值
数据整理过程中产生缺失值
"""
# 加载数据集 :
import pandas as pd
# 加载数据时可以通过keep_default_na 与 na_values 指定加载数据时的缺失值
pd1 = pd.read_csv('data/survey_visited.csv')
# 加载数据,不包含默认缺失值
pd2 = pd.read_csv('data/survey_visited.csv',keep_default_na = False)
# 加载数据,手动指定缺失值
"""
这段代码使用pandas库读取CSV文件data/survey_visited.csv,并将空字符串""识别为NaN值,同时禁用默认的NaN识别规则。
主要功能:
pd.read_csv(): 读取CSV格式文件
na_values=[""]: 将空字符串视为缺失值(NaN)
keep_default_na=False: 不使用pandas默认的缺失值识别规则
"""
pd3 = pd.read_csv('data/survey_visited.csv',na_values=[""],keep_default_na = False)
print(pd1.head(10))
print(pd2.head(10))
print(pd3.head(10))

处理缺失值
from itertools import dropwhile
# 导包
import pandas as pd
# 1: 读取数据
# 1.1:读取训练数据集
train=pd.read_csv('../data/titanic_train.csv')
# 1.2:读取测试数据集
test=pd.read_csv('../data/titanic_test.csv')
# 2:查看数据信息
print(train.shape) # (891, 12) (行数,列数)
# print(train.head())
# 3: 查看是否获救数据(无缺失值情况)
train['Survived'].value_counts()

# 4: 缺失值的可视化,查看缺失值的分布情况,绘制直方图
# 如果windows电脑没有安装missingno库,请执行命令:pip install missingno 安装即可
import missingno as msno
# 绘制直方图,查看缺失值的分布情况
msno.bar(train)
msno.heatmap(train)

处理方式-删除缺失值
# 1:查看个列(空值)的数据统计情况
train.isnull().sum()
# 2:删除缺失值(如果缺失值占比比较少,可以采用直接删除的方式)
# 参数解释:how: 删除方式:any-任意列有缺失值就删除,all-所有列都有缺失值才删除
# axis(默认是0):0-表示删除行,1-表示删除列
# inplace(默认是False):True-表示在原数据上操作,False-表示不在原数据上操作,返回新的数据集
# train.dropna(how='any',inplace=True)
# train.dropna(how='all',inplace=True)
train.dropna(how='any', subset=['Age'],inplace=True)
# train.dropna(how='any',axis=1)
处理方式-填充缺失值
# 填充的方式处理缺失值
# 导包
import pandas as pd
import missingno as msno
from matplotlib import pyplot as plt
# 1: 读取数据
# 1.1:读取训练数据集
train=pd.read_csv('../data/titanic_train.csv')
# 1.2:读取测试数据集
test=pd.read_csv('../data/titanic_test.csv')
# 2:查看数据信息
print(train.shape) # (891, 12) (行数,列数)
print(train.head())
print("---缺失值处理方式2-非时间序列数据---------------------")
print(train.isnull().sum())
# 使用fillna()方法填充缺失值
train['Age'].fillna(0, inplace=True)
print(train.isnull().sum())
print("---缺失值处理方式3-时间序列数据---------------------")
city_day = pd.read_csv('../data/city_day.csv',parse_dates=['Date'],index_col='Date')
city_day1=city_day.copy()
# print(city_day.head(100))
# 获取其中的一段数据
print(city_day1['Xylene'][50:68])
# 思路1:用空值的上一个值填充
# city_day1['Xylene'].fillna(method='ffill',inplace=True)
# print(city_day1['Xylene'][50:65])
# 思路2:用空值的下一个值填充
city_day1['Xylene'].fillna(method='bfill',inplace=True)
print(city_day1['Xylene'][50:68])
# city_day[]
print("---缺失值处理方式4:线性插值方法填充缺失值---------------------------------")
# 线性插值方法填充缺失值:即:结合前面和后面的非空值,来推导出一个缺失值的近似值
# limit_direction有是三个参数:both,forward,backward
# both:表示同时使用前面和后面的值进行插值
# forward:表示只使用前面的值进行插值
# backward:表示只使用后面的值进行插值
city_day1.interpolate(limit_direction="both",inplace=True)
print(city_day1['Xylene'][50:68])





更多推荐


所有评论(0)