当在apply中也计算出先前值时,Pandas中有没有一种方法可以使用dataframe.apply中的先前行值?


97

我有以下数据框:

 Index_Date    A    B    C    D
 ===============================
 2015-01-31    10   10   Nan  10
 2015-02-01     2    3   Nan  22 
 2015-02-02    10   60   Nan  280
 2015-02-03    10   100   Nan  250

要求:

 Index_Date    A    B    C    D
 ===============================
 2015-01-31    10   10   10   10
 2015-02-01     2    3   23   22
 2015-02-02    10   60   290  280
 2015-02-03    10   100  3000 250

Column C导出用于2015-01-31通过取valueD

然后,我需要使用valueC用于2015-01-31通过和乘法valueA2015-02-01添加B

我尝试使用,applyshift使用if else,这会导致出现关键错误。


为什么你在dataframes最后行的列不同的AB
安东·普罗托波夫

@Anton对此表示歉意。
ctrl-alt-delete

A和列中下一行的值是D多少?
jezrael

7
这是一个很好的问题。我对向量化解决方案也有类似的需求。如果pandas提供了一个版本apply(),该版本提供了用户函数能够在其计算中访问上一行中一个或多个值的版本,或者至少返回了一个值,然后该值在下一次迭代中“传递给自身”,那将是很好的。与for循环相比,这是否会提高效率?
比尔

@Bill,您可能对我刚刚添加的答案感兴趣,在这里numba通常是一个不错的选择。
jpp

Answers:


68

首先,创建派生值:

df.loc[0, 'C'] = df.loc[0, 'D']

然后遍历其余行并填充计算出的值:

for i in range(1, len(df)):
    df.loc[i, 'C'] = df.loc[i-1, 'C'] * df.loc[i, 'A'] + df.loc[i, 'B']


  Index_Date   A   B    C    D
0 2015-01-31  10  10   10   10
1 2015-02-01   2   3   23   22
2 2015-02-02  10  60  290  280

42
熊猫中有没有此功能的函数吗?
ctrl-alt-delete

1
输入取决于先前步骤的结果的计算的迭代性质使向量化变得复杂。您也许可以使用apply与循环进行相同计算的函数,但是在幕后这也将是循环。pandas.pydata.org/pandas-docs/version/0.17.1/generation/…–
Stefan

如果我使用此循环并在合并的数据帧上进行计算,并且找到了Nan,则它可以工作,但仅适用于Nan所在的行。没有引发任何错误,如果我尝试执行fillNa,我将得到AttributeError:'numpy.float64'对象没有属性'fillna'是否可以通过Nan跳过行或将值设置为零?
ctrl-alt-delete

您的意思是除以外的其他列中缺少值C吗?
Stefan

是的,您的解决方案很好。我只是确保在循环之前在数据框中填充Nans。
ctrl-alt-delete

43

给定一列数字:

lst = []
cols = ['A']
for a in range(100, 105):
    lst.append([a])
df = pd.DataFrame(lst, columns=cols, index=range(5))
df

    A
0   100
1   101
2   102
3   103
4   104

您可以使用shift引用上一行:

df['Change'] = df.A - df.A.shift(1)
df

    A   Change
0   100 NaN
1   101 1.0
2   102 1.0
3   103 1.0
4   104 1.0

10
在这种情况下这无济于事,因为开始时不知道上一行的值。它必须在每次迭代中计算,然后在下一次迭代中使用。
比尔

6
我仍然很感谢这个答案,因为我偶然发现了这个问题,寻找我确实知道上一行的值的情况。因此,感谢@kztd
Kevin

28

numba

对于不可矢量化的递归计算numba,使用JIT编译并与较低级别的对象配合使用,通常会带来较大的性能改进。您只需要定义一个常规for循环并使用decorator@njit或(对于旧版本)@jit(nopython=True)

对于合理大小的数据帧,与常规for循环相比,这可以将性能提高约30倍:

from numba import jit

@jit(nopython=True)
def calculator_nb(a, b, d):
    res = np.empty(d.shape)
    res[0] = d[0]
    for i in range(1, res.shape[0]):
        res[i] = res[i-1] * a[i] + b[i]
    return res

df['C'] = calculator_nb(*df[list('ABD')].values.T)

n = 10**5
df = pd.concat([df]*n, ignore_index=True)

