Pandas基础(六):缺失数据的类型与查找、运算与分组、填充与剔除、插值处理
DataWhale第十四期组队学习:Joyful-Pandas
Pandas缺失数据的内容包括
虚拟数据表
import pandas as pd
import numpy as np
df = pd.read_csv('data/table_missing.csv')
df.head()
School | Class | ID | Gender | Address | Height | Weight | Math | Physics | |
---|---|---|---|---|---|---|---|---|---|
0 | S_1 | C_1 | NaN | M | street_1 | 173 | NaN | 34.0 | A+ |
1 | S_1 | C_1 | NaN | F | street_2 | 192 | NaN | 32.5 | B+ |
2 | S_1 | C_1 | 1103.0 | M | street_2 | 186 | NaN | 87.2 | B+ |
3 | S_1 | NaN | NaN | F | street_2 | 167 | 81.0 | 80.4 | NaN |
4 | S_1 | C_1 | 1105.0 | NaN | street_4 | 159 | 64.0 | 84.8 | A- |
常用的方法有isna()
和notna()
两种,分别代表缺失的是与否
# 对Series使用会返回布尔列表
df['Physics'].isna().head()
df['Physics'].notna().head()
# 对DataFrame使用会返回布尔表(用布尔值替换原有表数据)
df.isna().head()
# 对DataFrame每列缺失值数量进行统计
df.isna().sum()
df.info() # 基本信息中也包含缺失值信息
上面两个方法是整体上的,使用上可以更进一步
例如找出Physics
为缺失值的条目(对行进行筛选)
df[df['Physics'].isna()]
例如找出不包含缺失值的条目(对行进行筛选)
# all可以理解为与操作,0为列,1为行
df[df.notna().all(1)]
在Pandas中,缺失有三种表达的符号
np.nan不等与任何东西,甚至不等于自己
np.nan == np.nan # False
np.nan == 0 # False
np.nan == None # False
equals函数在比较时会自动略过两侧全是np.nan的单元格,所以不影响结果
df.equals(df)
特殊的地方是,np.nan在numpy中的类型为浮点
这就导致了,即时原来是整数的列,只要有缺失值就会变为浮点型
type(np.nan) # float
pd.Series([1,2,3]).dtype # dtype('int64')
pd.Series([1,np.nan,3]).dtype # dtype('float64')
对于布尔类型的列表,如果是np.nan填充,那么np.nan的值会自动变为True而不是False
pd.Series([1,np.nan,3],dtype='bool')
# True True True dtype:bool
但是在修改一个布尔列表的时候,会改变列表类型,而不是赋值为True
s = pd.Series([True,False],dtype='bool')
s[1]=np.nan # 这里不会把False变为True
s
'''
0 1.0
1 NaN
dtype: float64
'''
总的来看,在我们读取一个表格后,无论是什么类型的数据,默认的缺失值都是np.nan类型
这就导致了整型列变为浮点,但是字符不能转化为浮点,所以变为object类型(‘O’),若原来是浮点型的则类型不变
df['ID'].dtype # dtype('float64')
df['Math'].dtype # dtype('float64')
df['Class'].dtype # dtype('O')
None是等于自身的,且布尔值为False,且修改布尔列表的时候不会改变数据类型
None == None # True
pd.Series([None],dtype='bool') # None变为False
s = pd.Series([True,False],dtype='bool')
s[0]=None
s
'''
0 False
1 False
dtype: bool
'''
s = pd.Series([1,0],dtype='bool')
s[0]=None
s
'''
0 False
1 False
dtype: bool
'''
但是若是传入数值类型之后,None会自动变为np.nan
因而除非是人工命名为None,否则不会自动出现在Pandas中
type(pd.Series([1,None])[1]) # numpy.float64
另外None在使用equals函数时不会被略过,因而下面的情况会返回False
pd.Series([None]).equals(pd.Series([np.nan]))
可以把NaT看作是时序版本的np.nan,和自己不相等,且使用equals函数时也会自动跳过
# 构造一个时序的序列
s_time = pd.Series([pd.Timestamp('20120101')]*5)
'''
0 2012-01-01
1 2012-01-01
2 2012-01-01
3 2012-01-01
4 2012-01-01
dtype: datetime64[ns]
'''
以下三种操作都会将原序列中的下标为2的一项替换为NaT
s_time[2] = None
s_time[2] = np.nan
s_time[2] = pd.NaT
'''
0 2012-01-01
1 2012-01-01
2 NaT
3 2012-01-01
4 2012-01-01
dtype: datetime64[ns]
'''
可以感觉到之前的缺失值很混乱,所以在1.0版本之后引入了统一缺失值处理办法
在之后的版本中鼓励用户使用新的数据类型和确实类型pd.NA
对于该种类型而言,它与原来标记int上的符号区别在于首字母大写:‘Int’
s_original = pd.Series([1, 2], dtype="int64")
'''
0 1
1 2
dtype: int64
'''
s_new = pd.Series([1, 2], dtype="Int64")
'''
0 1
1 2
dtype: Int64
'''
在这种类型下,之前的三种缺失值都会被替换为NA符号,且不会改变类型
s_new[1] = np.nan
'''
0 1
1
dtype: Int64
'''
s_original = pd.Series([1, 0], dtype="bool")
'''
0 True
1 False
dtype: bool
'''
s_new = pd.Series([0, 1], dtype="boolean")
'''
0 False
1 True
dtype: boolean
'''
s_new[0] = np.nan
s_new
'''
0
1 True
dtype: boolean
'''
该类型是1.0的一大创新,目的之一就是为了区分开原本含糊不清的object类型
它本质上也属于Nullable类型,因为并不会因为含有缺失而改变类型
s = pd.Series(['dog','cat'],dtype='string')
'''
0 dog
1 cat
dtype: string
'''
s[0] = np.nan
# s[0] = None
# s[0] = pd.NaT
'''
0
1 cat
dtype: string
'''
此外,和object类型的一点重要区别就在于,在调用字符方法后
string类型返回的是Nullable类型
object则会根据缺失类型和数据类型而改变
s = pd.Series(["a", None, "b"], dtype="string")
s.str.count('a')
'''
0 1
1
2 0
dtype: Int64
'''
s2 = pd.Series(["a", None, "b"], dtype="object")
s2.str.count("a")
'''
0 1.0
1 NaN
2 0.0
dtype: float64
'''
s.str.isdigit()
'''
0 False
1
2 False
dtype: boolean
'''
s2.str.isdigit()
'''
0 False
1 None
2 False
dtype: object
'''
逻辑运算
只需看该逻辑运算的结果是否依赖pd.NA的取值,如果依赖,则结果还是NA,如果不依赖,则直接计算结果
True | pd.NA # True
pd.NA | True # True
False | pd.NA #
False & pd.NA # False
True & pd.NA #
算术运算和比较运算
除了以下两种其他的该类运算都是NA
pd.NA ** 0 # 1
1 ** pd.NA # 1
这个函数的功能就是在读取数据的时候,就把数据列转化Nullable类型
pd.read_csv('data/table_missing.csv').convert_dtypes().dtypes
使用加法的时候,缺失值为0
使用乘法的时候,缺失值为1
使用累计函数的时候(累计和、积、百分比),缺失值自动略过
s = pd.Series([2,3,np.nan,4])
s.sum() # 9.0
s.prod() # 24.0
s.cumsum()
'''
0 2.0
1 5.0
2 NaN
3 9.0
dtype: float64
'''
df_g = pd.DataFrame({
'one':['A','B','C','D',np.nan],'two':np.random.randn(5)})
df_g.groupby('one').groups
'''
{'A': Int64Index([0], dtype='int64'),
'B': Int64Index([1], dtype='int64'),
'C': Int64Index([2], dtype='int64'),
'D': Int64Index([3], dtype='int64')}
'''
df['Physics'].fillna('missing').head() # 值填充
df['Physics'].fillna(method='ffill').head() # 前向填充
df['Physics'].fillna(method='backfill').head() # 后向填充
df_d = pd.DataFrame({
'A':[np.nan,np.nan,np.nan],
'B':[np.nan,3,2],
'C':[3,2,1]})
df_d.dropna(axis=0) # 去除有缺失值的行
df_d.dropna(axis=1) # 去除有缺失值的列
# how参数:全为缺失去除all、存在缺失去除any
df_d.dropna(axis=1,how='all') # 去除了第一列
# subset参数(即在某一组列范围中搜索缺失值)
# 去除了第一行,因为B中含有缺失值
df_d.dropna(axis=0,subset=['B','C'])
默认状态下,interpolate会对缺失的值进行线性插值,此时插值与索引数值无关
s = pd.Series([1,10,15,-5,-2,np.nan,np.nan,28])
s.interpolate()
'''
0 1.0
1 10.0
2 15.0
3 -5.0
4 -2.0
5 8.0
6 18.0
7 28.0
dtype: float64
'''
s.interpolate().plot()
method中的index和time选项可以使插值线性地依赖索引,即插值为索引的线性函数
# 主要与上式的差别
s.interpolate(method='index').plot()
如果索引是时间,那么可以按照时间长短插值
s_t = pd.Series([0,np.nan,10],index=[pd.Timestamp('2012-05-01'),
pd.Timestamp('2012-05-07'),
pd.Timestamp('2012-06-03')])
'''
2012-05-01 0.0
2012-05-07 NaN
2012-06-03 10.0
dtype: float64
'''
s_t.interpolate(method='time').plot()
高级指的是与线性插值相比较,例如样条插值、多项式插值、阿基玛插值等
ser = pd.Series(np.arange(1, 10.1, .25) ** 2 + np.random.randn(37))
missing = np.array([4, 13, 14, 15, 16, 17, 18, 20, 29])
ser[missing] = np.nan
methods = ['linear', 'quadratic', 'cubic']
df = pd.DataFrame({
m: ser.interpolate(method=m) for m in methods})
df.plot()
# 限制只能插值2个,第三个还是缺失值
s = pd.Series([1,np.nan,np.nan,np.nan,5])
s.interpolate(limit=2)
# limit_direction表示插值方向,可选forward,backward,both,默认前向
# 最后两个还是缺失值
s = pd.Series([np.nan,np.nan,1,np.nan,np.nan,np.nan,5,np.nan,np.nan,])
s.interpolate(limit_direction='backward')
# limit_area表示插值区域,可选inside,outside,默认None
# 只有夹在中间的插值了
s = pd.Series([np.nan,np.nan,1,np.nan,np.nan,np.nan,5,np.nan,np.nan,])
s.interpolate(limit_area='inside')
# 只有最后面的几个插值了
s = pd.Series([np.nan,np.nan,1,np.nan,np.nan,np.nan,5,np.nan,np.nan,])
s.interpolate(limit_area='outside')
第一步,计算单列缺失值的数量,计算单列总样本数
第二步,算出比例,得到一个列的布尔列表
第三步,利用这个布尔列表进行列索引或列删除
df.loc[:,(df.isna().sum()/df.isna().count()<0.25).values]
我的理解是Nullable类型是一种为了统一NaN,Null,NaT三类缺失值而诞生的新的类型
是在原来的数值、布尔、字符等类型的基础上进行小改,优化了当出现缺失值情况时的应对
引入这个设计时为了更好的处理缺失值,统一缺失值处理方法
可以查看缺失值出现的比例;查看缺失值之间的关联性;查看总体的缺失信息;根据缺失信息判断是否为有效数据;根据缺失信息清洗数据等等
q1 = pd.read_csv('data/Missing_data_one.csv')
q1.head()
A B C
0 not_NaN 0.922 4.0
1 not_NaN 0.700 NaN
2 not_NaN 0.503 8.0
3 not_NaN 0.938 4.0
4 not_NaN 0.952 10.0
(a)请以列类型读入数据,并选出C为缺失值的行。
q1[q1['C'].isna()]
(b)现需要将A中的部分单元转为缺失值,单元格中的最小转换概率为25%,且概率大小与所在行B列单元的值成正比
q1['A'] = pd.Series(list(zip(q1['A'].values,q1['B'].values))).apply(lambda x:x[0] if np.random.rand()>0.25*x[1]/q1['B'].min() else np.nan)
pd.read_csv('data/Missing_data_two.csv').head()
编号 地区 身高 体重 年龄 工资
0 1 A 157.50 NaN 47.0 15905.0
1 2 B 202.00 91.80 25.0 NaN
2 3 C 169.09 62.18 NaN NaN
3 4 A 166.61 59.95 77.0 5434.0
4 5 B 185.19 NaN 62.0 4242.0
(a)统计各列缺失的比例并选出在后三列中至少有两个非缺失值的行
q2.isna().sum()/q2.shape[0]
q2[q2.iloc[:,-3:].isna().sum(1)<=1].head()
(b)请结合身高列和地区列中的数据,对体重进行合理插值
q2_new = q2.copy()
for area,group in q2.groupby('地区'):
q2_new.loc[group.index,'体重'] = group[['身高','体重']].sort_values(by='身高').interpolate()['体重']
q2_new = q2_new.round(decimals=2)
q2_new.head()