【问题标题】:Calculate overlapping times in weekly room schedule计算每周房间计划中的重叠时间
【发布时间】:2021-07-01 14:08:09
【问题描述】:

我有一个包含一些房间调度数据的 DataFrame。

以下是周四和周五早上前几个条目的数据示例:

   DAYS BEGIN_TIME END_TIME
0    R      09:00    10:15
1    R      08:30    09:45
2    R      11:30    12:20
3    R      11:30    12:45
4    F      08:00    10:30
5    F      07:00    08:15
6    F      08:00    10:30

作为python定义:

df = pd.DataFrame({'DAYS': {0: 'R', 1: 'R', 2: 'R', 3: 'R', 4: 'F', 5: 'F', 6: 'F'},
                   'BEGIN_TIME': {0: '09:00', 1: '08:30', 2: '11:30', 3: '11:30', 4: '08:00', 5: '07:00', 6: '08:00'},
                   'END_TIME': {0: '10:15', 1: '09:45', 2: '12:20', 3: '12:45', 4: '10:30', 5: '08:15', 6: '10:30'}}
                  )

R 代表星期四,F 代表星期五。本专栏还有MTW

BEGIN_TIMEEND_TIME 表示某人使用房间的开始和结束时间,以小时和分钟为单位,采用 24 小时制,HH:MM。

我想确定房间发生碰撞的日期和时间(多人同时尝试使用房间)。

对于示例数据,我希望收到如下信息:

    DAYS BEGIN_TIME END_TIME   USERS
0    R      08:30     9:00       1
1    R      09:00     9:45       2
2    R      09:45    10:15       1
3    R      11:30    12:20       2
4    R      12:20    12:45       1
5    F      07:00     8:00       1
6    F      08:00    08:15       3
7    F      08:15    10:30       2

到目前为止,在我的研究中,我发现this answerCount overlapping time frames in a pandas dataframe, grouped by person

import pandas as pd

df = pd.DataFrame({'DAYS': {0: 'R', 1: 'R', 2: 'R', 3: 'R', 4: 'F', 5: 'F', 6: 'F'},
                   'BEGIN_TIME': {0: '09:00', 1: '08:30', 2: '11:30', 3: '11:30', 4: '08:00', 5: '07:00', 6: '08:00'},
                   'END_TIME': {0: '10:15', 1: '09:45', 2: '12:20', 3: '12:45', 4: '10:30', 5: '08:15', 6: '10:30'}}
                  )

# Convert to DateTime
df["BEGIN_TIME"] = df["BEGIN_TIME"].astype("datetime64[ns]")
df["END_TIME"] = df["END_TIME"].astype("datetime64[ns]")

# Code from linked SO Answer
ends = df['BEGIN_TIME'].values < df['END_TIME'].values[:, None]
starts = df['BEGIN_TIME'].values > df['BEGIN_TIME'].values[:, None]
same_group = (df['DAYS'].values == df['DAYS'].values[:, None])
df['OVERLAP'] = (ends & starts & same_group).sum(1)

print(df)

虽然这确实告诉我某些冲突,但在尝试具体找出发生冲突的时间时却无济于事。

我还查看了Pandas: Count time interval intersections over a group by,但这里的答案也只是计算重叠,而不是将范围分解为特定的重叠时间。

我不知道从这里去哪里,有人能指出正确的方向吗?

【问题讨论】:

  • 因为计划是以 15 分钟为间隔,您考虑创建一个集,在其中标记每个声明的 15 分钟间隔。因此,使用您的开始时间和结束时间来创建一个范围,并使用一个 for 循环来检查一个区间是否已经在集合中,如果是则报告,如果不是则声明它。

标签: python python-3.x pandas dataframe


【解决方案1】:

为每一天(约会的节奏)创建一个包含所有 15 分钟间隔的 DataFrame。然后我们可以使用np.braodcasting 来查看每天在给定时间有多少用户在使用房间。

import pandas as pd
import numpy as np

# Convert your times to a numeric type. 
for col in ['BEGIN_TIME', 'END_TIME']:
    df[col] = pd.to_datetime(df[col])
    df[col] = df[col] - df[col].dt.normalize()

# 15-min blocks Monday-Friday
df1 = (pd.concat([pd.DataFrame({'Time': pd.timedelta_range('00:00:00', '23:45:00', freq='15min')})]*5,
                 keys=list('MTWRF'), names=['Days', 'to_drop'])
         .reset_index()
         .drop(columns='to_drop'))
    
# For each day determine the overlap
l = []
for day, gp in df1.groupby('Days'):
    gp['users'] = ((gp['Time'].to_numpy() >= df.loc[df.DAYS.eq(day), 'BEGIN_TIME'].to_numpy()[:, None])
                     & (gp['Time'].to_numpy() <= df.loc[df.DAYS.eq(day), 'END_TIME'].to_numpy()[:, None])).sum(axis=0)
    l.append(gp['users'])

