【学习笔记】 第07章 数据清洗和准备
前言
上一章学的是云里雾里,没有足够合适的数据进行练习,但还是先试试这一章的内容
在本章中,会讨论处理缺失数据、重复数据、字符串操作和其它分析数据转换的工具。
处理缺失数据
在许多数据分析工作中,缺失数据是经常发生的。pandas的目标之一就是尽量轻松地处理缺失数据。andas对象的所有描述性统计默认都不包括缺失数据。
缺失数据在pandas中呈现的方式有些不完美,但对于大多数用户可以保证功能正常。对于数值数据,pandas使用浮点值NaN(Not a Number)表示缺失数据。我们称其为哨兵值,可以方便的检测出来
在统计应用中,NA数据可能是不存在的数据或者虽然存在,但是没有观察到(例如,数据采集中发生了问题)。当进行数据清洗以进行分析时,最好直接对缺失数据进行分析,以判断数据采集的问题或缺失数据可能导致的偏差。
Python内置的None值在对象数组中也可以作为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
总结
较多介绍了各种应对数据处理的方法(排除,选择,填补等等),其实学的并不扎实,但好在不是很急着使用,一切的一切都是为了nlp做准备,加油加油!
其中正则表达式一部分打算单独拿出来去学
由于随笔撰写越来越像是在把书籍复制粘贴,所以决定下一章尽量精简,不再事无巨细的记载了
下一章是聚合与规整,继续努力!
本文来自博客园,作者:Lugendary,转载请注明原文链接:https://www.cnblogs.com/lugendary/p/16013767.html