当在应用中也计算前一个值时,Pandas 中是不是可以使用 dataframe.apply 中的前一行值?

Posted

技术标签:

【中文标题】当在应用中也计算前一个值时,Pandas 中是不是可以使用 dataframe.apply 中的前一行值?【英文标题】:Is there a way in Pandas to use previous row value in dataframe.apply when previous value is also calculated in the apply?当在应用中也计算前一个值时,Pandas 中是否可以使用 dataframe.apply 中的前一行值? 【发布时间】:2016-04-23 16:34:49 【问题描述】:

我有以下数据框:

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 是通过采用 valueD 来为 2015-01-31 派生的。

然后我需要将Cvalue 用于2015-01-31 并乘以2015-02-01 上的Avalue 并添加B

我尝试使用if else 使用applyshift,结果出现关键错误。

【问题讨论】:

这是个好问题。我对矢量化解决方案也有类似的需求。如果 pandas 提供了 apply() 的版本,用户的函数能够访问前一行中的一个或多个值作为其计算的一部分,或者至少返回一个值,然后在下一行“传递给它自己”,那就太好了迭代。与 for 循环相比,这不会带来一些效率提升吗? 【参考方案1】:

首先,创建派生值:

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

【讨论】:

pandas 中是否有一个函数可以在没有循环的情况下执行此操作? 输入依赖于先前步骤的结果的计算的迭代性质使向量化复杂化。您也许可以将apply 与一个与循环执行相同计算的函数一起使用,但在幕后这也是一个循环。 pandas.pydata.org/pandas-docs/version/0.17.1/generated/… 如果我使用此循环并在合并的数据帧上进行计算并找到一个 Nan,它可以工作,但仅适用于 Nan 所在的行。没有错误抛出,如果我尝试一个 fillNa 我得到 AttributeError: 'numpy.float64' object has no attribute 'fillna' 有没有办法跳过带有 Nan 的行或将值设置为零? 您是指C以外的列中的缺失值吗? 是的,您的解决方案很好。我只是确保在循环之前将 Nans 填充到数据框中。【参考方案2】:

给定一列数字:

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

【讨论】:

这在这种情况下无济于事,因为前一行的值在开始时是未知的。它必须在每次迭代中计算,然后在下一次迭代中使用。 我仍然很感激这个答案,因为我偶然发现了这个,寻找一个我确实知道前一行的值的案例。所以谢谢@kztd 正是我想要的。这也工作得更快,因为它具有数组操作而不是像其他答案所建议的那样循环。【参考方案3】:

numba

对于不可矢量化的递归计算,numba 使用 JIT 编译并与较低级别的对象一起工作,通常会产生很大的性能改进。您只需要定义一个常规的for 循环并使用装饰器@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

【讨论】:

太棒了!我已经加速了我的函数,它从以前的值中计算值。谢谢! 如何在 jupyter-notebook 中使用@jit(nopython=True) @sergzemsk,正如你写的(在我的回答中),它被称为decorator。请注意,更高版本的 numba 支持快捷方式 @njit @jpp 我有if 条件,所以这个改进失败了。我收到一个错误“TypingError: Failed in nopython mode pipeline (step: nopython frontend)” @sergzemsk,我建议你问一个新问题,我不清楚if 语句的位置,为什么它没有被 numba 向量化。【参考方案4】:

在 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

【讨论】:

这个答案对我来说非常适合我的类似计算。我尝试使用 cumsum 和 shift 的组合,但这个解决方案效果更好。谢谢。【参考方案5】:

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

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

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 的 apply 以及跟踪先前计算值的全局变量。


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 秒 ± 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 秒 ± 64.4 毫秒(7 次运行的平均值 ± 标准偏差,每次 1 个循环)

所以平均快 0.57 倍。

【讨论】:

【参考方案6】:

这是一个老问题,但下面的解决方案(没有 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)

【讨论】:

这对.apply 做出了一些可能不正确的假设:如果.apply 被并行化或以您期望的顺序以外的任何顺序调用,则结果将不符合预期。 我同意您的担忧。这个anwser中的假设是基于这个线程的问题。此外,apply 默认情况下不会并行化...【参考方案7】:

一般来说,避免显式循环的关键是在 rowindex-1==rowindex 上加入(合并)数据帧的 2 个实例。

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

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

【讨论】:

以上是关于当在应用中也计算前一个值时,Pandas 中是不是可以使用 dataframe.apply 中的前一行值?的主要内容,如果未能解决你的问题,请参考以下文章

当在另一个表中找到关联值时,Oracle 在列中设置值

pandas 比较两个不同大小的数据帧映射值,并在缺失值时添加任意值

KnockoutJS - 禁用绑定 - 当父元素有值时如何禁用子元素

尝试在 NSMuableDictionary 中设置值时应用程序崩溃

pandas使用pct_change函数计算数据列的百分比变化:计算当前元素和前一个元素之间的百分比变化(包含NaN值的情况以及数据填充方法)

Pandas groupby - 当列是某个值时