小秩学数据分析数据蛙数据分析每周作业

pandas0.24.1文档3.3 基础功能(二)

2019-04-01  本文已影响16人  Lykit01

目录:
1 0.24.1版本新特性
2 安装
3马上开始
3.1 pandas概况
3.2 十分钟上手pandas
3.3 基础功能(一)
3.3 基础功能(二)

3.3.6 函数应用

如果要对pandas对象应用你自定义的函数或者第三方库的函数,必须了解以下三种方法。应用的方法合不合适取决于你的函数作用的具体对象:是整个DataFrame或Series,还是行或列,或者是每个元素?
1.应用表函数pipe()
2.应用行/列函数apply()
3.聚合接口agg()transform()
4.应用元素函数applymap()

3.3.6.1 应用表函数

DataFrame和Series当然能直接传递给函数作用。但是,如果要连续调用多个函数,建议使用pipe()方法。比较下面两种方法:

# f, g, and h are functions taking and returning ``DataFrames``
>>> f(g(h(df), arg1=1), arg2=2, arg3=3)

和下面的方法等价:

>>> (df.pipe(h)
...    .pipe(g, arg1=1)
...    .pipe(f, arg2=2, arg3=3))

pandas推荐使用第二种方式,它被叫做方法链。pipe方法让方法链中的各种函数的使用变得简单,包括自定义、第三方库或pandas自带的函数。
在上面的例子中,f、g和h函数都把DataFrame作为第一个参数。如果你的函数把DataFrame作为第二个参数会怎么样呢?在这种情况下,给pipe函数传入一个元组(callable,data_keyword)。.pipe将会把DataFrame放入元组中指定的参数位置上。
例如,我们能够用statsmodels库来做统计回归。他们的接口要求第一个参数是公式,第二个是数据。我们把函数、关键字对(sm.ols,'data')传给pipe:


In [142]: import statsmodels.formula.api as sm

In [143]: bb = pd.read_csv('data/baseball.csv', index_col='id')

In [144]: (bb.query('h > 0')   .....:    .assign(ln_h=lambda df: np.log(df.h))   .....:    .pipe((sm.ols, 'data'), 'hr ~ ln_h + year + g + C(lg)')   .....:    .fit()   .....:    .summary()   .....:  )   .....: Out[144]: <class 'statsmodels.iolib.summary.Summary'>"""                            OLS Regression Results                            ==============================================================================Dep. Variable:                     hr   R-squared:                       0.685Model:                            OLS   Adj. R-squared:                  0.665Method:                 Least Squares   F-statistic:                     34.28Date:                Tue, 12 Mar 2019   Prob (F-statistic):           3.48e-15Time:                        22:38:35   Log-Likelihood:                -205.92No. Observations:                  68   AIC:                             421.8Df Residuals:                      63   BIC:                             432.9Df Model:                           4                                         Covariance Type:            nonrobust                                         ===============================================================================                  coef    std err          t      P>|t|      [0.025      0.975]-------------------------------------------------------------------------------Intercept   -8484.7720   4664.146     -1.819      0.074   -1.78e+04     835.780C(lg)[T.NL]    -2.2736      1.325     -1.716      0.091      -4.922       0.375ln_h           -1.3542      0.875     -1.547      0.127      -3.103       0.395year            4.2277      2.324      1.819      0.074      -0.417       8.872g               0.1841      0.029      6.258      0.000       0.125       0.243==============================================================================Omnibus:                       10.875   Durbin-Watson:                   1.999
Prob(Omnibus):                  0.004   Jarque-Bera (JB):               17.298Skew:                           0.537   Prob(JB):                     0.000175
Kurtosis:                       5.225   Cond. No.                     1.49e+07
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.[2] The condition number is large, 1.49e+07. This might indicate that there arestrong multicollinearity or other numerical problems."""

pipe方法的灵感来自于Unix pipes和最近的dplyrmagritr,后者为R引入了流行的(%>%)运算符(read Pipe)。这里的Pipe实现非常干净,在Python中感觉很好。我们鼓励您查看pipe()的源代码。

3.3.6.2 应用行/列函数

通过apply()方法能沿着指定轴对DataFrame的元素应用任意函数。apply()就像统计描述的函数一样,也接受一个可选的axis参数。

In [145]: df.apply(np.mean)
Out[145]: 
one      0.613869
two      0.470270
three   -0.427633
dtype: float64

In [146]: df.apply(np.mean, axis=1)
Out[146]: 
a   -0.121116
b    0.361488
c    0.571564
d   -0.058569
dtype: float64

In [147]: df.apply(lambda x: x.max() - x.min())
Out[147]: 
one      1.757280
two      3.196734
three    2.065853
dtype: float64

In [148]: df.apply(np.cumsum)
Out[148]: 
        one       two     three
a  1.400810 -1.643041       NaN
b  1.044340 -0.597130  0.395023
c  1.841608  0.327385  0.387933
d       NaN  1.881078 -1.282898

In [149]: df.apply(np.exp)
Out[149]: 
        one       two     three
a  4.058485  0.193391       NaN
b  0.700143  2.845991  1.484418
c  2.219469  2.520646  0.992935
d       NaN  4.728902  0.188091

np.exp(x)返回自然底数e的x次方。

apply()也能通过函数名的字符串调用函数。

In [150]: df.apply('mean')
Out[150]: 
one      0.613869
two      0.470270
three   -0.427633
dtype: float64

In [151]: df.apply('mean', axis=1)
Out[151]: 
a   -0.121116
b    0.361488
c    0.571564
d   -0.058569
dtype: float64

传入apply()的函数的输出结果会影响DataFrame.apply()的最后输出结果,并遵从以下规则:

