我用Python每天写500字每天写1000字

数据规整化:清理、转换、合并、重塑

2018-02-01  本文已影响111人  弃用中

合并数据集

pandas对象中的数据可以通过一些内置的方式进行合并:

数据库风格的DataFrame合并

数据集的合并(merge)或连接(join)运算是通过一个或多个键将行连接起来的。这些运算是关系型数据库的核心。pandas的merge函数是对数据应用这些算法的主要切入点。

以一个简单的例子开始:

In [176]: df1 = pd.DataFrame({'key':['b','b','a','c','a','a','b'],
     ...: 'data1':range(7)})

In [177]: df2 = pd.DataFrame({'key':['a','b','d'],
     ...: 'data2':range(3)})

In [178]: df1
Out[178]: 
   data1 key
0      0   b
1      1   b
2      2   a
3      3   c
4      4   a
5      5   a
6      6   b

In [179]: df2
Out[179]: 
   data2 key
0      0   a
1      1   b
2      2   d

In [180]: pd.merge(df1,df2)
Out[180]: 
   data1 key  data2
0      0   b      1
1      1   b      1
2      6   b      1
3      2   a      0
4      4   a      0
5      5   a      0

注意,我们并没有指明要用哪个列进行连接。如果没有指明,merge就会将重叠列的列名当作键。不过,最好显式指定一下:

In [181]: pd.merge(df1,df2,on='key')
Out[181]: 
   data1 key  data2
0      0   b      1
1      1   b      1
2      6   b      1
3      2   a      0
4      4   a      0
5      5   a      0

如果两个对象的列名不同,也可以分别进行指定:

In [182]: df3 = pd.DataFrame({'lkey':['b','b','a','c','a','a','b'],
     ...: 'data1':range(7)})

In [183]: df4 = pd.DataFrame({'rkey':['a','b','d'],
     ...: 'data2':range(3)})

In [184]: pd.merge(df3,df4,left_on='lkey',right_on='rkey')
Out[184]: 
   data1 lkey  data2 rkey
0      0    b      1    b
1      1    b      1    b
2      6    b      1    b
3      2    a      0    a
4      4    a      0    a
5      5    a      0    a

可能你已经注意到,结果里面c和d以及与之相关的数据消失了。默认情况下,merge做的是“inner”连接;结果中的键是交集。其他方式还有“merge”、“right”以及“outer”。外连接求取的是键的并集,组合了左连接和右连接的效果:

In [185]: pd.merge(df1,df2,how='outer')
Out[185]: 
   data1 key  data2
0    0.0   b    1.0
1    1.0   b    1.0
2    6.0   b    1.0
3    2.0   a    0.0
4    4.0   a    0.0
5    5.0   a    0.0
6    3.0   c    NaN
7    NaN   d    2.0

要根据多个键进行合并,传入一个由列名组成的列表即可:


In [186]: left = pd.DataFrame({'key1':['foo','foo','bar'],
     ...: 'key2':['one','two','one'],
     ...: 'lval':[1,2,3]})

In [187]: right = pd.DataFrame({'key1':['foo','foo','bar','bar'],
     ...: 'key2':['one','one','one','two'],
     ...: 'rval':[4,5,6,7]})

In [188]: pd.merge(left,right,on=['key1','key2'],how='outer')
Out[188]: 
  key1 key2  lval  rval
0  foo  one   1.0   4.0
1  foo  one   1.0   5.0
2  foo  two   2.0   NaN
3  bar  one   3.0   6.0
4  bar  two   NaN   7.0

结果中会出现哪些键组合取决于所选的合并方式,你可以这样理解:多个键形成一系列元组,并将其当作单个连接键(当然,实际上不是这么回事)。

对于合并运算需要考虑的最后一个问题是对重复列名的处理,merge有一个实用的suffixes选项,用于指定附加到左右两个DataFrame对象的重叠列名上的字符串:

In [191]: pd.merge(left,right,on='key1')
Out[191]: 
  key1 key2_x  lval key2_y  rval
0  foo    one     1    one     4
1  foo    one     1    one     5
2  foo    two     2    one     4
3  foo    two     2    one     5
4  bar    one     3    one     6
5  bar    one     3    two     7

In [192]: pd.merge(left,right,on='key1',suffixes=('_left','_right'))
Out[192]: 
  key1 key2_left  lval key2_right  rval
0  foo       one     1        one     4
1  foo       one     1        one     5
2  foo       two     2        one     4
3  foo       two     2        one     5
4  bar       one     3        one     6
5  bar       one     3        two     7

merge函数的参数

参数 说明
left 参与合并的左侧DataFrame
right 参与合并的右侧DataFrame
how inner、outer、left、right其中之一。默认为inner
on 用于连接的列名。必须存在于左右两个DataFrame中。如果未指定,且其他连接键也未指定,则以left和right列名的交集作为连接键
left_on 左侧DataFrame中用作连接键的列
right_on 右侧DataFrame中用作连接键的列
left_index 将左侧的行索引用作器连接键
right_index 类似于left_inex
sort 根据连接键对合并后的数据进行排序,默认为True
suffixes 字符串值元组,用于追加到重叠列名的末尾,默认为(_x,_y)
copy 设置为False,可以在某些特殊情况下避免将数据复制到结果数据结构中。默认总是复制。

索引上的合并

