0%

第三章-数据清洗

处理缺失数据

对于数值数据,pandas使用浮点值NaN(Not a Number)表示缺失数据

初始化数据

1
2
3
4
5
6
7
8
9
In [10]: string_data = pd.Series(['aardvark', 'artichoke', np.nan, 'avocado'])

In [11]: string_data
Out[11]:
0 aardvark
1 artichoke
2 NaN
3 avocado
dtype: object

isnull()

判断当前数据是否是缺失值,是就返回true,否就返回false

1
2
3
4
5
6
7
In [12]: string_data.isnull()
Out[12]:
0 False
1 False
2 True
3 False
dtype: bool

利用索引直接置为None

1
2
3
4
5
6
7
8
9
In [13]: string_data[0] = None

In [14]: string_data.isnull()
Out[14]:
0 True
1 False
2 True
3 False
dtype: bool

滤除缺失数据

dropna

对于Series

等价于data[data.notnull()]

1
2
3
4
5
6
In [18]: data[data.notnull()]
Out[18]:
0 1.0
2 3.5
4 7.0
dtype: float64

对于DataFrame

默认丢弃任何含有缺失值的行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [19]: data = pd.DataFrame([[1., 6.5, 3.], [1., NA, NA],
....: [NA, NA, NA], [NA, 6.5, 3.]])

In [20]: cleaned = data.dropna()

In [21]: data
Out[21]:
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 [22]: cleaned
Out[22]:
0 1 2
0 1.0 6.5 3.0
传入how='all'将只丢弃全为NA的那些行:
1
2
3
4
5
6
In [23]: data.dropna(how='all')
Out[23]:
0 1 2
0 1.0 6.5 3.0
1 1.0 NaN NaN
3 NaN 6.5 3.0
丢弃全为NA的列,传入axis=1即可

先添加第四列为全NA的列:data[4] = NA

1
2
3
4
5
6
7
In [26]: data.dropna(axis=1, how='all')
Out[26]:
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参数删除指定确实个数的行

用切片将不用观察的行和列之间赋值NA,然后再用默认dropna丢弃

切片赋值NA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
n [27]: df = pd.DataFrame(np.random.randn(7, 3))

In [28]: df.iloc[:4, 1] = NA

In [29]: df.iloc[:2, 2] = NA

In [30]: df
Out[30]:
0 1 2
0 -0.204708 NaN NaN
1 -0.555730 NaN NaN
2 0.092908 NaN 0.769023
3 1.246435 NaN -1.296221
4 0.274992 0.228913 1.352917
5 0.886429 -2.001637 -0.371843
6 1.669025 -0.438570 -0.539741
1
2
3
4
5
6
In [31]: df.dropna()
Out[31]:
0 1 2
4 0.274992 0.228913 1.352917
5 0.886429 -2.001637 -0.371843
6 1.669025 -0.438570 -0.539741

填充缺失数据

主要使用fillna这个方法

将缺失值替换为指定的常数值

1
2
3
4
5
6
7
8
9
10
In [33]: df.fillna(0)
Out[33]:
0 1 2
0 -0.204708 0.000000 0.000000
1 -0.555730 0.000000 0.000000
2 0.092908 0.000000 0.769023
3 1.246435 0.000000 -1.296221
4 0.274992 0.228913 1.352917
5 0.886429 -2.001637 -0.371843
6 1.669025 -0.438570 -0.539741

通过字典对不同的列填充不同的值

1
2
3
4
5
6
7
8
9
10
In [34]: df.fillna({1: 0.5, 2: 0})
Out[34]:
0 1 2
0 -0.204708 0.500000 0.000000
1 -0.555730 0.500000 0.000000
2 0.092908 0.500000 0.769023
3 1.246435 0.500000 -1.296221
4 0.274992 0.228913 1.352917
5 0.886429 -2.001637 -0.371843
6 1.669025 -0.438570 -0.539741

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

1
2
3
4
5
6
7
8
9
10
11
12
In [35]: _ = df.fillna(0, inplace=True)

In [36]: df
Out[36]:
0 1 2
0 -0.204708 0.000000 0.000000
1 -0.555730 0.000000 0.000000
2 0.092908 0.000000 0.769023
3 1.246435 0.000000 -1.296221
4 0.274992 0.228913 1.352917
5 0.886429 -2.001637 -0.371843
6 1.669025 -0.438570 -0.539741

对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
32
33
34
35
In [37]: df = pd.DataFrame(np.random.randn(6, 3))

