《利用Python进行数据分析》第七章——数据清洗与准备2

本文介绍了数据处理的关键步骤,包括删除重复值以保持数据的唯一性,使用函数或映射进行数据转换,如将肉类名称转换为动物类型。此外,还讲解了如何替换特定值,重命名轴索引,以及离散化和分箱技术,例如对年龄数据进行分段。检测和过滤异常值,如通过设定阈值来处理异常数据。最后,讨论了随机抽样和计算虚拟变量的方法,如将分类变量转换为指标矩阵。这些技巧在数据分析和统计建模中非常实用。
摘要由CSDN通过智能技术生成


前言

在经过上文章对数据的缺失值进行过滤和补全,下面讲数据的一些转换,主要讲解数据的重新排列、过滤以及其他转换是另外一系列重要的操作。


一、数据转换

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


总结

以上就是本章的内容,本文介绍了数据转换是所涉及到的内容,包括删除重复值、使用函数进行数据转换、替代值的处理、重命名轴索引、离散化和分箱、检测和过滤异常值、置换和随机抽样和计算指标/虚拟变量。内容还是比较多的,可以重复观看。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值