Есть ли эффективный метод numpy или pandas для создания пользовательских ведер?

Есть ли в 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
...

This is how I would solve it using a loop:
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"])

В чем логика этого: «Итак, на 10.02.2020 сумма количеств за следующие 14 дней равна 6»?

Dani Mesejo 13.12.2020 13:48

Да точно, а на 11.02.2020 сумма за следующие 14 дней 4

moritzr92 13.12.2020 13:51

Вы должны лучше объяснить свой вопрос, как Дэни сказал, что трудно понять, что вы имеете в виду.

Erfan 13.12.2020 13:58

Я добавил пример того, как я бы сделал это с помощью цикла, только для одного элемента. Надеюсь, это поможет?

moritzr92 13.12.2020 15:17

Дайте определение «Производительность» и «Эффективность». почему вы думаете, что «использование циклов for» будет проблемой в вашем сценарии? Какие проблемы вызывает у вас ваш пример?

JeffUK 13.12.2020 15:17
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
5
119
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я определил исходный 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 функции:

  1. myCnt для подсчета суммы количества за указанный период времени:

    def myCnt(dd, grp, dltDays):
        return grp.loc[dd : dd + pd.Timedelta(dltDays, 'D')].sum()
    

    Параметры:

    • дд - дата начала,
    • grp - группа исходных количеств, но с индексом, установленным на дату,
    • dltDays - сколько дней от дд до последней даты.

    Обратите внимание, что доступ к необходимой части исходной группы осуществляется по индекс (используя loc), поэтому он должен работать довольно быстро.

  2. 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})
    

    Параметр — текущая группа строк.

    Поскольку вы не определили диапазон выходных дат для каждой группы, я предположил, что следующие «пограничные» даты:

    • start - дата начала в каждой группе,
    • конец - последняя дата в каждой группе + 1 день (для всех дальнейших, даты, если вы хотите их, результат будет содержать количество из 0).

    Но если вам нужны другие границы даты, установите их в соответствии с вашими желаю как 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 этот результат немного отличается от ваш ожидаемый результат.

Обоснование, почему мой результат правильный (а не ваш):

  1. Для 2020-02-10 диапазон дат до 2020-03-01 (включительно). Сумма количества за этот период 4 (на 2020-02-10 и 20 февраля 2020 г.).

  2. Для 2020-02-11 диапазон дат до 2020-03-02. Сумма количества за этот период также равна 4 (на 20.02.2020 и 2 марта 2020 г.).

  3. И так далее.

Видимо вы сначала создали свой ожидаемый результат для меньшего lead_time а потом, создавая выборку исходных данных, вы устанавливаете там 20 дней.

Большое спасибо! Вы правы, период не должен включать 2 марта.

moritzr92 13.12.2020 23:14
Ответ принят как подходящий

Вот предложение, которое не полностью лишено петель, но может сработать для вас. В качестве образца кадра 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, которую я не могу объяснить?

Большое спасибо! В моих первых тестах он работал очень быстро.

moritzr92 13.12.2020 23:18

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