Как создать функцию Python со скользящей суммой?

Я пытаюсь создать функцию, которая создает скользящий итог по коду по дням для приведенных ниже DataFrames, где In для каждого кода на дату вычитается из Out для каждого кода на дату, этот промежуточный итог вычитается из предыдущего общее количество дней, но общее количество должно быть> = 0 (я включил пример этого в желаемый результат ниже).

Ниже приведен пример моих входных данных и функции, которую я использую, а также пример желаемого результата.

df1 - В

s = """        Date    Code    Quantity
0   10/01/2019  A   20
3   11/01/2019  A   2
7   12/01/2019  A   4
11  13/01/2019  A   10
"""

df2 - аут

s ='''    Date     Code   Quantity
0   11/01/2019  A   5
3   12/01/2019  A   100
4   15/01/2019  A   1
6   16/01/2019  A   2
'''

Код

df3 = df1.merge(df2, how='outer', left_on=['date', 'code'], right_on=['date', 'code']).fillna(0)
df3['qty1'] = df3['qty_x'] - df3['qty_y']
df3['qty'] = 0
def final_adder(x):
    x.qty_x = x.qty_x
    print(x)
    return x
df_final = df3.groupby(['code']).apply(final_adder)
df_final['qty'] = df_final['qty'].clip(lower=0)
df_final.drop(['qty_x', 'qty_y','qty1'], inplace=True, axis=1)


          date code  qty_x  qty_y qty1  qty
0   10/01/2019   A   20.0    0.0  20.0    0
3   11/01/2019   A    2.0    5.0  -3.0    0
7   12/01/2019   A    4.0  100.0 -96.0    0
11  13/01/2019   A   10.0    0.0  10.0    0

Желаемый результат

s = """        Date    Code    Quantity
0   10/01/2019  A   20
3   11/01/2019  A   17
7   12/01/2019  A   0
11  13/01/2019  A   10
12  14/01/2019  A   10
15  15/01/2019  A   9
16  16/01/2019  A   7
"""

Самый простой - это цикл for после слияния.

Quang Hoang 22.12.2020 16:48
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
1
236
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Существует целый поджанр вопросов по SO, включающий кумулятивные операции с ограничениями (например: «сбросить до нуля, когда кумулятивная сумма становится отрицательной»). Это не то же самое, что кумулятивная операция с известными точками сброса (например, из другого столбца или там, где есть NaN и т. д.), поскольку условие включает само кумулятивное значение.

В текущих пандах или numpy нет чистого способа сделать это векторизованным способом.

Лучший (самый быстрый) способ, который я знаю, это этот ТАК ответ с участием numba. Немного изменено и адаптировано к вашей проблеме:

from numba import njit
@njit
def poscumsum(x):
    total = 0
    result = np.empty(x.shape)
    for i, y in enumerate(x):
        total += y
        if total < 0:
            total = 0
        result[i] = total
    return result

При этом вы можете сделать:

a = df1.set_index(['Code', 'Date'])
b = df2.set_index(['Code', 'Date'])
idx = a.index.union(b.index).sort_values()
df3 = (a.reindex(idx, fill_value=0) - b.reindex(idx, fill_value=0))
# optional: resample Date to daily within each group:
df3 = df3.groupby('Code').resample('D', level='Date').sum()
df3['Quantity'] = df3.groupby('Code')['Quantity'].transform(
    lambda g: poscumsum(g.values))

По данным, указанным в вопросе:

>>> df3
                 Quantity
Code Date                
A    2019-01-10        20
     2019-01-11        17
     2019-01-12         0
     2019-01-13        10
     2019-01-14        10
     2019-01-15         9
     2019-01-16         7

Если вы предпочитаете, вы также можете использовать слияние. Вот пример, где сохраняются все промежуточные результаты (для криминалистического анализа):

df3 = df1.merge(df2, on=['Code', 'Date'], how='outer', sort=True).fillna(0)
# optional: resample Date to daily within each group:
df3 = df3.set_index(['Code', 'Date']).groupby('Code').resample('D', level='Date').sum()
df3['diff'] = df3['Quantity_x'] - df3['Quantity_y']
df3['cumdiff'] = df3.groupby('Code')['diff'].transform(
   lambda g: poscumsum(g.values))

df3
# out:
                 Quantity_x  Quantity_y  diff  cumdiff
Code Date                                             
A    2019-01-10        20.0         0.0  20.0     20.0
     2019-01-11         2.0         5.0  -3.0     17.0
     2019-01-12         4.0       100.0 -96.0      0.0
     2019-01-13        10.0         0.0  10.0     10.0
     2019-01-14         0.0         0.0   0.0     10.0
     2019-01-15         0.0         1.0  -1.0      9.0
     2019-01-16         0.0         2.0  -2.0      7.0

Спасибо за это, он отлично работает, когда у меня есть один «код», однако, если я ввожу второй «код», «B», я получаю следующую ошибку: ValueError: не может обрабатывать неуникальный мультииндекс!

CTD91 22.12.2020 19:04

не уверен, что вы подразумеваете под «если я введу второй код». Я протестировал несколько кодов, смешанных вместе, и все работает отлично (хотя он исключает любые code, date, не входящие в df1). Вы можете обновить свой вопрос дополнительными примерами (теми, где текущий предлагаемый ответ не соответствует вашим потребностям).

Pierre D 22.12.2020 19:35

Я исправил ошибку сейчас, я только что немного отредактировал свои входные данные для df2 и желаемый результат. Если у кода есть «количество», я ищу, чтобы он появлялся каждый день в будущем, даже если он не появляется во входных или выходных кадрах данных каждый день. Я думаю, что обновленный вопрос теперь показывает это. Надеюсь это имеет смысл.

CTD91 22.12.2020 21:13

Я вижу: вам нужно все внешнее соединение. Без проблем. Обновленный ответ.

Pierre D 23.12.2020 00:32

Спасибо, теперь это работает намного лучше, единственный вопрос, который у меня остался, заключается в том, что 14-01-19, где нет ни входов, ни выходов, я надеялся, все еще появится в моем выводе с тем же cumdiff, что и в предыдущий день (в этом случай 10). Это возможно?

CTD91 23.12.2020 10:06

конечно, см. «необязательную повторную выборку» в обновленном ответе: просто вставьте ежедневную повторную выборку даты для каждой группы кода непосредственно перед выполнением совокупной суммы: df3 = df3.groupby('Code').resample('D', level='Date').sum().

Pierre D 23.12.2020 14:42

Давайте продолжим обсуждение в чате.

CTD91 23.12.2020 15:31

Другие вопросы по теме