pandas0.24.1文档3.3 基础功能(二)
目录:
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和最近的dplyr和magritr,后者为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()的最后输出结果,并遵从以下规则:
- 如果应用的函数返回一个Series,那么最终的输出结果是DataFrame。DataFrame的列会和应用的函数返回Series的索引对齐。
- 如果应用的函数返回其他类型的数据,那么最终的输出结果是Series。
通过result_type能够对默认行为进行覆盖,result_type可选参数有reduce、broadcast和expand。这将决定列表类型的输出结果是否和如何转换到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意味着使数据与特定轴上的一组给定标签相匹配。 它做了下面几件事:
- 对数据重新排序,与新的标签匹配
- 如果标签的位置没有数据,就插入缺失值(NA)
- 如果指定了,则使用逻辑(与处理时间序列数据高度相关)填充缺失标签的数据。
下面是一个简单的例子:
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参数(和连接和合并有关):
- join='outer':取索引的并集(默认)
- join='left':使用调用对象的索引
- join='right':使用传入的对象的索引
- join='inner':索引的交集
它返回带有两个重新索引的Series的元组。
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”。
简而言之,基本的迭代操作会产生如下结果:
- Series:值
- DataFrame:column标签
- Panel:item标签
下面举个例子,对DataFrame进行迭代会返回给你列名:
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的行进行迭代,可以使用以下方法:
- iterrows():对DataFrame的行进行迭代,返回(index,Series)对。这把rows转换为Series对象,可能会改变原来的数据类型,但也能带来一些性能提升。
- itertuples():对DataFrame的行进行迭代,返回命名了的元组。这比iterrows()快读了,并且在很多情况下如果要对DataFrame的值进行迭代,推荐使用这种方法。
itertuples这么说有点抽象,往后看,后面有详细的描述。
警告: 对pandas对象进行迭代通常非常慢。在很多情况下,并不需要对行进行迭代,而且可以用下面的方法进行避免。
- 寻找矢量化的解决方法:很多操作都能使用内置的方法、NumPy函数或者布尔索引
- 如果有一个能够对DataFrame、Series全局起作用的函数,最好使用apply()方法,而不是对值进行迭代。详情请看函数应用。
- 如果需要对值进行迭代操作而且性能又很重要,考虑用cython或numba写内部循环。具体例子请看增强性能部分。
警告: 对于你迭代的对象,永远不要修改值。修改并不能保证在所有情况下都有效,这取决于数据类型。如果迭代器返回的是副本而不是原数据的视图,修改它将不产生任何效果。
比如,下面的例子中的赋值是无效的:
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()对键值对进行迭代:
- Series:(index,标量值)对
- DataFrame:(column,Series)对
- Panel:(item,DataFrame)对
举个例子:
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也提供了强大的模式匹配功能,但是请注意模式匹配默认使用正则表达式(在一些情况下,正则表达式是常用的)。
完整的描述请看矢量化字符串方法。