DataFrame的连接键也可能位于其索引中。在这种情况下,你可以传入left_index=True或right_index=True(或两个都传)以说明索引应该被用作连接键:

In [7]: left1 = pd.DataFrame({'key':['a','b','a','a','b','c'],
   ...: 'value':range(6)})

In [8]: right1 = pd.DataFrame({'group_val':[3.5,7]},index=['a','b'])

In [9]: left1
Out[9]: 
  key  value
0   a      0
1   b      1
2   a      2
3   a      3
4   b      4
5   c      5

In [10]: right1
Out[10]: 
   group_val
a        3.5
b        7.0

In [11]: pd.merge(left1,right1,left_on='key',right_index=True)
Out[11]: 
  key  value  group_val
0   a      0        3.5
2   a      2        3.5
3   a      3        3.5
1   b      1        7.0
4   b      4        7.0

由于merge方法默认的是求连接键的交集,也可以通过外连接的方式得到它们的并集:

In [12]: pd.merge(left1,right1,left_on='key',right_index=True,how="outer")
Out[12]: 
  key  value  group_val
0   a      0        3.5
2   a      2        3.5
3   a      3        3.5
1   b      1        7.0
4   b      4        7.0
5   c      5        NaN

对于层次化索引的数据,就更复杂点:

In [13]: lefth = pd.DataFrame({'key1':['Ohio','Ohio','Ohio','Nevada','Nevada'],
    ...: 'key2':[2000,2001,2002,2001,2002],
    ...: 'data':np.arange(5)})

In [14]: righth = pd.DataFrame(np.arange(12).reshape(6,2),index=[['Neveda','Nevada','Ohio','Ohio','Ohio','Ohio'],[2001,2000,2000,2000,2001,2002]],columns=['event1','event2'])

In [15]: lefth
Out[15]: 
   data    key1  key2
0     0    Ohio  2000
1     1    Ohio  2001
2     2    Ohio  2002
3     3  Nevada  2001
4     4  Nevada  2002

In [16]: righth
Out[16]: 
             event1  event2
Neveda 2001       0       1
Nevada 2000       2       3
Ohio   2000       4       5
       2000       6       7
       2001       8       9
       2002      10      11

这种情况下,必须以列表的形式指明用作合并键的多个列(注意对重复索引值的处理):

In [17]: pd.merge(lefth,righth,left_on=['key1','key2'],right_index=True)
Out[17]: 
   data  key1  key2  event1  event2
0     0  Ohio  2000       4       5
0     0  Ohio  2000       6       7
1     1  Ohio  2001       8       9
2     2  Ohio  2002      10      11

In [18]: pd.merge(lefth,righth,left_on=['key1','key2'],right_index=True,how="outer")
Out[18]: 
   data    key1  key2  event1  event2
0   0.0    Ohio  2000     4.0     5.0
0   0.0    Ohio  2000     6.0     7.0
1   1.0    Ohio  2001     8.0     9.0
2   2.0    Ohio  2002    10.0    11.0
3   3.0  Nevada  2001     NaN     NaN
4   4.0  Nevada  2002     NaN     NaN
4   NaN  Neveda  2001     0.0     1.0
4   NaN  Nevada  2000     2.0     3.0

同时使用合并双方的索引也没问题:

In [19]: left2 = pd.DataFrame([[1,2],[3,4],[5,6]],index=['a','c','e'],
    ...: columns=['Ohio','Nevada'])

In [20]: right2 = pd.DataFrame([[7,8],[9,10],[11,12],[13,14]],index=['b','c','d','e'],columns=['Missouri','Alabama'])

In [21]: left2
Out[21]: 
   Ohio  Nevada
a     1       2
c     3       4
e     5       6

In [22]: right2
Out[22]: 
   Missouri  Alabama
b         7        8
c         9       10
d        11       12
e        13       14

In [23]: pd.merge(left2,right2,left_index=True,right_index=True,how="outer")
Out[23]: 
   Ohio  Nevada  Missouri  Alabama
a   1.0     2.0       NaN      NaN
b   NaN     NaN       7.0      8.0
c   3.0     4.0       9.0     10.0
d   NaN     NaN      11.0     12.0
e   5.0     6.0      13.0     14.0

DataFrame还有一个join实例方法,它能更为方便地实现按索引合并。它还可用于合并多个带有相同或相似索引地DataFrame对象,而不管它们之间地没有重叠的列。在上面的例子,我们可以编写:

In [24]: left2.join(right2,how="outer")
Out[24]: 
   Ohio  Nevada  Missouri  Alabama
a   1.0     2.0       NaN      NaN
b   NaN     NaN       7.0      8.0
c   3.0     4.0       9.0     10.0
d   NaN     NaN      11.0     12.0
e   5.0     6.0      13.0     14.0

DataFrame的join方法是在连接键上做左连接。它还支持参数DataFrame的索引跟调用者DataFrame的某个列之间的连接:

In [26]: left1.join(right1,on="key")
Out[26]: 
  key  value  group_val
0   a      0        3.5
1   b      1        7.0
2   a      2        3.5
3   a      3        3.5
4   b      4        7.0
5   c      5        NaN

最后,对于简单的索引合并,你还可以向join传入一组DataFrame(后面我们会介绍更为通用的concat函数,它也能实现此功能):

In [27]: another = pd.DataFrame([[7,8],[9,10],[11,12],[16,17]],
    ...: index=['a','c','e','f'],columns=['New York','Oregon'])

