【问题标题】:Pandas: Exponentially decaying sum with variable weightsPandas:权重呈指数衰减的总和
【发布时间】:2015-10-23 03:38:07
【问题描述】:

类似于这个问题Exponential Decay on Python Pandas DataFrame,我想快速计算数据框中某些列的指数衰减总和。但是,数据框中的行在时间上不是均匀分布的。因此,虽然exponential_sum[i] = column_to_sum[i] + np.exp(-const*(time[i]-time[i-1])) * exponential_sum[i-1],但np.exp(...) 的权重并没有考虑在内,对我来说如何改变这个问题并仍然利用 pandas/numpy 向量化并不明显。有没有针对这个问题的 pandas 矢量化解决方案?

为了说明所需的计算,下面是一个示例帧,其中A 的指数移动和存储在Sum 中,衰减常数为 1:

    time  A       Sum
0   1.00  1  1.000000
1   2.10  3  3.332871
2   2.13 -1  2.234370
3   3.70  7  7.464850
4  10.00  2  2.013708
5  10.20  1  2.648684

【问题讨论】:

  • 你能重新采样你的数据帧,使其间隔均匀吗?
  • @Alexander 我问的是总和,而不是平均值,尽管可能有明显的变换
  • @Alexander 我只是更仔细地阅读了这个问题,但我认为它没有解决我的问题,即如何进行矢量化 numpy/pandas 计算。我在 python 循环中计算指数和没有任何问题,我只是在足够大的帧上进行计算,以便能够对计算进行矢量化处理。
  • 能否提供一些示例数据?

标签: python pandas exponential


【解决方案1】:

这个问题比最初出现的要复杂。我最终使用 numba 的 jit 编译生成器函数来计算指数和。我的最终结果是在我的计算机上在一秒钟内计算出 500 万行的指数总和,希望这足以满足您的需求。

# Initial dataframe.
df = pd.DataFrame({'time': [1, 2.1, 2.13, 3.7, 10, 10.2], 
                   'A': [1, 3, -1, 7, 2, 1]})

# Initial decay parameter.
decay_constant = 1

我们可以将衰减权重定义为exp(-time_delta * decay_constant),并将其初始值设置为1:

df['weight'] = np.exp(-df.time.diff() * decay_constant)
df.weight.iat[0] = 1

>>> df
   A   time    weight
0  1   1.00  1.000000
1  3   2.10  0.332871
2 -1   2.13  0.970446
3  7   3.70  0.208045
4  2  10.00  0.001836
5  1  10.20  0.818731

现在我们将使用来自numba 的 jit 来优化计算指数和的生成器函数:

from numba import jit

@jit(nopython=True)
def exponential_sum(A, k):
    total = A[0]
    yield total
    for i in xrange(1, len(A)):  # Use range in Python 3.
        total = total * k[i] + A[i]
        yield total

我们将使用生成器将值添加到数据框:

df['expSum'] = list(exponential_sum(df.A.values, df.weight.values))

产生所需的输出:

>>> df
   A   time    weight    expSum
0  1   1.00  1.000000  1.000000
1  3   2.10  0.332871  3.332871
2 -1   2.13  0.970446  2.234370
3  7   3.70  0.208045  7.464850
4  2  10.00  0.001836  2.013708
5  1  10.20  0.818731  2.648684

让我们扩展到 500 万行并检查性能:

df = pd.DataFrame({'time': np.random.rand(5e6).cumsum(), 'A': np.random.randint(1, 10, 5e6)})
df['weight'] = np.exp(-df.time.diff() * decay_constant)
df.weight.iat[0] = 1

%%timeit -n 10 
df['expSum'] = list(exponential_sum(df.A.values, df.weight.values))
10 loops, best of 3: 726 ms per loop

【讨论】:

  • 我正在使用 Cython 来实现类似的解决方案,但一直希望能巧妙地使用我遗漏的 numpy/scipy。似乎共识是否定的。这个答案的变体似乎是你能做的最好的。
【解决方案2】:

在您链接到的answer 上展开,我想出了以下方法。

首先,请注意:

exponential_sum[i] = column_to_sum[i] + 
    np.exp(-const*(time[i]-time[i-1])) * column_to_sum[i-1] + 
    np.exp(-const*(time[i]-time[i-2])) * column_to_sum[i-2] + ...

因此,要进行的主要更改是生成权重空间以匹配上述公式。我是这样处理的:

time = pd.Series(np.random.rand(10)).cumsum()
weightspace = np.empty((10,10))
for i in range(len(time)):
    weightspace[i] = time - time[i]
weightspace = np.exp(weightspace)

不用担心矩阵的左下三角,它不会被使用。顺便说一句,必须有一种方法可以在没有循环的情况下生成权重空间。

然后在滚动函数中从权重空间中选择权重的方式略有变化:

def rollingsum(array):
    weights = weightspace[len(array)-1][:len(array)]
    # Convolve the array and the weights to obtain the result
    a = np.dot(array, weights).sum()
    return a

按预期工作:

dataset = pd.DataFrame(np.random.rand(10,3), columns=["A", "B","C"])
a = pd.expanding_apply(dataset, rollingsum)

【讨论】:

  • 这个解决方案的一个问题是权重空间现在非常大。在常规情况的解决方案中,它在数据框的大小上是线性的,现在是二次的。这对于大帧来说是个问题。大帧是需要矢量化解决方案的原因。这是不可避免的吗?
  • 缺少像@Alexander 这样的优化循环,恐怕我看不到另一种方法。
猜你喜欢
  • 2021-02-13
  • 1970-01-01
  • 2018-05-22
  • 2019-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多