Pandas Dataframe:重叠工时表条目替换与调整的向量化实现问询
问题描述
我正在处理从源系统导出至目标系统的Timesheet数据,源系统数据存在重叠条目,但目标系统不允许该情况。业务规则为:若出现重叠条目,介入事件将替代当前事件,直至介入事件结束后当前事件恢复。
源系统数据(含重叠)
| 员工 | 班次 | 类型 | 开始时间 | 结束时间 |
|---|---|---|---|---|
| 1 | SHFT-1 | Sleep | 19/06/2023 10:00PM | 20/06/2023 07:00AM |
| 1 | ACT-1 | Disturbance | 19/06/2023 11:00PM | 19/06/2023 11:30PM |
| 1 | ACT-2 | Disturbance | 20/06/2023 02:00AM | 20/06/2023 03:00AM |
| 1 | ACT-3 | Disturbance | 20/06/2023 06:30AM | 20/06/2023 07:00AM |
实际场景:员工原计划执行Sleep任务,期间出现3次Disturbance事件,每次干扰结束后回归Sleep任务,仅最后一次因Sleep任务结束无需回归。
目标系统要求格式
| 员工 | 班次 | 类型 | 开始时间 | 结束时间 |
|---|---|---|---|---|
| 1 | SHFT-1 | Sleep | 19/06/2023 10:00PM | 19/06/2023 11:00PM |
| 1 | ACT-1 | Disturbance | 19/06/2023 11:00PM | 19/06/2023 11:30PM |
| 1 | SHFT-1 | Sleep | 19/06/2023 11:30PM | 20/06/2023 02:00AM |
| 1 | ACT-2 | Disturbance | 20/06/2023 02:00AM | 20/06/2023 03:00AM |
| 1 | SHFT-1 | Sleep | 20/06/2023 03:00AM | 20/06/2023 06:30AM |
| 1 | ACT-3 | Disturbance | 20/06/2023 06:30AM | 20/06/2023 07:00AM |
需要找到比while not_finished iterate_all not_finished = changes_occured更高效的Pandas向量化实现方案。
基于Pandas的向量化实现方案
核心思路是提取所有关键时间节点(主事件起止、干扰事件起止),排序后生成连续时间区间,再为每个区间匹配对应事件类型,全程用批量操作替代循环迭代。
步骤1:数据预处理
先将时间列转换为datetime类型,方便后续时间计算:
import pandas as pd # 加载源数据 data = pd.DataFrame({ '员工': [1, 1, 1, 1], '班次': ['SHFT-1', 'ACT-1', 'ACT-2', 'ACT-3'], '类型': ['Sleep', 'Disturbance', 'Disturbance', 'Disturbance'], '开始时间': ['19/06/2023 10:00PM', '19/06/2023 11:00PM', '20/06/2023 02:00AM', '20/06/2023 06:30AM'], '结束时间': ['20/06/2023 07:00AM', '19/06/2023 11:30PM', '20/06/2023 03:00AM', '20/06/2023 07:00AM'] }) # 转换时间格式 data['开始时间'] = pd.to_datetime(data['开始时间'], format='%d/%m/%Y %I:%M%p') data['结束时间'] = pd.to_datetime(data['结束时间'], format='%d/%m/%Y %I:%M%p')
步骤2:分离主事件与干扰事件
假设主事件为Sleep类型,干扰事件为Disturbance,按员工分组处理(示例为单员工,多员工可外层加groupby('员工')):
# 提取主事件(单员工场景取第一条) main_event = data[data['类型'] == 'Sleep'].iloc[0] # 提取干扰事件并按开始时间排序 disturbances = data[data['类型'] == 'Disturbance'].sort_values('开始时间').reset_index(drop=True)
步骤3:生成排序后的关键时间点
收集主事件和所有干扰事件的起止时间,去重后排序:
# 收集所有时间节点 time_points = pd.Series( [main_event['开始时间'], main_event['结束时间']] + list(disturbances['开始时间']) + list(disturbances['结束时间']) ) # 去重并按时间排序 time_points = time_points.drop_duplicates().sort_values().reset_index(drop=True)
步骤4:匹配区间与事件类型
遍历时间区间,判断当前区间属于主事件还是干扰事件,生成结果:
# 初始化结果列表 result = [] for idx in range(len(time_points) - 1): interval_start = time_points[idx] interval_end = time_points[idx+1] # 检查当前区间是否被干扰事件覆盖 mask = (disturbances['开始时间'] <= interval_start) & (disturbances['结束时间'] >= interval_end) if mask.any(): # 匹配对应的干扰事件 dist_row = disturbances[mask].iloc[0] result.append({ '员工': dist_row['员工'], '班次': dist_row['班次'], '类型': dist_row['类型'], '开始时间': interval_start, '结束时间': interval_end }) else: # 匹配主事件 result.append({ '员工': main_event['员工'], '班次': main_event['班次'], '类型': main_event['类型'], '开始时间': interval_start, '结束时间': interval_end }) # 转换为DataFrame并格式化时间输出 final_df = pd.DataFrame(result) final_df['开始时间'] = final_df['开始时间'].dt.strftime('%d/%m/%Y %I:%M%p') final_df['结束时间'] = final_df['结束时间'].dt.strftime('%d/%m/%Y %I:%M%p')
验证结果
运行后final_df将完全匹配目标系统的格式,且所有时间判断、数据匹配均采用Pandas批量操作,避免了循环迭代的低效问题。
内容的提问来源于stack exchange,提问作者Paul O
相关产品推荐
相关产品推荐

