Я пытаюсь создать функцию, которая создает скользящий итог по коду по дням для приведенных ниже 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
"""






Существует целый поджанр вопросов по 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: не может обрабатывать неуникальный мультииндекс!
не уверен, что вы подразумеваете под «если я введу второй код». Я протестировал несколько кодов, смешанных вместе, и все работает отлично (хотя он исключает любые code, date, не входящие в df1). Вы можете обновить свой вопрос дополнительными примерами (теми, где текущий предлагаемый ответ не соответствует вашим потребностям).
Я исправил ошибку сейчас, я только что немного отредактировал свои входные данные для df2 и желаемый результат. Если у кода есть «количество», я ищу, чтобы он появлялся каждый день в будущем, даже если он не появляется во входных или выходных кадрах данных каждый день. Я думаю, что обновленный вопрос теперь показывает это. Надеюсь это имеет смысл.
Я вижу: вам нужно все внешнее соединение. Без проблем. Обновленный ответ.
Спасибо, теперь это работает намного лучше, единственный вопрос, который у меня остался, заключается в том, что 14-01-19, где нет ни входов, ни выходов, я надеялся, все еще появится в моем выводе с тем же cumdiff, что и в предыдущий день (в этом случай 10). Это возможно?
конечно, см. «необязательную повторную выборку» в обновленном ответе: просто вставьте ежедневную повторную выборку даты для каждой группы кода непосредственно перед выполнением совокупной суммы: df3 = df3.groupby('Code').resample('D', level='Date').sum().
Давайте продолжим обсуждение в чате.
Самый простой - это цикл for после слияния.