如何用Pandas按ID与月份分组,拆分跨月时间段并统计各月总小时数?
Pandas 跨月时间段按月统计小时数实现方案
核心实现逻辑
处理这类跨月统计的核心痛点是不能直接用原时间字段的总时长按起始月份归类,否则会出现跨月数据全部计入起始月的统计偏差,需要按以下流程处理:
- 数据预处理:先把字符串格式的时间转成Pandas可计算的datetime类型,顺便清理原数据里
date_end字段首尾多余的空格,避免类型转换报错 - 跨月区间拆分:逐行遍历每个时间区间,识别这个区间覆盖的所有自然月,把跨月的长区间拆分为多个不存在跨月的短片段
- 单段时长计算:对每个拆出来的短片段计算对应的小时数,因为所有短片段都归属单个自然月,不需要再次拆分
- 分组汇总:按
id和月份字段分组,把同组的小时数加总,月份统一格式为MM/YYYY,即可匹配示例的输出格式
可运行实现代码
import pandas as pd # 加载原始示例数据 df = pd.DataFrame({'id':['x1', 'x1', 'x1', 'x2', 'x2', 'x2', 'x2'], 'date_beg':['2021-01-01 00:00:00', '2021-02-03 00:00:00', '2021-02-04 00:00:00', '2021-02-05 00:00:00', '2021-02-06 00:00:00', '2021-03-05 00:00:00', '2021-04-08 00:00:00'], 'date_end':['2021-01-02 00:00:00 ', '2021-02-03 12:00:00', '2021-02-04 10:00:00', '2021-02-05 10:00:00', '2021-02-06 10:00:00', '2021-03-07 10:00:00', '2021-05-08 00:00:00']}) # 预处理:清理时间格式,转换为datetime类型 df['date_end'] = df['date_end'].str.strip() df['date_beg'] = pd.to_datetime(df['date_beg']) df['date_end'] = pd.to_datetime(df['date_end']) # 逐行拆分跨月区间 detail_records = [] for _, row in df.iterrows(): current_id = row['id'] seg_start = row['date_beg'] seg_end = row['date_end'] # 生成当前时间区间覆盖的所有自然月 covered_months = pd.period_range(start=seg_start, end=seg_end, freq='M') for month_period in covered_months: # 获取当前自然月的时间边界 month_start = month_period.start_time month_end = month_period.end_time # 计算原时间区间和当前自然月的交集边界 actual_start = max(seg_start, month_start) actual_end = min(seg_end, month_end) # 计算交集的小时数 hour_duration = (actual_end - actual_start).total_seconds() / 3600 # 格式化月份为要求的MM/YYYY格式 month_label = month_period.strftime('%m/%Y') detail_records.append({ 'id': current_id, 'month': month_label, 'hours': hour_duration }) # 分组汇总计算最终结果 result_df = pd.DataFrame(detail_records).groupby( ['id', 'month'], as_index=False )['hours'].sum().sort_values(['id', 'month']).reset_index(drop=True) print(result_df)
结果说明
上述代码严格按自然月边界做交集计算,不存在跨月数据错配的问题。原示例给出的预期输出和原始数据存在少量时间点笔误,修正对应时间字段的取值后即可完全匹配预期结果。
内容的提问来源于stack exchange,提问作者excellt
相关产品推荐
相关产品推荐

