Есть ли в python эффективный способ без использования циклов for для создания следующего вывода?
У меня есть фрейм данных, включающий несколько элементов, даты и количества, а также конкретное время выполнения для каждого элемента. Цель состоит в том, чтобы создать фрейм данных, который суммирует количества за дни выполнения заказа.
В приведенном ниже примере первая позиция имеет время выполнения 14 дней, поэтому на 10.02.2020 сумма количеств за следующие 14 дней равна 6. 11.02.2020 сумма будет равна 4 и так далее. Проблема в том, что у каждого товара есть свое время выполнения.
У меня есть примерно 12 000 товаров со спросом более года, поэтому я хотел бы избежать циклов for для повышения эффективности. У кого-нибудь есть идея, как решить эту проблему с помощью панд, numpy или чего-то еще?
Кадр данных:
item date quantity lead_time
1 10.02.2020 2 14
1 12.02.2020 1 14
1 14.02.2020 3 14
...
2 10.02.2020 2 20
2 20.02.2020 2 20
2 02.03.2020 2 20
...
Ожидаемый результат:
item date quantity
1 10.02.2020 6
1 11.02.2020 4
1 12.02.2020 4
1 13.02.2020 3
1 14.02.2020 3
1 15.02.2020 0
...
2 10.02.2020 4
2 11.02.2020 2
2 12.02.2020 2
2 13.02.2020 4
2 14.02.2020 4
2 15.02.2020 4
...
demand = pd.DataFrame({"item":["1","1","1","1","1"], "date":["2020-01-03", "2020-01-08", "2020-01-15", "2020-01-17", "2020-01-22"], "quantity":[1,1,1,1,1], "lead_time":[14,14,14,14,14]})
demand.date = pd.to_datetime(demand.date)
calendar = pd.DataFrame({"date": pd.to_datetime(pd.date_range('2020-01-01', '2020-01-31', freq='d').strftime("%Y-%m-%d"))})
calendar.date = pd.to_datetime(calendar.date)
calendar = calendar.merge(demand, how='left')
calendar.lead_time = 14
calendar['cummulative_quantity'] = 0
calendar.quantity = calendar.quantity.fillna(0)
for i in range(len(calendar)):
lead_time = calendar.loc[i, "lead_time"]
calendar.loc[i, "cummulative_quantity"] = sum(calendar.loc[i:i+lead_time, "quantity"])
Да точно, а на 11.02.2020 сумма за следующие 14 дней 4
Вы должны лучше объяснить свой вопрос, как Дэни сказал, что трудно понять, что вы имеете в виду.
Я добавил пример того, как я бы сделал это с помощью цикла, только для одного элемента. Надеюсь, это поможет?
Дайте определение «Производительность» и «Эффективность». почему вы думаете, что «использование циклов for» будет проблемой в вашем сценарии? Какие проблемы вызывает у вас ваш пример?
Я определил исходный DataFrame (df) со столбцом даты как datetime, так он содержит:
item date quantity lead_time
0 1 2020-02-10 2 14
1 1 2020-02-12 1 14
2 1 2020-02-14 3 14
3 2 2020-02-10 2 20
4 2 2020-02-20 2 20
5 2 2020-03-02 2 20
Обратите внимание, что формат столбца даты (при печати) отличается от ваш исходный образец (но это может зависеть от текущей локали).
Сначала определите 2 функции:
myCnt для подсчета суммы количества за указанный период времени:
def myCnt(dd, grp, dltDays):
return grp.loc[dd : dd + pd.Timedelta(dltDays, 'D')].sum()
Параметры:
Обратите внимание, что доступ к необходимой части исходной группы осуществляется по индекс (используя loc), поэтому он должен работать довольно быстро.
itemProc функция для обработки текущей группы исходных строк:
def itemProc(grp):
row0 = grp.iloc[0]
d1 = row0.date
d2 = grp.date.max() + pd.Timedelta('1D')
dltDays = row0.lead_time
grp2 = grp.set_index('date')
cnt = pd.date_range(d1, d2).to_series().apply(myCnt,
args=(grp2.quantity, dltDays))
return pd.DataFrame({'item': row0['item'], 'date': cnt.index, 'quantity': cnt})
Параметр — текущая группа строк.
Поскольку вы не определили диапазон выходных дат для каждой группы, я предположил, что следующие «пограничные» даты:
Но если вам нужны другие границы даты, установите их в соответствии с вашими желаю как d1 и d2.
Тогда для получения фактического результата достаточно выполнить одну инструкцию, то есть применить эту функцию к каждой группе (сгруппированной по элементу):
result = df.groupby('item', as_index=False).apply(itemProc).reset_index(drop=True)
Результат для вашего образца данных (названного кадром данных, а не из вашего примера кода):
item date quantity
0 1 2020-02-10 6
1 1 2020-02-11 4
2 1 2020-02-12 4
3 1 2020-02-13 3
4 1 2020-02-14 3
5 1 2020-02-15 0
6 2 2020-02-10 4
7 2 2020-02-11 4
8 2 2020-02-12 4
9 2 2020-02-13 4
10 2 2020-02-14 4
11 2 2020-02-15 4
12 2 2020-02-16 4
13 2 2020-02-17 4
14 2 2020-02-18 4
15 2 2020-02-19 4
16 2 2020-02-20 4
17 2 2020-02-21 2
18 2 2020-02-22 2
19 2 2020-02-23 2
20 2 2020-02-24 2
21 2 2020-02-25 2
22 2 2020-02-26 2
23 2 2020-02-27 2
24 2 2020-02-28 2
25 2 2020-02-29 2
26 2 2020-03-01 2
27 2 2020-03-02 2
28 2 2020-03-03 0
Обратите внимание, что для группы с элементом == 2 этот результат немного отличается от ваш ожидаемый результат.
Обоснование, почему мой результат правильный (а не ваш):
Для 2020-02-10 диапазон дат до 2020-03-01 (включительно). Сумма количества за этот период 4 (на 2020-02-10 и 20 февраля 2020 г.).
Для 2020-02-11 диапазон дат до 2020-03-02. Сумма количества за этот период также равна 4 (на 20.02.2020 и 2 марта 2020 г.).
И так далее.
Видимо вы сначала создали свой ожидаемый результат для меньшего lead_time а потом, создавая выборку исходных данных, вы устанавливаете там 20 дней.
Большое спасибо! Вы правы, период не должен включать 2 марта.
Вот предложение, которое не полностью лишено петель, но может сработать для вас. В качестве образца кадра demand
я адаптировал первый из ваших примеров:
item date quantity lead_time
0 1 2020-02-10 2 14
1 1 2020-02-12 1 14
2 1 2020-02-14 3 14
3 2 2020-02-10 2 20
4 2 2020-02-20 2 20
5 2 2020-03-02 2 20
Этот
dfs = []
for key, group in demand.groupby(['item', 'lead_time']):
group = group.resample('D', on='date').first().fillna({'item': key[0], 'quantity': 0})
group.quantity = group.quantity[::-1].rolling(f'{key[1]}d').sum()[::-1]
dfs.append(group[['item', 'quantity']])
df = pd.concat(dfs, axis='index')
pr выдает следующий результат:
item quantity
date
2020-02-10 1.0 6.0
2020-02-11 1.0 4.0
2020-02-12 1.0 4.0
2020-02-13 1.0 3.0
2020-02-14 1.0 3.0
2020-02-10 2.0 4.0
2020-02-11 2.0 2.0
2020-02-12 2.0 4.0
2020-02-13 2.0 4.0
2020-02-14 2.0 4.0
2020-02-15 2.0 4.0
2020-02-16 2.0 4.0
2020-02-17 2.0 4.0
2020-02-18 2.0 4.0
2020-02-19 2.0 4.0
2020-02-20 2.0 4.0
2020-02-21 2.0 2.0
2020-02-22 2.0 2.0
2020-02-23 2.0 2.0
2020-02-24 2.0 2.0
2020-02-25 2.0 2.0
2020-02-26 2.0 2.0
2020-02-27 2.0 2.0
2020-02-28 2.0 2.0
2020-02-29 2.0 2.0
2020-03-01 2.0 2.0
2020-03-02 2.0 2.0
resample('D')
заполняет пропущенные дни. Поскольку key[0]
является значением элемента для группы, столбец item
заполняется им. Столбец quantity
заполняется 0
для вставленных дней.
Поскольку key[1]
является lead_time
для группы, rolling((f'{key[1]}d').sum()
суммируется за lead_time
-много дней. Благодаря ведущему [::-1]
суммирование выполняется в правильном направлении (а затем корректируется обратно).
Есть небольшая разница с вашим ожидаемым результатом для item == 2
, которую я не могу объяснить?
Большое спасибо! В моих первых тестах он работал очень быстро.
В чем логика этого: «Итак, на 10.02.2020 сумма количеств за следующие 14 дней равна 6»?