In [152]: tsdf = pd.DataFrame(np.random.randn(1000, 3), columns=['A', 'B', 'C'],
   .....:                     index=pd.date_range('1/1/2000', periods=1000))
   .....: 

In [153]: tsdf.apply(lambda x: x.idxmax())
Out[153]: 
A   2000-06-10
B   2001-07-04
C   2002-08-09
dtype: datetime64[ns]

你也可以给apply()方法传递额外的参数和关键字。假设你要应用下面的函数:

def subtract_and_divide(x, sub, divide=1):
    return (x - sub) / divide

你可以按下面的方式应用这个函数:

df.apply(subtract_and_divide, args=(5,), divide=3)

另一个有用的功能便是传递Series方法来对每行/列进行Series操作:

In [154]: tsdf
Out[154]: 
                   A         B         C
2000-01-01 -0.652077 -0.239118  0.841272
2000-01-02  0.130224  0.347505 -0.385666
2000-01-03 -1.700237 -0.925899  0.199564
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.339319 -0.978307  0.689492
2000-01-09  0.601495 -0.630417 -1.040079
2000-01-10  1.511723 -0.427952 -0.400154

In [155]: tsdf.apply(pd.Series.interpolate)
Out[155]: 
                   A         B         C
2000-01-01 -0.652077 -0.239118  0.841272
2000-01-02  0.130224  0.347505 -0.385666
2000-01-03 -1.700237 -0.925899  0.199564
2000-01-04 -1.292326 -0.936380  0.297550
2000-01-05 -0.884415 -0.946862  0.395535
2000-01-06 -0.476503 -0.957344  0.493521
2000-01-07 -0.068592 -0.967825  0.591507
2000-01-08  0.339319 -0.978307  0.689492
2000-01-09  0.601495 -0.630417 -1.040079
2000-01-10  1.511723 -0.427952 -0.400154

apply()还有一个参数raw,默认为false,在应用函数前会先把没行/列转换成一个Series。如果设置参数raw为true,要应用的函数会接收到一个n维数组(ndarray)对象,这样会带来潜在的性能提升,如果你不需要索引功能的话。

3.3.6.3 聚合接口(Aggregation API)

这是0.20.0版本的新特性。
聚合接口允许用户通过简单的方法同时传递多个聚合操作。这个API接口在pandas对象中是非常类似的,请看分组group API窗口函数window functions API重采样resample API。聚合的使用方法是DataFrame.aggregate()或别名DataFrame.agg()
我们使用和上面类似的DataFrame来说明:

In [156]: tsdf = pd.DataFrame(np.random.randn(10, 3), columns=['A', 'B', 'C'],
   .....:                     index=pd.date_range('1/1/2000', periods=10))
   .....: 

In [157]: tsdf.iloc[3:7] = np.nan

In [158]: tsdf
Out[158]: 
                   A         B         C
2000-01-01  0.396575 -0.364907  0.051290
2000-01-02 -0.310517 -0.369093 -0.353151
2000-01-03 -0.522441  1.659115 -0.272364
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08 -0.057890  1.148901  0.011528
2000-01-09 -0.155578  0.742150  0.107324
2000-01-10  0.531797  0.080254  0.833297

使用单个函数时,agg()和apply()是等价的。你也可以直接传入函数名的字符串。这就返回一个包含聚合结果的Series:

In [159]: tsdf.agg(np.sum)
Out[159]: 
A   -0.118055
B    2.896420
C    0.377923
dtype: float64

In [160]: tsdf.agg('sum')
Out[160]: 
A   -0.118055
B    2.896420
C    0.377923
dtype: float64

# these are equivalent to a ``.sum()`` because we are aggregating
# on a single function
In [161]: tsdf.sum()
Out[161]: 
A   -0.118055
B    2.896420
C    0.377923
dtype: float64

对Series使用单个聚合会返回一个标量值:

In [162]: tsdf.A.agg('sum')
Out[162]: -0.11805495013260869
3.3.6.4 通过多个函数进行聚合

你能通过列表的方式来传递多个聚合操作参数。每个聚合函数的结果将会是产生的DataFrame的一列。行名会用聚合函数的名字命名:

In [163]: tsdf.agg(['sum'])
Out[163]: 
            A        B         C
sum -0.118055  2.89642  0.377923

多个函数返回多行结果:

In [164]: tsdf.agg(['sum', 'mean'])
Out[164]: 
             A         B         C
sum  -0.118055  2.896420  0.377923
mean -0.019676  0.482737  0.062987

对于Series,多个函数返回一个Series,索引则是函数名:

In [165]: tsdf.A.agg(['sum', 'mean'])
Out[165]: 
sum    -0.118055
mean   -0.019676
Name: A, dtype: float64

传递一个lambda函数将会返回一个<lambda>命名的行:

In [166]: tsdf.A.agg(['sum', lambda x: x.mean()])
Out[166]: 
sum        -0.118055
<lambda>   -0.019676
Name: A, dtype: float64

传递一个自定义的函数将会返回函数名命名的行:

In [167]: def mymean(x):
   .....:     return x.mean()
   .....: 

In [168]: tsdf.A.agg(['sum', mymean])
Out[168]: 
sum      -0.118055
mymean   -0.019676
Name: A, dtype: float64
3.3.6.5 通过字典进行聚合

你可以给DataFrame.agg函数传入一个列名到标量或标量表的字典,这样可以自定义哪些函数作用哪些函数。注意这样产生的结果并没有特定的顺序,你可以使用OrderedDict来保证排序。

In [169]: tsdf.agg({'A': 'mean', 'B': 'sum'})
Out[169]: 
A   -0.019676
B    2.896420
dtype: float64

对一个列传递一个列表的方法将会产生一个DataFrame。对于所有的聚合函数将会得到一个矩阵格式的结果。结果将包含所有的聚合函数。函数没有指定的列的位置将是NaN。

