在加载了,在加载了

【学习笔记】 第07章 数据清洗和准备

前言

上一章学的是云里雾里,没有足够合适的数据进行练习,但还是先试试这一章的内容
在本章中,会讨论处理缺失数据、重复数据、字符串操作和其它分析数据转换的工具。

处理缺失数据

在许多数据分析工作中,缺失数据是经常发生的。pandas的目标之一就是尽量轻松地处理缺失数据。andas对象的所有描述性统计默认都不包括缺失数据。
缺失数据在pandas中呈现的方式有些不完美,但对于大多数用户可以保证功能正常。对于数值数据,pandas使用浮点值NaN(Not a Number)表示缺失数据。我们称其为哨兵值,可以方便的检测出来
在统计应用中,NA数据可能是不存在的数据或者虽然存在,但是没有观察到(例如,数据采集中发生了问题)。当进行数据清洗以进行分析时,最好直接对缺失数据进行分析,以判断数据采集的问题或缺失数据可能导致的偏差。
Python内置的None值在对象数组中也可以作为NA
表7-1 NA处理方法

滤除缺失数据

过滤掉缺失数据的办法有很多种。你可以通过pandas.isnull或布尔索引的手工方法,但dropna可能会更实用一些。对于一个Series,dropna返回一个仅含非空数据和索引值的Series
而对于DataFrame对象,事情就有点复杂了。你可能希望丢弃全NA或含有NA的行或列。dropna默认丢弃任何含有缺失值的行
传入how='all'将只丢弃全为NA的那些行,用这种方式丢弃列,只需传入axis=1即可

填充缺失数据

你可能不想滤除缺失数据(有可能会丢弃跟它有关的其他数据),而是希望通过其他方式填补那些“空洞”。对于大多数情况而言,fillna方法是最主要的函数。通过一个常数调用fillna就会将缺失值替换为那个常数值
若是通过一个字典调用fillna,就可以实现对不同的列填充不同的值
fillna默认会返回新对象,但也可以对现有对象进行就地修改
只要有些创新,你就可以利用fillna实现许多别的功能。比如说,你可以传入Series的平均值或中位数

数据转换

目前为止介绍的都是数据的重排。另一类重要操作则是过滤、清理以及其他的转换工作

移除重复数据

DataFrame的duplicated方法返回一个布尔型Series,表示各行是否是重复行(前面出现过的行)
还有一个与此相关的drop_duplicates方法,它会返回一个DataFrame,重复的数组会标为False
这两个方法默认会判断全部列,你也可以指定部分列进行重复项判断

In [6]: data = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'],^M
   ...:    ....:                      'k2': [1, 1, 2, 3, 3, 4, 4]})^M
   ...:

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

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

In [9]: data['v1'] = range(7)

In [10]: data
Out[10]:
    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 [11]: data.drop_duplicates(['k1'])
Out[11]:
    k1  k2  v1
0  one   1   0
1  two   1   1

利用函数或映射进行数据转换

对于许多数据集,可能希望根据数组、Series或DataFrame列中的值来实现转换工作。

In [12]: 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 [13]: data
Out[13]:
          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

In [14]:

In [14]: meat_to_animal = {
    ...:   'bacon': 'pig',
    ...:     'pulled pork': 'pig',
    ...:       'pastrami': 'cow',
    ...:         'corned beef': 'cow',
    ...:           'honey ham': 'pig',
    ...:             'nova lox': 'salmon'
    ...:             }//假设你想要添加一列表示该肉类食物来源的动物类型。我们先编写一个不同肉类到动物的映射

In [15]: lowercased = data['food'].str.lower()

In [16]: data['animal'] = lowercased.map(meat_to_animal)//Series的map方法可以接受一个函数或含有映射关系的字典型对象,但是这里有一个小问题,即有些肉类的首字母大写了,而另一些则没有。因此,我们还需要使用Series的str.lower方法,将各个值转换为小写

In [17]: data
Out[17]:
          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

In [18]: data['food'].map(lambda x: meat_to_animal[x.lower()])//我们也可以传入一个能够完成全部这些工作的函数
Out[18]:
0       pig
1       pig
2       pig
3       cow
4       cow
5       pig
6       cow
7       pig
8    salmon
Name: food, dtype: object

使用map是一种实现元素级转换以及其他数据清理工作的便捷方式

替换值

利用fillna方法填充缺失数据可以看做值替换的一种特殊情况。前面已经看到,map可用于修改对象的数据子集,而replace则提供了一种实现该功能的更简单、更灵活的方式。
如果你希望一次性替换多个值,可以传入一个由待替换值组成的列表以及一个替换值

这里在此我做了很多尝试,想起来pandas大多数情况是只调用不修改的,展示的只是视图而已

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

In [29]: data = pd.Series([1., -999., 2., -999., -1000., 3.])

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

In [31]: data = pd.Series([1., -999., 2., -999., -1000., 3.])

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

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

In [34]: data.replace({-999: 1,-1000:0})
Out[34]:
0    1.0
1    1.0
2    2.0
3    1.0
4    0.0
5    3.0
dtype: float64

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

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

离散化和面元划分

为了便于分析,连续数据常常被离散化或拆分为“面元”(bin)。
In [75]: ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
接下来将这些数据划分为“18到25”、“26到35”、“35到60”以及“60以上”几个面元。要实现该功能,你需要使用pandas的cut函数

In [36]:  ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]

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

In [38]: cats = pd.cut(ages, bins)