In [28]: left2.join([right2,another])
Out[28]: 
   Ohio  Nevada  Missouri  Alabama  New York  Oregon
a     1       2       NaN      NaN         7       8
c     3       4       9.0     10.0         9      10
e     5       6      13.0     14.0        11      12

In [29]: left2.join([right2,another],how='outer')
Out[29]: 
   Ohio  Nevada  Missouri  Alabama  New York  Oregon
a   1.0     2.0       NaN      NaN       7.0     8.0
b   NaN     NaN       7.0      8.0       NaN     NaN
c   3.0     4.0       9.0     10.0       9.0    10.0
d   NaN     NaN      11.0     12.0       NaN     NaN
e   5.0     6.0      13.0     14.0      11.0    12.0
f   NaN     NaN       NaN      NaN      16.0    17.0

轴向连接

另一种数据合并运算也被称作连接(concatenation)、绑定(binding)或堆叠(stacking)。NumPy有一个用于合并原始NumPy数组的concatenation函数:

In [33]: arr = np.arange(12).reshape(3,4)

In [34]: arr
Out[34]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

In [35]: np.concatenate([arr,arr],axis=1)
Out[35]: 
array([[ 0,  1,  2,  3,  0,  1,  2,  3],
       [ 4,  5,  6,  7,  4,  5,  6,  7],
       [ 8,  9, 10, 11,  8,  9, 10, 11]])

对于pandas对象(如Series和DataFrame),带有标签的轴使你能够进一步推广数组的连接运算。具体点说,你还需要考虑以下这些东西:

pandas的concat函数提供了一种能够解决这些问题的可靠方式。假设有三个没有重叠索引的Series:

In [36]: s1 = pd.Series([0,1],index=['a','b'])

In [37]: s2 = pd.Series([2,3,4],index=['c','d','e'])

In [38]: s3 = pd.Series([5,6],index=['f','g'])

In [39]: pd.concat([s1,s2,s3])
Out[39]: 
a    0
b    1
c    2
d    3
e    4
f    5
g    6
dtype: int64

默认情况下,concat是在axis=0上工作的,最终产生一个新的Series。如果传入axis=1,则结果就会变成DataFrame(axis=1是列):

In [40]: pd.concat([s1,s2,s3],axis=1)
Out[40]: 
     0    1    2
a  0.0  NaN  NaN
b  1.0  NaN  NaN
c  NaN  2.0  NaN
d  NaN  3.0  NaN
e  NaN  4.0  NaN
f  NaN  NaN  5.0
g  NaN  NaN  6.0

这种情况下,另外一条轴上没有重叠,从索引的有序并集(外连接)上就可以看出来。传入join='inner'即可得到它们的交集:

In [47]: s4 = pd.concat([s1*5,s3])

In [48]: pd.concat([s1,s4],axis=1)
Out[48]: 
     0  1
a  0.0  0
b  1.0  5
f  NaN  5
g  NaN  6

In [50]: pd.concat([s1,s4],axis=1,join='inner')
Out[50]: 
   0  1
a  0  0
b  1  5

可以通过join_axes指定要在其他轴上使用的索引:

In [51]: pd.concat([s1,s4],axis=1,join_axes=[['a','c','b','e']])
Out[51]: 
     0    1
a  0.0  0.0
c  NaN  NaN
b  1.0  5.0
e  NaN  NaN

不过有个问题,参与连接的片段在结果中区分不开。假设你想要在连接轴上创建一个层次化索引。使用keys参数即可达到这个目的:

In [52]: result = pd.concat([s1,s1,s3],keys=['one','two','three'])

In [53]: result
Out[53]: 
one    a    0
       b    1
two    a    0
       b    1
three  f    5
       g    6
dtype: int64

result.unstack()
Out[54]: 
         a    b    f    g
one    0.0  1.0  NaN  NaN
two    0.0  1.0  NaN  NaN
three  NaN  NaN  5.0  6.0

如果沿着axis=1对Series进行合并,则keys就会成为DataFrame的列头:

In [60]: pd.concat([s1,s2,s3],axis=1,keys=['one','two','three'])
Out[60]: 
   one  two  three
a  0.0  NaN    NaN
b  1.0  NaN    NaN
c  NaN  2.0    NaN
d  NaN  3.0    NaN
e  NaN  4.0    NaN
f  NaN  NaN    5.0
g  NaN  NaN    6.0

同样的逻辑对DataFrame对象也是一样:

In [61]: df1 = pd.DataFrame(np.arange(6).reshape(3,2),index=['a','b','c'],
    ...: columns=['one','two'])

In [62]: df2 = pd.DataFrame(5+np.arange(4).reshape(2,2),index=['a','c'],columns=['three','four'])

In [63]: pd.concat([df1,df2],axis=1,keys=['level1','level2'])
Out[63]: 
  level1     level2     
     one two  three four
a      0   1    5.0  6.0
b      2   3    NaN  NaN
c      4   5    7.0  8.0

最后一个需要考虑的问题是,和当前分析工作无关的DataFrame行索引:

In [69]: df1 = pd.DataFrame(np.random.randn(3,4),columns=['a','b','c','d'])

In [70]: df2 = pd.DataFrame(np.random.randn(2,3),columns=['b','d','a'])

In [71]: df1
Out[71]: 
          a         b         c         d