# Join the results back to our 15 minute skeleton
df1 = pd.concat([df1, pd.concat(l)], axis=1)

#Check and see the times on Thursday
df1.loc[df1.Days.eq('R') & df1.Time.between('07:00:00', '14:00:00')]

    Days            Time  users
316    R 0 days 07:00:00      0
317    R 0 days 07:15:00      0
318    R 0 days 07:30:00      0
319    R 0 days 07:45:00      0
320    R 0 days 08:00:00      0
321    R 0 days 08:15:00      0
322    R 0 days 08:30:00      1
323    R 0 days 08:45:00      1
324    R 0 days 09:00:00      2
325    R 0 days 09:15:00      2
326    R 0 days 09:30:00      2
327    R 0 days 09:45:00      2
328    R 0 days 10:00:00      1
329    R 0 days 10:15:00      1
330    R 0 days 10:30:00      0
331    R 0 days 10:45:00      0
332    R 0 days 11:00:00      0
333    R 0 days 11:15:00      0
334    R 0 days 11:30:00      2
335    R 0 days 11:45:00      2
336    R 0 days 12:00:00      2
337    R 0 days 12:15:00      2
338    R 0 days 12:30:00      1
339    R 0 days 12:45:00      1
340    R 0 days 13:00:00      0
341    R 0 days 13:15:00      0
342    R 0 days 13:30:00      0
343    R 0 days 13:45:00      0
344    R 0 days 14:00:00      0

【讨论】:

    【解决方案2】:

    考虑到您的数据集相对较小,为此的伪代码算法可以是这样的。

    collisions = dict()  # {room: [(collision_start, collision_end)]}
    for each reservation R1:
        for each other reservation R2 where R2.room=R1.room:
            if R2.end_time > R1.start_time and R2.start_time < R1.end_time:
                # COLLISION... you need to edit code below to make sure key exists
                collisions[R].append((start_of_collision, end_of_collision))
    

    要确定start_of_collisionend_of_collision 需要更多的工作,因为您需要检查3 个案例。

    • 案例 1:R1R2 开始之前开始,在R2 结束之前结束。

      (start_of_collision, end_of_collision) = (R2.start, R1.end)
      
    • 案例 2:R1R2 开始之后开始,在R2 结束之前结束。

      (start_of_collision, end_of_collision) = (R1.start, R1.end)
      
    • 案例3:R1R2开始之后开始,在R2结束之后结束。

      (start_of_collision, end_of_collision) = (R1.start, R2.end)
      

    【讨论】:

      【解决方案3】:

      在我看来,正确的方法是重新格式化您的数据,这样您就有了一系列事件(“开始”或“结束”),只有一个日期列。然后你可以按时间戳排序,做一个简单的计数器:

      import pandas as pd
      
      df = pd.DataFrame({'DAYS': {0: 'R', 1: 'R', 2: 'R', 3: 'R', 4: 'F', 5: 'F', 6: 'F'},
                         'BEGIN_TIME': {0: '09:00', 1: '08:30', 2: '11:30', 3: '11:30', 4: '08:00', 5: '07:00', 6: '08:00'},
                         'END_TIME': {0: '10:15', 1: '09:45', 2: '12:20', 3: '12:45', 4: '10:30', 5: '08:15', 6: '10:30'}}
                        )
      
      days="MTWRF"
      
      # Convert to DateTime
      df["BEGIN_TIME"] = df["BEGIN_TIME"].astype("datetime64[ns]")
      df["END_TIME"] = df["END_TIME"].astype("datetime64[ns]")
      
      # Convert to a more useful format.
      
      newdata = []
      for row in df.iterrows():
          row = row[1]
          newdata.append((
              row["DAYS"],
              "start",
              row["BEGIN_TIME"]
          ))
          newdata.append((
              row["DAYS"],
              "end",
              row["END_TIME"]
          ))
      newdata.sort(key=lambda r: (days.index(r[0]),r[2]))
      print(newdata)
      
      count = 0
      for row in newdata:
          if row[1] == 'start':
              count += 1
          else:
              count -= 1
          print( row[0], row[2].strftime("%H:%M"), count )
      

      输出不是你想要的,但希望你能从这里看到如何到达那里。

      R 08:30 1   
      R 09:00 2   
      R 09:45 1   
      R 10:15 0   
      R 11:30 1   
      R 11:30 2   
      R 12:20 1   
      R 12:45 0   
      F 07:00 1   
      F 08:00 2   
      F 08:00 3   
      F 08:15 2   
      F 10:30 1   
      F 10:30 0   
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-01
        • 1970-01-01
        相关资源
        最近更新 更多