本文转载自微信公众号「数仓宝贝库」,作者王恺 等。转载本文请联系数仓宝贝库公众号。
01处理缺失数据
缺失数据是数据文件中最常见的问题之一。在Pandas中的缺失值表示为NA,其中数值类型的缺失值标记为NaN(Not a Number),datetime类型的缺失值标记为NaT(Not a Time)。缺失值的存在可能会引起后续的数据分析错误。
在清洗数据之前,首先要确定数据中是否存在缺失值以及缺失值的确切位置。Pandas提供了isna()和notna()方法,用于快速确定Series和DataFrame对象中缺失值的位置,其语法格式如下:
- pd.isna(data) 或者 data.isna()
- pd.notna(data) 或者 data.notna()
data可以是一个Series对象,返回值为布尔Series对象;也可以是一个DataFrame对象,返回值为布尔DataFrame对象;还可以是一个标量值,此时返回一个布尔值。对于isna方法,data中如果包含NA值,则返回值对应的位置为True,其余正常元素对应的位置为False。notna方法与isna方法相反,data中如果包含NA值,则返回值对应的位置为False,其余正常元素对应的位置为True。
Pandas提供了几种处理缺失值的方法,即为缺失值重新赋值、删除缺失值所在的行、删除数据缺失率较高的列等。删除缺失值的方法一般用于缺失值较少、对整体数据影响不大的情况。
1)Pandas提供了fillna方法用于将缺失值重新赋值为新的元素值,常用的语法格式如下:
- result=data.fillna(value,method=None,…)
其中data既可以是Series对象,也可以是DataFrame对象。value可以是一个固定的元素,比如0;value也可以是一个字典对象、Series对象或DataFrame对象,用于将data中匹配标签(Series对象)或匹配列标签(DataFrame对象)所对应的缺失值替换为不同的值,未与value匹配的data中的缺失值则不会被替换。method表示填充NA值的方法,缺省时默认为None。method='ffill'或者'method=pad'时使用上一个有效值填充NA值,而method='bfill'或者'method=backfill'时使用下一个有效值来填充NA值。fillna的返回值为新赋值的Series或DataFrame对象。
2)Pandas提供了dropna方法,实现按行或列删除NA值的功能,其语法格式如下:
- result =data.dropna(axis=0,how='any',…)
如果data是Series对象,则axis只能等于0,直接删除所有的NA值。如果data是DataFrame对象,参数axis为0或'index',实现删除缺失值所在的行;如果设置axis为1或'columns',则删除缺失值所在的列;axis缺省时默认为0。how = 'any'表示只要有NA值存在,就会删除所在的行或列;how='all'表示只有当全部元素都是NA值时才会执行删除操作;how缺省时默认为'any'。dropna的返回值为删掉缺失值的Series或DataFrame对象。
3)Pandas还为Series和DataFrame提供了interpolate()方法,通过插值法补充缺失的数据点,其语法格式如下:
- result=data.interpolate(method='linear',axis=0,…)
method表示使用的插值方法,缺省时默认为线性插值'linear'。常用的还有
'time',即根据时间间隔进行插值。除此之外,method还提供了更高级的插值方法,比如Scipy库中的'nearest'、'zero'、'slinear'、'quadratic'、'cubic'、
'spline'、'barycentric'、'polynomial'等。axis参数的用法与dropna方法相同。
下面通过代码清单演示Pandas提供的处理缺失数据方法的用法。
- 代码清单 Pandas处理缺失数据方法的用法示例
-
- 1 import pandas as pd
-
- 2 import numpy as np
-
- 3 df = pd.DataFrame({'A': [1, 2.1, np.nan, 4.7, 5.6], 'B': [.25, np.nan,
-
- np.nan, 4, 12.2]})
-
- 4 print('df:\n',df)
-
- 5 print('df中的元素是否为缺失值:\n',pd.isna(df))
-
- 6 df1 = df.fillna(0)#用固定值来填充
-
- 7 print('用0填充缺失值后的数据:\n',df1)
-
- 8 df2 = df.fillna(value={'A': 1, 'B': 2}) #将A、B列中的NaN分别替换为1、2
-
- 9 print('用字典填充缺失值后的数据:\n',df2)
-
- 10 df3 = df.fillna(df.mean()) #用每列的平均值来填充
-
- 11 print('用每列的平均值填充缺失值后的数据:\n',df3)
-
- 12 df4 = df.dropna()
-
- 13 print('删除缺失值后的数据:\n',df4)
-
- 14 df5=df.interpolate()
-
- 15 print('线性插值法填充缺失值后的数据:\n',df5)
-
- 16 df6= df.interpolate(method='polynomial',order=2)
-
- 17 print('多项式插值法填充缺失值后的数据:\n',df6)
程序执行结束后,输出结果如下:
- df:
-
- A B
-
- 0 1.0 0.25
-
- 1 2.1 NaN
-
- 2 NaN NaN
-
- 3 4.7 4.00
-
- 4 5.6 12.20
-
- df中的元素是否为缺失值:
-
- A B
-
- 0 False False
-
- 1 False True
-
- 2 True True
-
- 3 False False
-
- 4 False False
-
- 用0填充缺失值后的数据:
-
- A B
-
- 0 1.0 0.25
-
- 1 2.1 0.00
-
- 2 0.0 0.00
-
- 3 4.7 4.00
-
- 4 5.6 12.20
-
- 用字典填充缺失值后的数据:
-
- A B
-
- 0 1.0 0.25
-
- 1 2.1 2.00
-
- 2 1.0 2.00
-
- 3 4.7 4.00
-
- 4 5.6 12.20
-
- 用每列的平均值填充缺失值后的数据:
-
- A B
-
- 0 1.00 0.250000
-
- 1 2.10 5.483333
-
- 2 3.35 5.483333
-
- 3 4.70 4.000000
-
- 4 5.60 12.200000
-
- 删除缺失值后的数据:
-
- A B
-
- 0 1.0 0.25
-
- 3 4.7 4.00
-
- 4 5.6 12.20
-
- 线性插值法填充缺失值后的数据:
-
- A B
-
- 0 1.0 0.25
-
- 1 2.1 1.50
-
- 2 3.4 2.75
-
- 3 4.7 4.00
-
- 4 5.6 12.20
-
- 多项式插值法填充缺失值后的数据:
-
- A B
-
- 0 1.000000 0.250
-
- 1 2.100000 -1.975
-
- 2 3.433333 -0.725
-
- 3 4.700000 4.000
-
- 4 5.600000 12.200
下面对代码清单中的代码做简要说明。
- 第3行代码通过字典创建了一个5行2列的DataFrame对象df,其中使用Numpy库中的np.nan设置了几个缺失值,如第4行print函数的输出结果所示。
- 第5行代码通过isna方法来确认df中的哪些元素是缺失值,返回的结果中,True表示df中对应位置为缺失值,False则表示对应位置为正常值。
- 第6行代码通过fillna方法使用固定值0对df中的缺失值进行填充,并将返回结果赋值给新的DataFrame对象df1,如第7行print函数的输出结果所示。
- 第8行代码通过fillna方法使用字典{'A': 1, 'B': 2}对df中的缺失值进行填充,A列中的NaN填充为1,B列中的NaN填充为2,并将返回结果赋值给新的DataFrame对象df2,如第9行print函数的输出结果所示。
- 第10行代码通过fillna方法,使用df.mean()对df中的缺失值进行填充。df.mean()的返回值是Series对象类型,表示df每列的平均值,fillna方法再用该平均值对df每列的缺失值依次进行填充,并将返回结果赋值给新的DataFrame对象df3,如第11行print函数的输出结果所示。
- 第12行代码通过dropna方法对df中的缺失值进行删除,采用默认的参数设置,即删除所有至少包含一个缺失值的行,并将返回结果赋值给新的DataFrame对象df4,如第13行print函数的输出结果所示。
- 第14行代码通过interpolate方法对df中的缺失值进行插值处理,采用默认的参数设置,即以列为单位进行线性插值,并将返回结果赋值给新的DataFrame对象df5,如第15行print函数的输出结果所示。
- 第16行代码通过interpolate方法对df中的缺失值进行了插值处理,参数method='polynomial'表示采用多项式插值法,order=2指定多项式的阶数为2,并将返回结果赋值给新的DataFrame对象df6,如第17行print函数的输出结果所示。
Tips
上文介绍的fillna、dropna、interpolate等处理缺失值的方法都是在数据的拷贝上进行处理,不会改变原数据。
02删除重复数据
除数据缺失之外,数据文件中还可能存在重复的数据,这会对分析结果产生影响,因此,在数据清洗阶段还需要删除重复数据。
要识别数据中是否存在重复行,可以使用Pandas提供的duplicated方法。常用的语法格式如下:
- data.duplicated(subset=None,keep='first',…)
data可以是一个Series对象,也可以是一个DataFrame对象,返回值为一个表示重复行的布尔类型Series对象。当data是Series对象时,duplicated方法中没有subset参数。subset是列标签参数,表示考虑某些特定列来标识重复数据,缺省时默认为考虑全部列。keep决定标记哪个重复数据,缺省时默认为'first',即对于data中的每一组重复数据,第一次出现的位置标记为False,其他重复出现的位置则标记为True。keep='last'时则表示重复数据最后一次出现的位置才标记为False,其余位置为True。
可以使用drop_duplicates方法删除重复的行,常用的语法格式如下:
- result=data.drop_duplicates(subset=None,keep='first',…)
drop_duplicates方法的参数含义与duplicated方法相同。keep参数决定保留哪一行重复数据。返回值为删掉重复数据的Series或DataFrame对象。
Pandas还可以利用drop_duplicates方法处理数据标签中存在重复项的情况,具体方法是:先使用Index.duplicated方法确定数据标签中是否存在重复值,然后再利用得到的布尔数组对数据执行行切片。
下面通过如下代码清单演示Pandas提供的处理重复数据方法的用法。
- 代码清单 Pandas处理重复数据方法的用法示例
-
- 1 import pandas as pd
-
- 2 df = pd.DataFrame({'brand': ['YumYum', 'YumYum', 'Indomie', 'Indomie',
-
- 'Indomie'], 'style': ['cup', 'cup', 'cup', 'pack', 'pack'], 'rating': [4,
-
- 4, 3.5, 15, 5]},index=['a', 'a', 'b', 'c', 'd'])
-
- 3 print('df:\n',df)
-
- 4 print('对于全列,df的行中是否存在重复项:\n',df.duplicated())
-
- 5 df1=df.drop_duplicates()
-
- 6 print('删除上述重复项后的df:\n',df1)
-
- 7 print('对于brand和style列,df的行中是否存在重复项:\n',df.duplicated(subset= ['brand', 'style']))
-
- 8 df2=df.drop_duplicates(subset= ['brand', 'style'])
-
- 9 print('删除上述重复项后的df:\n',df2)
-
- 10 print('df的index是否存在重复项:\n',df.index.duplicated(keep='last'))
-
- 11 df3=df[~df.index.duplicated(keep='last')]
-
- 12 print('删除index重复项后的df:\n',df3)
程序执行结束后,输出结果如下:
- df:
-
- brand style rating
-
- a YumYum cup 4.0
-
- a YumYum cup 4.0
-
- b Indomie cup 3.5
-
- c Indomie pack 15.0
-
- d Indomie pack 5.0
-
- 对于全列,df的行中是否存在重复项:
-
- a False
-
- a True
-
- b False
-
- c False
-
- d False
-
- dtype: bool
-
- 删除上述重复项后的df:
-
- brand style rating
-
- a YumYum cup 4.0
-
- b Indomie cup 3.5
-
- c Indomie pack 15.0
-
- d Indomie pack 5.0
-
- 对于brand和style列,df的行中是否存在重复项:
-
- a False
-
- a True
-
- b False
-
- c False
-
- d True
-
- dtype: bool
-
- 删除上述重复项后的df:
-
- brand style rating
-
- a YumYum cup 4.0
-
- b Indomie cup 3.5
-
- c Indomie pack 15.0
-
- df的index是否存在重复项:
-
- [ True False False False False]
-
- 删除index重复项后的df:
-
- brand style rating
-
- a YumYum cup 4.0
-
- b Indomie cup 3.5
-
- c Indomie pack 15.0
-
- d Indomie pack 5.0
下面对代码清单中的代码做简要说明。
- 第2行代码通过字典创建了一个5行3列的DataFrame对象df,并设置index参数为['a', 'a', 'b', 'c', 'd'],如第3行print函数的输出结果所示。
- 第4行代码通过duplicated方法来确认df中是否存在重复数据,采用了默认的参数设置,即根据df的所有列来标识重复数据。对于df中的每一组重复数据,第一次出现的位置标记为False,其他重复出现的位置则标记为True,返回值为Series对象。从print函数的输出结果分析得到,df中的第2行为重复数据。
- 第5行代码通过drop_duplicates方法删除了第4行代码中确定的重复数据所在的行,并将返回结果赋值给新的DataFrame对象df1,如第6行print函数的输出结果所示。
- 第7行代码通过duplicated方法来确认df中是否存在重复数据,subset= ['brand', 'style']表示根据df的brand和style两列来标识重复数据。对于df中的每一组重复数据,第一次出现的位置标记为False,其他重复出现的位置则标记为True,返回值为Series对象。从print函数的输出结果分析得到,df中的第2行和第5行为重复数据。
- 第8行代码通过drop_duplicates方法删除了第6行代码中确定的重复数据所在的行,并将返回结果赋值给新的DataFrame对象df2,如第9行print函数的输出结果所示。
- 第10行代码通过duplicated方法来确认df.index(即df的行标签)中是否存在重复项,keep='last'表示对于df.index中的每一组重复数据,最后一次出现的位置标记为Fasle,其他重复出现的位置则标记为True,返回值为一维布尔数组。从print函数的输出结果分析得到,df中第1行的行标签为重复项。
- 第11行代码通过布尔数组切片的方法删除了df中行标签重复的行。首先对第10行代码得到的布尔数组进行取反操作,即将第1行重复项标记为Fasle,其他项标记为True;然后再利用得到的新布尔数组对df进行切片,截取True对应的行,并将返回结果赋值给新的DataFrame对象df3,如第12行print函数的输出结果所示。
Tips
本文摘编于《Python数据分析与应用》,经出版方授权发布。