In [38]: df.iloc[2:, 1] = NA

In [39]: df.iloc[4:, 2] = NA

In [40]: df
Out[40]:
0 1 2
0 0.476985 3.248944 -1.021228
1 -0.577087 0.124121 0.302614
2 0.523772 NaN 1.343810
3 -0.713544 NaN -2.370232
4 -1.860761 NaN NaN
5 -1.265934 NaN NaN

In [41]: df.fillna(method='ffill')
Out[41]:
0 1 2
0 0.476985 3.248944 -1.021228
1 -0.577087 0.124121 0.302614
2 0.523772 0.124121 1.343810
3 -0.713544 0.124121 -2.370232
4 -1.860761 0.124121 -2.370232
5 -1.265934 0.124121 -2.370232

In [42]: df.fillna(method='ffill', limit=2)
Out[42]:
0 1 2
0 0.476985 3.248944 -1.021228
1 -0.577087 0.124121 0.302614
2 0.523772 0.124121 1.343810
3 -0.713544 0.124121 -2.370232
4 -1.860761 NaN -2.370232
5 -1.265934 NaN -2.370232

传入Series的平均值或中位数:

1
2
3
4
5
6
7
8
9
10
In [43]: data = pd.Series([1., NA, 3.5, NA, 7])

In [44]: data.fillna(data.mean())
Out[44]:
0 1.000000
1 3.833333
2 3.500000
3 3.833333
4 7.000000
dtype: float64

数据转换

移除重复数据

1
2
3
4
5
6
7
8
9
10
11
12
13
In [45]: data = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'],
....: 'k2': [1, 1, 2, 3, 3, 4, 4]})

In [46]: data
Out[46]:
k1 k2
0 one 1
1 two 1
2 one 2
3 two 3
4 one 3
5 two 4
6 two 4

duplicated方法

表示各行是否是重复行(前面出现过的行):

1
2
3
4
5
6
7
8
9
10
In [47]: data.duplicated()
Out[47]:
0 False
1 False
2 False
3 False
4 False
5 False
6 True
dtype: bool

drop_duplicates方法

指定部分列进行重复项判断。假设我们还有一列值,且只希望根据k1列过滤重复项:

duplicated和drop_duplicates默认保留的是第一个出现的值组合。传入keep='last'则保留最后一个:

1
2
3
4
5
6
7
8
9
In [51]: data.drop_duplicates(['k1', 'k2'], keep='last')
Out[51]:
k1 k2 v1
0 one 1 0
1 two 1 1
2 one 2 2
3 two 3 3
4 one 3 4
6 two 4 6

利用函数或映射进行数据转换(主要使用map)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [52]: data = pd.DataFrame({'food': ['bacon', 'pulled pork', 'bacon',
....: 'Pastrami', 'corned beef', 'Bacon',
....: 'pastrami', 'honey ham', 'nova lox'],
....: 'ounces': [4, 3, 12, 6, 7.5, 8, 3, 5, 6]})

In [53]: data
Out[53]:
food ounces
0 bacon 4.0
1 pulled pork 3.0
2 bacon 12.0
3 Pastrami 6.0
4 corned beef 7.5
5 Bacon 8.0
6 pastrami 3.0
7 honey ham 5.0
8 nova lox 6.0

添加一列表示该肉类食物来源的动物类型

编写一个不同肉类到动物的映射

1
2
3
4
5
6
7
8
meat_to_animal = {
'bacon': 'pig',
'pulled pork': 'pig',
'pastrami': 'cow',
'corned beef': 'cow',
'honey ham': 'pig',
'nova lox': 'salmon'
}

调用map前,有些肉类肉类首字母大写,比如前面0 bacon和5 Bacon,所以需要先转换成小写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
In [55]: lowercased = data['food'].str.lower()

In [56]: lowercased
Out[56]:
0 bacon
1 pulled pork
2 bacon
3 pastrami
4 corned beef
5 bacon
6 pastrami
7 honey ham
8 nova lox
Name: food, dtype: object

然后使用字典形式在原先data基础上再加1列,用map实现肉类与肉类动物来源一一对应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
In [57]: data['animal'] = lowercased.map(meat_to_animal)

In [58]: data
Out[58]:
food ounces animal
0 bacon 4.0 pig
1 pulled pork 3.0 pig
2 bacon 12.0 pig
3 Pastrami 6.0 cow
4 corned beef 7.5 cow
5 Bacon 8.0 pig
6 pastrami 3.0 cow
7 honey ham 5.0 pig
8 nova lox 6.0 salmon

