#python #pandas #dataframe #datetime #timestamp
Вопрос:
У меня есть фрейм данных pandas с более чем 100 отметками времени, который определяет нерабочее время машины:
>>> off_time
date (index) start end
2020-07-04 18:00:00 23:50:00
2020-08-24 00:00:00 08:00:00
2020-08-24 14:00:00 16:00:00
2020-09-04 00:00:00 23:59:59
2020-10-05 18:00:00 22:00:00
У меня также есть второй кадр данных (называемый данными) с более чем 1000 метками времени, определяющими продолжительность некоторых процессов:
>>> data
process-name start-time end-time duration
name1 2020-07-17 08:00:00 00:00 2020-07-18 22:00:00 00:00 1 day 14:00:00
name2 2020-08-24 01:00:00 00:00 2020-08-24 12:00:00 00:00 14:00:00
name3 2020-09-20 07:00:00 00:00 2020-09-20 19:00:00 00:00 12:00:00
name4 2020-09-04 16:00:00 00:00 2020-09-04 18:50:00 00:00 02:50:00
name5 2020-10-04 11:00:00 00:00 2020-10-05 20:00:00 00:00 1 day 09:00:00
Чтобы получить эффективное рабочее время для каждого процесса в данных, теперь мне нужно вычесть нерабочее время из продолжительности. Например, я должен вычесть время между 18 и 20 для процесса «Имя 5», так как это время запланировано как нерабочее время.
Я написал код со многими условиями if-else, которые я рассматриваю как потенциальный источник ошибок! Есть ли простой способ рассчитать эффективное время без использования слишком большого количества «если бы»? Любая помощь была бы очень признательна.
Ответ №1:
Настройте образцы данных (я добавил пару строк в ваши образцы, чтобы включить некоторые крайние случаи).:
######### OFF TIMES
off = pd.DataFrame([
["2020-07-04", dt.time(18), dt.time(23,50)],
["2020-08-24", dt.time(0), dt.time(8)],
["2020-08-24", dt.time(14), dt.time(16)],
["2020-09-04", dt.time(0), dt.time(23,59,59)],
["2020-10-04", dt.time(15), dt.time(18)],
["2020-10-05", dt.time(18), dt.time(22)]], columns= ["date", "start", "end"])
off["date"] = pd.to_datetime(off["date"])
off = off.set_index("date")
### Convert start and end times to datetimes in UTC timezone, since that is much
### easier to handle and fits the other data
off["start"] = pd.to_datetime(off.index.astype("string") " " off.start.astype("string") " 00:00")
off["end"] = pd.to_datetime(off.index.astype("string") " " off.end.astype("string") " 00:00")
off
>>
start end
date
2020-07-04 2020-07-04 18:00:00 00:00 2020-07-04 23:50:00 00:00
2020-08-24 2020-08-24 00:00:00 00:00 2020-08-24 08:00:00 00:00
2020-08-24 2020-08-24 14:00:00 00:00 2020-08-24 16:00:00 00:00
2020-09-04 2020-09-04 00:00:00 00:00 2020-09-04 23:59:59 00:00
2020-10-04 2020-10-04 15:00:00 00:00 2020-10-04 18:00:00 00:00
2020-10-05 2020-10-05 18:00:00 00:00 2020-10-05 22:00:00 00:00
######### PROCESS TIMES
data = pd.DataFrame([
["name1","2020-07-17 08:00:00 00:00","2020-07-18 22:00:00 00:00"],
["name2","2020-08-24 01:00:00 00:00","2020-08-24 12:00:00 00:00"],
["name3","2020-09-20 07:00:00 00:00","2020-09-20 19:00:00 00:00"],
["name4","2020-09-04 16:00:00 00:00","2020-09-04 18:50:00 00:00"],
["name5","2020-10-04 11:00:00 00:00","2020-10-05 20:00:00 00:00"],
["name6","2020-09-03 10:00:00 00:00","2020-09-06 05:00:00 00:00"]
], columns = ["process", "start", "end"])
data["start"] = pd.to_datetime(data["start"])
data["end"] = pd.to_datetime(data["end"])
data["duration"] = data.end -data.start
data
>>
process start end duration
0 name1 2020-07-17 08:00:00 00:00 2020-07-18 22:00:00 00:00 1 days 14:00:00
1 name2 2020-08-24 01:00:00 00:00 2020-08-24 12:00:00 00:00 0 days 11:00:00
2 name3 2020-09-20 07:00:00 00:00 2020-09-20 19:00:00 00:00 0 days 12:00:00
3 name4 2020-09-04 16:00:00 00:00 2020-09-04 18:50:00 00:00 0 days 02:50:00
4 name5 2020-10-04 11:00:00 00:00 2020-10-05 20:00:00 00:00 1 days 09:00:00
5 name6 2020-09-03 10:00:00 00:00 2020-09-06 05:00:00 00:00 2 days 19:00:00
Как вы можете видеть, я добавил строку off
в 2020-10-04, чтобы у name5 было 2 выходных времени, что может произойти в ваших данных и должно быть обработано правильно. (это означает, что в примере в вашем вопросе вам нужно вычесть 5 часов вместо 2)
Я также добавил имя процесса 6, которое длится несколько дней.
Это мое решение, которое будет применено к каждой строке в data
def get_relevant_off(pr):
relevant = off[off.end.gt(pr["start"]) amp; off.start.lt(pr["end"])].copy()
if not relevant.empty:
relevant.loc[relevant["start"].lt(pr["start"]), "start"] = pr["start"]
relevant.loc[relevant["end"].gt(pr["end"]), "end"] = pr["end"]
to_subtract = (relevant.end - relevant.start).sum()
return pr["duration"] - to_subtract
else: return pr.duration
Объяснение:
- первая строка в подмножестве функций содержит соответствующие строки
off
, основанные на строкеpr
- замените начальные значения, которые ниже, чем начальные значения процесса, на начальные значения процесса, и сделайте то же самое с конечными значениями, так как мы не хотим суммировать все время простоя, а только то, что на самом деле происходит одновременно с процессом.
- получите продолжительность нерабочего времени, вычитая начало с конца и суммируя их
- затем вычтите это из общей продолжительности.
data["effective"] = data.apply(get_relevant_off, axis= 1)
data
>>
process start end duration effective
0 name1 2020-07-17 08:00:00 00:00 2020-07-18 22:00:00 00:00 1 days 14:00:00 1 days 14:00:00
1 name2 2020-08-24 01:00:00 00:00 2020-08-24 12:00:00 00:00 0 days 11:00:00 0 days 04:00:00
2 name3 2020-09-20 07:00:00 00:00 2020-09-20 19:00:00 00:00 0 days 12:00:00 0 days 12:00:00
3 name4 2020-09-04 16:00:00 00:00 2020-09-04 18:50:00 00:00 0 days 02:50:00 0 days 00:00:00
4 name5 2020-10-04 11:00:00 00:00 2020-10-05 20:00:00 00:00 1 days 09:00:00 1 days 04:00:00
5 name6 2020-09-03 10:00:00 00:00 2020-09-06 05:00:00 00:00 2 days 19:00:00 1 days 19:00:01
Предостережение: Я предполагаю, что нерабочее время никогда не перекрывается. Кроме того, мне понравилась эта проблема, но у меня больше нет времени, чтобы тратить его на тестирование, поэтому дайте мне знать, если я упустил некоторые крайние случаи, которые ее нарушают, и я постараюсь найти время, чтобы исправить это.
Комментарии:
1. Привет, Страйдер, огромное спасибо за ваше время и за ваш чистый и информативный ответ! Я использую вашу функцию в разных сценариях, и она отлично работает! 🙂