In [170]: tsdf.agg({'A': ['mean', 'min'], 'B': 'sum'})
Out[170]: 
             A        B
mean -0.019676      NaN
min  -0.522441      NaN
sum        NaN  2.89642
3.3.6.6 混合数据类型

当遇到混合数据类型的DataFrame时,不能直接聚合,.agg()将只作用于能够聚合的值。这和分组(groupby)的.agg工作原理类似:

In [171]: mdf = pd.DataFrame({'A': [1, 2, 3],
   .....:                     'B': [1., 2., 3.],
   .....:                     'C': ['foo', 'bar', 'baz'],
   .....:                     'D': pd.date_range('20130101', periods=3)})
   .....: 

In [172]: mdf.dtypes
Out[172]: 
A             int64
B           float64
C            object
D    datetime64[ns]
dtype: object
In [173]: mdf.agg(['min', 'sum'])
Out[173]: 
     A    B          C          D
min  1  1.0        bar 2013-01-01
sum  6  6.0  foobarbaz        NaT
3.3.6.7 自定义描述函数

使用.agg()可以轻松创建自定义描述函数,类似于内置的描述函数。

In [174]: from functools import partial

In [175]: q_25 = partial(pd.Series.quantile, q=0.25)

In [176]: q_25.__name__ = '25%'

In [177]: q_75 = partial(pd.Series.quantile, q=0.75)

In [178]: q_75.__name__ = '75%'

In [179]: tsdf.agg(['count', 'mean', 'std', 'min', q_25, 'median', q_75, 'max'])
Out[179]: 
               A         B         C
count   6.000000  6.000000  6.000000
mean   -0.019676  0.482737  0.062987
std     0.408577  0.836785  0.420419
min    -0.522441 -0.369093 -0.353151
25%    -0.271782 -0.253617 -0.201391
median -0.106734  0.411202  0.031409
75%     0.282958  1.047213  0.093315
max     0.531797  1.659115  0.833297
3.3.6.8 转换接口(Transform API)

这是0.20.0版本的新特性。
transform()方法会返回一个和原数据相同大小和同样索引的对象。它的接口允许你同时提供多个操作而不是一个一个传递函数。这和.agg的接口很类似。
我们用一个和上面用过的DataFrame类似的对象来说明:

In [180]: tsdf = pd.DataFrame(np.random.randn(10, 3), columns=['A', 'B', 'C'],
   .....:                     index=pd.date_range('1/1/2000', periods=10))
   .....: 

In [181]: tsdf.iloc[3:7] = np.nan

In [182]: tsdf
Out[182]: 
                   A         B         C
2000-01-01 -1.219234 -1.652700 -0.698277
2000-01-02  1.858653 -0.738520  0.630364
2000-01-03 -0.112596  1.525897  1.364225
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08 -0.527790 -1.715506  0.387274
2000-01-09 -0.569341  0.569386  0.134136
2000-01-10 -0.413993 -0.862280  0.662690

转换整个数据结构。.transform()允许传入的函数有:NumPy函数、函数名字符串或者用户自定义函数。

In [183]: tsdf.transform(np.abs)
Out[183]: 
                   A         B         C
2000-01-01  1.219234  1.652700  0.698277
2000-01-02  1.858653  0.738520  0.630364
2000-01-03  0.112596  1.525897  1.364225
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.527790  1.715506  0.387274
2000-01-09  0.569341  0.569386  0.134136
2000-01-10  0.413993  0.862280  0.662690

In [184]: tsdf.transform('abs')
Out[184]: 
                   A         B         C
2000-01-01  1.219234  1.652700  0.698277
2000-01-02  1.858653  0.738520  0.630364
2000-01-03  0.112596  1.525897  1.364225
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.527790  1.715506  0.387274
2000-01-09  0.569341  0.569386  0.134136
2000-01-10  0.413993  0.862280  0.662690

In [185]: tsdf.transform(lambda x: x.abs())
Out[185]: 
                   A         B         C
2000-01-01  1.219234  1.652700  0.698277
2000-01-02  1.858653  0.738520  0.630364
2000-01-03  0.112596  1.525897  1.364225
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.527790  1.715506  0.387274
2000-01-09  0.569341  0.569386  0.134136
2000-01-10  0.413993  0.862280  0.662690

这里transform()只接收了一个函数。这等价于ufunc应用。

ufunc:universal function,对数组的每个元素进行运算的函数。NumPy的许多函数都是如此。

In [186]: np.abs(tsdf)
Out[186]: 
                   A         B         C
2000-01-01  1.219234  1.652700  0.698277
2000-01-02  1.858653  0.738520  0.630364
2000-01-03  0.112596  1.525897  1.364225
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.527790  1.715506  0.387274
2000-01-09  0.569341  0.569386  0.134136
2000-01-10  0.413993  0.862280  0.662690

对单个Series使用transform()并只传入一个函数,这样就会返回单个Series。

In [187]: tsdf.A.transform(np.abs)
Out[187]: 
2000-01-01    1.219234
2000-01-02    1.858653
2000-01-03    0.112596
2000-01-04         NaN
2000-01-05         NaN
2000-01-06         NaN
2000-01-07         NaN
2000-01-08    0.527790
2000-01-09    0.569341
2000-01-10    0.413993
Freq: D, Name: A, dtype: float64
3.3.6.9 通过多个函数进行转换

传递多个函数给transform()将会返回一个有多重索引的列的DataFrame。列的第一层将是原始的列名,第二层将是传入的函数的名字。

In [188]: tsdf.transform([np.abs, lambda x: x + 1])
Out[188]: 
                   A                   B                   C          
            absolute  <lambda>  absolute  <lambda>  absolute  <lambda>
