pandas расчет среднего количества значений в неделю по месяцам

Нужно посчитать среднее количество покупок в неделю по месяцам для каждого id. Каждая дата означает одну совершенную покупку.

Датафрейм:

df = pd.DataFrame({
    "id": [
        "01",
        "01",
        "01",
        "01",
        "02",
        "02",
        "02",
        "02"
    ],
    "date": [
        "2022-07-06",
        "2022-07-08",
        "2022-07-15",
        "2022-08-11",
        "2022-07-06",
        "2022-07-08",
        "2022-07-15",
        "2022-08-11"
    ]
})

Нужно получить:

id  month   week_mean
01  07      1.5
01  08      1
02  07      1.5
02  08      1

Мой ход решения такой. Даты, например, 2022-07-06 и 2022-07-08 относятся к одной неделе, т.е. внутри это недели эти даты (покупки) нужно сложить. Далее выводить среднее между всеми неделями месяца для каждого id.

df['date'] = pd.to_datetime(df['date']) # перевожу в формат даты
df['week'] = df['date'].dt.strftime('%U') # добавляю колонку с номером недели
df['date'] = df['date'].astype('str')
df['date'] = df['date'].str.split('-').str[0]+'-'+ df['date'].str.split('-').str[1] # привожу колонку к формату "год-месяц"

df1 = pd.merge(df, df.groupby(['id', 'week'], as_index = False).agg({'date':'count'})\
         .rename(columns={'date' : 'buy_count'}), on=('id','week')).drop_duplicates() # считаю количество заказов в каждую неделю и присоединяю колонку date

df1.groupby(['id', 'date'], as_index = False).agg({'buy_count':'mean'}) # считаю среднее по неделям

В результате получаю такой датафрейм:

    id  date    buy_count
0   01  2022-07 1.5
1   01  2022-08 1.0
2   02  2022-07 1.5
3   02  2022-08 1.0

И он вроде бы ок, но что, если одна из недель будет принадлежать сразу двум месяцам? Датафрейм, например, будет такой:

df = pd.DataFrame({"id": ["01", "01", "01", "01", "02", "02", "02", "02"],
                      "date": ["2022-06-29", "2022-07-02", "2022-07-15", "2022-08-11", 
                               "2022-07-06", "2022-07-08", "2022-07-15", "2022-08-11"]})

И тогда приведенный выше код будет давать неправильный результат:

    id  date    buy_count
0   01  2022-06 2.0
1   01  2022-07 1.5
2   01  2022-08 1.0
3   02  2022-07 1.5
4   02  2022-08 1.0

Что говорит о том, что мой подход с нумерацией недель не верный.


Ответы (2 шт):

Автор решения: strawdog

Что-то у меня неуклюжее решение получается. возможно, я чего-то не понял:

res = pd.DataFrame()
for i in df["id"].unique(): # разбираем датафрейм на части по значению id
    t = df[df["id"]==i].set_index("date").resample("W-MON").count()
    #^^ выбираем данные для текущего id, устанавливаем колонку с датой в 
    # качестве индекса, чтобы можно было делать ресамплинг
    # делаем ресамплинг данных по календарным неделям и считаем количество 
    # id в каждой неделе
    t = t.loc[~(t==0).all(axis=1)]
    # ^^ выбираем только те недели, в которых есть покупки
    s = pd.DataFrame(t.groupby(t.index.to_period("M"))["id"].mean())
    # ^^ группируем по месяцам и считаем среднее
    s.columns = ["buy_value"] # переименовываем колонку
    s["id"] = i # создаем колонку с именем id
    s = s.reset_index() # восстанавливаем индекс
    res = pd.concat([res, s]) # добавляем полученное в результат
    
print(res)    
→ Ссылка
Автор решения: SergFSM

альтернативный вариант:

res = (df.groupby(['id',df['date'].dt.month,df['date'].dt.isocalendar().week]).
       count().
       groupby(level=[0,1])['date'].
       mean().
       rename('week_mean').
       reset_index())

print(res)
'''
   id  date  week_mean
0  01     7        1.5
1  01     8        1.0
2  02     7        1.5
3  02     8        1.0
→ Ссылка