Pandas数据转换未达预期:如何拆分同申请日的分段休假数据?
问题描述
现有如下结构的DataFrame:
Name date leave marked_leave_bfr_days A 8/1/2021 1 3 A 8/2/2021 1 4 A 8/3/2021 1 5 A 8/4/2021 1 5 A 8/5/2021 1 6 A 8/6/2021 1 7 A 8/7/2021 1 8 A 8/8/2021 0 -1 A 8/9/2021 0 -1 A 8/10/2021 1 12 A 8/11/2021 1 13 A 8/12/2021 0 -1 B 8/4/2021 1 1 B 8/5/2021 1 1 B 8/6/2021 1 3 B 8/7/2021 0 -1 B 8/8/2021 0 -1 B 8/9/2021 0 -1 B 8/10/2021 0 -1 B 8/11/2021 0 -1
字段说明:
Name:员工编码leave:布尔字段(1=休假,0=不休假)marked_leave_bfr_days:休假申请距离date的提前天数
需要转换为如下结构的DataFrame:
Name date leave marked_leave_bfr_days leave_applied leave_start leave_end no_of_leaves A 8/1/2021 1 3 7/29/2021 8/1/2021 8/3/2021 3 A 8/2/2021 1 4 7/29/2021 8/1/2021 8/3/2021 3 A 8/3/2021 1 5 7/29/2021 8/1/2021 8/3/2021 3 A 8/4/2021 1 5 7/30/2021 8/4/2021 8/7/2021 4 A 8/5/2021 1 6 7/30/2021 8/4/2021 8/7/2021 4 A 8/6/2021 1 7 7/30/2021 8/4/2021 8/7/2021 4 A 8/7/2021 1 8 7/30/2021 8/4/2021 8/7/2021 4 A 8/8/2021 0 -1 -1 -1 -1 -1 A 8/9/2021 0 -1 -1 -1 -1 -1 A 8/10/2021 1 12 7/29/2021 8/10/2021 8/11/2021 2 A 8/11/2021 1 13 7/29/2021 8/10/2021 8/11/2021 2 A 8/12/2021 0 -1 -1 -1 -1 -1 B 8/4/2021 1 1 8/3/2021 8/4/2021 8/4/2021 1 B 8/5/2021 1 1 8/4/2021 8/5/2021 8/5/2021 1 B 8/6/2021 1 3 8/3/2021 8/6/2021 8/6/2021 1 B 8/7/2021 0 -1 -1 -1 -1 -1 B 8/8/2021 0 -1 -1 -1 -1 -1 B 8/9/2021 0 -1 -1 -1 -1 -1 B 8/10/2021 0 -1 -1 -1 -1 -1 B 8/11/2021 0 -1 -1 -1 -1 -1
用户当前使用的代码:
df.loc[df.leave==1, 'leave_applied'] = (df['date'] - df['marked_leave_bfr_days'].map(timedelta)) df = df[df.leave==1].groupby(['Name', 'leave_applied']).agg({'date':['min', 'max']}).reset_index()
问题:该代码会将同一员工、同一申请日的非连续休假日期段合并(比如员工A在7/29/2021申请的8/1-8/3和8/10-8/11会被合并成一个时间段),无法得到预期结果。
解决方案
核心思路:先识别同一员工、同一申请日内的连续休假日期段,再按这个分段进行聚合,最后合并回原表。
完整代码
import pandas as pd from datetime import timedelta # 1. 读取并预处理数据:转换date列为datetime格式 df = pd.read_csv('your_data.csv') # 替换为你的数据读取方式 df['date'] = pd.to_datetime(df['date']) # 2. 计算leave_applied列,非休假记录先设为NaN df['leave_applied'] = df.apply( lambda row: row['date'] - timedelta(days=row['marked_leave_bfr_days']) if row['leave'] == 1 else pd.NA, axis=1 ) # 3. 仅处理休假记录,识别连续日期段 leave_df = df[df['leave'] == 1].copy() # 按Name、leave_applied分组,判断当前日期与前一日是否连续,生成分组标签 leave_df['date_diff'] = leave_df.groupby(['Name', 'leave_applied'])['date'].diff().dt.days leave_df['segment'] = (leave_df['date_diff'] != 1).cumsum() # 4. 按Name、leave_applied、segment聚合,得到每个休假段的信息 agg_df = leave_df.groupby(['Name', 'leave_applied', 'segment']).agg( leave_start=('date', 'min'), leave_end=('date', 'max'), no_of_leaves=('date', 'count') ).reset_index(drop=['segment']) # 5. 将聚合结果合并回原DataFrame df = df.merge(agg_df, on=['Name', 'leave_applied'], how='left') # 6. 处理非休假记录的填充,将NaN替换为'-1' fill_values = { 'leave_applied': '-1', 'leave_start': '-1', 'leave_end': '-1', 'no_of_leaves': -1 } df = df.fillna(fill_values) # 可选:将日期列转换回原字符串格式(如需要) df['date'] = df['date'].dt.strftime('%m/%d/%Y') df['leave_applied'] = df.apply( lambda row: row['leave_applied'].strftime('%m/%d/%Y') if row['leave'] == 1 else row['leave_applied'], axis=1 ) df['leave_start'] = df.apply( lambda row: row['leave_start'].strftime('%m/%d/%Y') if row['leave'] == 1 else row['leave_start'], axis=1 ) df['leave_end'] = df.apply( lambda row: row['leave_end'].strftime('%m/%d/%Y') if row['leave'] == 1 else row['leave_end'], axis=1 ) print(df)
代码说明
- 日期预处理:将
date列转换为datetime格式,确保日期计算的准确性。 - 计算申请日期:对休假记录计算
leave_applied,非休假记录设为缺失值。 - 识别连续休假段:
- 按
Name和leave_applied分组,计算当前日期与前一日的差值。 - 当差值不等于1时,说明是新的休假段,通过
cumsum()生成分段标签。
- 按
- 聚合分段信息:按
Name、leave_applied和分段标签聚合,得到每个休假段的开始/结束日期、天数。 - 合并与填充:将聚合结果合并回原表,非休假记录的新增字段填充为
-1。 - 格式转换(可选):如果需要将日期列转回原字符串格式,添加最后几步的格式转换。
内容的提问来源于stack exchange,提问作者Lata
相关产品推荐
相关产品推荐

