如何在上一个值很重要时使用`apply()`或其他矢量化方法

4
假设我有一个DataFrame,其第一列是随机数,其他列将基于前一列的值。

enter image description here

为了方便使用,假设我想让每个数字都是前一个数字的平方。那么它看起来应该像下面这样。

enter image description here

我知道可以编写一个相当简单的循环来完成此操作,但我也知道在Python/Pandas中循环通常不是最有效的。如何使用apply()rolling_apply()完成此操作?或者,如何以其他更有效的方式完成?
下面是我的(失败的)尝试:
In [12]: a = pandas.DataFrame({0:[1,2,3,4,5],1:0,2:0,3:0})

In [13]: a
Out[13]: 
   0  1  2  3
0  1  0  0  0
1  2  0  0  0
2  3  0  0  0
3  4  0  0  0
4  5  0  0  0

In [14]: a = a.apply(lambda x: x**2)

In [15]: a
Out[15]: 
    0  1  2  3
0   1  0  0  0
1   4  0  0  0
2   9  0  0  0
3  16  0  0  0
4  25  0  0  0


In [16]: a = pandas.DataFrame({0:[1,2,3,4,5],1:0,2:0,3:0})

In [17]: pandas.rolling_apply(a,1,lambda x: x**2)
C:\WinPython64bit\python-3.5.2.amd64\lib\site-packages\spyderlib\widgets\externalshell\start_ipython_kernel.py:1: FutureWarning: pd.rolling_apply is deprecated for DataFrame and will be removed in a future version, replace with 
        DataFrame.rolling(center=False,window=1).apply(args=<tuple>,kwargs=<dict>,func=<function>)
  # -*- coding: utf-8 -*-
Out[17]: 
      0    1    2    3
0   1.0  0.0  0.0  0.0
1   4.0  0.0  0.0  0.0
2   9.0  0.0  0.0  0.0
3  16.0  0.0  0.0  0.0
4  25.0  0.0  0.0  0.0

In [18]: a = pandas.DataFrame({0:[1,2,3,4,5],1:0,2:0,3:0})

In [19]: a = a[:-1]**2

In [20]: a
Out[20]: 
    0  1  2  3
0   1  0  0  0
1   4  0  0  0
2   9  0  0  0
3  16  0  0  0

In [21]: 

所以,我的问题主要是如何在DataFrame计算中引用前一列的值。


a[1] = a[0].apply(lambda x: x**2) 等等 - Vaishali
4个回答

4
你所描述的是一种循环关系,目前我认为没有任何非循环的方法来实现该操作。例如,applyrolling_apply仍然依赖于在开始之前具有所有需要的数据,并在结束时一次性输出所有结果数据。也就是说,它们不允许你使用同一系列的早期值来计算下一个值。请参见这个问题以及这个问题以及pandas中的该问题
从实际角度来看,在您的示例中,您只需要填写三列,因此进行三遍循环(如其他答案中所示)可能不会对性能造成重大影响。

感谢您的回复。我的实际DataFrame要大得多,并且包含一些逻辑。这只是一个简化的示例,以了解如何从效率的角度最好地处理它。这些链接很有帮助。 - Kyle

3
a[1] = a[0].apply(lambda x: x**2)
a[2] = a[1].apply(lambda x: x**2)
a[3] = a[2].apply(lambda x: x**2)

将会给你

    0   1   2   3
0   1   1   1   1
1   2   4   16  256
2   3   9   81  6561
3   4   16  256 65536
4   5   25  625 390625

3

很遗憾,据我所知,没有不需要循环的方法来实现这个功能。但是,你不需要遍历每个值,只需要遍历每一列。你可以在前一列上调用apply,并将下一列设置为返回的值:

a = pd.DataFrame({0:[1,2,3,4,5],1:0,2:0,3:0})

for i in range(3):
    a[i+1] = a[i].apply(lambda x: x**2)

我喜欢这个。简单明了。我的上一个尝试使用完整的循环(和我的完整数据源,附加逻辑,并应用scipy.fv())花费了2分钟,而这种方法只需要不到一秒钟。我感觉真的很愚蠢 :)。 - Kyle

3
在这种特殊情况下,我们知道以下有关列的信息:
  • 0 将是存在于该列的任何内容的1次幂。
  • 1 将是位于列 0 的任何内容的2次幂。
  • 2 将是位于列 1 的任何内容的2次幂...
    • 或将是位于列 0 的任何内容的4次幂。
  • 3 将是位于列 2 的任何内容的2次幂...
    • 或将是位于列 1 的任何内容的4次幂...
    • 或将是位于列 0 的任何内容的8次幂。
因此,我们可以使用以下代码对您的示例进行向量化:
np.power(df.values[:, [0]], np.power(2, np.arange(4)))

array([[     1,      1,      1,      1],
       [     2,      4,     16,    256],
       [     3,      9,     81,   6561],
       [     4,     16,    256,  65536],
       [     5,     25,    625, 390625]])

将此内容包装在漂亮的数据框中

pd.DataFrame(
    np.power(df.values[:, [0]], np.power(2, np.arange(4))),
    df.index, df.columns)

   0   1    2       3
0  1   1    1       1
1  2   4   16     256
2  3   9   81    6561
3  4  16  256   65536
4  5  25  625  390625

我非常喜欢这个方法,它确实回答了我的问题。然而,我接受了另一个答案,因为我的问题是泛化的。我应该知道有一种更专门的方法来实现我特定的问题。 - Kyle
@Kyle,有几点需要注意。第一:请知道,如果你发现其他回答比当前被接受的回答更合适,你可以接受其他回答。事实上,这是鼓励的。我的回答有不少被取消接受,但我并不介意。第二:有时候,更困难的问题需要更多时间来回答。如果你想得到一个高质量的回答,耐心等待吧。没有必要急于接受答案。你也可以坦率地告诉那些已经回答的人,你正在等待。第三:我更关心正确性,而不是15个声望点数。 - piRSquared
1
@Kyle,所有这些都说了。我完全赞同Kewl的答案。其他人会根据自己的意愿投赞成或反对票。 :-) - piRSquared

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