一个能够完成全部这些工作的函数:

1
2
3
4
5
6
7
8
9
10
11
12
In [59]: data['food'].map(lambda x: meat_to_animal[x.lower()])
Out[59]:
0 pig
1 pig
2 pig
3 cow
4 cow
5 pig
6 cow
7 pig
8 salmon
Name: food, dtype: object

替换值(主要使用replace)

map可用于修改对象的数据子集,而replace则提供了一种实现该功能的更简单、更灵活的方式

1
2
3
4
5
6
7
8
9
10
In [60]: data = pd.Series([1., -999., 2., -999., -1000., 3.])

In [61]: data
Out[61]:
0 1.0
1 -999.0
2 2.0
3 -999.0
4 -1000.0
5 3.0

-999这个值可能是一个表示缺失数据的标记值。要将其替换为pandas能够理解的NA值,我们可以利用replace来产生一个新的Series(除非传入inplace=True):

1
2
3
4
5
6
7
8
9
In [62]: data.replace(-999, np.nan)
Out[62]:
0 1.0
1 NaN
2 2.0
3 NaN
4 -1000.0
5 3.0
dtype: float64

一次性替换多个值

可以传入一个由待替换值组成的列表以及一个替换值

1
2
3
4
5
6
7
8
9
In [63]: data.replace([-999, -1000], np.nan)
Out[63]:
0 1.0
1 NaN
2 2.0
3 NaN
4 NaN
5 3.0
dtype: float64

每个值有不同的替换值

可以传递一个替换列表:

1
2
3
4
5
6
7
8
9
In [64]: data.replace([-999, -1000], [np.nan, 0])
Out[64]:
0 1.0
1 NaN
2 2.0
3 NaN
4 0.0
5 3.0
dtype: float64

传入的参数也可以是字典:

1
2
3
4
5
6
7
8
9
In [65]: data.replace({-999: np.nan, -1000: 0})
Out[65]:
0 1.0
1 NaN
2 2.0
3 NaN
4 0.0
5 3.0
dtype: float64

注:data.replace方法与data.str.replace不同,后者做的是字符串的元素级替换。我们会在后面学习Series的字符串方法。

重命名轴索引

跟Series中的值一样,轴标签也可以通过函数或映射进行转换,从而得到一个新的不同标签的对象。轴还可以被就地修改,而无需新建一个数据结构

data.index中map

1
2
3
In [66]: data = pd.DataFrame(np.arange(12).reshape((3, 4)),
....: index=['Ohio', 'Colorado', 'New York'],
....: columns=['one', 'two', 'three', 'four'])

使用匿名函数实现修改所有列

one two three four
Ohio 0 1 2 3
Colorado 4 5 6 7
New York 8 9 10 11
1
2
3
4
In [67]: transform = lambda x: x[:4].upper()

In [68]: data.index.map(transform)
Out[68]: Index(['OHIO', 'COLO', 'NEW '], dtype='object')

就地修改

1
2
3
4
5
6
7
8
In [69]: data.index = data.index.map(transform)

In [70]: data
Out[70]:
one two three four
OHIO 0 1 2 3
COLO 4 5 6 7
NEW 8 9 10 11

rename

创建转换版不修改原式数据

创建数据集的转换版(而不是修改原始数据),比较实用的方法是rename:

1
2
3
4
5
6
In [71]: data.rename(index=str.title, columns=str.upper)
Out[71]:
ONE TWO THREE FOUR
Ohio 0 1 2 3
Colo 4 5 6 7
New 8 9 10 11

部分轴标签的更新:

用字典指明将原来换成对应的什么

1
2
3
4
5
6
7
In [72]: data.rename(index={'OHIO': 'INDIANA'},
....: columns={'three': 'peekaboo'})
Out[72]:
one two peekaboo four
INDIANA 0 1 2 3
COLO 4 5 6 7
NEW 8 9 10 11

就地修改

inplace=True

1
2
3
4
5
6
7
8
In [73]: data.rename(index={'OHIO': 'INDIANA'}, inplace=True)

In [74]: data
Out[74]:
one two three four
INDIANA 0 1 2 3
COLO 4 5 6 7
NEW 8 9 10 11

检测和过滤异常值

一个含有正态分布数据的DataFrame

1
2
3
4
5
6
7
8
9
10
11
12
13
In [92]: data = pd.DataFrame(np.random.randn(1000, 4))

