Pandas. Посчитать кол-во часов событий в сутках с учетом пересечения времени и рабочих дней
Друзья, добрый день!
Есть такой датасет. Каждая строчка показывает длительность некого события (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']
})
Ответы (1 шт):
У меня получается какое-то громоздкое решение, но должно работать (разумеется, все даты в исходном фрейме должны иметь тип 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

