Pandas中的apply函数如何使用来自多个行的输入?

4

我需要对一个数据框进行操作,该操作需要使用多行输入。以简单的示例说明,如果所有输入都来自单个行,则可以执行以下操作:

df['c'] = df[['a','b']].apply(lambda x: awesome stuff, axis=1) 
# or 
df['d'] = df[['b','c']].shift(1).apply(...) # to get the values from the previous row

然而,如果我需要当前行的'a'和前一行的'b',有没有一种方法可以使用apply完成呢?我可以添加一个新的'bshift'列,然后只使用df[['a','bshift']],但似乎应该有更直接的方法。
相关但独立的是,在访问df中的特定值时,是否有一种方法将标记索引与整数偏移量结合起来?例如,我知道当前行的标签,但需要前一行。类似于df.at['labelIknow'-1, 'a'](当然不起作用)。这是当我被迫遍历行时使用的。提前致谢。
编辑:一些关于我的工作内容等信息。我有一个包含OHLC条形图的pandas存储(每个证券一个表)。在进行回测时,目前我将需要安全性的完整日期范围拉入内存,然后将其重新采样为适合手头测试的频率。然后我执行一些矢量化操作,例如交易进入信号等。最后,我循环遍历从开始到结束的数据,执行实际的回测,例如检查交易进出口、回撤等——这个循环部分是我试图加速的部分。

我认为这样的事情是不可能的。你到底想要实现什么?也许,apply() 不是开始的正确方法。 - FooBar
我目前正在迭代地进行回测,并尝试加快代码运行速度。每一行(时间点)都取决于之前发生了什么,但也取决于当前时间正在发生的事情。在每次迭代中有许多东西(目前约50行代码),但我想尽可能将其中的大部分内容整合成适用函数,看看是否有帮助。 - fantabolous
1
逐行应用(Apply())并不能神奇地使您的代码更快。如果有一种操作可以在列方向上预先计算一些值,那么您应该事先这样做。(就像您已经做的那样,移动列就是其中之一)。 - FooBar
关于apply不会神奇地更快的观点已经被接受。想一想,我认为做多个apply可能最终比单个大型for循环慢。在https://dev59.com/cmAf5IYBdhLWcg3wOQq2#24871316的第一个答案让我想尝试一下。 - fantabolous
添加了一些背景。考虑到您提到的应用和速度问题,我想我会尽可能地进行向量化处理,然后再看看剩下什么,随后有更多问题时再回来问。如果我试图发布所有代码,那只会造成负担过重。感谢您的建议。 - fantabolous
2个回答

3
这应该可以直接回答您的问题并让您使用apply,尽管我不确定它是否比两行代码的解决方案更优。至少它避免了创建额外的变量。
df['c'] = pd.concat([ df['a'], df['a'].shift() ], axis=1).apply(np.mean,axis=1)
例如,这将把当前和上一个行中'a'值的平均值放入'c'中。
这不太通用,但对于简单的情况,您可以像这样做(继续使用平均值示例):
df['c'] = ( df['a'] + df['a'].shift() ) / 2
在我的小数据集上,这比concat()方法快约10倍。如果您可以以这种风格编写代码,我想这可能是最快的方法。
您还可以尝试使用stack()和分层索引来重新整理数据。这将是将所有变量放入同一行中的一种方法,但我认为它可能比concat方法或仅通过shift()创建中间变量更复杂。

谢谢。我对你的第一种方法进行了计时,与添加一个移位列并在完成应用后删除它相比,有趣的是它们在我尝试的任何df大小上几乎完全相同(而且速度不太快),所以我想时间都花在了应用本身上。确实,第二种方法(矢量化)要快得多,我会尽可能多地使用它,但不幸的是,我认为不可能将所有内容都矢量化。 - fantabolous
1
@fantabolous,问题不仅在于apply,而且在于连接/合并或创建新变量时的一定程度的昂贵。在我的示例数据中,大约有一半的时间成本是由于连接,另一半是由于应用。只需用mean()替换apply(mean),您将看到它仍然较慢。 - JohnE
很有趣,你提到了那个。我尝试了只用.mean(axis = 1),在我10000x2的randn数据框上大约需要2.5ms,而使用.apply(np.mean,axis = 1)则需要480ms。无论哪种情况,都包括添加一个新的“bshift”列,然后再将其删除。如果我使用您的.concat方法,情况也是一样的:直接执行.mean更快。 - fantabolous
1
我并不真正理解pandas的内部机制,但Jeff在其他答案中解释说mean()保留在cythonized空间中,而apply()会回退到常规python空间,因此如果可能的话,始终避免使用apply()。 - JohnE

1
对于第一部分,我认为这种事情是不可能的。如果您更新想要实现的内容,我可以更新此答案。
另外看第二部分,您的数据结构似乎过于依赖行的顺序。这通常不是您管理数据库的方式。同样,如果您告诉我们您的总体目标,我们可能会提示您解决方案(以及更好的数据结构方式)。
无论如何,如果您知道给定索引标签,则获取前一行的一种方法是执行以下操作:
df.ix[:'labelYouKnow'].iloc[-2]

请注意,从效率上来说,这不是最优的做法,因此您可能需要改进数据库结构以避免这种情况的发生。

索引是一个排序和重新采样(即定期间隔)的时间序列,因此前一个条始终是过去的恒定时间增量。对于第二部分,我目前正在添加一个带有数据移位的列(我也可以通过偏移计算索引,例如.ix[currenttime - DateOffset(seconds=1), 'a'],但那似乎效率低下),但只是希望有一些高效的pandas选择技巧我不知道。也许我太过苛求了 :) - fantabolous

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