0  2.240595 -0.978214 -2.582510  1.166495
1 -1.069589  1.448100 -0.511939 -0.047077
2  0.744933  1.829291  0.782055 -1.012039

In [72]: df2
Out[72]: 
          b         d         a
0 -1.259407 -1.117981  1.036470
1  2.338679 -0.140818 -0.167361

In [73]: pd.concat([df1,df2],ignore_index=True)
Out[73]: 
          a         b         c         d
0  2.240595 -0.978214 -2.582510  1.166495
1 -1.069589  1.448100 -0.511939 -0.047077
2  0.744933  1.829291  0.782055 -1.012039
3  1.036470 -1.259407       NaN -1.117981
4 -0.167361  2.338679       NaN -0.140818

In [74]: pd.concat([df1,df2])
Out[74]: 
          a         b         c         d
0  2.240595 -0.978214 -2.582510  1.166495
1 -1.069589  1.448100 -0.511939 -0.047077
2  0.744933  1.829291  0.782055 -1.012039
0  1.036470 -1.259407       NaN -1.117981
1 -0.167361  2.338679       NaN -0.140818

concat函数的参数

参数 说明
objs 参与连接的pandas对象的列表或字典。唯一必需的参数
axis 指明连接的轴向,默认为0
join inner、outer其中之一,默认为outer
join_axes 指明用于其他n-1条轴的索引,不执行并集/交集运算
keys 与连接对象有关的值,用于形成连接轴向上的层次化索引。可以是任意值的列表或数组、元组数组、数组列表
levels 指定用作层次化索引各级别上的索引,如果设置了keys的话
names 用于创建分层级别的名称,如果设置了keys和(或)levels的话
verify_integrity 检查结果对象新轴上的重复情况,如果发现则引发异常。默认False允许重复
ignore_index 不保留连接轴上的索引,产生一组新索引range(total_length)

合并重叠数据

还有一种数据组合问题不能用简单的合并(merge)或连接(concatenation)运算来处理。比如说,你可能有索引全部或部分重叠的两个数据集。给这个例子增加一点启发性,我们使用NumPy的where函数,它用于表达一种矢量化的if-else:

In [75]: a = pd.Series([np.nan,2.5,np.nan,3.5,4.5,np.nan],
    ...: index=['f','e','d','c','b','a'])

In [76]: b = pd.Series(np.arange(len(a),dtype=np.float64),index=['f','e','d','c','b','a'])

In [77]: b[-1] = np.nan

In [78]: a
Out[78]: 
f    NaN
e    2.5
d    NaN
c    3.5
b    4.5
a    NaN
dtype: float64

In [79]: b
Out[79]: 
f    0.0
e    1.0
d    2.0
c    3.0
b    4.0
a    NaN
dtype: float64

In [80]: np.where(pd.isnull(a),b,a)
Out[80]: array([ 0. ,  2.5,  2. ,  3.5,  4.5,  nan])

Series有一个combine_first方法,实现的也是一样的功能,而且会进行数据对齐:

In [83]: b[:-2].combine_first(a[:2])
Out[83]: 
c    3.0
d    2.0
e    1.0
f    0.0
dtype: float64

对于DataFrame,combine_first自然也会在列上做同样的事情,因此可以将其看作:用参数对象中的数据为调用者对象的缺失数据“打补丁”:

In [86]: df1 = pd.DataFrame({'a':[1,np.nan,5,np.nan],'b':[np.nan,2,np.nan,6],
    ...: 'c':range(2,18,4)})

In [87]: df2 = pd.DataFrame({'a':[5,4,np.nan,3,7],
    ...: 'b':[np.nan,3,4,6,8]})

In [88]: df1.combine_first(df2)
Out[88]: 
     a    b     c
0  1.0  NaN   2.0
1  4.0  2.0   6.0
2  5.0  4.0  10.0
3  3.0  6.0  14.0
4  7.0  8.0   NaN

重塑和轴向转换

有许多用于重新排列表格型数据的基础运算。这些函数也称作重塑(reshape)或轴向旋转(pivot)运算。

重塑层次化索引

层次化索引为DataFrame数据的重排任务提供了一种具有良好一致性的方式。主要功能有二:

将通过一系列的范例来讲解这些操作。

In [92]: data = pd.DataFrame(np.arange(6).reshape(2,3),index=pd.Index(['Ohio','Colorado'],name='state'),columns=pd.Index(['one','two','three'],name='number'))

In [93]: data
Out[93]: 
number    one  two  three
state                    
Ohio        0    1      2
Colorado    3    4      5

使用该数据的stack方法即可将列转换为行,得到一个Series:

In [94]: result = data.stack()

In [95]: result
Out[95]: 
state     number
Ohio      one       0
          two       1
          three     2
Colorado  one       3
          two       4
          three     5
dtype: int32

对于一个层次化索引的Series,你可以用unstack将其重排为一个DataFrame:

In [96]: result.unstack()
Out[96]: 
number    one  two  three
state                    
Ohio        0    1      2
Colorado    3    4      5

默认情况下,unstack操作的是最内层(stack也是如此)。传入分层级别的编号或名称即可对其他级别进行unstack操作:

In [97]: result.unstack(0)
Out[97]: 
state   Ohio  Colorado
number                
one        0         3
two        1         4
three      2         5

In [98]: result.unstack('state')
Out[98]: 
state   Ohio  Colorado
number                
one        0         3
two        1         4
three      2         5