# benchmarking on Python 3.6.0, Pandas 0.19.2, NumPy 1.11.3, Numba 0.30.1
# calculator() is same as calculator_nb() but without @jit decorator
%timeit calculator_nb(*df[list('ABD')].values.T)  # 14.1 ms per loop
%timeit calculator(*df[list('ABD')].values.T)     # 444 ms per loop

1
这真是棒极了!我已经加速了我的功能,该功能从以前的值开始计算值。谢谢!
Artem Malikov

如何@jit(nopython=True)在jupyter-notebook中使用?
谢尔盖姆斯克

1
@sergzemsk,正如您所写(以及我的回答)一样,它被称为装饰器。注意numba的更高版本支持该快捷方式@njit
jpp

@jpp我有病,if所以这种改善失败了。我收到一个错误“ TypingError:在nopython模式管道中失败(步骤:nopython前端)”
sergzemsk

@sergzemsk,我建议您问一个新问题if,为什么我不知道该语句位于何处,为什么不通过numba将其向量化。
jpp

23

在numpy数组上应用递归函数将比当前答案更快。

df = pd.DataFrame(np.repeat(np.arange(2, 6),3).reshape(4,3), columns=['A', 'B', 'D'])
new = [df.D.values[0]]
for i in range(1, len(df.index)):
    new.append(new[i-1]*df.A.values[i]+df.B.values[i])
df['C'] = new

输出量

      A  B  D    C
   0  1  1  1    1
   1  2  2  2    4
   2  3  3  3   15
   3  4  4  4   64
   4  5  5  5  325

3
通过类似的计算,此答案对我而言非常有效。我尝试结合使用cumsum和shift,但是此解决方案效果更好。谢谢。
西蒙(Simon)

这对我来说也很完美,谢谢。我在尝试各种形式的迭代,迭代,应用等,这似乎很容易理解和表现。
查姆

10

尽管问这个问题已经有一段时间了,但我还是会发表我的答案,希望对大家有所帮助。

免责声明:我知道此解决方案不是标准的,但我认为它很好用。

import pandas as pd
import numpy as np

data = np.array([[10, 2, 10, 10],
                 [10, 3, 60, 100],
                 [np.nan] * 4,
                 [10, 22, 280, 250]]).T
idx = pd.date_range('20150131', end='20150203')
df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx)
df
               A    B     C    D
 =================================
 2015-01-31    10   10    NaN  10
 2015-02-01    2    3     NaN  22 
 2015-02-02    10   60    NaN  280
 2015-02-03    10   100   NaN  250

def calculate(mul, add):
    global value
    value = value * mul + add
    return value

value = df.loc['2015-01-31', 'D']
df.loc['2015-01-31', 'C'] = value
df.loc['2015-02-01':, 'C'] = df.loc['2015-02-01':].apply(lambda row: calculate(*row[['A', 'B']]), axis=1)
df
               A    B     C     D
 =================================
 2015-01-31    10   10    10    10
 2015-02-01    2    3     23    22 
 2015-02-02    10   60    290   280
 2015-02-03    10   100   3000  250

因此,基本上,我们使用applyfrom from pandas和全局变量的帮助来跟踪先前的计算值。


for循环时间比较:

data = np.random.random(size=(1000, 4))
idx = pd.date_range('20150131', end='20171026')
df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx)
df.C = np.nan

df.loc['2015-01-31', 'C'] = df.loc['2015-01-31', 'D']

%%timeit
for i in df.loc['2015-02-01':].index.date:
    df.loc[i, 'C'] = df.loc[(i - pd.DateOffset(days=1)).date(), 'C'] * df.loc[i, 'A'] + df.loc[i, 'B']

每个循环3.2 s±114毫秒(平均±标准偏差,共运行7次,每个循环1次)

data = np.random.random(size=(1000, 4))
idx = pd.date_range('20150131', end='20171026')
df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx)
df.C = np.nan

def calculate(mul, add):
    global value
    value = value * mul + add
    return value

value = df.loc['2015-01-31', 'D']
df.loc['2015-01-31', 'C'] = value

%%timeit
df.loc['2015-02-01':, 'C'] = df.loc['2015-02-01':].apply(lambda row: calculate(*row[['A', 'B']]), axis=1)

每个循环1.82 s±64.4 ms(平均±标准偏差,共7次运行,每个循环1次)

因此平均快0.57倍。


0

通常,避免显式循环的关键是在rowindex-1 == rowindex上联接(合并)数据框的2个实例。

然后,您将拥有一个包含r和r-1行的大数据框,可以在其中执行df.apply()函数。

但是,创建大型数据集的开销可能抵消了并行处理的好处。

马丁

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.