一、处理缺失数据

1.1 判断缺失数据

缺失数据在pandas中呈现的方式有些不完美,但对于大多数用户可以保证功能正常。对于数值数据,pandas使用浮点值NaN(Not a Number)表示缺失数据。常被称为哨兵值,可以方便的检查出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pandas as pd
import numpy as np
string_data = pd.Series(["aardvark","artichoke",np.nan,"avocado"])

print(string_data)
print(string_data.isnull())

# 结果
0 aardvark
1 artichoke
2 NaN
3 avocado
dtype: object
0 False
1 False
2 True
3 False
dtype: bool

在pandas中,我们采用了R语言中的惯用法,即将缺失值表示为NA,它表示不可用not available。在统计应用中,NA数据可能是不存在的数据或者虽然存在,但是没有观察到(例如,数据采集中发生了问题)。当进行数据清洗以进行分析时,最好直接对缺失数据进行分析,以判断数据采集的问题或缺失数据可能导致的偏差。

Python内置的None值在对象数组中也可以作为NA:

1
2
3
4
5
6
7
8
9
10
···
string_data[0] = None
print(string_data.isnull())

# 结果
0 True
1 False
2 True
3 False
dtype: bool

pandas项目中还在不断优化内部细节以更好处理缺失数据,像用户API功能,例如pandas.isnull,去除了许多恼人的细节。下表列出来一些关于缺失数据处理的函数。

1.2 滤除缺失数据

过滤掉缺失数据的办法有很多种。你可以通过pandas.isnull或布尔索引的手工方法,但dropna可能会更实用一些。对于一个Series,dropna返回一个仅含非空数据和索引值的Series:

1
2
3
4
5
6
7
8
9
10
import numpy as np
import pandas as pd
data = pd.Series([1, np.nan, 3.5, np.nan, 7])
print(data.dropna())

# 结果
0 1.0
2 3.5
4 7.0
dtype: float64

等价于:

1
2
3
4
5
6
7
8
···
print(data[data.notnull()])

# 结果
0 1.0
2 3.5
4 7.0
dtype: float64

而对于DataFrame对象,事情就有点复杂了。你可能希望丢弃全NA或含有NA的行或列。dropna默认丢弃任何含有缺失值的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from numpy import nan as NA 
data = pd.DataFrame([[1., 6.5, 3.], [1., NA, NA], [NA, NA, NA], [NA, 6.5, 3.]])
print("data\n",data)
cleaned = data.dropna()
print("cleaned\n", cleaned)

# 结果
data
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
cleaned
0 1 2
0 1.0 6.5 3.0

传入how='all'将只会丢弃全为NA的行:

1
2
3
4
5
6
7
8
···
print(data.dropna(how='all'))

# 结果
0 1 2
0 1.0 6.5 3.0
1 1.0 NaN NaN
3 NaN 6.5 3.0

用这种方式丢弃列,只需要传入axis=1即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
···
data[4] = NA
print(data)
print(data.droupna(axis=1, how="all"))

# 结果
0 1 2 4
0 1.0 6.5 3.0 NaN
1 1.0 NaN NaN NaN
2 NaN NaN NaN NaN
3 NaN 6.5 3.0 NaN
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

另一个滤除DataFrame行的问题涉及时间序列数据。假设只想只想保留下一部分观测数据,可以用thresh参数实现目的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
df = pd.DataFrame(np.random.randn(7,3))
print(df)
df.iloc[:4, 1] = NA
print(df)
df.iloc[:2, 2] = NA
print(df)
print("-----滤除缺失值-----")
print(df.dropna())
print(df.dropna(thresh=2)) # 这是根据表头来的