In [93]: data.describe()
Out[93]:
0 1 2 3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean 0.049091 0.026112 -0.002544 -0.051827
std 0.996947 1.007458 0.995232 0.998311
min -3.645860 -3.184377 -3.745356 -3.428254
25% -0.599807 -0.612162 -0.687373 -0.747478
50% 0.047101 -0.013609 -0.022158 -0.088274
75% 0.756646 0.695298 0.699046 0.623331
max 2.653656 3.525865 2.735527 3.366626

在列中找指定范围的值

找出某列中绝对值大小超过3的值

1
2
3
4
5
6
7
In [94]: col = data[2]

In [95]: col[np.abs(col) > 3]
Out[95]:
41 -3.399312
136 -3.745356
Name: 2, dtype: float64

挑选指定行,any

要选出全部含有“超过3或-3的值”的行,你可以在布尔型DataFrame中使用any方法

1
2
3
4
5
6
7
8
9
10
11
12
13
In [96]: data[(np.abs(data) > 3).any(1)]
Out[96]:
0 1 2 3
41 0.457246 -0.025907 -3.399312 -0.974657
60 1.951312 3.260383 0.963301 1.201206
136 0.508391 -0.196713 -3.745356 -1.520113
235 -0.242459 -3.056990 1.918403 -0.578828
258 0.682841 0.326045 0.425384 -3.428254
322 1.179227 -3.184377 1.369891 -1.074833
544 -3.548824 1.553205 -2.186301 1.277104
635 -0.578093 0.193299 1.397822 3.366626
782 -0.207434 3.525865 0.283070 0.544635
803 -3.645860 0.255475 -0.549574 -1.907459

any(1)表示每一行,any(0)表示每一列

限制值的范围

1
2
3
4
5
6
7
8
9
10
11
12
13
In [97]: data[np.abs(data) > 3] = np.sign(data) * 3

In [98]: data.describe()
Out[98]:
0 1 2 3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean 0.050286 0.025567 -0.001399 -0.051765
std 0.992920 1.004214 0.991414 0.995761
min -3.000000 -3.000000 -3.000000 -3.000000
25% -0.599807 -0.612162 -0.687373 -0.747478
50% 0.047101 -0.013609 -0.022158 -0.088274
75% 0.756646 0.695298 0.699046 0.623331
max 2.653656 3.000000 2.735527 3.000000

根据数据的值是正还是负,np.sign(data)可以生成1和-1:

1
2
3
4
5
6
7
8
In [99]: np.sign(data).head()
Out[99]:
0 1 2 3
0 -1.0 1.0 -1.0 1.0
1 1.0 -1.0 1.0 -1.0
2 1.0 1.0 1.0 -1.0
3 -1.0 -1.0 1.0 -1.0
4 -1.0 1.0 -1.0 -1.0

计算指标/哑变量

get_dummies

实现独热编码

独热编码就是用来表示当前这个数据点属于什么类,如果是这个类对应位置就是1,不是这个类对应位置就是0.

下面这个例子是对key这一列进行独热编码。以第0行为例。第0行是b,所以在独热矩阵中第0行,b对应的列是1,a和c对应的列是0,表示第一行表示的数是b

加前缀

1
2
3
4
5
6
7
8
9
10
11
In [112]: df_with_dummy = df[['data1']].join(dummies)

In [113]: df_with_dummy
Out[113]:
data1 key_a key_b key_c
0 0 0 1 0
1 1 0 1 0
2 2 1 0 0
3 3 0 0 1
4 4 1 0 0
5 5 0 1 0

MovieLens 1M数据集:

读取

利用read_table,从路径下读取文件,其中以"::"作为分隔符,列名不用原来的列名,而用之前规定好的列名

从数据集中抽取出不同的genre值

1
2
3
4
5
6
In [117]: all_genres = []

In [118]: for x in movies.genres:
.....: all_genres.extend(x.split('|'))

In [119]: genres = pd.unique(all_genres)
1
2
3
4
5
6
In [120]: genres
Out[120]:
array(['Animation', "Children's", 'Comedy', 'Adventure', 'Fantasy',
'Romance', 'Drama', 'Action', 'Crime', 'Thriller','Horror',
'Sci-Fi', 'Documentary', 'War', 'Musical', 'Mystery', 'Film-Noir',
'Western'], dtype=object)

构建指标DataFrame

首先从全零开始

全零矩阵的行是之前读取的电影部数,列是之前抽取的genre值

1
2
3
zero_matrix = np.zeros((len(movies), len(genres)))
dummies = pd.DataFrame(zero_matrix, columns=genres)
dummies