如果不是所有的级别值都能在各分组中找到的话,则unstack操作可能会引入缺失数据:

In [99]: s1 = pd.Series([0,1,2,3],index=['a','b','c','d'])

In [100]: s2 = pd.Series([4,5,6],index=['c','d','e'])

In [101]: data2 = pd.concat([s1,s2],keys=['one','two'])

In [102]: data2
Out[102]: 
one  a    0
     b    1
     c    2
     d    3
two  c    4
     d    5
     e    6
dtype: int64

In [103]: data2.unstack()
Out[103]: 
       a    b    c    d    e
one  0.0  1.0  2.0  3.0  NaN
two  NaN  NaN  4.0  5.0  6.0

stack默认会滤除缺失数据,因此该运算是可逆的:

In [104]: data2.unstack().stack()
Out[104]: 
one  a    0.0
     b    1.0
     c    2.0
     d    3.0
two  c    4.0
     d    5.0
     e    6.0
dtype: float64

In [105]: data2.unstack().stack(dropna=False)
Out[105]: 
one  a    0.0
     b    1.0
     c    2.0
     d    3.0
     e    NaN
two  a    NaN
     b    NaN
     c    4.0
     d    5.0
     e    6.0
dtype: float64

在对DataFrame进行unstack操作时,作为旋转轴的级别将会成为结果中的最低级别:

In [110]: df.unstack('state')
Out[110]: 
side   left          right         
state  Ohio Colorado  Ohio Colorado
number                             
one       0        3     5        8
two       1        4     6        9
three     2        5     7       10
In [111]: df.unstack('state').stack('side')
Out[111]: 
state         Colorado  Ohio
number side                 
one    left          3     0
       right         8     5
two    left          4     1
       right         9     6
three  left          5     2
       right        10     7

pivot函数是一个快捷方式:用set_index创建层次化索引,再用unstack重塑。

数据转换

移除重复数据

DataFrame中常常会出现重复行。


In [122]: data = pd.DataFrame({'k1':['one']*3 + ['two']*4,
     ...: 'k2':[1,1,2,3,3,4,4]})

In [123]: data
Out[123]: 
    k1  k2
0  one   1
1  one   1
2  one   2
3  two   3
4  two   3
5  two   4
6  two   4

In [124]: data.duplicated()
Out[124]: 
0    False
1     True
2    False
3    False
4     True
5    False
6     True
dtype: bool

DataFrame的duplicated方法返回一个布尔型Series,表示各行是否是重复行。

drop_duplicates方法可以返回一个移除了重复行的DataFrame:

In [125]: data.drop_duplicates()
Out[125]: 
    k1  k2
0  one   1
2  one   2
3  two   3
5  two   4

以上的两个方法会默认判断全部列,你也可以指定部分列进行重复项判断。假设你还有一列,且只希望根据k1过滤重复项:

In [128]: data['v1'] = range(7)

In [129]: data
Out[129]: 
    k1  k2  v1
0  one   1   0
1  one   1   1
2  one   2   2
3  two   3   3
4  two   3   4
5  two   4   5
6  two   4   6

In [130]: data.drop_duplicates(['k1'])
Out[130]: 
    k1  k2  v1
0  one   1   0
3  two   3   3

duplicated和drop_duplicates默认保留第一个出现的值组合。

利用函数或映射进行数据转换

在对数据集进行转换时,你可能希望根据数组、Series或DataFrame列中的值来实现该转换工作。来看一个例子:


In [133]: data = pd.DataFrame({'food':['bacon','pulled pork','bacon','Pastrami','conrned beef','Bacon','pastrami','honey ham','nova lox'],
     ...: 'ounces':[4,3,12,6,7.5,8,3,5,6]})

In [134]: data
Out[134]: 
           food  ounces
0         bacon     4.0
1   pulled pork     3.0
2         bacon    12.0
3      Pastrami     6.0
4  conrned beef     7.5
5         Bacon     8.0
6      pastrami     3.0
7     honey ham     5.0
8      nova lox     6.0

假设你想要添加一列表示该肉类食物来源的动物类型。先编写一个肉类到动物的映射:


In [135]: meat_to_animal = {
     ...: 'bacon':'pig',
     ...: 'pulled pork':'pig',
     ...: 'pastrami':'cow',
     ...: 'corned beef':'cow',
     ...: 'honey ham':'pig',
     ...: 'nova lox':'salmon'
     ...: }

Series的map方法可以接受一个函数或含有映射关系的字典型对象。

In [136]: data['animal'] = data['food'].map(str.lower).map(meat_to_animal)

In [137]: data
Out[137]: 
           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

也可以传入一个能够完成全部这些工作的函数:

In [145]: data['food'].map(lambda x:meat_to_animal[x.lower()])
Out[145]: 
0       pig
1       pig
2       pig
3       cow
4       cow
5       pig
6       cow
7       pig
8    salmon
Name: food, dtype: object

使用map是一种实现元素级转换以及其他数据清理工作的便捷方式。

替换值

利用fillna方法填充缺失数据可以看作值替换的一种特殊情况。虽然前面提到的map可用于修改对象的数据子集,而replace则提供了一种实现该功能的更简单、更灵活的方式。

In [146]: data = pd.Series([1,-999,2,-999,-1000,3])

In [147]: data
Out[147]: 
0       1
1    -999
2       2
3    -999
4   -1000
5       3
dtype: int64

