рассчитайте эффективное время процесса путем вычитания нерабочего времени

#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. Привет, Страйдер, огромное спасибо за ваше время и за ваш чистый и информативный ответ! Я использую вашу функцию в разных сценариях, и она отлично работает! 🙂