【问题标题】:Calculate new value based on decreasing value根据递减值计算新值
【发布时间】:2015-02-23 15:13:16
【问题描述】:

问题:

我想做的是将Series 中的值逐步减小一个不断减小的基数。

我不确定这方面的术语 - 我确实认为我可以用 cumsumdiff 做点什么,但我认为我正在带领自己在那里追逐野鹅......

起始代码:

import pandas as pd

ALLOWANCE = 100
values = pd.Series([85, 10, 25, 30])

期望的输出:

desired = pd.Series([0, 0, 20, 30])

理由:

ALLOWANCE 的基数开始 - Series 中的每个值都减去剩余的数量,津贴本身也是如此,因此发生以下步骤:

  • 从 100 开始,我们可以完全删除 85,所以它变成 0,我们现在将 15 留下为 ALLOWANCE
  • 下一个值是10,我们仍然有15,所以这又变成了0,我们还有5
  • 下一个值是25 - 我们只剩下5,所以它变成了20,现在我们没有更多的余量了。
  • 下一个值是30,由于没有余量,所以值保持为30

【问题讨论】:

  • 我会将values 变量重命名为expenses,将desired 变量重命名为debts,与allowance 结合使用可以让读者了解您想要完成的任务,甚至无需看着文字,imo。

标签: python pandas


【解决方案1】:

按照您对cumsumdiff 的初步想法,您可以这样写:

>>> (values.cumsum() - ALLOWANCE).clip_lower(0).diff().fillna(0)
0     0
1     0
2    20
3    30
dtype: float64

这是values 减去限额的累计总和。负值被裁剪为零(因为我们不关心数字,直到我们透支了我们的津贴)。从那里,您可以计算差异。

但是,如果第一个值可能大于允许值,则首选以下两行变化:

s = (values.cumsum() - ALLOWANCE).clip_lower(0)
desired = s.diff().fillna(s)

这会用“第一个值 - 容差”值填充第一个 NaN 值。因此,在 ALLOWANCE 降低到 75 的情况下,它会以 Series([10, 10, 25, 30]) 的形式返回 desired

【讨论】:

  • 这似乎无法处理 Series > ALLOWANCE 的第一个元素的位置 :(
  • @JonClements 你只需要附加.fillna(0)
  • @EdChum 不能' 这样做 - 认为我需要使用类似于 Carsten 的答案,如果系列中的第一个值仍然是 85,而 ALLOWANCE 是 70,则结果是 @987654335 @ - 这是不正确的 - 它应该是 15
  • 我已经选择了你的答案和 Carsten 的答案之间的混合体——我喜欢这个答案中的 clip_lower(),尽管 Carsten 是第一个指出 .fillna(0) 会产生不正确结果的人。 (虽然你已经纠正了 - 谢谢)
【解决方案2】:

您对cumsumdiff 的想法有效。它看起来并不太复杂;不确定是否有更短的解决方案。首先,我们计算累积和,对其进行运算,然后返回(diff 有点像cumsum 的反函数)。

import math

c = values.cumsum() - ALLOWANCE
# now we've got [-15, -5, 20, 50]
c[c < 0] = 0 # negative values don't make sense here

# (c - c.shift(1)) # <-- what I had first: diff by accident

# it is important that we don't fill with 0, in case that the first
# value is greater than ALLOWANCE
c.diff().fillna(math.max(0, values[0] - ALLOWANCE))

【讨论】:

    【解决方案3】:

    这可能不是那么高效,但目前这是 Pandas 使用 rolling_apply 执行此操作的方式:

    In [53]:
    
    ALLOWANCE = 100
    def reduce(x):
        global ALLOWANCE
        # short circuit if we've already reached 0
        if ALLOWANCE == 0:
            return x
        val = max(0, x - ALLOWANCE)
        ALLOWANCE = max(0, ALLOWANCE - x)
        return val
    
    pd.rolling_apply(values, window=1, func=reduce)
    Out[53]:
    0     0
    1     0
    2    20
    3    30
    dtype: float64
    

    或者更简单地说:

    In [58]:
    
    values.apply(reduce)
    Out[58]:
    0     0
    1     0
    2    20
    3    30
    dtype: int64
    

    【讨论】:

    • 可能有更好的方法来重写我的函数,我不是 python 专家,我想这可以使用生成器重写,但由于某种原因它并不能很好地工作。理想情况下,如果余量已经为 0,我会将其短路并返回传入的行值
    • 它确实为我指明了正确的方向并给了我一些想法...非常感谢 - 现在阅读rolling_apply
    【解决方案4】:

    它应该与 while 循环一起使用:

    ii = 0
    while (ALLOWANCE > 0 and ii < len(values)):
        if (ALLOWANCE > values[ii]):
            ALLOWANCE -= values[ii]
            values[ii] = 0
        else:
            values[ii] -= ALLOWANCE
            ALLOWANCE = 0
        ii += 1 
    

    【讨论】:

    • 谢谢。虽然这可行,但我还计划在 pandas 中进行其他操作 - 所以如果可能的话,我真的在寻求基于 pandas 的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-14
    相关资源
    最近更新 更多