利用python进行数据分析(其三)
数据清洗和准备
在数据分析和建模的过程中,在数据准备上(加载、清理、转换以及重塑)需要相当多的时间。因此,pandas和内置的Python标准库提供了一组高级的、灵活的、快速的工具,可以有效地将数据规整成所想要的格式。
1. 处理缺失数据
缺失数据在pandas中呈现的方式比较不完美,对于数值数据,pandas使用浮点值NAN(Not a Number)表示缺失数据,即哨兵值,可以方便检测出缺失值:
In [10]: string_data = pd.Series(['aardvark', 'artichoke', np.na
n, 'avocado'])
In [11]: string_data
Out[11]:
0 aardvark
1 artichoke
2 NaN
3 avocado
dtype: object
In [12]: string_data.isnull()
Out[12]:
0 False
1 False
2 True
3 False
dtype: bool
pandas项目中还在不断优化内部细节以更好处理缺失数据,向用户API功能,例如pandas.isnull,去除冗余细节。以下列出一些关于缺失数据处理的函数:
滤除缺失数据
过滤缺失数据有多中方法,可以通过pandas.isnull或布尔索引的手工方法,但dropna更实用。对于一个Series,dropna返回一个仅含非空数据和索引值的Series:
In [15]: from numpy import nan as NA
In [16]: data = pd.Series([1, NA, 3.5, NA, 7])
In [17]: data.dropna()
Out[17]:
0 1.0
2 3.5
4 7.0
dtype: float64
填充缺失数据
对于大多数情况,fillna方法是主要填补缺失数据的函数,通过一个常数调用fillna就会将缺失值替换成那个常数值:
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
以下列出fillna的参考:
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
2.数据转换
数据转换主要指的是过滤、清理以及其他转换工作
移除重复数据
DataFrame中出现重复行有多种原因,例如:
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
DataFrame的duplicated方法返回一个布尔型Series,表示各行是否是重复行:
In [47]: data.duplicated()
Out[47]:
0 False
1 False
2 False
3 False
4 False
5 False
6 True
dtype: bool
利用函数或映射进行数据转换
对于许多数据集,可以根据数组、Series或DataFrame列中的值来实现转换工作。
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
Series的map方法可以接受一个函数或映射关系的字典型对象,同时还需要使用Series的str.lower方法,将各个值转换成小写:
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
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
替换值
利用fillna方法填充缺失数据可以看做成值替换的一种特殊情况,map可用于修改对象的数据子集,而replace则提供一种实现该功能的更简单、更灵活的方式:
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
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
或者传入的参数是其他类型:
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
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
重命名轴索引
跟Series中的值一样,轴标签可以通过函数或映射进行转换,从而得到一个新的不同标签的对象,亦可以被就地修改,无需新建一个数据结构:
In [66]: data = pd.DataFrame(np.arange(12).reshape((3, 4)),
....: index=['Ohio', 'Colorado', 'New Yor
k'],
....: columns=['one', 'two', 'three', 'fo
ur'])
跟Series一样,轴索引也有一个map方法:
In [67]: transform = lambda x: x[:4].upper()
In [68]: data.index.map(transform)
Out[68]: Index(['OHIO', 'COLO', 'NEW '], dtype='object')
离散化和面元划分
为了便于分析,连续数据常常被离散化或拆分为“面元”(bin):
In [75]: ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
In [76]: bins = [18, 25, 35, 60, 100]
In [77]: cats = pd.cut(ages, bins)
In [78]: cats
Out[78]:
[(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]): [(18, 25] < (25, 35] < (35, 60]
< (60, 100]]
同时,pandas返回一个特殊的Categorical对象,结果展示pandas.cut划分的面元:
In [79]: cats.codes
Out[79]: array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)
In [80]: cats.categories
Out[80]:
IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]]
closed='right',
dtype='interval[int64]')
In [81]: pd.value_counts(cats)
Out[81]:
(18, 25] 5
(35, 60] 3
(25, 35] 3
(60, 100] 1
dtype: int64
检测和过滤异常值
过滤或变化异常值(outlier)在很大程度上就是运用数组运算:
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或-3的值”的行,可以在DataFrame中使用any方法:
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
排列和随机采样
利用numpy.random.permutation函数可以实现对Series或DataFrame的列的排列工作(permuting,随机重排序)。通过需要排列的轴的长度调用permutation,可产生一个表示新顺序的整数数组:
In [100]: df = pd.DataFrame(np.arange(5 * 4).reshape((5, 4)))
In [101]: sampler = np.random.permutation(5)
In [102]: sampler
Out[102]: array([3, 1, 4, 2, 0])
接着就可以在基于iloc的索引操作或take函数中使用该数组:
In [103]: df
Out[103]:
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 [104]: df.take(sampler)
Out[104]:
0 1 2 3
3 12 13 14 15
1 4 5 6 7
4 16 17 18 19
2 8 9 10 11
0 0 1 2 3
计算指标/哑变量
另外一种常用于统计建模或者机器学习的转换方式是:将分类变量(categorical variable)转换成“哑变量”或“指标矩阵”。如果DataFrame中的某一列含有k个不同的值,则可以派生一个k列矩阵或DataFrame(其值全为1或0),pandas有一个get_dummies函数可以实现该功能:
In [109]: df = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'
],
.....: 'data1': range(6)})
In [110]: pd.get_dummies(df['key'])
Out[110]:
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参数可以实现该功能:
In [111]: dummies = pd.get_dummies(df['key'], prefix='key')
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
字符串操作
Python能够成为流行的数据处理语言,部分原因是其简单易用的字符串和文本处理功能,大部分文本运算都直接做成字符串对象的内置方法。对于更为复杂的模式匹配和文本操作,pandas发挥很好地作用,能够对整组数据应用字符串表达式和正则表达式,而且能处理烦人的缺失数据。
字符串对象方法
对于许多字符串处理和脚本应用,可以采用内置的字符串方法:
In [134]: val = 'a,b, guido'
In [135]: val.split(',')
Out[135]: ['a', 'b', ' guido']
还有更快更符合Python风格的方式是,向字符串“::”的join方法传入一个列表或元祖:
In [140]: '::'.join(pieces)
Out[140]: 'a::b::guido'
replace用于将指定模式替换成另一模式,通过传入空字符串,常常用于删除模式:
In [146]: val.replace(',', '::')
Out[146]: 'a::b:: guido'
In [147]: val.replace(',', '')
Out[147]: 'ab guido'
以下列出了Python内置的字符串方法
正则表达式
正则表达式提供了一种灵活的在文本中搜索或匹配字符串模式的方式。正则表达式,常称做regex,是根据正则表达式言语编写的字符串,Python内置的re模块负责对字符串应用正则表达式。
re模块的函数可以分为撒个大类:模式匹配、替换以及拆分。描述一个或多个空白符的regex是\s+:
In [148]: import re
In [149]: text = "foo bar\t baz \tqux"
In [150]: re.split('\s+', text)
Out[150]: ['foo', 'bar', 'baz', 'qux']
调用re.split(’\s+’,text),正则表达式会先被编译,然后再在text上调用其spilt方法。
In [151]: regex = re.compile('\s+')
In [152]: regex.split(text)
Out[152]: ['foo', 'bar', 'baz', 'qux']
常用的正则表达式如下:
pandas的矢量化字符串函数
清理待分析的散乱数据时,常常需要做一些字符串规整化工作。
In [167]: data = {'Dave': 'dave@google.com', 'Steve': 'steve@gma
il.com',
.....: 'Rob': 'rob@gmail.com', 'Wes': np.nan}
In [168]: data = pd.Series(data)
In [169]: data
Out[169]:
Dave dave@google.com
Rob rob@gmail.com
Steve steve@gmail.com
Wes NaN
dtype: object
In [170]: data.isnull()
Out[170]:
Dave False
Rob False
Steve False
Wes True
dtype: bool
有两个办法可以实现矢量化的元素获取操作:要么使用str.get,要么在str属性上使用索引:
In [174]: matches = data.str.match(pattern, flags=re.IGNORECASE)
In [175]: matches
Out[175]:
Dave True
Rob True
Steve True
Wes NaN
dtype: object
要访问嵌入列表中的元素,我们可以传递索引到这个函数中:
In [176]: matches.str.get(1)
Out[176]:
Dave NaN
Rob NaN
Steve NaN
Wes NaN
dtype: float64
In [177]: matches.str[0]
Out[177]:
Dave NaN
Rob NaN
Steve NaN
Wes NaN
dtype: float64
介绍更多的pandas字符串方法