Pandas. Посчитать кол-во часов событий в сутках с учетом пересечения времени и рабочих дней

Рейтинг: 0Ответов: 1Опубликовано: 31.03.2023

Друзья, добрый день!

Есть такой датасет. Каждая строчка показывает длительность некого события (c datefrom по datetill).

И есть длительность рабочего дня (с work_day_from по work_day_till).

Строка всегда определяет один день. И все значения в строке в диапазоне одного дня.

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

Стоит задача посчитать длительность этих событий в течение дня для каждого id (т.е. группировка по столбцам id и date).

Проблема в том, что:

А) события могут пересекаться в течение дня

Б) события могут выходить за рамки рабочего дня

На скриншоте ниже пояснения.

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

Спасибо большое за предложенные варианты :-)

Начальный набор данных для теста:

df = pd.DataFrame({
               "id": [1, 1, 2, 2, 3, 4],
         "datefrom": ['01.01.2023 09:30', '01.01.2023 13:30', '01.01.2023 10:00', '01.01.2023 11:00', '01.01.2023 10:30', '01.01.2023 17:30'],
         "datetill": ['01.01.2023 10:30', '01.01.2023 14:30', '01.01.2023 11:30', '01.01.2023 12:30', '01.01.2023 12:30', '01.01.2023 18:30'],
    "work_day_from": ['01.01.2023 09:00', '01.01.2023 09:00', '01.01.2023 09:00', '01.01.2023 09:00', '01.01.2023 09:00', '01.01.2023 09:00'],
    "work_day_till": ['01.01.2023 18:00', '01.01.2023 18:00', '01.01.2023 18:00', '01.01.2023 18:00', '01.01.2023 18:00', '01.01.2023 18:00'],
             "date": ['01.01.2023', '01.01.2023', '01.01.2023', '01.01.2023', '01.01.2023', '01.01.2023']
})

Ответы

▲ 2Принят

У меня получается какое-то громоздкое решение, но должно работать (разумеется, все даты в исходном фрейме должны иметь тип datetime):

durations = pd.DataFrame()
for i, g in df.groupby(["id", "date"]):
    # сначала обрезаем время задач по границам рабочего времени:
    res = g.apply(lambda x: [max(x["datefrom"], x["work_day_from"]), min(x["datetill"], x["work_day_till"])], axis=1).explode()
    # ищем пересечения времени (отрицательный дифф):
    diffs = [x for x in res.diff() if x < pd.Timedelta('-1 days +23:59:00')]
    # считаем длительности и суммируем:
    duration = res.groupby(res.index).apply(lambda x: x.max() - x.min()).sum()
    # если для группы существует один или более отрицательных диффов
    # вычитаем их из общей длительности:
    if len(diffs):
        duration += pd.Series(diffs).sum()
    # добавляем результат в итоговый датафрейм:
    durations = pd.concat([durations, pd.Series({i:duration})])

durations:

                              0
(1, 01.01.2023) 0 days 02:00:00
(2, 01.01.2023) 0 days 02:30:00
(3, 01.01.2023) 0 days 02:00:00
(4, 01.01.2023) 0 days 00:30:00