1.1处理缺失值
pandas的目标之一就是尽可能无痛地处理缺失值,例如pandas对象的所有描述性统计信息默认情况下是排除缺失值的。对于数值型数据,pandas使用浮点值NaN(Not a number)来表示缺失值,在pandas中,我们将缺失值称为NA(not available)。在统计学应用中,NA数据可以是不存在的数据或者是存在但不可观察的数据(例如在数据收集过程中出现了问题)。当清洗数据用于分析时,对缺失数据本身进行分析以确定数据收集问题或数据丢失导致的数据偏差通常很重要。Python内建的None值在对象数组中也被当作NA处理。
NA处理方法:
1.1.1过滤缺失值
有多种过滤缺失值的方法。可以使用pandas.isnull和布尔值索引手动地过滤缺失值,也可以使用dropna来过滤。在Series上使用dropna,它会返回Series中所有的非空数据及其索引值:
in:
from numpy import nan as NA
data = pd.Series([1,NA,3.5,NA,7])
data.dropna()
out:
0 1.0
2 3.5
4 7.0
dtype: float64
上面的代码等价于:
in:
data[data.notnull()]
out:
0 1.0
2 3.5
4 7.0
dtype: float64
当处理DataFrame对象时,会稍微复杂一些。dropna默认情况下会删除包含缺失值的行:
in:
data = pd.DataFrame([[1,6.5,3],[1,NA,NA],[NA,NA,NA],[NA,6.5,3]])
data
out:
0 1 2
0 1.0 6.5 3.0
1 1.0 NaN NaN
2 NaN NaN NaN
3 NaN 6.5 3.0
in:
data.dropna()
out:
0 1 2
0 1.0 6.5 3.0
传入how = ‘all’,将删除所有值均为NA的行:
in:
data.dropna(how = 'all')
out:
0 1 2
0 1.0 6.5 3.0
1 1.0 NaN NaN
3 NaN 6.5 3.0
如果要对列进行同样的操作,只需传入参数axis = 1
in:
data.dropna(how = 'all',axis = 1)
out:
0 1 2
0 1.0 6.5 3.0
1 1.0 NaN NaN
2 NaN NaN NaN
3 NaN 6.5 3.0
若想保留一定数量的观察值的行或列,可以用thresh参数来表示:
in:
df = pd.DataFrame(np.random.randn(7,3))
df.iloc[:4,1] = NA
df.iloc[:2,2] = NA
df
out:
0 1 2
0 -0.087487 NaN NaN
1 -0.763685 NaN NaN
2 0.768197 NaN 0.989586
3 -0.793039 NaN 0.515711
4 0.813376 1.179414 -1.997658
5 0.114463 1.775586 -0.096684
6 -0.538589 0.783257 -2.108953
in:
df.dropna(thresh = 2)
out:
0 1 2
2 0.768197 NaN 0.989586
3 -0.793039 NaN 0.515711
4 0.813376 1.179414 -1.997658
5 0.114463 1.775586 -0.096684
6 -0.538589 0.783257 -2.108953
1.1.2补全缺失值
大多数情况下,主要使用fillna方法来补全缺失值。调用fillna时,可以使用一个常数来代替缺失值:
in:
df.fillna(0)
out:
0 1 2
0 -0.087487 0.000000 0.000000
1 -0.763685 0.000000 0.000000
2 0.768197 0.000000 0.989586
3 -0.793039 0.000000 0.515711
4 0.813376 1.179414 -1.997658
5 0.114463 1.775586 -0.096684
6 -0.538589 0.783257 -2.108953
在调用fillna时使用字典,可以为不同列设定不同的填充值
in:
df.fillna({1:0.5,2:0})
out:
0 1 2
0 -0.087487 0.500000 0.000000
1 -0.763685 0.500000 0.000000
2 0.768197 0.500000 0.989586
3 -0.793039 0.500000 0.515711
4 0.813376 1.179414 -1.997658
5 0.114463 1.775586 -0.096684
6 -0.538589 0.783257 -2.108953
fillna返回的是一个新的对象,但也可以修改已经存在的对象:
in:
_ = df.fillna(0,inplace = True)
#或df.fillna(0,inplace = True)
df
out:
0 1 2
0 -0.855959 0.000000 0.000000
1 -2.634261 0.000000 0.000000
2 -1.618698 0.000000 0.745549
3 -0.631199 0.000000 -0.521110
4 0.316833 -0.661527 -0.295758
5 -0.348348 -1.188701 0.856542
6 1.151774 0.164366 -0.645428
in:
df.fillna(method = 'bfill') #bfill 是向前填充 ffill是向后填充
out:
0 1 2
0 -0.900219 0.582162 2.132188
1 -0.837954 0.582162 2.132188
2 0.425441 0.582162 2.132188
3 1.232482 0.582162 0.293976
4 -0.926626 0.582162 -0.681543
5 0.589723 -0.721490 0.665564
6 -0.808692 0.419491 -0.116198
in:
df.fillna(method = 'bfill',limit = 2) #用limit限制每列或每行可以替代NaN的数目
out:
0 1 2
0 -0.900219 NaN 2.132188
1 -0.837954 NaN 2.132188
2 0.425441 0.582162 2.132188
3 1.232482 0.582162 0.293976
4 -0.926626 0.582162 -0.681543
5 0.589723 -0.721490 0.665564
6 -0.808692 0.419491 -0.116198
可以将平均数或中位数用于填充缺失值,如:
data.fillna(data.mean())
fillna函数参数:
1.2数据转换
1.2.1删除重复值
in:
data = pd.DataFrame({'k1':['one','two']*3+['two'],'k2':[1,1,2,3,3,4,4]})
data
out:
k1 k2
0 one 1
1 two 1
2 one 2
3 two 3
4 one 3
5 two 4
6 two 4
DataFrame的duplicated方法返回的是一个布尔值Series,这个Series反映的是每一行是否存在重复:
in:
data.duplicated()
out:
0 False
1 False
2 False
3 False
4 False
5 False
6 True
dtype: bool
drop_duplicates返回的是DataFrame,内容是duplicated返回数组中为False的部分:
in:
data.drop_duplicates()
out:
k1 k2
0 one 1
1 two 1
2 one 2
3 two 3
4 one 3
5 two 4
这些方法都是默认对列进行操作,可以指定数据的任何子集来检测是否有重复。
in:
data['v1'] = range(7)
data
out:
k1 k2 v1
0 one 1 0
1 two 1 1
2 one 2 2
3 two 3 3
4 one 3 4
5 two 4 5
6 two 4 6
in:
data.drop_duplicates(['k1'])
out:
k1 k2 v1
0 one 1 0
1 two 1 1
duplicated和drop_duplicates默认都是保留第一个观测到的值,传入参数keep = 'last',将会返回最后一个
1.2.2使用函数或映射进行数据转换
对于许多数据集,可能希望基于DataFrame中的数组、列或列中的数值进行一些转换。
对于如下数据:
in:
data = pd.DataFrame({'food':['bacon','pulled pork','bacon','pastrami','corned
beef','bacon','pastrami','honey ham','nova lox'],'ounces':[4,3,12,6,7,8,3,5,6]})
data
out:
food ounces
0 bacon 4
1 pulled pork 3
2 bacon 12
3 pastrami 6
4 corned beef 7
5 bacon 8
6 pastrami 3
7 honey ham 5
8 nova lox 6
要添加一列用于表明每种食物的动物肉类型。先写一个事物和肉类的映射:
in:
meat_to_animal = {
'bacon':'pig',
'pulled pork':'pig',
'pastrami':'cow',
'corned beef':'cow',
'honey ham':'pig',
'nova lox':'salmon'
}
Series的map方法接受一个函数或一个包含映射关系的字典型对象。
in:
data['animal'] = data['food'].map(meat_to_animal)
data
out:
food ounces animal
0 bacon 4 pig
1 pulled pork 3 pig
2 bacon 12 pig
3 pastrami 6 cow
4 corned beef 7 cow
5 bacon 8 pig
6 pastrami 3 cow
7 honey ham 5 pig
8 nova lox 6 salmon
使用map是一种可以便携执行按元素转换及其他清洗相关操作的方法
1.2.3替代值
可以用replace来进行替换某些值,replace方法一般是生成新的数据,若要修改原数据,需要传入inplace = True:
in:
data = pd.Series([1,-999,2,-999,-100,3])
data
out:
0 1
1 -999
2 2
3 -999
4 -100
5 3
dtype: int64
in:
data.replace(-999,np.nan)
out:
0 1.0
1 NaN
2 2.0
3 NaN
4 -100.0
5 3.0
dtype: float64
如果想一次替代多个值,可以传入一个列表和一个替代值:
in:
data.replace([-999,-100],np.nan)
out:
0 1.0
1 NaN
2 2.0
3 NaN
4 NaN
5 3.0
dtype: float64
要将不同的值替换为不同的值,可以传入替代值的列表:
in:
data.replace([-999,-100],[np.nan,0])
out:
0 1.0
1 NaN
2 2.0
3 NaN
4 0.0
5 3.0
dtype: float64
参数也可以通过字典进行传递:
in:
data.replace({-999:np.nan,-100:0})
out:
0 1.0
1 NaN
2 2.0
3 NaN
4 0.0
5 3.0
dtype: float64
1.2.4重命名轴索引
与Series类似,轴索引也有一个map方法,结合该方法可以更改轴索引
in:
data = pd.DataFrame(np.arange(12).reshape((3,4)),index = ['ohio','colorado','new york'],columns = ['one','two','three','four'])
data
out:
one two three four
ohio 0 1 2 3
colorado 4 5 6 7
new york 8 9 10 11
in:
transform = lambda x: x[:4].upper()
data.index.map(transform)
out:
Index(['OHIO', 'COLO', 'NEW '], dtype='object')
in:
data.index = data.index.map(transform)
data
out:
one two three four
OHIO 0 1 2 3
COLO 4 5 6 7
NEW 8 9 10 11
如果想要创建数据集转换后的版本,并且不修改原有的数据集,一个有用的方法是rename:
in:
data.rename(index = str.title,columns = str.upper)
out:
ONE TWO THREE FOUR
Ohio 0 1 2 3
Colo 4 5 6 7
New 8 9 10 11
rename可以结合字典型对象使用,为轴标签的子集提供新的值:
in:
data.rename(index = {'OHIO':'indina'})
out:
one two three four
indina 0 1 2 3
COLO 4 5 6 7
NEW 8 9 10 11
1.2.5离散化和分箱
连续值经常需要进行离散化,或者分离成“箱子”进行研究。比如年龄:
为了实现年龄的分组,可以使用pandas中的cut
in:
ages = [20,22,25,27,21,23,37,31,64,45,41,32]
bins = [18,25,35,60,100]
cats = pd.cut(ages,bins)
cats
out:
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]
Length: 12
Categories (4, interval[int64, right]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]
pandas返回的对象是一个特殊的Categorical对象。我们看到的输出描述了由pandas.cut计算出的箱。可以把它当作一个表示箱名的字符串数组,在内部包含一个categoreis(类别)数组,它制定了不同的类别名称以及codes属性中的ages(年龄)数据标签:
in:
cats.codes
out:
array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)
in:
cats.categories
out:
IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]], dtype='interval[int64, right]')
in:
pd.value_counts(cats)
out:
(18, 25] 5
(25, 35] 3
(35, 60] 3
(60, 100] 1
dtype: int64
pd.value_counts(cats)是对pandas.cut的结果中的箱数量的计数
与区间的数学符号一致,小括号表示边是开放的,中括号表示它是封闭的,即闭区间。可以通过传递right = False来改变哪一边是封闭的。
也可以通过向labels选项传递一个列表或数组来传入自定义的箱名
in:
group_names = ['youths','youngadult','middleaged','senior']
pd.cut(ages,bins,labels = group_names)
out:
['youths', 'youths', 'youths', 'youngadult', 'youths', ..., 'youngadult', 'senior', 'middleaged', 'middleaged', 'youngadult']
Length: 12
Categories (4, object): ['youths' < 'youngadult' < 'middleaged' < 'senior']
如果传入cut的不是显示的箱边,而是箱的数量(整数),pandas将根据数据中的最小值和最大值来计算出等长的箱
in:
data = np.random.rand(20)
pd.cut(data,4,precision = 2)
out:
[(0.06, 0.26], (0.26, 0.47], (0.47, 0.67], (0.47, 0.67], (0.47, 0.67], ..., (0.67, 0.88], (0.06, 0.26], (0.26, 0.47], (0.67, 0.88], (0.47, 0.67]]
Length: 20
Categories (4, interval[float64, right]): [(0.06, 0.26] < (0.26, 0.47] < (0.47, 0.67] < (0.67, 0.88]]
precision = 2 的选项将十进制精度限制在两位
qcut是一个与分箱密切相关的函数,基于样本分位数进行分箱。取决于数据的分布,使用cut通常不会使每个箱具有相同数据量的数据点。由于qcut使用样本的分位数,可以通过qcut获得等长的箱。
in:
data = np.random.randn(1000) #正态分布
cats = pd.qcut(data,4) #切成4份
cats
out:
[(-3.645, -0.703], (-0.0383, 0.668], (-0.703, -0.0383], (-3.645, -0.703], (-0.0383, 0.668], ..., (-0.703, -0.0383], (-0.0383, 0.668], (-0.0383, 0.668], (0.668, 3.156], (-3.645, -0.703]]
Length: 1000
Categories (4, interval[float64, right]): [(-3.645, -0.703] < (-0.703, -0.0383] < (-0.0383, 0.668] < (0.668, 3.156]]
in:
pd.value_counts(cats)
out:
(-3.645, -0.703] 250
(-0.703, -0.0383] 250
(-0.0383, 0.668] 250
(0.668, 3.156] 250
dtype: int64
与cut类似,可以传入自定义的分位数(0和1之间的数据,包括边)
in:
pd.qcut(data,[0,0.1,0.5,0.9,1])
out:
[(-1.32, -0.0383], (-0.0383, 1.289], (-1.32, -0.0383], (-3.645, -1.32], (-0.0383, 1.289], ..., (-1.32, -0.0383], (-0.0383, 1.289], (-0.0383, 1.289], (1.289, 3.156], (-3.645, -1.32]]
Length: 1000
Categories (4, interval[float64, right]): [(-3.645, -1.32] < (-1.32, -0.0383] < (-0.0383, 1.289] < (1.289, 3.156]]