2000-01-01  1.219234 -0.219234  1.652700 -0.652700  0.698277  0.301723
2000-01-02  1.858653  2.858653  0.738520  0.261480  0.630364  1.630364
2000-01-03  0.112596  0.887404  1.525897  2.525897  1.364225  2.364225
2000-01-04       NaN       NaN       NaN       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN       NaN       NaN       NaN
2000-01-08  0.527790  0.472210  1.715506 -0.715506  0.387274  1.387274
2000-01-09  0.569341  0.430659  0.569386  1.569386  0.134136  1.134136
2000-01-10  0.413993  0.586007  0.862280  0.137720  0.662690  1.662690

对一个Series传入多个函数会产生一个DataFrame。产生的列名将是传入的函数名。

In [189]: tsdf.A.transform([np.abs, lambda x: x + 1])
Out[189]: 
            absolute  <lambda>
2000-01-01  1.219234 -0.219234
2000-01-02  1.858653  2.858653
2000-01-03  0.112596  0.887404
2000-01-04       NaN       NaN
2000-01-05       NaN       NaN
2000-01-06       NaN       NaN
2000-01-07       NaN       NaN
2000-01-08  0.527790  0.472210
2000-01-09  0.569341  0.430659
2000-01-10  0.413993  0.586007
3.3.6.10 通过字典进行转换

通过传递函数的字典将能自定义对哪些列使用哪些函数。

In [190]: tsdf.transform({'A': np.abs, 'B': lambda x: x + 1})
Out[190]: 
                   A         B
2000-01-01  1.219234 -0.652700
2000-01-02  1.858653  0.261480
2000-01-03  0.112596  2.525897
2000-01-04       NaN       NaN
2000-01-05       NaN       NaN
2000-01-06       NaN       NaN
2000-01-07       NaN       NaN
2000-01-08  0.527790 -0.715506
2000-01-09  0.569341  1.569386
2000-01-10  0.413993  0.137720

如果对一个列传递了多个函数(字典里用列表表示),将会产生一个多层索引的DataFrame,列名也将是传入的函数。

In [191]: tsdf.transform({'A': np.abs, 'B': [lambda x: x + 1, 'sqrt']})
Out[191]: 
                   A         B          
            absolute  <lambda>      sqrt
2000-01-01  1.219234 -0.652700       NaN
2000-01-02  1.858653  0.261480       NaN
2000-01-03  0.112596  2.525897  1.235272
2000-01-04       NaN       NaN       NaN
2000-01-05       NaN       NaN       NaN
2000-01-06       NaN       NaN       NaN
2000-01-07       NaN       NaN       NaN
2000-01-08  0.527790 -0.715506       NaN
2000-01-09  0.569341  1.569386  0.754577
2000-01-10  0.413993  0.137720       NaN
3.3.6.11 应用元素函数

并不是所有的函数都能矢量化(接受NumPy数组并且返回另一个数组或者值),DataFrame的applumap()方法和Series的map()方法能够接受python的任何函数(接收单个值并返回单个值的函数)。比如:

In [192]: df4
Out[192]: 
        one       two     three
a  1.400810 -1.643041       NaN
b -0.356470  1.045911  0.395023
c  0.797268  0.924515 -0.007090
d       NaN  1.553693 -1.670830

In [193]: def f(x):
   .....:     return len(str(x))
   .....: 

In [194]: df4['one'].map(f)
Out[194]: 
a    18
b    19
c    18
d     3
Name: one, dtype: int64

In [195]: df4.applymap(f)
Out[195]: 
   one  two  three
a   18   19      3
b   19   18     19
c   18   18     21
d    3   18     19

Series.map()有额外的特性,它能连接(link)/映射(map)其他Series。这和合并函数有关:

In [196]: s = pd.Series(['six', 'seven', 'six', 'seven', 'six'],
   .....:               index=['a', 'b', 'c', 'd', 'e'])
   .....: 

In [197]: t = pd.Series({'six': 6., 'seven': 7.})

In [198]: s
Out[198]: 
a      six
b    seven
c      six
d    seven
e      six
dtype: object

In [199]: s.map(t)
Out[199]: 
a    6.0
b    7.0
c    6.0
d    7.0
e    6.0
dtype: float64

3.3.7 重新索引和改变标签

reindex()是pandas中基本的数据对齐方法。它被用来实现几乎所有基于标签对齐的函数特性。reindex意味着使数据与特定轴上的一组给定标签相匹配。 它做了下面几件事:

In [200]: s = pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])

In [201]: s
Out[201]: 
a   -0.368437
b   -0.036473
c    0.774830
d   -0.310545
e    0.709717
dtype: float64

In [202]: s.reindex(['e', 'b', 'f', 'd'])
Out[202]: 
e    0.709717
b   -0.036473
f         NaN
d   -0.310545
dtype: float64

上面的Series中原本不包含“f”标签,因此结果出现了NaN值。
对于DataFrame,你能够同时对index和columns进行重新索引:

In [203]: df
Out[203]: 
        one       two     three
a  1.400810 -1.643041       NaN
b -0.356470  1.045911  0.395023
c  0.797268  0.924515 -0.007090
d       NaN  1.553693 -1.670830

In [204]: df.reindex(index=['c', 'f', 'b'], columns=['three', 'two', 'one'])
Out[204]: 
      three       two       one
c -0.007090  0.924515  0.797268
f       NaN       NaN       NaN
b  0.395023  1.045911 -0.356470

也可以通过axis关键字来指定要重新索引的轴。

In [205]: df.reindex(['c', 'f', 'b'], axis='index')
Out[205]: 
        one       two     three
c  0.797268  0.924515 -0.007090
f       NaN       NaN       NaN
b -0.356470  1.045911  0.395023

注意包含实际轴标签的索引可以在对象之间共享。如果我们有一个Series和DataFrame,可以做下面的操作:

