如何使用pandas-python递归地构造数据框的一列?

12

给出这样的数据框 df

id_      val     
11111    12
12003    22
88763    19
43721    77
...

我希望向df中添加一列diff,每行的值等于该行中的val减去前一行中的diff并乘以0.4再加上前一天的diff。
diff = (val - diff_previousDay) * 0.4 + diff_previousDay

第一行中的diff等于该行中的val * 4。也就是说,预期的df应该是:

id_      val     diff   
11111    12      4.8
12003    22      11.68
88763    19      14.608
43721    77      ...

我尝试过:

mul = 0.4
df['diff'] = df.apply(lambda row: (row['val'] - df.loc[row.name, 'diff']) * mul + df.loc[row.name, 'diff'] if int(row.name) > 0 else row['val'] * mul, axis=1) 

但是遇到了如下错误:

TypeError: ("不支持 float 和 NoneType 的减法操作", '发生在索引 1 处')

您知道如何解决这个问题吗?谢谢!


你可以使用 itertuplesiterrows - IanS
4个回答

10

您可以使用:

df.loc[0, 'diff'] = df.loc[0, 'val'] * 0.4

for i in range(1, len(df)):
    df.loc[i, 'diff'] = (df.loc[i, 'val'] - df.loc[i-1, 'diff']) * 0.4  + df.loc[i-1, 'diff']

print (df)
     id_  val     diff
0  11111   12   4.8000
1  12003   22  11.6800
2  88763   19  14.6080
3  43721   77  39.5648

计算的迭代性质使得输入依赖于前一步骤的结果,这会给向量化带来复杂性。也许你可以使用apply函数,使用与循环相同的计算方法,但在幕后这仍然是一个循环。


我认为这是唯一的解决方案,因为向量化方法不可用。但令人惊讶的是速度非常快 :) - user5779223
3
抱歉 @user5779223,但这个过程不够快!我有一个由1.7百万行和11列组成的数据集,我需要按照一个具有大约80k个不同值的列进行“分组(groupby)”,并且应用这种运行聚合运算(其中包含一些“if”语句)。cumsumcumcount分别运行在800微秒和300微秒之内。应用回调函数,在GroupByDataframe上执行iterrows需要4分钟。我正在检查是否可以使用numba来帮助解决这个问题。 - Tomasz Gandor
@TomaszGandor 问得有点晚了,但是 Numba 对你有用吗?我有 7000 万行数据,尝试基于递归值和条件语句生成新变量。我想知道如何尽可能地加快这个过程。 - Pleastry
@Turtle - 我不记得最后是怎么结束的 ;) 如果今天面临这个问题,我会安装modin(https://modin.readthedocs.io/en/latest/using_modin.html)并检查它是否有帮助。 - Tomasz Gandor
@TomaszGandor 噢,或许之后的项目可以看看这个。我尝试在大型嵌套循环上使用 Numba,执行时间比常规 Python 代码缩短了一半以上。不过,将所有输入从 Pandas 转换为 numpy 还是有点麻烦的。 - Pleastry

5

递归函数不易向量化。但是,您可以使用 numba 优化您的算法。这比常规循环更可取。

from numba import jit

@jit(nopython=True)
def foo(val):
    diff = np.zeros(val.shape)
    diff[0] = val[0] * 0.4
    for i in range(1, diff.shape[0]):
        diff[i] = (val[i] - diff[i-1]) * 0.4 + diff[i-1]
    return diff

df['diff'] = foo(df['val'].values)

print(df)

     id_  val     diff
0  11111   12   4.8000
1  12003   22  11.6800
2  88763   19  14.6080
3  43721   77  39.5648

1
如果您在pandas中使用apply,那么您不应该再在lambda函数中使用dataframe。
在lambda函数中,您的对象在所有情况下都应该是“row”。

但是我该如何提取类似于当前行之前的数据呢? - user5779223
如果轴=1,则无法在apply中进行操作。每一行都被视为一个独立的数据结构,行的顺序并不重要。如果想要提取先前的值,可以使用.shift()创建一个新列,然后在新行上应用并在行内进行减法运算。 - Michael Tamillow

1

我想补充一下jezrael的答案,提供另一种选择。我的答案类似,但我发现速度更快:

def calc_diff(val: pd.Series) -> pd.Series:
    diff = pd.Series(0.0, index=range(len(val)))
    diff[0] = val[0]
    for i in range(1, len(val)):
        result[i] = (val[i] - diff[i-1]) * 0.4 + diff[i-1]
    return result
df['diff'] = calc_diff(df['val'])

我使用了10,000行随机数字进行测试,结果是194毫秒,而jezrael的方法需要4秒。


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