文章目录
前言
在经过上文章对数据的缺失值进行过滤和补全,下面讲数据的一些转换,主要讲解数据的重新排列、过滤以及其他转换是另外一系列重要的操作。
一、数据转换
1.1 删除重复值
当DataFrame出现重复行时:
import pandas as pd
data = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'],
'k2': [1, 1, 2, 3, 3, 4, 4]
})
print(data)
---------------------------------------------------------
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,这个Series反映的是每一个行是否存在重复(与之前出现过的行相同)情况:
a = data.duplicated()
print(a)
---------------------------------------------------------
0 False
1 False
2 False
3 False
4 False
5 False
6 True
dtype: bool
drop_duplicates返回的是DataFrame,内容是duplicated返回数组中为Flase的部分:
a = data.drop_duplicates()
print(a)
---------------------------------------------------------
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)
print(data)
a = data.drop_duplicates(['k1'])
print(a)
---------------------------------------------------------
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
k1 k2 v1
0 one 1 0
1 two 1 1
duplicated和drop_duplicates默认都是保留第一个观测到的值。传入参数keep=‘last’将会返回最后一个:
a = data.drop_duplicates(['k1','k2'],keep='last')
print(a)
---------------------------------------------------------
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
1.2 使用函数或映射进行数据转换
对于许多数据集,可能希望基于DataFrame中的数组、列或列中的数值进行一些转换。考虑下面这些收集到的关于肉类的假设数据:
data1 = 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]})
print(data1)
---------------------------------------------------------
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方法接收一个函数或一个包含映射关系的字典型对象,但是这里有些肉类大写了,而另一部的肉类没有。因此,我们需要使用Series的str.lower方法将每个值都转换为小写:
lo = data1['food'].str.lower()
print(lo)
data1['animal'] = lo.map(meat_to_animal)
print(data1)
---------------------------------------------------------
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
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
我们也可以传入一个能够完成所有工作的函数:
a = data1['food'].map(lambda x: meat_to_animal[x.lower()])
print(a)
---------------------------------------------------------
0 pig
1 pig
2 pig
3 cow
4 cow
5 pig
6 cow
7 pig
8 salmon
Name: food, dtype: object
1.3 替代值
使用fillna填充缺失值是通用值替换的特殊案例。前面你已经看到,map可以用来修改一个对象中的子集的值,但是replace提供了更为简单灵活的实现。让我们考虑下面的Series:
data = pd.Series([1., -999., 2., -999., -1000., 3.])
print(data)
---------------------------------------------------------
0 1.0
1 -999.0
2 2.0
3 -999.0
4 -1000.0
5 3.0
dtype: float64
-999可能是缺失值的标识。如果要用NA来替代这些值,我们可以使用replace方法生成新的Series,如果你想要一次替代多个值,你可以传入一个列表或替代者:
a = data.replace([-999,-1000],np.nan)
print(a)
---------------------------------------------------------
0 1.0
1 NaN
2 2.0
3 NaN
4 NaN
5 3.0
dtype: float64
要将不同的值替换为不同的值,可以传入替代值的列表:
a = data.replace([-999,-1000],[np.nan,0])
print(a)
---------------------------------------------------------
0 1.0
1 NaN
2 2.0
3 NaN
4 0.0
5 3.0
dtype: float64
参数也可以通过字典传递:
a1 = data.replace({-999:np.nan,-1000:0})
print(a1)
---------------------------------------------------------
0 1.0
1 NaN
2 2.0
3 NaN
4 0.0
5 3.0
dtype: float64
注意:data.replace方法与data.str.replace方法是不同的,data.str.replace是对字符串进行按元素替代的。
1.4 重命名轴索引
和Series中的值一样,可以通过函数或某种形式的映射对轴标签进行类似的转换,生成新的且带有不同标签的对象。你也可以在不生成新的数据结构的情况下修改轴。下面是简单的示例:
data = pd.DataFrame(np.arange(12).reshape((3,4)),
index=['Geats','Tynoon','Buffa'],
columns=['one','two','three','four'])
print(data)
---------------------------------------------------------
one two three four
Geats 0 1 2 3
Tynoon 4 5 6 7
Buffa 8 9 10 11
与Series类似,轴索引与有一个map方法:
transform = lambda x:x[:4].upper()
a = data.index.map(transform)
print(a)
---------------------------------------------------------
Index(['GEAT', 'TYNO', 'BUFF'], dtype='object')
你可以赋值给index,修改DataFrame:
data.index = a
print(data)
---------------------------------------------------------
one two three four
GEAT 0 1 2 3
TYNO 4 5 6 7
BUFF 8 9 10 11
如果你想要传建数据集转换后的版本,并且不修改原有的数据集,一个有用的方法是rename:
a = data.rename(index= str.title,columns=str.upper)
print(a)
---------------------------------------------------------
ONE TWO THREE FOUR
Geat 0 1 2 3
Tyno 4 5 6 7
Buff 8 9 10 11
值得注意的是,rename可以结合字典型对象使用,为轴标签的子集提供新的值:
a = data.rename(index={'GEAT':'BO0ST'},
columns={'three':'peekaboo'})
print(a)
---------------------------------------------------------
one two peekaboo four
BO0ST 0 1 2 3
TYNO 4 5 6 7
BUFF 8 9 10 11
rename可以让你从手动复制DataFrame并为其分配索引和列属性的烦琐工作中解放出来。如果你想要修改原有的数据集,传入inplace = True:
data.rename(index={'GEAT':'MAGNUM'},inplace=True)
print(data)
---------------------------------------------------------
one two three four
MAGNUM 0 1 2 3
TYNO 4 5 6 7
BUFF 8 9 10 11
1.5 离散化和分箱
连续值经常需要离散化,或者分离成“箱子”进行分析。假设你有某项研究中一组人群的数据,你想要将他们进行分组,放入离散的年龄框中:
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
让我们将这些年龄分为18~ 25,26~ 35,36~60以及61以上若干组。为了实现这个,你可以使用pandas中的cut:
bins = [18,25,35,60,100]
cats = pd.cut(ages,bins)
print(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, right]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]
pandas返回的对象是一个特殊的Categorical对象。你看到的输出描述了由pandas.cut计算出的箱。你可以将它当作一个表示箱名的字符串数组;它在内部包含一个categories(类别)数组,它指定了不同的类别名称以及codes属性中的ages(年龄)数据标签:
a = cats.codes
print(a)
a = cats.categories
print(a)
a = pd.value_counts(cats)
print(a)
---------------------------------------------------------
[0 0 0 1 0 0 2 1 3 2 2 1]
IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]], dtype='interval[int64, right]')
(18, 25] 5
(25, 35] 3
(35, 60] 3
(60, 100] 1
dtype: int64
请注意,pd.value_counts(cats)是对pandas,cut结果中的箱数量的计数。
与区间的数学符号一致,小括号是开放的,中括号表示它是封闭的(包括边)。你可以通过传递right=False来改变哪一边是封闭的:
a = pd.cut(ages,bins,right=False)
print(a)
---------------------------------------------------------
[[18, 25), [18, 25), [25, 35), [25, 35), [18, 25), ..., [25, 35), [60, 100), [35, 60), [35, 60), [25, 35)]
Length: 12
Categories (4, interval[int64, left]): [[18, 25) < [25, 35) < [35, 60) < [60, 100)]
你也可以通过向labels选项传递一个列表或数组来传入自定义的箱名:
group_names = ['Youth', 'YoungAdult', 'middleAged', 'Senior']
a = pd.cut(ages,bins,labels=group_names)
print(a)
---------------------------------------------------------
['Youth', 'Youth', 'Youth', 'YoungAdult', 'Youth', ..., 'YoungAdult', 'Senior', 'middleAged', 'middleAged', 'YoungAdult']
Length: 12
Categories (4, object): ['Youth' < 'YoungAdult' < 'middleAged' < 'Senior']
如果你传给cut整数个的箱来代替显示的箱边,pandas将根据数据中的最小值和最大值计算出等长的箱。请考虑一些均匀分布的数据被切成四份的情况:
data = np.random.rand(20)
a = pd.cut(data,4,precision=2)
print(a)
---------------------------------------------------------
[(0.37, 0.55], (0.2, 0.37], (0.73, 0.9], (0.73, 0.9], (0.2, 0.37], ..., (0.37, 0.55], (0.37, 0.55], (0.2, 0.37], (0.2, 0.37], (0.55, 0.73]]
Length: 20
Categories (4, interval[float64, right]): [(0.2, 0.37] < (0.37, 0.55] < (0.55, 0.73] < (0.73, 0.9]]
precision=2的选项将十进制精度限制在两位
qcut是一个与分箱密切相关的函数,它基于样本分位数进行分箱。取决于数据的分布,使用cut通常不会使每个箱具有相同数据量的数据点。由于qcut使用样本的分位数,你可以通过qcut获得等长的箱:
data = np.random.randn(1000)
cats = pd.qcut(data,4)
print(cats)
a = pd.value_counts(cats)
print(a)
---------------------------------------------------------
[(0.717, 3.009], (0.717, 3.009], (-2.976, -0.626], (-0.626, 0.0361], (0.0361, 0.717], ..., (0.717, 3.009], (-2.976, -0.626], (-0.626, 0.0361], (-2.976, -0.626], (0.717, 3.009]]
Length: 1000
Categories (4, interval[float64, right]): [(-2.976, -0.626] < (-0.626, 0.0361] < (0.0361, 0.717] <
(0.717, 3.009]]
(-3.183, -0.63] 250
(-0.63, -0.0164] 250
(-0.0164, 0.674] 250
(0.674, 3.123] 250
dtype: int64
与cut类似,你可以传入自定义的分位数(0和1之间的数据,包括边):
data = np.random.randn(1000)
cats = pd.qcut(data,[0,0.1,0.5,0.9,1])
print(cats)
---------------------------------------------------------
[(-1.329, -0.0537], (-1.329, -0.0537], (-1.329, -0.0537], (-1.329, -0.0537], (-3.154, -1.329], ..., (-1.329, -0.0537], (1.295, 3.552], (-0.0537, 1.295], (-1.329, -0.0537], (-0.0537, 1.295]]
Length: 1000
Categories (4, interval[float64, right]): [(-3.154, -1.329] < (-1.329, -0.0537] < (-0.0537, 1.295] <
(1.295, 3.552]]
1.6 检测和过滤异常值
过滤或转换异常值在很大程度上是应用数组操作的事情。考虑一个具有正态分布数据的DataFrame:
data = pd.DataFrame(np.random.randn(1000,4))
a = data.describe()
print(a)
---------------------------------------------------------
0 1 2 3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean -0.056513 0.008749 0.075350 0.023352
std 0.996655 1.009051 0.966136 0.997315
min -3.206578 -3.589616 -3.588871 -3.903948
25% -0.751567 -0.677255 -0.588948 -0.614353
50% -0.036994 0.010502 0.072625 -0.008465
75% 0.588882 0.689625 0.739949 0.668664
max 3.153782 3.106950 2.933020 3.290888
假如你想要找出一列中绝对值大于三的值:
col = data[2]
print(col)
a = col[np.abs(col)>3]
print(a)
---------------------------------------------------------
0 0.595003
1 -0.273944
2 -1.738326
3 -0.226538
4 -0.922602
...
995 1.286794
996 -1.005505
997 -0.522980
998 0.823535
999 0.885288
Name: 2, Length: 1000, dtype: float64
248 -3.032550
899 -3.588871
Name: 2, dtype: float64
要选出所有值大于3或小于-3的行,你可以对布尔值DataFrame使用any方法:
a = data[(np.abs(data) > 3).any(1)]
print(a)
---------------------------------------------------------
0 1 2 3
11 1.832790 1.714719 0.156690 3.290888
142 -3.206578 -3.589616 -0.188575 0.629367
248 1.054107 1.274588 -3.032550 -0.069402
324 -0.295443 3.106950 0.209289 -1.528087
554 3.153782 -2.244017 0.370417 -1.144793
624 -3.085844 -0.655362 1.048191 -0.547927
625 -0.449728 3.079447 0.471546 -0.173849
680 0.497273 -3.522812 -0.239648 1.645030
684 0.634405 -0.096680 -0.139889 3.201017
808 0.310883 0.381769 0.184578 -3.045689
899 0.102649 0.020356 -3.588871 0.594405
906 0.179568 0.597743 -0.025829 -3.903948
918 -0.715967 -3.451885 -0.298292 0.460636
其中在DataFrame使用any方法返回的是一个Series数组,返回任何元素是否为真,若行/列中有一个为真则返回真,若一行或者一列全部为假才返回假。而用到上式例子就是,找出所有值都大于3或小于-3的值。any当中的参数axis=0,表示
返回索引为原始列标签的Series(从一列中判断)。axis=1,表示返回一个索引为原始索引(行)的Series(从一行中判断)。
值可以根据这些标准来设置,下面代码限制了-3到3之间的数值:
data[np.abs(data)>3] = np.sign(data)*3
a = data.describe()
print(a)
---------------------------------------------------------
0 1 2 3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean -0.056374 0.010126 0.075971 0.023810
std 0.995281 1.003394 0.963973 0.992478
min -3.000000 -3.000000 -3.000000 -3.000000
25% -0.751567 -0.677255 -0.588948 -0.614353
50% -0.036994 0.010502 0.072625 -0.008465
75% 0.588882 0.689625 0.739949 0.668664
max 3.000000 3.000000 2.933020 3.000000
语句**np.sgin(data)**根据数据中的值的正负分别生成1和-1的数值:
a = np.sign(data)
print(a)
---------------------------------------------------------
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
.. ... ... ... ...
995 1.0 -1.0 1.0 1.0
996 1.0 1.0 -1.0 1.0
997 1.0 1.0 -1.0 1.0
998 -1.0 1.0 1.0 1.0
999 -1.0 -1.0 1.0 -1.0
1.7 置换和随机抽样
使用numpy.random.permutation对DataFrame中Series或行进行置换(随机重排序)是非常方便的。在调用permutation时根据你想要的轴长度可以产生一个表示新顺序的整数数组:
df = pd.DataFrame(np.arange(5*4).reshape((5,4)))
print(df)
sampler = np.random.permutation(5)
print(sampler)
---------------------------------------------------------
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
[4 0 1 2 3]
整数数组可以用在基于iloc的索引或等价的take函数中:
a = df.take(sampler)
a1 = df.iloc[sampler]
print(a)
print(a1)
---------------------------------------------------------
0 1 2 3
4 16 17 18 19
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
3 12 13 14 15
0 1 2 3
4 16 17 18 19
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
3 12 13 14 15
可以传递参数n = 值来指定抽取的行数(随机):
a = df.sample(n=3)
print(a)
---------------------------------------------------------
0 1 2 3
4 16 17 18 19
0 0 1 2 3
2 8 9 10 11
要生成一个带有替代值的样本(允许有重复选择),将replace=True传入sample方法。白话来讲就是取行数据后,允许放回再重新抽取。而replace=False就是不允许,取完一行后,下次只能取其他行。:
choice = pd.Series([5,7,-1,6,4])
print(choice)
draws = choice.sample(n=10,replace=True)
print(draws)
---------------------------------------------------------
0 5
1 7
2 -1
3 6
4 4
dtype: int64
3 6
1 7
0 5
4 4
3 6
3 6
4 4
3 6
0 5
0 5
dtype: int64
1.8 计算指标/虚拟变量
将分类变量转换为“虚拟”或“指标”矩阵是另一种用于统计建模和机器学习的转换操作。如果DataFrame中的一列有k个不同的值,则可以衍生一个k列的值为1或0的矩阵或DataFrame。pandas有一个get_dummies函数用于实现该功能。
df = pd.DataFrame({'key':['b','b','a','c','a','b']})
print(df)
a = pd.get_dummies(df['key'])
print(a)
---------------------------------------------------------
key
0 b
1 b
2 a
3 c
4 a
5 b
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方法中有一个前缀参数用于实现该功能:
dummies = pd.get_dummies(df['key'],prefix='key')
print(dummies)
---------------------------------------------------------
key_a key_b key_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
将get_dummmies与cut等离散化函数结合使用是统计应用的一个有用的方法:
values = np.random.rand(10)
print(values)
bins = [0,0.2,0.4,0.6,0.8,1]
a = pd.get_dummies(pd.cut(values,bins))
print(a)
---------------------------------------------------------
[0.74686713 0.39037649 0.64728861 0.2533784 0.79801761 0.91994586
0.98235794 0.73360613 0.93896189 0.83537642]
(0.0, 0.2] (0.2, 0.4] (0.4, 0.6] (0.6, 0.8] (0.8, 1.0]
0 0 0 0 1 0
1 0 1 0 0 0
2 0 0 0 1 0
3 0 1 0 0 0
4 0 0 0 1 0
5 0 0 0 0 1
6 0 0 0 0 1
7 0 0 0 1 0
8 0 0 0 0 1
9 0 0 0 0 1
总结
以上就是本章的内容,本文介绍了数据转换是所涉及到的内容,包括删除重复值、使用函数进行数据转换、替代值的处理、重命名轴索引、离散化和分箱、检测和过滤异常值、置换和随机抽样和计算指标/虚拟变量。内容还是比较多的,可以重复观看。