Pandas重采样当累计函数返回数据框时

4
我希望使用 pandas 的 resampling 函数,但要应用自定义的函数。我遇到的问题是自定义函数返回一个 pandas 数据帧而不是单个数组。
以下示例说明了我的问题:
>>> import pandas as pd
>>> import numpy as np
>>> def f(data):
...     return ((1+data).cumprod(axis=0)-1)
... 
>>> data = np.random.randn(1000,3)
>>> index = pd.date_range("20170101", periods = 1000, freq="B")
>>> df = pd.DataFrame(data= data, index =index) 

假设我想将工作日重新采样为商业月末频率:

>>> resampler = df.resample("BM")

如果我现在应用我的函数f,我得不到期望的结果。我想要从f的输出中获取最后一行。
>>> resampler.apply(f)

这是因为我的函数 f 中的 cumprod 返回了一个 pandas 数据帧。我可以编写我的 f 函数,使其仅返回最后一行。然而,我还想在其他地方使用该函数来返回整个数据帧。这可以通过在函数 f 中引入一个标志(例如 "last_row")来解决,该标志控制是返回完整数据帧还是仅返回最后一行。但是,这种解决方案似乎有些麻烦。


如果你想要的是首先应用该函数,然后选择该月的最后一个工作日,我认为这可以简化。实际上,这不需要重新采样,而是重新采样和累积函数的组合使这变得棘手。 - JohnE
2个回答

2

只需定义带有 last_row 参数的函数 f。您可以将其默认设置为 False,以便返回整个数据框。当设置为 True 时,它将返回最后一行。

def f(data, last_row=False):
    df = ((1+data).cumprod(axis=0)-1)
    if last_row:
        return df.iloc[-1]
    return df

获取最后一行
df.resample('BM').apply(f, last_row=True)

                    0           1          2
2017-01-31   0.185662   -0.580058  -1.004879
2017-02-28  -1.004035   -0.999878  17.059846
2017-03-31  -0.995280   -1.000001  -1.000507
2017-04-28  -1.000656 -240.369487  -1.002645
2017-05-31  47.646827  -72.042190  -1.000016
....

像之前一样,返回所有行。

df.resample('BM').apply(f)

谢谢,目前我已经这样做了,但在我看来它看起来相当丑陋。难道没有更符合Python风格的方法吗? - math
丑陋?这是Python的方式。 - Ted Petrou
这绝对是一个Pythonic的解决方案。另一个使用lambda的解决方案:resampler.apply(lambda x: f(x).iloc[-1])。所以你不需要改变函数f() - qvpham
这是一个不错的答案,但恕我直言,它并不是最佳答案,因为它通过反复计算cumprod有些低效(我这么说是基于我对答案工作原理的诚实理解,但我可能是错误的。我很快会发布一种替代方法,你可以自行判断)。 - JohnE

2
我认为您可以按照以下方式重构代码,这样对于更大的数据帧来说会更快:
(1+df).resample('BM').prod() - 1


                   0         1         2
2017-01-31 -0.999436 -1.259078 -1.000215
2017-02-28 -1.221404  0.342863  9.841939
2017-03-31 -0.820196 -1.002598 -0.450662
2017-04-28 -1.000299  2.739184 -1.035557
2017-05-31 -0.999986 -0.920445 -2.103289

这与@TedPetrou的答案相同,尽管我们使用了不同的随机种子,但您可以轻松地自行测试。虽然实际上,我仍在解决为什么这样通过prod()而不是cumprod()给出相同的答案。无论如何,正如您所看到的,我在这里使用的是直觉和逆向工程的混合方法,并将在双重检查后进行更新...
对于这个相对较小的1000行数据框,这种方式只快了两倍左右,但如果增加行数,您会发现这种方式比较好扩展(在10000行时大约快250倍)。
其他方法:它们与上述方法(以及彼此)给出不同的答案,但我想知道它们是否更接近您要寻找的答案?
(1+df).resample('BM').mean().expanding().apply( lambda x: x.prod() - 1)

(1+df).expanding().apply( lambda x: x.prod() - 1).resample('BM').mean()

你可以直接使用.prod()作为一个方法(我认为可能尚未实现)。 - Jeff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接