数据清理

合并数据集

离散化和面元划分
为了方便分析,连续数据常常被离散化或拆分为“面元”,可以看下面的例子:

ages = [20,22,25,27,21,23,37,31,61,55,29]

可以看到上面这组表示年龄的数据非常的杂乱无序,接下来需要用到cat函数来对它们进行分割

In [90]: bins = [18, 25, 35, 60, 100]

In [91]: cats  = pd.cut(ages, bins)
ca
In [92]: cats
Out[92]:
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (35, 60], (25, 35], (60, 100], (35, 60], (25, 35]]
Length: 11
Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]

在这里pandas返回了一个categorical对象,其结果就是cut函数的结果所产生的面元。其中包含有了表示不同分类的类型数组。
cats对象中的codes属性表示数据所在分组的标签:

In [93]: cats.codes
Out[93]: array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 1], dtype=int8)

还可以对每个分组进行统计

In [94]: pd.value_counts(cats)
Out[94]:
(18, 25]     5
(25, 35]     3
(35, 60]     2
(60, 100]    1
dtype: int64

跟数学的区间符号一样,圆括号属于开端,方括号代表闭端。当然这个也是可以修改的(通过修改参数right=False或者left=False):

In [95]: cats  = pd.cut(ages, bins,right=False)

In [96]: cats
Out[96]:
[[18, 25), [18, 25), [25, 35), [25, 35), [18, 25), ..., [35, 60), [25, 35), [60, 100), [35, 60), [25, 35)]
Length: 11
Categories (4, interval[int64]): [[18, 25) < [25, 35) < [35, 60) < [60, 100)]

这样就变成左边为闭端,右边为开端了。

如果数据太多太乱,区间表示不方便阅读也可以对每个面元区间进行命名:

In [97]: group_names = ['Youth','YoungAdult','MiddleAdult','OldAdult']

In [98]: pd.cut(ages, bins, labels = group_names)
Out[98]:
[Youth, Youth, Youth, YoungAdult, Youth, ..., MiddleAdult, YoungAdult, OldAdult, MiddleAdult, YoungAdult]
Length: 11
Categories (4, object): [Youth < YoungAdult < MiddleAdult < OldAdult]

我们分别对不同年龄段进行了命名,命名后会适用到整个categorical对象中。

检测和过滤异常值
过滤和变换异常值在数据清理过程中是个很常用的功能,它的核心点就是数组运算:

In [103]: data = DataFrame(np.random.randn(1000,4))

In [104]: data.describe()
Out[104]:
                 0            1            2            3
count  1000.000000  1000.000000  1000.000000  1000.000000
mean     -0.004025     0.014359    -0.019060    -0.011858
std       1.041391     0.984948     0.983725     0.998798
min      -3.241226    -4.064789    -3.089875    -2.980788
25%      -0.711723    -0.643988    -0.631027    -0.689812
50%       0.016191     0.012086    -0.060776     0.004594
75%       0.670837     0.723381     0.669204     0.644564
max       3.236404     3.262301     3.237268     2.856121

假设要从该DataFrame中找出绝对值大于3的值:

In [105]: col = data[2]  #找出第二列中绝对值大于3的值

In [106]: col[np.abs(col) > 3]
Out[106]:
74     3.237268
660   -3.089875
Name: 2, dtype: float64

如果要找出绝对值大于3的值所在的行

In [116]: data[(np.abs(data) > 3).any(1)]
Out[116]:
            0         1         2         3
74   0.753715  0.476283  3.237268 -0.820739
104 -3.241226 -0.397786 -0.037499  1.565827
247 -3.063914 -0.113276  0.720286 -0.416318
394  0.067374  3.262301  0.803749 -0.366958
509  3.236404  1.009665 -0.212735  1.644595
607 -0.418046 -4.064789 -0.487579  0.754295
611  3.059083 -1.537251  1.249639 -0.300786
660  0.087979 -0.907134 -3.089875  0.235577

可以结合sign函数对值进行约束,下面代码将data控制在-3至3之间:

In [127]: data[np.abs(data) > 3] = np.sign(data) * 3

In [128]: data.describe()
Out[128]:
                 0            1            2            3
count  1000.000000  1000.000000  1000.000000  1000.000000
mean     -0.004015     0.015162    -0.019208    -0.011858
std       1.039599     0.980267     0.982690     0.998798
min      -3.000000    -3.000000    -3.000000    -2.980788
25%      -0.711723    -0.643988    -0.631027    -0.689812
50%       0.016191     0.012086    -0.060776     0.004594
75%       0.670837     0.723381     0.669204     0.644564
max       3.000000     3.000000     3.000000     2.856121

排列或随机取样

