Python 在“应用”中也计算上一行值时,是否有方法在dataframe.apply中使用上一行值?

Python 在“应用”中也计算上一行值时,是否有方法在dataframe.apply中使用上一行值?,python,pandas,dataframe,for-loop,iteration,Python,Pandas,Dataframe,For Loop,Iteration,我有以下数据帧: 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 ==

我有以下数据帧:

 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
列C
是针对
2015-01-31
通过取
D
值而得出的

然后我需要使用
2015-01-31
,乘以
2015-02-01
A
,然后添加
B


我尝试了
apply
shift
使用
if-else
,这会给出一个键错误。

首先,创建派生值:

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

在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

给定一列数字:

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

虽然这个问题已经问了一段时间了,但我会发布我的答案,希望它能帮助一些人

免责声明:我知道此解决方案不是标准解决方案,但我认为它运行良好

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
所以基本上我们使用一个来自pandas的变量,并借助一个全局变量来跟踪先前的计算值


循环的
时间比较:

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']
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
3.2 s±114 ms/圈(7次运行的平均值±标准偏差,每次1圈)

1.82 s±64.4 ms/圈(7次运行的平均值±标准偏差,每次1圈)

因此,平均速度提高了0.57倍

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

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

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']
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

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

然后会有一个包含r和r-1行的大数据帧,从中可以执行df.apply()函数


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

这是一个老问题,但下面的解决方案(没有for循环)可能会有所帮助:

def new_fun(df):
    prev_value = df.iloc[0]["C"]
    def func2(row):
        # non local variable ==> will use pre_value from the new_fun function
        nonlocal prev_value
        new_value =  prev_value * row['A'] + row['B']
        prev_value = row['C']
        return new_value
    # This line might throw a SettingWithCopyWarning warning
    df.iloc[1:]["C"] = df.iloc[1:].apply(func2, axis=1)
    return df

df = new_fun(df)


为什么数据帧中的最后一行与列
A
B
不同?@Anton现在对此表示歉意。列
A
和列
D
中下一行的值是什么?这是一个好问题。我同样需要一个矢量化的解决方案。如果提供了
apply()
版本,用户的函数能够访问上一行中的一个或多个值作为其计算的一部分,或者至少返回一个值,然后在下一次迭代中“传递给自身”,那就太好了。与for循环相比,这难道不会带来一些效率提高吗?@Bill,你可能对我刚才添加的内容感兴趣,
numba
通常是一个很好的选择。pandas中有没有一个函数可以在没有循环的情况下实现这一点?计算的迭代性质,其中输入取决于前面步骤的结果,这使得矢量化变得复杂。您可以将
apply
与执行与循环相同计算的函数一起使用,但在幕后这也是一个循环。如果我使用这个循环并对合并的数据帧进行计算,它会找到一个Nan,但它只对带有Nan的行起作用。如果我尝试使用fillNa,则不会引发任何错误。我会得到AttributeError:'numpy.float64'对象没有属性'fillNa'是否有任何方法可以跳过带有Nan的行或将值设置为零?您的意思是在
C
以外的列中缺少值?是的,您的解决方案很好。我只是确保在循环之前在数据帧中填充NAN。这个答案对于我来说非常适用于类似的计算。我尝试使用cumsum和shift的组合,但这个解决方案效果更好。谢谢。这对我来说也很好,谢谢。我一直在努力解决许多形式的iTerrow、itertuples、apply等问题,这似乎很容易理解和实现。在这种情况下,这不会有帮助,因为前一行的值在开始时是未知的。每次迭代都要计算它,然后在下一次迭代中使用。我仍然很感激这个答案,因为我偶然发现了这个问题,正在寻找一个我确实知道上一行值的例子。所以谢谢@kztdIt太棒了!我加速了我的功能,它计算值​​从以前的值。谢谢如何在jupyter笔记本中使用
@jit(nopyton=True)
?@sergzemsk,正如您所写的(在我的回答中),它被称为一个。注意:numba的更高版本支持快捷方式
@njit
@jpp,我有
if
条件,因此此改进失败。我收到一个错误“TypingError:在nopython模式管道中失败(步骤:nopython frontend)”@sergzemsk,我建议你问一个新问题,我不清楚if
语句的位置,为什么它没有被numba矢量化。这使得一些关于
.apply
的假设可能不成立:如果
.apply
被并行化或调用的顺序不同于您预期的顺序,结果将不符合预期。我同意您的担忧。本anwser中的假设基于此线程的问题。此外,默认情况下应用程序不会并行化。。。