In [206]: rs = s.reindex(df.index)

In [207]: rs
Out[207]: 
a   -0.368437
b   -0.036473
c    0.774830
d   -0.310545
dtype: float64

In [208]: rs.index is df.index
Out[208]: True

这意味着重新索引得到的Series的索引和DataFrame的索引是同一个对象。

下面是0.21.0版本的新特性。
DataFrame.reindex()还支持轴风格(axis-style)的调用约定,即你可以指定单个标签参数及其应用的轴。

In [209]: df.reindex(['c', 'f', 'b'], axis='index')
Out[209]: 
        one       two     three
c  0.797268  0.924515 -0.007090
f       NaN       NaN       NaN
b -0.356470  1.045911  0.395023

In [210]: df.reindex(['three', 'two', 'one'], axis='columns')
Out[210]: 
      three       two       one
a       NaN -1.643041  1.400810
b  0.395023  1.045911 -0.356470
c -0.007090  0.924515  0.797268
d -1.670830  1.553693       NaN

另见:MultiIndex/Advanced Indexing,这是完成重新索引操作更简单的方法。
注意:在编写性能敏感的代码时,有一个很好的理由花一些时间成为一个重新索引的忍者(不要直接用重新索引):许多操作在预先对齐的数据上更快。 加和两个未对齐的DataFrame会触发重新索引步骤。对于探索性分析,您几乎不会注意到差异(因为reindex已经进行了大量优化),但是当CPU运行效率很重要时,在这里进行一些直接的reindex调用可能会产生影响。

3.3.7.1 重新索引来和另一对象对齐

你可能会想把一个对象的轴标签变得和另一个一样。虽然直接操作语法简单,但是很冗长,既然这是一个非常常见的操作,我们可以使用reindex_like()方法来简化它:

In [211]: df2
Out[211]: 
        one       two
a  1.400810 -1.643041
b -0.356470  1.045911
c  0.797268  0.924515

In [212]: df3
Out[212]: 
        one       two
a  0.786941 -1.752170
b -0.970339  0.936783
c  0.183399  0.815387

In [213]: df.reindex_like(df2)
Out[213]: 
        one       two
a  1.400810 -1.643041
b -0.356470  1.045911
c  0.797268  0.924515
3.3.7.2 通过align方法对齐对象

同时对齐两个对象最快的途径就是使用align()方法。它支持join参数(和连接和合并有关):

In [214]: s = pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])

In [215]: s1 = s[:4]

In [216]: s2 = s[1:]

In [217]: s1.align(s2)
Out[217]: 
(a   -0.610263
 b   -0.170883
 c    0.367255
 d    0.273860
 e         NaN
 dtype: float64,
 a         NaN
 b   -0.170883
 c    0.367255
 d    0.273860
 e    0.314782
 dtype: float64)

In [218]: s1.align(s2, join='inner')
Out[218]: 
(b   -0.170883
 c    0.367255
 d    0.273860
 dtype: float64,
 b   -0.170883
 c    0.367255
 d    0.273860
 dtype: float64)

In [219]: s1.align(s2, join='left')
Out[219]: 
(a   -0.610263
 b   -0.170883
 c    0.367255
 d    0.273860
 dtype: float64, 
 a         NaN
 b   -0.170883
 c    0.367255
 d    0.273860
 dtype: float64)

对于DataFrame,连接方法将会默认应用到index和columns。

In [220]: df.align(df2, join='inner')
Out[220]: 
(        one       two
 a  1.400810 -1.643041
 b -0.356470  1.045911
 c  0.797268  0.924515,
 one       two
 a  1.400810 -1.643041
 b -0.356470  1.045911
 c  0.797268  0.924515)

你也可以传入可选的axis参数来指定要对齐的轴:

In [221]: df.align(df2, join='inner', axis=0)
Out[221]: 
(        one       two     three
 a  1.400810 -1.643041       NaN
 b -0.356470  1.045911  0.395023
 c  0.797268  0.924515 -0.007090,
 one       two
 a  1.400810 -1.643041
 b -0.356470  1.045911
 c  0.797268  0.924515)

如果你将一个Series传入DataFrame.align(),通过axis参数你可以选择是跟DataFrame的index还是columns对齐:

In [222]: df.align(df2.iloc[0], axis=1)
Out[222]: 
(        one     three       two
 a  1.400810       NaN -1.643041
 b -0.356470  0.395023  1.045911
 c  0.797268 -0.007090  0.924515
 d       NaN -1.670830  1.553693,
 one      1.400810
 three         NaN
 two     -1.643041
 Name: a, dtype: float64)
3.3.7.3 重新索引时填充

reindex()有一个可选的参数——填充方法,可以从下面的表中选择:

方法 行为
pad/ffill 向前填充
bfill/backfill 向后填充
nearest 从最近索引值填充

现在我们通过一个简单的Series来解释这些填充方法。

In [223]: rng = pd.date_range('1/3/2000', periods=8)

In [224]: ts = pd.Series(np.random.randn(8), index=rng)

In [225]: ts2 = ts[[0, 3, 6]]

In [226]: ts
Out[226]: 
2000-01-03   -0.082578
2000-01-04    0.768554
2000-01-05    0.398842
2000-01-06   -0.357956
2000-01-07    0.156403
2000-01-08   -1.347564
2000-01-09    0.253506
2000-01-10    1.228964
Freq: D, dtype: float64

In [227]: ts2
Out[227]: 
2000-01-03   -0.082578
2000-01-06   -0.357956
2000-01-09    0.253506
dtype: float64

In [228]: ts2.reindex(ts.index)
Out[228]: 
2000-01-03   -0.082578
2000-01-04         NaN
2000-01-05         NaN
2000-01-06   -0.357956
2000-01-07         NaN
2000-01-08         NaN
2000-01-09    0.253506
2000-01-10         NaN
Freq: D, dtype: float64