# 结果
0 1 2
0 0.338764 0.555826 0.982879
1 1.377187 1.301776 0.293192
2 0.671224 0.887082 -1.901940
3 -0.246175 0.756483 -0.034251
4 1.776606 -1.924073 -0.141436
5 1.107341 -0.935829 -1.276003
6 0.352095 1.011681 -0.849741
0 1 2
0 0.338764 NaN 0.982879
1 1.377187 NaN 0.293192
2 0.671224 NaN -1.901940
3 -0.246175 NaN -0.034251
4 1.776606 -1.924073 -0.141436
5 1.107341 -0.935829 -1.276003
6 0.352095 1.011681 -0.849741
0 1 2
0 0.338764 NaN NaN
1 1.377187 NaN NaN
2 0.671224 NaN -1.901940
3 -0.246175 NaN -0.034251
4 1.776606 -1.924073 -0.141436
5 1.107341 -0.935829 -1.276003
6 0.352095 1.011681 -0.849741
-----滤除缺失值-----
0 1 2
4 1.776606 -1.924073 -0.141436
5 1.107341 -0.935829 -1.276003
6 0.352095 1.011681 -0.849741
0 1 2
2 0.671224 NaN -1.901940
3 -0.246175 NaN -0.034251
4 1.776606 -1.924073 -0.141436
5 1.107341 -0.935829 -1.276003
6 0.352095 1.011681 -0.849741

1.3 填充缺失数据

你可能不想滤除缺失数据(有可能会丢弃跟它有关的其他数据),而是希望通过其他方式填补那些“空洞”。对于大多数情况而言,fillna方法是最主要的函数。通过一个常数调用fillna就会将缺失值替换为那个常数值:

1
2
3
4
5
6
7
8
9
10
11
12
···
df.fillna(0)

# 结果
0 1 2
0 1.305311 0.000000 0.000000
1 -0.073801 0.000000 0.000000
2 -0.060627 0.000000 -0.593883
3 0.548139 0.000000 0.641778
4 -1.184230 1.254661 -1.126495
5 -1.200294 -0.782388 -1.953092
6 0.250766 -0.331018 0.574662

若是通过一个字典调用fillna,就可以实现对不同的列填充不同的值:

1
2
3
4
5
6
7
8
9
10
11
12
···
print(df.fillna({1:0.5, 2:0}))

# 结果
0 1 2
0 -0.198972 0.500000 0.000000
1 -0.146856 0.500000 0.000000
2 0.895844 0.500000 -0.072794
3 -0.597936 0.500000 -1.414083
4 0.388478 2.227255 1.836909
5 0.049245 -0.598432 -0.133057
6 0.081092 -0.244341 0.211493

fillna默认返回新对象,但也可以对现有对象进行就地修改:

1
2
3
4
5
6
7
8
9
10
11
12
···
_ = df.fillna(0, inplace=True)

# 结果
0 1 2
0 -1.154844 0.500000 0.000000
1 -1.027803 0.500000 0.000000
2 0.729127 0.500000 -0.622004
3 0.544168 0.500000 -0.743561
4 -0.308872 0.654360 -0.208569
5 -0.052516 0.406727 -0.322353
6 -1.189762 -0.297617 0.503907

对reindexing有效的那些插值方法也可用于fillna:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
···
df = pf.DataFrame(np.random.randon(6,3))
df.iloc[2:, 1] = NA
df.iloc[4:, 2] = NA

print(df)
print(df.fillna(method="ffill"))
print(df.fillna(method="ffill", limit=2))

# 结果
0 1 2
0 0.607541 0.954887 -0.417059
1 -2.160793 -0.328896 -0.480799
2 -0.632083 NaN -1.436467
3 -0.707566 NaN -1.436972
4 -0.140556 NaN NaN
5 -0.148451 NaN NaN
0 1 2
0 0.607541 0.954887 -0.417059
1 -2.160793 -0.328896 -0.480799
2 -0.632083 -0.328896 -1.436467
3 -0.707566 -0.328896 -1.436972
4 -0.140556 -0.328896 -1.436972
5 -0.148451 -0.328896 -1.436972
0 1 2
0 0.607541 0.954887 -0.417059
1 -2.160793 -0.328896 -0.480799
2 -0.632083 -0.328896 -1.436467
3 -0.707566 -0.328896 -1.436972
4 -0.140556 NaN -1.436972
5 -0.148451 NaN -1.436972

只要有些创新,你就可以利用fillna实现许多别的功能。比如说,你可以传入Series的平均值或中位数:

1
2
3
4
5
6
7
8
9
10
11
···
data = pd.Series([1., NA, 3.5, NA, 7])
print(data.fillna(data.mean()))

# 结果
0 1.000000
1 3.833333
2 3.500000
3 3.833333
4 7.000000
dtype: float64