In [148]: data.replace(-999,np.nan)
Out[148]: 
0       1.0
1       NaN
2       2.0
3       NaN
4   -1000.0
5       3.0
dtype: float64

如果你希望一次性替换多个值,可以传入一个由待替换值组成的列表以及一个替换值:

In [149]: data.replace([-999,-1000],np.nan)
Out[149]: 
0    1.0
1    NaN
2    2.0
3    NaN
4    NaN
5    3.0
dtype: float64

如果你希望对不同的值进行不同的替换,则传入一个由替换关系组成的列表或字典即可:

In [150]: data.replace([-999,-1000],[np.nan,0])
Out[150]: 
0    1.0
1    NaN
2    2.0
3    NaN
4    0.0
5    3.0
dtype: float64

In [151]: data.replace({-999:np.nan,-1000:0})
Out[151]: 
0    1.0
1    NaN
2    2.0
3    NaN
4    0.0
5    3.0
dtype: float64

重命名轴索引

和Series中的值一样,轴标签也可以通过函数或映射进行转换,从而得到一个新对象。轴还可以被就地修改,而无需新建一个数据结构。

In [152]: data = pd.DataFrame(np.arange(12).reshape(3,4),index=['Ohio','Colorado','New York'],
     ...: columns=['one','two','three','four'])

In [153]: data
Out[153]: 
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
New York    8    9     10    11

In [154]: data.index.map(str.upper)
Out[154]: Index(['OHIO', 'COLORADO', 'NEW YORK'], dtype='object')

In [155]: data.index = data.index.map(str.upper)

In [156]: data
Out[156]: 
          one  two  three  four
OHIO        0    1      2     3
COLORADO    4    5      6     7
NEW YORK    8    9     10    11

如果想要创建数据集的转换版(而不是修改原始数据),比较实用的方法是rename:

In [157]: data.rename(index=str.title,columns=str.upper)
Out[157]: 
          ONE  TWO  THREE  FOUR
Ohio        0    1      2     3
Colorado    4    5      6     7
New York    8    9     10    11

rename可以结合字典型对象实现对部分轴标签的更新:


In [159]: data.rename(index={'OHIO':'INDIANA'},
     ...: columns={'three':'peekaboo'})
Out[159]: 
          one  two  peekaboo  four
INDIANA     0    1         2     3
COLORADO    4    5         6     7
NEW YORK    8    9        10    11

rename帮我们实现了:复制DataFrame并对其索引和列标签进行赋值。如果希望就地修改某个数据集,传入inplace=True即可。

离散化和面元划分

为了便于分析,连续数据常常被离散化或拆分为“面元”(bin)。假设有一组人员数据,而你希望将它们划分为不同的年龄组:

In [3]: ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]

接下来将这些数据划分为“18到25”、“26到35”、“35到60”以及“60以上”几个面元。要实现该功能,你需要使用pandas的cut函数:

In [4]: bins = [18, 25, 35, 60, 100]

In [5]: cats = pd.cut(ages,bins)

In [6]: cats
Out[6]: 
[(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划分的面元。你可以将其看做一组表示面元名称的字符串。它的底层含有一个表示不同分类名称的类型数组,以及一个codes属性中的年龄数据的标签:

In [9]: cats.codes
Out[9]: array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)

In [10]: cats.categories
Out[10]: 
IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]]
              closed='right',
              dtype='interval[int64]')

In [11]: pd.value_counts(cats)
Out[11]: 
(18, 25]     5
(35, 60]     3
(25, 35]     3
(60, 100]    1
dtype: int64

跟“区间”的数学符号一样,圆括号表示开端,而方括号则表示闭端(包括)。哪边是闭端可以通过right=False进行修改:

In [12]: pd.cut(ages, [18, 26, 36, 61, 100], right=False)
Out[12]: 
[[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)]

可以通过传递一个列表或数组到labels,设置自己的面元名称:

In [13]: group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']

In [14]: pd.cut(ages, bins, labels=group_names)
Out[14]: 
[Youth, Youth, Youth, YoungAdult, Youth, ..., YoungAdult, Senior, MiddleAged, MiddleAged, YoungAdult]
Length: 12
Categories (4, object): [Youth < YoungAdult < MiddleAged < Senior]

如果向cut传入的是面元的数量而不是确切的面元边界,则它会根据数据的最小值和最大值计算等长面元。下面这个例子中,我们将一些均匀分布的数据分成四组:

In [19]: data = np.random.rand(20)

# precision=2,限定小数只有两位
In [20]: pd.cut(data, 4, precision=2)
Out[20]: 
[(0.15, 0.36], (0.15, 0.36], (0.36, 0.57], (0.79, 1.0], (0.36, 0.57], ..., (0.57, 0.79], (0.79, 1.0], (0.79, 1.0], (0.57, 0.79], (0.57, 0.79]]
Length: 20
Categories (4, interval[float64]): [(0.15, 0.36] < (0.36, 0.57] < (0.57, 0.79] < (0.79, 1.0]]

qcut是一个非常类似于cut的函数,它可以根据样本分位数对数据进行面元划分。根据数据的分布情况,cut可能无法使各个面元中含有相同数量的数据点。而qcut由于使用的是样本分位数,因此可以得到大小基本相等的面元:

In [21]: data = np.random.randn(1000)

In [22]: cats = pd.qcut(data, 4)