In [229]: ts2.reindex(ts.index, method='ffill')
Out[229]: 
2000-01-03   -0.082578
2000-01-04   -0.082578
2000-01-05   -0.082578
2000-01-06   -0.357956
2000-01-07   -0.357956
2000-01-08   -0.357956
2000-01-09    0.253506
2000-01-10    0.253506
Freq: D, dtype: float64

In [230]: ts2.reindex(ts.index, method='bfill')
Out[230]: 
2000-01-03   -0.082578
2000-01-04   -0.357956
2000-01-05   -0.357956
2000-01-06   -0.357956
2000-01-07    0.253506
2000-01-08    0.253506
2000-01-09    0.253506
2000-01-10         NaN
Freq: D, dtype: float64

In [231]: ts2.reindex(ts.index, method='nearest')
Out[231]: 
2000-01-03   -0.082578
2000-01-04   -0.082578
2000-01-05   -0.357956
2000-01-06   -0.357956
2000-01-07   -0.357956
2000-01-08    0.253506
2000-01-09    0.253506
2000-01-10    0.253506
Freq: D, dtype: float64

这些方法要求索引是已经按升序或降序排好序了。
注意通过fillna(没有'nearest'这个方法)或者interpolate方法也可以达到同样的效果。

In [232]: ts2.reindex(ts.index).fillna(method='ffill')
Out[232]: 
2000-01-03   -0.082578
2000-01-04   -0.082578
2000-01-05   -0.082578
2000-01-06   -0.357956
2000-01-07   -0.357956
2000-01-08   -0.357956
2000-01-09    0.253506
2000-01-10    0.253506
Freq: D, dtype: float64

做填充时如果index不是单调递增或递减时reindex()会报错。fillna()和interpolate()方法不会对index的索引做如何检查。

3.3.7.4 重新索引时填充的限制

重新索引填充时,limit和tolerance参数能做额外限制。limit指定了连续匹配时的最大填充数目。

In [233]: ts2.reindex(ts.index, method='ffill', limit=1)
Out[233]: 
2000-01-03   -0.082578
2000-01-04   -0.082578
2000-01-05         NaN
2000-01-06   -0.357956
2000-01-07   -0.357956
2000-01-08         NaN
2000-01-09    0.253506
2000-01-10    0.253506
Freq: D, dtype: float64

而tolerance指定了要填充的索引和有值的索引之间的最大距离。

In [234]: ts2.reindex(ts.index, method='ffill', tolerance='1 day')
Out[234]: 
2000-01-03   -0.082578
2000-01-04   -0.082578
2000-01-05         NaN
2000-01-06   -0.357956
2000-01-07   -0.357956
2000-01-08         NaN
2000-01-09    0.253506
2000-01-10    0.253506
Freq: D, dtype: float64

注意如果对DatetimeIndex、TimedeltaIndex或PeriodIndex使用填充时,如果可能tolerance参数将被强制转换成Timedelta。这允许你用合适的字符串指定tolerance参数。

3.3.7.5 在轴上删除标签

和reindex比较相关的方法是drop()函数。它会移除轴上的一些标签。

In [235]: df
Out[235]: 
        one       two     three
a  1.400810 -1.643041       NaN
b -0.356470  1.045911  0.395023
c  0.797268  0.924515 -0.007090
d       NaN  1.553693 -1.670830

In [236]: df.drop(['a', 'd'], axis=0)
Out[236]: 
        one       two     three
b -0.356470  1.045911  0.395023
c  0.797268  0.924515 -0.007090

In [237]: df.drop(['one'], axis=1)
Out[237]: 
        two     three
a -1.643041       NaN
b  1.045911  0.395023
c  0.924515 -0.007090
d  1.553693 -1.670830

注意下面的方法也可行,但是不那么简洁明确:

In [238]: df.reindex(df.index.difference(['a', 'd']))
Out[238]: 
        one       two     three
b -0.356470  1.045911  0.395023
c  0.797268  0.924515 -0.007090
3.3.7.6 重命名/映射标签

rename()允许你基于一些映射(比如字典或Series)或其他函数来给轴重新打上标签。

In [239]: s
Out[239]: 
a   -0.610263
b   -0.170883
c    0.367255
d    0.273860
e    0.314782
dtype: float64

In [240]: s.rename(str.upper)
Out[240]: 
A   -0.610263
B   -0.170883
C    0.367255
D    0.273860
E    0.314782
dtype: float64

当传入一个函数时,函数必须能对所有标签都返回一个值(而且必须是唯一值)。也可以用字典或者Series。

In [241]: df.rename(columns={'one': 'foo', 'two': 'bar'},
   .....:           index={'a': 'apple', 'b': 'banana', 'd': 'durian'})
   .....: 
Out[241]: 
             foo       bar     three
apple   1.400810 -1.643041       NaN
banana -0.356470  1.045911  0.395023
c       0.797268  0.924515 -0.007090
durian       NaN  1.553693 -1.670830

如果一个行/列标签在映射中没有,那么就不会被重命名。注意,映射中多余的标签并不会报错。

下面是0.21.0版本的新特性。
DataFrame.rename()也支持轴风格(axis-style)的调用约定,即你可以指定单个映射及其映射的轴。

In [242]: df.rename({'one': 'foo', 'two': 'bar'}, axis='columns')
Out[242]: 
        foo       bar     three
a  1.400810 -1.643041       NaN
b -0.356470  1.045911  0.395023
c  0.797268  0.924515 -0.007090
d       NaN  1.553693 -1.670830

In [243]: df.rename({'a': 'apple', 'b': 'banana', 'd': 'durian'}, axis='index')
Out[243]: 
             one       two     three
