美文网首页我爱编程
【Chapter 7.2 】数据转换

【Chapter 7.2 】数据转换

作者: 蜘蛛的梦呓 | 来源:发表于2018-05-25 22:59 被阅读0次

    【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
    
    

    相关文章

      网友评论

        本文标题:【Chapter 7.2 】数据转换

        本文链接:https://www.haomeiwen.com/subject/kllpjftx.html