In [23]: cats
Out[23]: 
[(0.608, 2.892], (0.608, 2.892], (-2.985, -0.695], (-0.0327, 0.608], (0.608, 2.892], ..., (0.608, 2.892], (-0.0327, 0.608], (-0.0327, 0.608], (-0.695, -0.0327], (0.608, 2.892]]
Length: 1000
Categories (4, interval[float64]): [(-2.985, -0.695] < (-0.695, -0.0327] < (-0.0327, 0.608] < (0.608, 2.892]]

In [24]: pd.value_counts(cats)
Out[24]: 
(0.608, 2.892]       250
(-0.0327, 0.608]     250
(-0.695, -0.0327]    250
(-2.985, -0.695]     250
dtype: int64

与cut类似,你也可以传递自定义的分位数(0到1之间的数值,包含端点):

In [25]: pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.])
Out[25]: 
[(0.147, 0.288], (0.288, 0.694], (0.288, 0.694], (0.694, 0.98], (0.288, 0.694], ..., (0.288, 0.694], (0.694, 0.98], (0.98, 0.998], (0.694, 0.98], (0.288, 0.694]]
Length: 20
Categories (4, interval[float64]): [(0.147, 0.288] < (0.288, 0.694] < (0.694, 0.98] < (0.98, 0.998]]

检测和过滤异常值

异常值的过滤或变换运算在很大程度上其实就是数组运算。看一个例子:

In [177]: np.random.seed(12345)

In [178]: data = pd.DataFrame(np.random.randn(1000,4))

In [179]: data.describe()
Out[179]: 
                 0            1            2            3
count  1000.000000  1000.000000  1000.000000  1000.000000
mean     -0.067684     0.067924     0.025598    -0.002298
std       0.998035     0.992106     1.006835     0.996794
min      -3.428254    -3.548824    -3.184377    -3.745356
25%      -0.774890    -0.591841    -0.641675    -0.644144
50%      -0.116401     0.101143     0.002073    -0.013611
75%       0.616366     0.780282     0.680391     0.654328
max       3.366626     2.653656     3.260383     3.927528

假设你想找出某列中绝对值大于3的值:

In [181]: col[np.abs(col) > 3]
Out[181]: 
97     3.927528
305   -3.399312
400   -3.745356
Name: 3, dtype: float64

要选出全部含有“超过3或-3的值”的行,可以利用布尔型DataFrame以及any方法:

In [184]: data[(np.abs(data)>3).any(1)]
Out[184]: 
            0         1         2         3
5   -0.539741  0.476985  3.248944 -1.021228
97  -0.774363  0.552936  0.106061  3.927528
102 -0.655054 -0.565230  3.176873  0.959533
305 -2.315555  0.457246 -0.025907 -3.399312
324  0.050188  1.951312  3.260383  0.963301
400  0.146326  0.508391 -0.196713 -3.745356
499 -0.293333 -0.242459 -3.056990  1.918403
523 -3.428254 -0.296336 -0.439938 -0.867165
586  0.275144  1.179227 -3.184377  1.369891
808 -0.362528 -3.548824  1.553205 -2.186301
900  3.366626 -2.372214  0.851010  1.332846

根据这些条件,可以轻松地对值进行设置。下面地代码可以将值限制在区间-3到3以内:

In [190]: data[np.abs(data)>3] = np.sign(data) * 3

In [191]: data.describe()
Out[191]: 
                 0            1            2            3
count  1000.000000  1000.000000  1000.000000  1000.000000
mean     -0.067623     0.068473     0.025153    -0.002081
std       0.995485     0.990253     1.003977     0.989736
min      -3.000000    -3.000000    -3.000000    -3.000000
25%      -0.774890    -0.591841    -0.641675    -0.644144
50%      -0.116401     0.101143     0.002073    -0.013611
75%       0.616366     0.780282     0.680391     0.654328
max       3.000000     2.653656     3.000000     3.000000

排列和随机采样

利用numpy.random.permutation函数可以轻松实现对Series或DataFrame的列的排列工作。通过需要排列的轴的长度调用permutation,可产生一个表示新顺序的整数数组:

In [196]: df = pd.DataFrame(np.arange(5*4).reshape(5,4))

In [197]: sampler = np.random.permutation(5)

In [198]: sampler
Out[198]: array([1, 0, 2, 3, 4])

In [199]: df
Out[199]: 
    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 [200]: df.take(sampler)
Out[200]: 
    0   1   2   3
1   4   5   6   7
0   0   1   2   3
2   8   9  10  11
3  12  13  14  15
4  16  17  18  19

要通过替换的方式产生样本,最快的方式是通过np.random.randint得到一组随机整数:

In [201]: bag = np.array([5,7,-1,6,4])

In [202]: sampler = np.random.randint(0,len(bag),size=10)

In [203]: sampler
Out[203]: array([2, 4, 4, 4, 4, 2, 2, 2, 0, 3])

In [204]: draws = bag.take(sampler)

In [205]: draws
Out[205]: array([-1,  4,  4,  4,  4, -1, -1, -1,  5,  6])

计算指标/哑变量

另一种常用于统计建模或机器学习的转换方式是:将分类变量(categorical variable)转换为“哑变量”或“指标矩阵”。

如果DataFrame的某一列中含有k个不同的值,则可以派生出一个k列矩阵或DataFrame(其值全为1和0)。pandas有一个get_dummies函数可以实现该功能(其实自己动手做一个也不难)。使用之前的一个DataFrame例子:

In [27]: df = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'],'data1': range(6)})

In [28]: df
Out[28]: 
   data1 key
0      0   b
1      1   b
2      2   a
3      3   c
4      4   a
5      5   b

In [29]: pd.get_dummies(df['key'])
Out[29]: 
   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 [30]: dummies = pd.get_dummies(df['key'], prefix='key')

In [31]: df_with_dummy = df[['data1']].join(dummies)

In [32]: df_with_dummy
Out[32]: 
   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

一个对统计应用有用的秘诀是:结合get_dummies和诸如cut之类的离散化函数:

In [33]: np.random.seed(12345)

In [34]: values = np.random.rand(10)

In [35]: values
Out[35]: 
array([ 0.92961609,  0.31637555,  0.18391881,  0.20456028,  0.56772503,
        0.5955447 ,  0.96451452,  0.6531771 ,  0.74890664,  0.65356987])

In [36]: bins = [0, 0.2, 0.4, 0.6, 0.8, 1]

In [37]: pd.get_dummies(pd.cut(values, bins))
Out[37]: 
   (0.0, 0.2]  (0.2, 0.4]  (0.4, 0.6]  (0.6, 0.8]  (0.8, 1.0]
0           0           0           0           0           1
1           0           1           0           0           0
2           1           0           0           0           0
3           0           1           0           0           0
4           0           0           1           0           0
5           0           0           1           0           0
6           0           0           0           0           1
7           0           0           0           1           0
8           0           0           0           1           0
9           0           0           0           1           0

字符串操作

大部分文本运算都直接做成了字符串对象的内置方法。对于更为复杂的模式匹配和文本操作,则可能需要用到正则表达式。。pandas对此进行了加强,它能使你能够对整组数据应用字符串表达式和正则表达式,而且能处理烦人的缺失数据。

字符串对象方法

Python内置的字符串方法

方法 说明
count 返回字串在字符串中出现的次数(非重叠)
endswith、startswith 如果字符串以某个后缀结尾(以某个前缀开头),则返回True
join 将字符串用作连接其他字符串序列的分隔符
index 如果在字符串中找到子串,则返回子串第一个字符所在的位置。如果没有找到,则引发ValuleError
find 如果在字符串中找到子串,则返回第一个发现的子串的第一个字符所在的位置。如果没有找到,则返回-1
rfind 如果在字符串中找到子串,则返回最后一个发现的子串的第一个字符所在的位置。如果没有找到,则返回-1
replace 用另一字符串替换指定子串
strip、rstrip、lstrip 去除空白符(包括换行符)
split 通过指定的分隔符将字符串拆分为一组子串
lower、upper 分别将字母字符转换为小写或大写
ljust、rjust 用空格(或其他字符)填充字符串的空白侧以返回符合最低宽度的字符串

正则表达式

正则表达式方法

方法 说明
findall、finditer 返回字符串中所有的非重叠匹配模式,findall返回的是由所有模式组成的列表,而finditer则返回一个迭代器逐个返回
match 从字符串起始位置匹配模式,还可以对模式各部分进行分组。如果匹配到模式,则返回一个匹配项对象,否则返回None
search 扫描整个字符串以匹配模式。如果找到则返回一个匹配项对象。和match不同,其匹配项可以位于字符串的任意位置,而不仅仅是起始处。
split 根据找到的模式将字符串拆分为数段
sub、subn 将字符串中所有的(sub)或前n各(subn)模式替换为指定表达式。

pandas中矢量化的字符串函数

通过map函数,所有字符串和正则表达式方法都能被应用于(传入lambda表达式或其他函数)各个值,但是如果存在NA就会报错。为了解决这个问题,Series有一些能够跳过NA值得字符串操作方法。通过Series的str属性即可访问这些方法。例如,我们可以通过str.contains检查各个电子邮件地址是否含有“gmail”:

In [160]: data = {'Dava':'dave@google.com','Steve':'steve@gmail.com',
     ...: 'Rob':'rob@gmail.com','Wes':np.nan}

In [161]: data = pd.Series(data)

In [162]: data.str.contains('gmail')
Out[162]: 
Dava     False
Rob       True
Steve     True
Wes        NaN
dtype: object

也可以使用正则表达式,还可以加上任意re选项(如IGNORECASE):

In [163]: pattern = '([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\\.([A-Z]{2,4})'

In [164]: import re

In [165]: data.str.findall(pattern,flags=re.IGNORECASE)
Out[165]: 
Dava     [(dave, google, com)]
Rob        [(rob, gmail, com)]
Steve    [(steve, gmail, com)]
Wes                        NaN
dtype: object

有两个办法可以实现矢量化的元素获取操作:要么使用str.get,要么在str属性上使用索引。


In [171]: matches = data.str.match(pattern,flags=re.IGNORECASE)

In [172]: matches
Out[172]: 
Dava     True
Rob      True
Steve    True
Wes       NaN
dtype: object

In [173]: matches.str.get(1)
Out[173]: 
Dava    NaN
Rob     NaN
Steve   NaN
Wes     NaN
dtype: float64

In [176]: data.str[:5]
Out[176]: 
Dava     dave@
Rob      rob@g
Steve    steve
Wes        NaN
dtype: object

矢量化字符串方法

以上。

上一篇下一篇

猜你喜欢

热点阅读