Я предполагаю, что ваш фрейм данных называется df
. Я также предполагаю, что даты не повторяются для данного ClientID
и располагаются в порядке возрастания (если это не так, выполните суммирование по группам и отсортируйте результат так, чтобы он был).
Суть моего решения заключается в том, что для данного ClientID и даты.
- Используйте groupby.transform, чтобы разделить эту проблему по ClientID.
- Используйте
rolling
, чтобы проверить следующие 7 строк для дат, которые находятся в пределах 1 недели. - В этих 7 строках даты в промежутке времени помечены как True (= 1). Даты, которые не помечены как Ложные (= 0).
- В этих 7 строках умножьте столбец «Заказы» на маркировку дат «Истина / Ложь».
- Суммируйте результат.
На самом деле, мы используем 8 строк, потому что, например, SuMoTuWeThFrSaSu имеет 8 дней.
Что затрудняет то, что скользящий агрегирует столбцы по одному за раз и поэтому явно не позволяет работать с несколькими столбцами при агрегировании. Если бы это было так, вы могли бы сделать фильтр, используя столбец даты, и использовать его для суммирования заказов.
Однако есть лазейка: вы можете использовать несколько столбцов, если вы счастливы их переправить через индекс!
Я использую некоторые вспомогательные функции. Примечание. Под a
понимается серия pandas с 8 строками и значениями «Заказы» с указанием «Дата» в индексе.
Любопытно узнать, какова производительность ваших реальных данных.
import pandas as pd
data = {
'ClientID': {0: 123, 1: 123, 2: 123, 3: 456, 4: 456, 5: 456},
'Date': {0: '2020-03-01', 1: '2020-03-05', 2: '2020-03-10',
3: '2020-02-22', 4: '2020-02-25', 5: '2020-02-28'},
'Orders': {0: 23, 1: 10, 2: 7, 3: 3, 4: 15, 5: 5}
}
df = pd.DataFrame(data)
# Make sure the dates are datetimes
df['Date'] = pd.to_datetime(df['Date'])
# Put into index so we can smuggle them through "rolling"
df = df.set_index(['ClientID', 'Date'])
def date(a):
# get the "Date" index-column from the dataframe
return a.index.get_level_values('Date')
def previous_week(a):
# get a column of 0s and 1s identifying the previous week,
# (compared to the date in the last row in a).
return (date(a) >= date(a)[-1] - pd.DateOffset(days=7)) * (date(a) < date(a)[-1])
def previous_week_order_total(a):
#compute the order total for the previous week
return sum(previous_week(a) * a)
def total_last_week(group):
# for a "ClientID" compute all the "previous week order totals"
return group.rolling(8, min_periods=1).apply(previous_week_order_total, raw=False)
# Ok, actually compute this
df['Orders_Last_Week'] = df.groupby(['ClientID']).transform(total_last_week)
# Reset the index back so you can have the ClientID and Date columns back
df = df.reset_index()
Приведенный выше код основан на том факте, что прошедшая неделя охватывает не более 7 строк данных, т. Е. 7 дней в неделе (хотя в вашем примере это на самом деле меньше 7)
Если ваше временное окно отличается от недели, вам нужно заменить все ссылки на продолжительность недели с точки зрения лучшего деления ваших временных отметок.
Например, если ваш временные метки даты расположены не ближе 1 секунды, и вас интересует временное окно в 1 минуту (например, "Orders_last_minute"), замените pd.DateOffset(days=7)
на pd.DateOffset(seconds=60)
и group.rolling(8,...
на group.rolling(61,....)
Очевидно, этот код немного пессимистичен c: в этом случае для каждой строки он всегда просматривает 61 строку. К сожалению, rolling
не предлагает подходящую функцию переменного размера окна. Я подозреваю, что в некоторых случаях python l oop, который использует тот факт, что датафрейм отсортирован по дате, может работать быстрее, чем это частично векторизованное решение.