通过减去非工作时间计算流程的有效时间

calculate effective time of a process by subtracting non-working-time

我有一个 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

为了得到数据中每个进程的有效工作时间,我现在必须从持续时间中减去非工作时间。例如,我必须减去进程“姓名5”的18到20之间的时间,因为这个时间计划为非工作时间。

我写的代码有很多 if-else 条件,我认为这是潜在的错误来源!有没有一种干净的方法来计算有效时间而不使用太多 if-else?任何帮助将不胜感激。

设置示例数据(我在您的示例中添加了几行以包含一些边缘情况):

######### 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

如您所见,我在 2020 年 10 月 4 日向 off 添加了一行,因此 name5 有 2 个关闭时间,这可能发生在您的数据中,需要正确处理。 (这意味着在您问题的示例中,您需要减去 5 小时而不是 2 小时) 我还添加了多天的进程名称6。

这是我的解决方案,将应用于data

中的每一行
def get_relevant_off(pr):
    relevant = off[off.end.gt(pr["start"]) & 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

解释:

  • 函数中的第一行根据 pr
  • 行对 off 的相关行进行子集化
  • 将低于进程启动的关闭启动替换为进程启动,并对结束执行相同的操作,因为我们不想对整个关闭时间求和,而只想对实际与进程同时进行的时间求和。
  • 通过从关闭结束减去关闭开始并求和得到关闭时间的持续时间
  • 然后从总持续时间中减去它。
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

警告:我假设关闭时间永远不会重叠。另外,我喜欢这个问题,但没有更多时间花在测试上了,所以让我知道我是否忽略了一些破坏它的边缘情况,我会尽量找时间修复它。