【Chapter 7.2 】数据转换
本章到目前为止介绍的都是数据的重排。另一类重要操作则是过滤、清理以及其他的转换工作。
1 删除重复值
data = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'],
'k2': [1, 1, 2, 3, 3, 4, 4]})
data
Out[468]:
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返回的是一个boolean Series,表示一个row是否是重复的(根据前一行来判断):
data.duplicated()
Out[469]:
0 False
1 False
2 False
3 False
4 False
5 False
6 True
dtype: bool
drop_duplicateds返回一个DataFrame,会删除重复的部分:
data.drop_duplicates()
Out[470]:
k1 k2
0 one 1
1 two 1
2 one 2
3 two 3
4 one 3
5 two 4
上面两种方法都默认考虑所有列;另外,我们可以指定一部分来检测重复值。假设我们只想检测'k1'列的重复值:
data['v1'] = range(7)
data
Out[471]:
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
data.drop_duplicates(['k1'])
Out[472]:
k1 k2 v1
0 one 1 0
1 two 1 1
duplicated和drop_duplicated默认保留第一次观测到的数值组合。设置keep='last'
能返回最后一个:
data.drop_duplicates(['k1'])
Out[472]:
k1 k2 v1
0 one 1 0
1 two 1 1
data.drop_duplicates(['k1', 'k2'], keep='last')
Out[473]:
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
data.drop_duplicates(['k1'],keep = 'last')
Out[474]:
k1 k2 v1
4 one 3 4
6 two 4 6
2 用函数和映射来转换数据
对于许多数据集,你可能希望根据数组、Series或DataFrame列中的值来实现转换工作。我们来看看下面这组有关肉类的数据:
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]})
data
Out[475]:
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
假设你想加一列,表明每种肉来源的动物是什么。我们可以写一个映射:
meat_to_animal = {
'bacon': 'pig',
'pulled pork': 'pig',
'pastrami': 'cow',
'corned beef': 'cow',
'honey ham': 'pig',
'nova lox': 'salmon'
}
用于series的map方法接受一个函数,或是一个字典,包含着映射关系,但这里有一个小问题,有些肉是大写,有些是小写。因此,我们先用str.lower把所有的值变为小写:
lowercased = data['food'].str.lower()
lowercased
Out[477]:
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['animal'] = lowercased.map(meat_to_animal)
data
Out[478]:
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
我们也可以用一个函数解决上面的问题:
data['food'].map(lambda x: meat_to_animal[x.lower()])
Out[479]:
0 pig
1 pig
2 pig
3 cow
4 cow
5 pig
6 cow
7 pig
8 salmon
Name: food, dtype: object
使用map是一个很简便的方法,用于element-wise转换和其他一些数据清洗操作。
3 Replacing Values(替换值)
其实fillna是一个特殊换的替换操作。map可以用于修改一个object里的部分值,但是replace能提供一个更简单和更灵活的方法做到这点。下面是一个series:
data = pd.Series([1., -999., 2., -999., -1000., 3.])
data
Out[480]:
0 1.0
1 -999.0
2 2.0
3 -999.0
4 -1000.0
5 3.0
dtype: float64
3 替换值
其实fillna是一个特殊换的替换操作。map可以用于修改一个object里的部分值,但是replace能提供一个更简单和更灵活的方法做到这点。下面是一个series:
data = pd.Series([1., -999., 2., -999., -1000., 3.])
data
Out[481]:
0 1.0
1 -999.0
2 2.0
3 -999.0
4 -1000.0
5 3.0
dtype: float64
这里-999可能是用来表示缺失值的标识符。用NA来替代的话,用replace,会产生一个新series(除非使用inplace=True):
data.replace(-999, np.nan)
Out[482]:
0 1.0
1 NaN
2 2.0
3 NaN
4 -1000.0
5 3.0
dtype: float64
-999这个值可能是一个表示缺失数据的标记值。要将其替换为pandas能够理解的NA值,我们可以利用replace来产生一个新的Series(除非传入inplace=True):
data.replace(-999, np.nan)
Out[483]:
0 1.0
1 NaN
2 2.0
3 NaN
4 -1000.0
5 3.0
dtype: float64
如果你希望一次性替换多个值,可以传入一个由待替换值组成的列表以及一个替换值::
data.replace([-999, -1000], np.nan)
Out[484]:
0 1.0
1 NaN
2 2.0
3 NaN
4 NaN
5 3.0
dtype: float64
对于不同的值用不同的替换值,也是导入一个list:
data.replace([-999, -1000], [np.nan, 0])
Out[485]:
0 1.0
1 NaN
2 2.0
3 NaN
4 0.0
5 3.0
dtype: float64
参数也可以是一个dict:
data.replace({-999: np.nan, -1000: 0})
Out[486]:
0 1.0
1 NaN
2 2.0
3 NaN
4 0.0
5 3.0
dtype: float64
注意:data.replace方法和data.str.replace方法是不同的,后者会对string进行element-wise替换。
4 Renaming Axis Indexes(重命名Axis Indexes)
像是series里的value一样,axis label也能类似地是函数或映射来转换,产生一个新的object。当然也可以设置in-place不产生新的数据:
data = pd.DataFrame(np.arange(12).reshape((3, 4)),
index=['Ohio', 'Colorado', 'New York'],
columns=['one', 'two', 'three', 'four'])
data
Out[487]:
one two three four
Ohio 0 1 2 3
Colorado 4 5 6 7
New York 8 9 10 11
与series相同,axis index有一个map方法:
transform = lambda x: x[:4].upper()
transform
Out[488]: <function __main__.<lambda>>
data.index
Out[489]: Index(['Ohio', 'Colorado', 'New York'], dtype='object')
#可以赋值给index,以in-place的方式修改DataFrame:
data.index.map(transform)
Out[490]: Index(['OHIO', 'COLO', 'NEW '], dtype='object')
如果想要创建数据集的转换版(而不是修改原始数据),比较实用的方法是rename:
data.rename(index=str.title, columns=str.upper)
Out[491]:
ONE TWO THREE FOUR
Ohio 0 1 2 3
Colorado 4 5 6 7
New York 8 9 10 11
注意,rename能用于dict一样的oject,
data.rename(index={'OHIO': 'INDIANA'},
columns={'three': 'pekaboo'})
Out[492]:
one two pekaboo four
Ohio 0 1 2 3
Colorado 4 5 6 7
New York 8 9 10 11
rename能让你避免陷入手动赋值给index和columns的杂务中。可以用inplace直接修改原始数据:
data.rename(index={'OHIO': 'INDIANA'}, inplace=True)
data
Out[493]:
one two three four
Ohio 0 1 2 3
Colorado 4 5 6 7
New York 8 9 10 11
5 Discretization and Binning(离散化和装箱)
连续型数据经常被离散化或分散成bins(分箱)来分析。假设你有一组数据,你想把人分到不同的年龄组里:
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
我们把这些分到四个bin里,19~25, 26~35, 36~60, >60。可以用pandas里的cut:
bins = [18, 25, 35, 60, 100]
cats = pd.cut(ages, bins)
cats
[(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]]
返回的是一个特殊的Categorical object。我们看到的结果描述了pandas.cut如何得到bins。可以看作是一个string数组用来表示bin的名字,它内部包含了一个categories数组,用来记录不同类别的名字,并伴有表示ages的label(可以通过codes属性查看):
cats.codes #4种情况
Out[496]: array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)
cats.categories
Out[497]:
IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]]
closed='right',
dtype='interval[int64]')
pd.value_counts(cats)
Out[498]:
(18, 25] 5
(35, 60] 3
(25, 35] 3
(60, 100] 1
这里pd.value_counts(cats)是pandas.cut后bin的数量。
这里我们注意一下区间。括号表示不包含,方括号表示包含。你可以自己设定哪一边关闭(right=False):
pd.cut(ages, [18, 26, 36, 61, 100], right=False)
Out[499]:
[[18, 26), [18, 26), [18, 26), [26, 36), [18, 26), ..., [26, 36), [61, 100), [36, 61), [36, 61), [26, 36)]
Length: 12
Categories (4, interval[int64]): [[18, 26) < [26, 36) < [36, 61) < [61, 100)]
你也可以用一个list或数组给labels选项来设定bin的名字:
group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']
pd.cut(ages, bins, labels=group_names)
Out[501]:
[Youth, Youth, Youth, YoungAdult, Youth, ..., YoungAdult, Senior, MiddleAged, MiddleAged, YoungAdult]
Length: 12
Categories (4, object): [Youth < YoungAdult < MiddleAged < Senior]
如果你只是给一个bins的数量来cut,而不是自己设定每个bind的范围,cut会根据最大值和最小值来计算等长的bins。比如下面我们想要做一个均匀分布的四个bins:
data = np.random.rand(20)
pd.cut(data, 4, precision=2)
Out[503]:
[(0.022, 0.26], (0.022, 0.26], (0.26, 0.5], (0.26, 0.5], (0.26, 0.5], ..., (0.5, 0.74], (0.26, 0.5], (0.74, 0.98], (0.022, 0.26], (0.74, 0.98]]
Length: 20
Categories (4, interval[float64]): [(0.022, 0.26] < (0.26, 0.5] < (0.5, 0.74] < (0.74, 0.98]]
precision=2选项表示精确到小数点后两位。
一个近似的函数,qcut,会按照数据的分位数来分箱。取决于数据的分布,用cut通常不能保证每一个bin有一个相同数量的数据点。而qcut是按百分比来切的,所以可以得到等数量的bins:
data = np.random.randn(1000) # Normally distributed
cats = pd.qcut(data, 4) # Cut into quartiles
cats
Out[505]:
[(-0.0426, 0.637], (-0.0426, 0.637], (0.637, 3.835], (-3.259, -0.725], (-0.0426, 0.637], ..., (-3.259, -0.725], (0.637, 3.835], (-0.725, -0.0426], (-3.259, -0.725], (-3.259, -0.725]]
Length: 1000
Categories (4, interval[float64]): [(-3.259, -0.725] < (-0.725, -0.0426] < (-0.0426, 0.637] < (0.637, 3.835]]
pd.value_counts(cats)
Out[506]:
(0.637, 3.835] 250
(-0.0426, 0.637] 250
(-0.725, -0.0426] 250
(-3.259, -0.725] 250
类似的,在cut中我们可以自己指定百分比:
cats2 = pd.cut(data, [0, 0.1, 0.5, 0.9, 1.]) # 累进的百分比
cats2
Out[507]:
[(0.1, 0.5], (0.1, 0.5], NaN, NaN, (0.1, 0.5], ..., NaN, NaN, NaN, NaN, NaN]
Length: 1000
Categories (4, interval[float64]): [(0.0, 0.1] < (0.1, 0.5] < (0.5, 0.9] < (0.9, 1.0]]
pd.value_counts(cats2)
Out[508]:
(0.1, 0.5] 154
(0.5, 0.9] 127
(0.0, 0.1] 33
(0.9, 1.0] 30
dtype: int64
在之后的章节我们还会用到cut和qcut,这些离散函数对于量化和群聚分析很有用。
6 Detecting and Filtering Outliers(检测和过滤异常值)
过滤或转换异常值是数组操作的一个重头戏。下面的DataFrame有正态分布的数据:
data = pd.DataFrame(np.random.randn(1000, 4))
data.describe()
Out[509]:
0 1 2 3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean -0.025633 -0.062057 0.007312 -0.037653
std 1.029759 0.989084 0.999814 1.031975
min -3.808553 -3.442724 -2.647014 -4.198109
25% -0.735954 -0.713492 -0.691054 -0.742947
50% -0.068758 -0.079244 -0.048442 0.019906
75% 0.619517 0.628789 0.728752 0.661834
max 3.822244 3.775342 3.229284 3.698836
假设我们想要找一个列中,绝对值大于3的数字:
data.head()
Out[510]:
0 1 2 3
0 -0.046264 0.420009 -0.244728 -1.907207
1 1.207292 -0.163423 0.043483 0.543991
2 -0.902596 -1.747752 -0.217981 -2.412121
3 0.233481 -0.741199 0.330361 -0.478363
4 -0.621485 -0.850046 0.795616 1.558565
col = data[2]
col.head()
Out[511]:
0 -0.244728
1 0.043483
2 -0.217981
3 0.330361
4 0.795616
Name: 2, dtype: float64
col[np.abs(col) > 3]
Out[512]:
141 3.115218
153 3.216137
384 3.229284
Name: 2, dtype: float64
选中所有绝对值大于3的行,可以用any方法在一个boolean DataFrame上:
data[(np.abs(data) > 3)].head()
Out[513]:
0 1 2 3
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 NaN NaN NaN NaN
4 NaN NaN NaN NaN
data[(np.abs(data) > 3).any(1)] # any中axis=1表示column
Out[514]:
0 1 2 3
141 -0.863282 -1.357953 3.115218 -0.298364
153 -0.090615 0.307913 3.216137 -1.518234
201 -1.221955 3.775342 0.719315 1.125198
220 -1.204790 -3.351440 -0.562853 -0.183570
271 -0.498556 -1.282907 0.387269 -4.198109
.. ... ... ... ...
480 3.297639 -1.583162 0.037219 -0.883607
551 3.822244 -1.058748 0.759103 1.042670
558 -3.808553 -1.112588 -1.458445 -1.363454
597 -0.839910 1.555326 0.885032 3.698836
798 -3.382429 -0.283277 -1.388250 0.855298
下面是把绝对值大于3的数字直接变成-3或3:
data[np.abs(data) > 3] = np.sign(data) * 3
data[21:23]
Out[516]:
0 1 2 3
21 2.043230 -1.900945 0.284129 0.357881
22 0.443324 0.377316 -0.034172 1.592983
data.describe()
Out[517]:
0 1 2 3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean -0.025562 -0.062038 0.006751 -0.037154
std 1.022234 0.983840 0.998077 1.025519
min -3.000000 -3.000000 -2.647014 -3.000000
25% -0.735954 -0.713492 -0.691054 -0.742947
50% -0.068758 -0.079244 -0.048442 0.019906
75% 0.619517 0.628789 0.728752 0.661834
max 3.000000 3.000000 3.000000 3.000000
np.sign(data)会根据值的正负号来得到1或-1:
np.sign(data).head()
Out[518]:
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
7 Permutation and Random Sampling(排列和随机采样)
排列(随机排序)一个series或DataFrame中的row,用numpy.random.permutation函数很容易就能做到。调用permutation的时候设定好你想要进行排列的axis,会产生一个整数数组表示新的顺序:
df = pd.DataFrame(np.arange(5 * 4).reshape((5, 4)))
df
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
sampler = np.random.permutation(5)
sampler
Out[519]: array([4, 2, 3, 0, 1])
这个数组能被用在基于iloc上的indexing或take函数:
df.take(sampler)
Out[520]:
0 1 2 3
4 16 17 18 19
3 12 13 14 15
2 8 9 10 11
1 4 5 6 7
0 0 1 2 3
为了选中一个随机的子集,而且没有代替功能(既不影响原来的值,返回一个新的series或DataFrame),可以用sample方法:
df.sample(n=3)
0 1 2 3
0 0 1 2 3
3 12 13 14 15
4 16 17 18 19
如果想要生成的样本带有替代功能(即允许重复),给sample中设定replace=True:
choices = pd.Series([5, 7, -1, 6, 4])
draws = choices.sample(n=10, replace=True)
draws
4 4
4 4
1 7
1 7
1 7
1 7
4 4
3 6
1 7
1 7
dtype: int64
8 Computing Indicator/Dummy Variables(计算指示器/虚拟变量)
Dummy Variables:虚拟变量,又称虚设变量、名义变量或哑变量,用以反映质的属性的一个人工变量,是量化了的自变量,通常取值为0或1。
另一种在统计模型上的转换或机器学习应用是把一个categorical variable(类别变量)变为一个dummy or indicator matrix(虚拟或指示器矩阵)。如果DataFrame中的一列有k个不同的值,我们可以用一个矩阵或DataFrame用k列来表示,1或0。pandas有一个get_dummies函数实现这个工作,当然,你自己设计一个其实也不难。这里举个例子:
df = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'],
'data1': range(6)})
df
pd.get_dummies(df['key'])
a b c
0 0.0 1.0 0.0
1 0.0 1.0 0.0
2 1.0 0.0 0.0
3 0.0 0.0 1.0
4 1.0 0.0 0.0
5 0.0 1.0 0.0
在一些情况里,如果我们想要给column加一个prefix, 可以用data.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
如果DataFrame中的某行同属于多个分类,则事情就会有点复杂。看一下MovieLens 1M数据集,14章会更深入地研究它:
In [114]: mnames = ['movie_id', 'title', 'genres']
In [115]: movies = pd.read_table('datasets/movielens/movies.dat', sep='::',
.....: header=None, names=mnames)
In [116]: movies[:10]
Out[116]:
movie_id title genres
0 1 Toy Story (1995) Animation|Children's|Comedy
1 2 Jumanji (1995) Adventure|Children's|Fantasy
2 3 Grumpier Old Men (1995) Comedy|Romance
3 4 Waiting to Exhale (1995) Comedy|Drama
4 5 Father of the Bride Part II (1995) Comedy
5 6 Heat (1995) Action|Crime|Thriller
6 7 Sabrina (1995) Comedy|Romance
7 8 Tom and Huck (1995) Adventure|Children's
8 9 Sudden Death (1995)
Action
9 10 GoldenEye (1995) Action|Adventure|Thriller