在Pandas数据框中,有没有一种方法可以使用之前计算的行值和不同列的总和?

4

I have the following dataframe:

                     A          B
2021-05-19 07:00:00  Nan        Nan
2021-05-19 07:30:00  0.00       Nan
2021-05-19 08:00:00  0.00       Nan
2021-05-19 08:30:00  0.00       Nan
2021-05-19 09:00:00  19.91      Nan
2021-05-19 09:30:00  0.11       Nan
2021-05-19 10:00:00  0.00       Nan
2021-05-19 10:30:00  22.99      Nan
2021-05-19 11:00:00  0.00       Nan

需求:

                     A          B
2021-05-19 07:00:00  Nan        0.00
2021-05-19 07:30:00  0.00       0.00
2021-05-19 08:00:00  0.00       0.00
2021-05-19 08:30:00  0.00       0.00
2021-05-19 09:00:00  19.91      3.32
2021-05-19 09:30:00  0.11       2.78
2021-05-19 10:00:00  0.00       2.32
2021-05-19 10:30:00  22.99      5.76
2021-05-19 11:00:00  0.00       4.80

计算 B 列:

B1 = A1
B2 = ((B1*5)+A2)/6
B3 = ((B2*5)+A3)/6
B4 = ((B3*5)+A4)/6
etc.

我已经尝试使用Python的shift函数进行计算,但这并不起作用,如果有人能帮助我找到正确的方向,那就太好了。


你不能真正使用 shift,因为下一个值取决于先前值的计算,依此类推。 - Anurag Dhadse
4个回答

2

通过一些数学运算,我们可以将这个递归公式转换为类似于等比数列的形式:

df["B"] = (df.A
             .fillna(0)
             .expanding()
             .apply(lambda s: (1/6)*(s * ((5/6) ** np.arange(len(s))[::-1])).sum() + (5/6)**s.size*s.iloc[0]))

这相当于

N := window.size

B_j = (5/6)^(N-1) A_1 + (1/6) \sum_{j=2}^{N} (5/6)^(N-j) A_j

窗口为expanding,对应于代码中的s。然而,在代码中,我们将A_1与其他元素相加,因此取其中的1/6;因此,我们添加剩余的5/6,因此在其前面有(5/6)^N(而不是N-1);输出结果相同。我们还将A中的NaN转换为0,以防止它们传播。

                         A         B
2021-05-19 07:00:00    NaN  0.000000
2021-05-19 07:30:00   0.00  0.000000
2021-05-19 08:00:00   0.00  0.000000
2021-05-19 08:30:00   0.00  0.000000
2021-05-19 09:00:00  19.91  3.318333
2021-05-19 09:30:00   0.11  2.783611
2021-05-19 10:00:00   0.00  2.319676
2021-05-19 10:30:00  22.99  5.764730
2021-05-19 11:00:00   0.00  4.803942

这就是问题的终结了吗 ;) - Anurag Dhadse
@AnuragDhadse 你的 :) 是什么意思? - Mustafa Aydın
df["B"] = (df.A.expanding().apply(lambda s: (s * ((5/6) ** np.arange(len(s))[::-1])).sum()/6 + (5/6)*s.sizes.iloc[0]))的输出为: NameError: name 'np' is not defined - Tenzin
1
@Tenzin 是的,在你分享的第一个版本的问题中没有 NaN,但我看到它已经被添加了。为了解决这个问题,你可以事先用0填充 NaNdf.A = df.A.fillna(0) 然后再运行上面的代码。 - Mustafa Aydın
或者选择对于填充NaN的A采用扩展窗口,以保持A不变;修改完成。 - Mustafa Aydın
显示剩余2条评论

2
我们可以定义一个名为 fast_sum 的函数来执行所需的计算,然后使用即时编译技术将此函数编译成机器代码,以便它能够以类似于 C 的速度更有效地运行。
import numba

@numba.jit(nopython=True)
def fast_sum(a):
    b = np.zeros_like(a)
    b[0] = a[0]
    for i in range(1, len(a)):
        b[i] = (b[i - 1] * 5 + a[i]) / 6 
    return b

df['B'] = fast_sum(df['A'].fillna(0).to_numpy())

                         A         B
2021-05-19 07:00:00   0.00  0.000000
2021-05-19 07:30:00   0.00  0.000000
2021-05-19 08:00:00   0.00  0.000000
2021-05-19 08:30:00   0.00  0.000000
2021-05-19 09:00:00  19.91  3.318333
2021-05-19 09:30:00   0.11  2.783611
2021-05-19 10:00:00   0.00  2.319676
2021-05-19 10:30:00  22.99  5.764730
2021-05-19 11:00:00   0.00  4.803942

对包含 90000 行的示例数据框进行性能测试

df = pd.concat([df] * 10000, ignore_index=True)

%%timeit
df['B'] = fast_sum(df['A'].fillna(0).to_numpy())
# 1.62 ms ± 93.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

2
你可以循环遍历DataFrame,并将列B设置为每个值,因为每个值都依赖于其自身的上一个值。
for i, date in enumerate(df.index):
    if i==0:
        df.at[date, "B"] = 0
    else:
        df.at[date, "B"] = (df["B"].iat[i-1]*5+df.at[date, "A"])/6
df
>>
                         A         B
2021-05-19 07:00:00   0.00  0.000000
2021-05-19 07:30:00   0.00  0.000000
2021-05-19 08:00:00   0.00  0.000000
2021-05-19 08:30:00   0.00  0.000000
2021-05-19 09:00:00  19.91  3.318333
2021-05-19 09:30:00   0.11  2.783611
2021-05-19 10:00:00   0.00  2.319676
2021-05-19 10:30:00  22.99  5.764730
2021-05-19 11:00:00   0.00  4.803942

循环显然需要时间,而且效率不高。 - Anurag Dhadse
是的,但每个列值都取决于其自身的先前值,因此我不知道如何实现“shift”。请随意提供更有效的答案。 - not_speshal
这给了我以下错误: File“pandas\_libs\index.pyx”,第96行,pandas._libs.index.IndexEngine.set_value File“pandas\_libs\index.pyx”,第107行,pandas._libs.index.IndexEngine.set_value File“pandas\_libs\index.pyx”,第595行,pandas._libs.index.convert_scalar ValueError:无法将nan分配给整数系列 - Tenzin
你能检查一下 df.dtypes 的输出吗?或者在我的代码前面加上这行 df = df.astype('float64') - not_speshal
1
即使我找不到比循环更有效的方法。不能使用shift和cumsum解决 :/。这对我来说看起来还不错。 - Pygirl
显示剩余2条评论

0
如果你想表达函数
B[i] = (A[i-1] * 5 + A[i])/6

你在使用 shift 方面走上了正确的道路

B = ((A.shift(1)*5)+A)/6
B.iat[0] = A.iat[0]

然而,如果你想表达递归函数

B[i] = (B[i-1] * 5 + A[i])/6

那么你就不能使用向量化的pandas操作,只能使用普通的Python代码来计算,正如另一个答案所指出的。


移位不应该是 A.shift(-1) 吗? - Anurag Dhadse
B2 是 B1 和 A2 的函数,而不是 A1 和 A2 的函数。 - not_speshal
不,shift(1)将所有值都向前移动。因此,A.shift(1)的第一个元素是nan,第二个元素是A[0] - Micah Smith
但是检查他写的计算,看起来移位应该是向后的。 - Anurag Dhadse
这个并没有按照预期工作,我刚刚尝试了:df["B"] = ((df["B"].shift(1)*5)+df["A"])/6。在计算中,B2应该采用前一个B1的值。 - Tenzin

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