Найти пересекающиеся сроки

Есть таблица со сроками проведения промо-акций (date_from, date_to) на различные товары (product_id) в разных магазинах (store_id). Мне нужно выяснить, есть ли пересекающиеся сроки промо-акций для пары товар-магазин? Желательно ещё добавить столбец с метками, типа "Пересекается\ Не пересекается".

введите сюда описание изображения

product_id store_id promo_id date_from date_to
1 1 8 2024-01-01 2024-01-07
1 2 9 2024-01-08 2024-01-14
2 1 1 2024-02-19 2024-02-25
2 2 10 2023-03-13 2023-03-19
3 1 3 2023-03-20 2023-03-26
3 2 11 2023-05-08 2023-05-14
4 1 12 2024-02-12 2024-02-18
4 2 13 2024-04-01 2024-04-07
5 1 14 2023-07-03 2023-07-09
5 2 20 2024-07-29 2024-08-04

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

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

Вдвоём с ChatGPT вот удалось родить нужный код (она по моим указаниям написала функцию mark_overlaps, причём первоначально она хотела использовать цикл с iterrows вместо векторного сравнения с помощью shift). Я ещё добавил пересекающийся диапазон в конце (специально с неправильной сортировкой событий), в ваших данных его не было.

План проверки простой:

  • Группируем строки по магазину и товару
  • Сортируем группу по дате начала
  • Сравниваем в каждой строке дату начала с датой окончания из предыдущей строки, чтобы найти пересечения
  • Если хоть в одной строке группы есть пересечение, помечаем дополнительным флагом всю группу
from io import StringIO
import pandas as pd

def mark_overlaps(group: pd.DataFrame) -> pd.DataFrame:
    group = group.sort_values("date_from").reset_index(drop=True)
    group["prev_end"] = group["date_to"].shift()
    group["overlap_row"] = group["date_from"] <= group["prev_end"]
    group["has_overlap"] = group["overlap_row"].any()
    return group.drop(columns="prev_end")

data = """
product_id  store_id    promo_id    date_from   date_to
1   1   8   2024-01-01  2024-01-07
1   2   9   2024-01-08  2024-01-14
2   1   1   2024-02-19  2024-02-25
2   2   10  2023-03-13  2023-03-19
3   1   3   2023-03-20  2023-03-26
3   2   11  2023-05-08  2023-05-14
4   1   12  2024-02-12  2024-02-18
4   2   13  2024-04-01  2024-04-07
5   1   14  2023-07-03  2023-07-09
5   2   21  2024-08-03  2024-08-05
5   2   20  2024-07-29  2024-08-04
"""

df = pd.read_csv(StringIO(data), sep='\t') #, parse_dates=['date_from', 'date_to'])

result = df.groupby(["product_id", "store_id"], group_keys=False) \
           .apply(mark_overlaps) \
           .reset_index(drop=True)

Результат:

product_id store_id promo_id date_from date_to overlap_row has_overlap
0 1 1 8 2024-01-01 2024-01-07 False False
1 1 2 9 2024-01-08 2024-01-14 False False
2 2 1 1 2024-02-19 2024-02-25 False False
3 2 2 10 2023-03-13 2023-03-19 False False
4 3 1 3 2023-03-20 2023-03-26 False False
5 3 2 11 2023-05-08 2023-05-14 False False
6 4 1 12 2024-02-12 2024-02-18 False False
7 4 2 13 2024-04-01 2024-04-07 False False
8 5 1 14 2023-07-03 2023-07-09 False False
9 5 2 20 2024-07-29 2024-08-04 False True
10 5 2 21 2024-08-03 2024-08-05 True True

Пояснения:

  • overlap_row - диапазон дат в этой строке пересекается с предыдущим в данной группе
  • has_overlap - в данной группе магазин & продукт есть пересечения
→ Ссылка