在传递给apply()的自定义函数中访问先前计算的结果

时间:2017-01-21 18:17:39

标签: python python-3.x pandas

我在Python中使用Pandas,我希望在将自定义函数应用于系列时访问上一次计算的结果。

大致相同:

import pandas

# How can I obtain previous_result?
def foo(value, previous_result = None):

    # On the first iteration there is no previous result
    if previous_result is None:
        previous_result = value

    return value + previous_result

series = pandas.Series([1,2,3])
print(series.apply(foo))

这也可以推广到"如何将n以前的结果传递给函数?"。我知道series.rolling()但是即使滚动我也无法获得之前的结果,只能获得输入系列的先前值。

1 个答案:

答案 0 :(得分:5)

您描述的最特殊的操作类型有cummaxcummincumprodcumsumf(x) = x + f(x-1))。

expanding个对象可以找到更多功能:均值,标准差,方差峰度,偏度,相关性等。

对于最常见的情况,您可以将expanding().apply()与自定义函数一起使用。例如,

from functools import reduce  # For Python 3.x
ser.expanding().apply(lambda r: reduce(lambda prev, value: prev + 2*value, r))

相当于f(x) = 2x + f(x-1)

我列出的方法经过优化并运行得非常快,但是当您使用自定义功能时,性能会变差。对于指数平滑,pandas开始优于1000系列的循环,但expanding().apply()的性能降低非常糟糕:

np.random.seed(0)    
ser = pd.Series(70 + 5*np.random.randn(10**4))    
ser.tail()
Out: 
9995    60.953592
9996    70.211794
9997    72.584361
9998    69.835397
9999    76.490557
dtype: float64


ser.ewm(alpha=0.1, adjust=False).mean().tail()
Out: 
9995    69.871614
9996    69.905632
9997    70.173505
9998    70.139694
9999    70.774781
dtype: float64

%timeit ser.ewm(alpha=0.1, adjust=False).mean()
1000 loops, best of 3: 779 µs per loop

使用循环:

def exp_smoothing(ser, alpha=0.1):
    prev = ser[0]
    res = [prev]
    for cur in ser[1:]:
        prev = alpha*cur + (1-alpha)*prev
        res.append(prev)
    return pd.Series(res, index=ser.index)

exp_smoothing(ser).tail()
Out: 
9995    69.871614
9996    69.905632
9997    70.173505
9998    70.139694
9999    70.774781
dtype: float64

%timeit exp_smoothing(ser)
100 loops, best of 3: 3.54 ms per loop

总时间仍为毫秒,但expanding().apply()

ser.expanding().apply(lambda r: reduce(lambda p, v: 0.9*p+0.1*v, r)).tail()
Out: 
9995    69.871614
9996    69.905632
9997    70.173505
9998    70.139694
9999    70.774781
dtype: float64

%timeit ser.expanding().apply(lambda r: reduce(lambda p, v: 0.9*p+0.1*v, r))
1 loop, best of 3: 13 s per loop

cummincumsum等方法已经过优化,只需要x的当前值和函数的先前值。但是,使用自定义函数,复杂度为O(n**2)。这主要是因为存在功能的先前值和x的当前值不足以计算功能的当前值的情况。对于cumsum,您可以使用以前的cumsum并添加当前值以达到结果。你不能这样做,比如几何平均数。这就是expanding即使是中等大小的系列也无法使用的原因。

通常,迭代一个系列并不是一个非常昂贵的操作。对于DataFrames,它需要返回每行的副本,因此效率非常低,但系列不是这种情况。当然,你应该在可用时使用矢量化方法,但如果不是这样,那么使用for循环来完成递归计算等任务就可以。