如何基于员工班次起止时间生成15分钟粒度的在岗人数统计数组?
问题:基于员工班次的15分钟粒度在岗人数统计
技术需求
如何基于员工班次的Start_Time和End_Time,生成按日期及15分钟增量统计在岗人数的数组或DataFrame?
输入数据
员工排班数据包含Employee_ID、Start_Time、End_Time字段,具体数据如下:
| 员工ID | 开始时间 | 结束时间 |
|---|---|---|
| 1 | 202303150100000000000000 | 202303150245000000000000 |
| 2 | 202303150015000000000000 | 202303150700000000000000 |
| 3 | 202303150230000000000000 | 202303160100000000000000 |
期望输出
按日期+15分钟粒度统计在岗人数的DataFrame,支持跨天班次延续统计,示例如下:
| 日期 | 12 | 12:15 | 12:30 | 12:45 | 0 | 0:15 | 0:30 | 0:45 | 1 | 1:15 | ... | 24 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2023/3/15 | 0 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | ... | 1 |
| 2023/3/16 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 |
当前进展与问题
已实现小时粒度统计,但无法准确处理非整点起止的班次(例如2:45开始的班次被错误记录为2:00开始)。现有实现代码存在逻辑缺陷,需要优化为15分钟粒度的精准统计。
解决方案
以下是优化后的代码,通过时间序列展开和区间匹配实现精准统计:
步骤1:数据预处理(解析时间字段)
首先将原始的字符串格式时间转换为datetime类型,方便后续时间操作:
import pandas as pd import numpy as np # 构造示例数据 data = { 'Employee_ID': [1, 2, 3], 'Start_Time': ['202303150100000000000000', '202303150015000000000000', '202303150230000000000000'], 'End_Time': ['202303150245000000000000', '202303150700000000000000', '202303160100000000000000'] } df = pd.DataFrame(data) # 解析时间字符串为datetime类型 df['Start_Time'] = pd.to_datetime(df['Start_Time'], format='%Y%m%d%H%M%S%f') df['End_Time'] = pd.to_datetime(df['End_Time'], format='%Y%m%d%H%M%S%f')
步骤2:生成15分钟粒度的时间序列
覆盖所有班次涉及的日期范围,生成每15分钟一个间隔的时间点:
# 获取所有班次的最小开始时间和最大结束时间,确定时间范围 min_time = df['Start_Time'].min().floor('D') # 从最早日期的0点开始 max_time = df['End_Time'].max().ceil('D') # 到最晚日期的次日0点结束 # 生成15分钟间隔的时间序列 time_intervals = pd.date_range(start=min_time, end=max_time, freq='15min') # 将时间序列拆分为日期和时段列 time_df = pd.DataFrame({'timestamp': time_intervals}) time_df['Date'] = time_df['timestamp'].dt.date time_df['Time_Slot'] = time_df['timestamp'].dt.strftime('%H:%M') # 处理00:00显示为24:00,匹配期望输出格式 time_df['Time_Slot'] = time_df['Time_Slot'].replace('00:00', '24:00')
步骤3:匹配每个时段的在岗员工数
通过交叉连接和区间判断,统计每个时段的在岗人数:
# 交叉连接员工表和时段表,生成所有可能的员工-时段组合 cross_df = df.merge(time_df, how='cross') # 判断员工在该时段是否在岗:时段的起始时间在员工班次的[Start_Time, End_Time)区间内 cross_df['On_Duty'] = (cross_df['timestamp'] >= cross_df['Start_Time']) & (cross_df['timestamp'] < cross_df['End_Time']) # 按日期和时段分组,统计在岗人数 result_df = cross_df.groupby(['Date', 'Time_Slot'])['On_Duty'].sum().unstack(fill_value=0) # 调整时段顺序,匹配期望输出的排列逻辑 slot_order = [ '12:00', '12:15', '12:30', '12:45', '00:15', '00:30', '00:45', '01:00', '01:15', '01:30', '01:45', '02:00', '02:15', '02:30', '02:45', '03:00', '03:15', '03:30', '03:45', '04:00', '04:15', '04:30', '04:45', '05:00', '05:15', '05:30', '05:45', '06:00', '06:15', '06:30', '06:45', '07:00', '07:15', '07:30', '07:45', '08:00', '08:15', '08:30', '08:45', '09:00', '09:15', '09:30', '09:45', '10:00', '10:15', '10:30', '10:45', '11:00', '11:15', '11:30', '11:45', '24:00' ] # 调整列顺序,补充缺失的时段(如果有) result_df = result_df.reindex(columns=slot_order, fill_value=0) # 重置索引,将Date作为列 result_df = result_df.reset_index() # 格式化日期显示为YYYY/M/D格式 result_df['Date'] = result_df['Date'].apply(lambda x: x.strftime('%Y/%m/%d'))
最终输出效果
运行上述代码后,result_df即为符合需求的DataFrame,包含日期列和每15分钟的在岗人数统计,支持跨天班次的正确统计。
内容的提问来源于stack exchange,提问作者Sarah
相关产品推荐
相关产品推荐

