Рассчитать новое значение, основанное на уменьшении значения

Проблема:

Что мне нравится делать, это шаг за шагом уменьшить значение в Series с помощью непрерывно уменьшающейся базовой фигуры.

Я не уверен в терминологии для этого - я думал, что могу что-то сделать с cumsum и diff, но я думаю, что веду себя на дикой охоте на гусей...

Начальный код:

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 left.
  • Следующее значение 25 - осталось только 5 слева, поэтому это становится 20, и теперь мы не имеем никакого дополнительного разрешения.
  • Следующее значение 30, а так как нет разрешения, значение остается равным 30.

Ответы

Ответ 1

Следуя первоначальной идее cumsum и diff, вы можете написать:

>>> (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, он возвращает desired как Series([10, 10, 25, 30]).

Ответ 2

Ваша идея с cumsum и diff работает. Это не выглядит слишком сложным; не уверен, есть ли еще более короткое решение. Во-первых, мы вычисляем кумулятивную сумму, действуем на нее, а затем возвращаемся назад (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

Ответ 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