apple   1.400810 -1.643041       NaN
banana -0.356470  1.045911  0.395023
c       0.797268  0.924515 -0.007090
durian       NaN  1.553693 -1.670830

rename()方法也提供了一个inplace参数,默认为False,即在原数据的副本上进行相关操作。传入inplace=True会直接对原数据进行更改。

下面是0.18.0版本的新特性。
最后,rename()也接受一个或多个标量值来更改Series的.name属性。

In [244]: s.rename("scalar-name")
Out[244]: 
a   -0.610263
b   -0.170883
c    0.367255
d    0.273860
e    0.314782
Name: scalar-name, dtype: float64

下面是0.24.0版本的新特性。
rename_axis()rename_axis()允许指定要改变的多层索引的名字。

In [245]: df = pd.DataFrame({'x': [1, 2, 3, 4, 5, 6],
   .....:                    'y': [10, 20, 30, 40, 50, 60]},
   .....:                   index=pd.MultiIndex.from_product([['a', 'b', 'c'], [1, 2]],
   .....:                   names=['let', 'num']))
   .....: 

In [246]: df
Out[246]: 
         x   y
let num       
a   1    1  10
    2    2  20
b   1    3  30
    2    4  40
c   1    5  50
    2    6  60

In [247]: df.rename_axis(index={'let': 'abc'})
Out[247]: 
         x   y
abc num       
a   1    1  10
    2    2  20
b   1    3  30
    2    4  40
c   1    5  50
    2    6  60

In [248]: df.rename_axis(index=str.upper)
Out[248]: 
         x   y
LET NUM       
a   1    1  10
    2    2  20
b   1    3  30
    2    4  40
c   1    5  50
    2    6  60

3.3.8 迭代(Iteration)

pandas不同类型的对象迭代返回的结果不同。Series迭代时会被看作数组,会返回一个个值。而对于DataFrame和Panel这种数据结构,会遵循字典迭代的约定,会返回对象的“keys”。
简而言之,基本的迭代操作会产生如下结果:

In [249]: df = pd.DataFrame({'col1': np.random.randn(3),
   .....:                    'col2': np.random.randn(3)}, index=['a', 'b', 'c'])
   .....: 

In [250]: for col in df:
   .....:     print(col)
   .....: 
col1
col2

pandas对象也有类似于字典iteritems()的方法去对(key,value)对进行迭代。
要对DataFrame的行进行迭代,可以使用以下方法:

itertuples这么说有点抽象,往后看,后面有详细的描述。

警告: 对pandas对象进行迭代通常非常慢。在很多情况下,并不需要对行进行迭代,而且可以用下面的方法进行避免。

In [251]: df = pd.DataFrame({'a': [1, 2, 3], 'b': ['a', 'b', 'c']})

In [252]: for index, row in df.iterrows():
   .....:     row['a'] = 10
   .....: 

In [253]: df
Out[253]: 
   a  b
0  1  a
1  2  b
2  3  c
3.3.8.1 iteritems方法

与字典的交互一致,iteritems()对键值对进行迭代:

In [254]: for item, frame in wp.iteritems():
   .....:     print(item)
   .....:     print(frame)
   .....: 
Item1
                   A         B         C         D
2000-01-01 -1.157892 -1.344312  0.844885  1.075770
2000-01-02 -0.109050  1.643563 -1.469388  0.357021
2000-01-03 -0.674600 -1.776904 -0.968914 -1.294524
2000-01-04  0.413738  0.276662 -0.472035 -0.013960
2000-01-05 -0.362543 -0.006154 -0.923061  0.895717
Item2
                   A         B         C         D
2000-01-01  0.805244 -1.206412  2.565646  1.431256
2000-01-02  1.340309 -1.170299 -0.226169  0.410835
2000-01-03  0.813850  0.132003 -0.827317 -0.076467
2000-01-04 -1.187678  1.130127 -1.436737 -1.413681
2000-01-05  1.607920  1.024180  0.569605  0.875906
3.3.8.2 iterrows方法

iterrows()方法允许你像Series对象一样对DataFrame的rows进行迭代。它会返回一个迭代器,会生成index值以及和index一起的包含每行数据的Series。

In [255]: for row_index, row in df.iterrows():
   .....:     print(row_index, row, sep='\n')
   .....: 
0
a    1
b    a
Name: 0, dtype: object
1
a    2
b    b
Name: 1, dtype: object
2
a    3
b    c
Name: 2, dtype: object

注意: 因为iterrows返回每行的Series,它并不保存行的数据类型。(数据类型保存在DataFrame的每列中)。举个例子:

In [256]: df_orig = pd.DataFrame([[1, 1.5]], columns=['int', 'float'])

In [257]: df_orig.dtypes
Out[257]: 
int        int64
float    float64
dtype: object

In [258]: row = next(df_orig.iterrows())[1]

In [259]: row
Out[259]: 
int      1.0
float    1.5
Name: 0, dtype: float64

返回的Series中的所有值都被转换到了floats数据类型,包括第一列中的整数。

In [260]: row['int'].dtype
Out[260]: dtype('float64')

In [261]: df_orig['int'].dtype
Out[261]: dtype('int64')

如果想要保存数据类型,建议使用itertuples(),它会返回命名的元组,而且比iterrows()快多了。
比如,一种人为的转换DataFrame的方法可能是:

In [262]: df2 = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]})

In [263]: print(df2)
   x  y
0  1  4
1  2  5
2  3  6

In [264]: print(df2.T)
   0  1  2
x  1  2  3
y  4  5  6

In [265]: df2_t = pd.DataFrame({idx: values for idx, values in df2.iterrows()})

In [266]: print(df2_t)
   0  1  2
x  1  2  3
y  4  5  6
3.3.8.3 itertuples方法