In [39]: cats
Out[39]:
[(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划分的面元。你可以将其看做一组表示面元名称的字符串。它的底层含有一个表示不同分类名称的类型数组,以及一个codes属性中的年龄数据的标签

检测和过滤异常值

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

In [44]: data
Out[44]:
            0         1         2         3
0   -0.363903 -1.278023 -1.718323 -2.747957
1    1.037538  0.840784 -0.144361  0.355591
2   -0.272585  1.983460  0.338699  1.018303
3   -1.583209 -1.392340 -1.998593 -0.084851
4   -0.524281 -1.086736 -0.999823  0.859482
..        ...       ...       ...       ...
995  0.696939  0.258398  1.431371 -0.100909
996  0.329427  0.343470 -0.823664  0.538415
997  2.095085  0.226578 -1.015634 -2.331116
998 -0.944052 -0.700679 -0.847058 -0.769666
999  1.283432  0.391991 -0.541897 -1.436875

[1000 rows x 4 columns]

In [45]: data.describe()
Out[45]:
                 0            1            2            3
count  1000.000000  1000.000000  1000.000000  1000.000000
mean     -0.050277     0.063577     0.042917    -0.023500
std       0.984850     1.005470     1.023675     1.009030
min      -3.647421    -3.211393    -2.966168    -3.370681
25%      -0.687954    -0.584375    -0.623441    -0.688921
50%      -0.059857     0.050337     0.043822    -0.042049
75%       0.626286     0.704441     0.710952     0.639140
max       2.854289     3.045197     3.312462     2.914750

In [46]: col = data[2]

In [47]: col
Out[47]:
0     -1.718323
1     -0.144361
2      0.338699
3     -1.998593
4     -0.999823
         ...
995    1.431371
996   -0.823664
997   -1.015634
998   -0.847058
999   -0.541897
Name: 2, Length: 1000, dtype: float64

In [48]: col[np.abs(col) > 3]
Out[48]:
153    3.264977
378    3.098591
433    3.312462
Name: 2, dtype: float64

In [49]: data[(np.abs(data) > 3).any(1)]
Out[49]:
            0         1         2         3
153 -0.444463  2.005838  3.264977  1.802070
207 -3.634144 -0.915001 -0.584681 -0.507880
378 -0.841497 -0.780359  3.098591 -0.763214
393 -3.647421 -0.404315 -0.325813  0.568900
432 -0.060457 -0.390066 -1.155703 -3.236473
433  0.825561  0.258779  3.312462 -2.143935
485  0.935750 -3.164354  0.458492  0.610785
489 -3.324985 -1.043230  1.085755 -0.417958
498  0.447374  1.247860  0.329587 -3.370681
572 -0.046206 -3.211393  0.763012 -0.078630
682  1.002848 -0.957785 -0.245168 -3.230028
688  0.357851  3.045197 -0.645623 -0.236254

排列和随机采样

利用numpy.random.permutation函数可以轻松实现对Series或DataFrame的列的排列工作(permuting,随机重排序)。通过需要排列的轴的长度调用permutation,可产生一个表示新顺序的整数数组
利用numpy.random.permutation函数可以轻松实现对Series或DataFrame的列的排列工作(permuting,随机重排序)。通过需要排列的轴的长度调用permutation

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

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

In [55]: df
Out[55]:
    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 [56]: df.take(sampler)
Out[56]:
    0   1   2   3
3  12  13  14  15
1   4   5   6   7
2   8   9  10  11
4  16  17  18  19
0   0   1   2   3

计算指标/哑变量

另一种常用于统计建模或机器学习的转换方式是:将分类变量(categorical variable)转换为“哑变量”或“指标矩阵”。

如果DataFrame的某一列中含有k个不同的值,则可以派生出一个k列矩阵或DataFrame(其值全为1和0)。pandas有一个get_dummies函数可以实现该功能

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

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

In [59]: pd.get_dummies(df['key'])
Out[59]:
   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的列加上一个前缀,以便能够跟其他数据进行合并。get_dummies的prefix参数可以实现该功能

字符串操作


正则表达式

正则表达式提供了一种灵活的在文本中搜索或匹配(通常比前者复杂)字符串模式的方式。正则表达式,常称作regex,是根据正则表达式语言编写的字符串。Python内置的re模块负责对字符串应用正则表达式。

pandas的矢量化字符串函数

清理待分析的散乱数据时,常常需要做一些字符串规整化工作。更为复杂的情况是,含有字符串的列有时还含有缺失数据
通过data.map,所有字符串和正则表达式方法都能被应用于(传入lambda表达式或其他函数)各个值,但是如果存在NA(null)就会报错。为了解决这个问题,Series有一些能够跳过NA值的面向数组方法,进行字符串操作。通过Series的str属性即可访问这些方法。例如,我们可以通过str.contains检查各个电子邮件地址是否含有"gmail

In [67]:  data = {'Dave': 'dave@google.com', 'Steve': 'steve@gmail.com',
    ...:    .....:         'Rob': 'rob@gmail.com', 'Wes': np.nan}

In [68]: data = pd.Series(data)

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

In [70]: data.isnull()
Out[70]:
Dave     False
Steve    False
Rob      False
Wes       True
dtype: bool

In [71]: data.str.contains('gmail')
Out[71]:
Dave     False
Steve     True
Rob       True
Wes        NaN
dtype: object

表7-5 部分矢量化字符串方法

总结

较多介绍了各种应对数据处理的方法(排除,选择,填补等等),其实学的并不扎实,但好在不是很急着使用,一切的一切都是为了nlp做准备,加油加油!
其中正则表达式一部分打算单独拿出来去学
由于随笔撰写越来越像是在把书籍复制粘贴,所以决定下一章尽量精简,不再事无巨细的记载了
下一章是聚合与规整,继续努力!

posted @ 2022-03-16 17:49  Lugendary  阅读(40)  评论(0编辑  收藏  举报