利用numpy.random.permutation函数可以对DataFrame或者Series进行重新排序,也可以叫随机排序

In [135]: df = DataFrame(np.arange(5*4).reshape((5,4)))

In [136]: df
Out[136]:
    0   1   2   3
0   0   1   2   3
1   4   5   6   7
2   8   9  10  11
3  12  13  14  15
4  16  17  18  19

In [137]: sampler = np.random.permutation(5)

In [139]: sampler
Out[139]: array([0, 4, 3, 2, 1])

In [140]: df.iloc[sampler]
Out[140]:
    0   1   2   3
0   0   1   2   3
4  16  17  18  19
3  12  13  14  15
2   8   9  10  11
1   4   5   6   7

随机取样

# 在DataFrame中随机抽取了3行数据
In [144]: df.sample(n=3)
Out[144]:
    0   1   2   3
4  16  17  18  19
3  12  13  14  15
1   4   5   6   7

计算指标或哑变量
是一种将分类变量(category valiable)转换成计算指标或哑变量用于统计建模或机器学习。
如果DataFrame的某一列中含有k个不同的值,则可以派生出一个k列矩阵或DataFrame(其值全为0或1)。pandas中有个get_dummies即可实现该功能。

In [149]: s = Series(list('abca'))

In [162]: s
Out[162]:
0    a
1    b
2    c
3    a
dtype: object

In [150]: pd.get_dummies(s)
Out[150]:
   a  b  c
0  1  0  0
1  0  1  0
2  0  0  1
3  1  0  0

通过get_dummies函数我们将Series对象s中的N个不同的值衍生出了N个列,衍生后的a,b,c就是哑变量。

In [155]: df = DataFrame({'key':['b','b','a','c','a','b'],'data1':range(6)})

In [161]: df
Out[161]:
  key  data1
0   b      0
1   b      1
2   a      2
3   c      3
4   a      4
5   b      5

In [158]: pd.get_dummies(df['key'])
Out[158]:
   a  b  c
0  0  1  0
1  0  1  0
2  1  0  0
3  0  0  1
4  1  0  0
5  0  1  0

同样也可以作用的DataFrame中的某一列,生成哑变量后可以做后续分析。

正则表达式

正则常用于数据的搜索与匹配,re模块是用于正则的包,其中分为三大类:模式匹配、替换以及拆分。
拆分字符串

In [1]: import re

In [2]: text = "foo       bar\t baz   \t qux"

In [3]: re.split('\s+', text)  #这里要拆分就必须告诉re.split要去除空白符,所以用'\s+'来表示
Out[3]: ['foo', 'bar', 'baz', 'qux']

考虑到效率方面,这样写会造成每出现一次re.split('\s+', text),该正则就会被编译一次,所以可以以面向对象的思路做修改:

In [4]: regex = re.compile('\s+')

In [5]: regex.split(text)
Out[5]: ['foo', 'bar', 'baz', 'qux']

这样有了regex对象就能重复使用它而且只会编译一次正则。

如果反过来想知道这个正则在字符串中匹配到了哪些,那么可以用findall函数

In [6]: regex.findall(text)
Out[6]: ['       ', '\t ', '   \t ']

pandas的矢量化字符串函数

清理待分析的散乱数据时,经常需要将字符串规整化。

In [9]: data = {'Dave':'dave@google.com','Steve':'steve@gmail.com','Rob':'rob@gmail.c
   ...: om','Wes':np.nan}
In [12]: data = pd.Series(data)

In [13]: data
Out[13]:
Dave     dave@google.com
Steve    steve@gmail.com
Rob        rob@gmail.com
Wes                  NaN
dtype: object

如果数据中有NaN值,那在通过map函数对数据做规整的时候会报错,有一些方法可以让我们找到哪里有空缺值:
上面例子中可以用正则来匹配到电子邮件:

In [15]: pattern = '^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$'

In [18]: data.str.findall(pattern, flags=re.IGNORECASE)
Out[18]:
Dave     [.com]
Steve    [.com]
Rob      [.com]
Wes         NaN
dtype: object

要访问列表中的元素,可以传递任意索引到这两个函数中:

In [31]: matches.str.get(2)
Out[31]:
Dave    NaN
Steve   NaN
Rob     NaN
Wes     NaN
dtype: float64

# 这两个函数是等价的

In [34]: matches.str[4]
Out[34]:
Dave    NaN
Steve   NaN
Rob     NaN
Wes     NaN
dtype: float64

常用的pandas字符串方法

image

##总结
本章学习了数据库的合并离散数据的划分排列数据哑变量字符串规整化等。

在做数据分析之前先做清理觉得很有必要,可以确保效率并且在分析的时候避免出现问题。