itertuples()方法会返回迭代器,它会生成DataFrame每行的命名的元组。元组的第一个元素是与行相关的index值,剩下的值是对应的值。
比如:

In [267]: for row in df.itertuples():
   .....:     print(row)
   .....: 
Pandas(Index=0, a=1, b='a')
Pandas(Index=1, a=2, b='b')
Pandas(Index=2, a=3, b='c')

这个方法并不把row变为Series,它仅仅以命名的元组返回值。因此,itertuples()会保存值的数据类型,并且会比iterrows()快很多。
注意: 如果列名称时无效的python标识符、重复的或以下划线开头,那么它们将被重命名为位置名称。如果columns的个数太多(>255),那么将返回一个普通的元组。

3.3.9 时间访问器(.dt accessor)

如果Series是类似的日期时间/时间段的Series,那么这个Series有一个访问器,可以方便地返回日期时间这样的属性。返回值也是Series,并和原Series是一样的索引。

# datetime
In [268]: s = pd.Series(pd.date_range('20130101 09:10:12', periods=4))

In [269]: s
Out[269]: 
0   2013-01-01 09:10:12
1   2013-01-02 09:10:12
2   2013-01-03 09:10:12
3   2013-01-04 09:10:12
dtype: datetime64[ns]

In [270]: s.dt.hour
Out[270]: 
0    9
1    9
2    9
3    9
dtype: int64

In [271]: s.dt.second
Out[271]: 
0    12
1    12
2    12
3    12
dtype: int64

In [272]: s.dt.day
Out[272]: 
0    1
1    2
2    3
3    4
dtype: int64

这个功能还支持下面的表达式:

In [273]: s[s.dt.day == 2]
Out[273]: 
1   2013-01-02 09:10:12
dtype: datetime64[ns]

也可以很简便地转换时区:

In [274]: stz = s.dt.tz_localize('US/Eastern')

In [275]: stz
Out[275]: 
0   2013-01-01 09:10:12-05:00
1   2013-01-02 09:10:12-05:00
2   2013-01-03 09:10:12-05:00
3   2013-01-04 09:10:12-05:00
dtype: datetime64[ns, US/Eastern]

In [276]: stz.dt.tz
Out[276]: <DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>

你还可以连续做这些类型的操作:

In [277]: s.dt.tz_localize('UTC').dt.tz_convert('US/Eastern')
Out[277]: 
0   2013-01-01 04:10:12-05:00
1   2013-01-02 04:10:12-05:00
2   2013-01-03 04:10:12-05:00
3   2013-01-04 04:10:12-05:00
dtype: datetime64[ns, US/Eastern]

你也可以利用Series.dt.strftime()将日期值转化为字符串,这也同样支持标准的strftime()语法。

# DatetimeIndex
In [278]: s = pd.Series(pd.date_range('20130101', periods=4))

In [279]: s
Out[279]: 
0   2013-01-01
1   2013-01-02
2   2013-01-03
3   2013-01-04
dtype: datetime64[ns]

In [280]: s.dt.strftime('%Y/%m/%d')
Out[280]: 
0    2013/01/01
1    2013/01/02
2    2013/01/03
3    2013/01/04
dtype: object
# PeriodIndex
In [281]: s = pd.Series(pd.period_range('20130101', periods=4))

In [282]: s
Out[282]: 
0    2013-01-01
1    2013-01-02
2    2013-01-03
3    2013-01-04
dtype: period[D]

In [283]: s.dt.strftime('%Y/%m/%d')
Out[283]: 
0    2013/01/01
1    2013/01/02
2    2013/01/03
3    2013/01/04
dtype: object

.dt访问器可用于Period和TimeDelta数据类型。

# period
In [284]: s = pd.Series(pd.period_range('20130101', periods=4, freq='D'))

In [285]: s
Out[285]: 
0    2013-01-01
1    2013-01-02
2    2013-01-03
3    2013-01-04
dtype: period[D]

In [286]: s.dt.year
Out[286]: 
0    2013
1    2013
2    2013
3    2013
dtype: int64

In [287]: s.dt.day
Out[287]: 
0    1
1    2
2    3
3    4
dtype: int64
# timedelta
In [288]: s = pd.Series(pd.timedelta_range('1 day 00:00:05', periods=4, freq='s'))

In [289]: s
Out[289]: 
0   1 days 00:00:05
1   1 days 00:00:06
2   1 days 00:00:07
3   1 days 00:00:08
dtype: timedelta64[ns]

In [290]: s.dt.days
Out[290]: 
0    1
1    1
2    1
3    1
dtype: int64

In [291]: s.dt.seconds
Out[291]: 
0    5
1    6
2    7
3    8
dtype: int64

In [292]: s.dt.components
Out[292]: 
   days  hours  minutes  seconds  milliseconds  microseconds  nanoseconds
0     1      0        0        5             0             0            0
1     1      0        0        6             0             0            0
2     1      0        0        7             0             0            0
3     1      0        0        8             0             0            0

*注意: 如果你对非日期的值使用Series.dt,将会报错。

3.3.10 矢量化字符串方法

Series自带一套字符串处理方法,可以很方便地对数组中的每个元素进行操作。可能最重要的是,这些方法不包括对缺失值(即NA值)的自动处理。这些方法可以通过Series的str属性访问到,而且方法名字和python字符串内置的方法的名字是一致的。比如:

In [293]: s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat'])

In [294]: s.str.lower()
Out[294]: 
0       a
1       b
2       c
3    aaba
4    baca
5     NaN
6    caba
7     dog
8     cat
dtype: object

pandas也提供了强大的模式匹配功能,但是请注意模式匹配默认使用正则表达式(在一些情况下,正则表达式是常用的)。
完整的描述请看矢量化字符串方法

上一篇下一篇

猜你喜欢

热点阅读