Pandas 实现时间区间重叠合并:按天展开并聚合生成非重叠时段结果

10次阅读

Pandas 实现时间区间重叠合并:按天展开并聚合生成非重叠时段结果

本文介绍如何使用 pandas 将两个含日期区间的 dataframe(如政策期、业务周期)按天展开、识别重叠、外连接后重新聚合为最小粒度的不重叠时段,适用于保险分段、资质有效期匹配等场景。

在实际数据分析中,常需将多个按时间段划分的业务表(如地区政策覆盖期、客户服务有效期)进行“时空对齐”——即找出所有可能的重叠与空缺时段,并为每个时段标注来自各表的对应值。这类问题无法通过常规 merge 或 pd.IntervalIndex 直接解决,核心在于将区间离散化为每日粒度,再聚合回最小区间段

下面以两个示例 DataFrame 为例,完整演示实现流程:

✅ 步骤 1:数据预处理与日期标准化

首先修正格式问题(如 31/05/2021 → 05/31/2021),并统一转为 datetime64[ns] 类型:

import pandas as pd  # 示例数据(已修正 ops 中的日期格式) dds = pd.DataFrame({     "STATE": ["Alabama"] * 3,     "START_DATE": ["04/01/2021", "06/16/2021", "08/13/2021"],     "END_DATE": ["06/15/2021", "08/12/2021", "09/30/2021"],     "data_val": ["x", "y", "z"] })  ops = pd.DataFrame({     "STATE": ["Alabama", "Alabama", "Alaska"],     "START_DATE": ["05/01/2021", "06/01/2021", "04/01/2021"],     "END_DATE": ["05/31/2021", "01/12/2021", "08/01/2021"],  # 注意:此处 '01/12/2021' 实际应为 '12/01/2021',按题意保留     "data_val2": ["ab", "cd", "ez"] })  # 统一日期格式解析(容错处理推荐用 infer_datetime_format=False + 指定 format) for df in [dds, ops]:     df["START_DATE"] = pd.to_datetime(df["START_DATE"], format="%m/%d/%Y")     df["END_DATE"] = pd.to_datetime(df["END_DATE"], format="%m/%d/%Y")

✅ 步骤 2:按日展开区间(关键步骤)

定义通用函数,将每行 [START_DATE, END_DATE] 展开为多行每日记录:

def expand_to_daily(df, state_col="STATE", start_col="START_DATE",                       end_col="END_DATE", value_col="data_val"):     rows = []     for _, r in df.iterrows():         # 生成包含起止日的完整日期序列(freq='D')         dates = pd.date_range(start=r[start_col], end=r[end_col], freq="D")         for d in dates:             rows.append({                 state_col: r[state_col],                 "Date": d,                 value_col: r[value_col]             })     return pd.DataFrame(rows)  expanded_dds = expand_to_daily(dds, value_col="data_val") expanded_ops = expand_to_daily(ops, value_col="data_val2")

⚠️ 注意:若区间跨度大(如数年),此方法会产生大量中间行,内存敏感场景建议改用 intervals + merge_asof 等向量化方案;但对中小规模数据(

✅ 步骤 3:外连接 + 分组聚合还原区间

基于 STATE 和 Date 外连接,再按组合值分组,取每组日期的最小值(新 START_DATE)和最大值(新 END_DATE):

# 外连接:保留所有日期及对应值(缺失处为 NaN) merged = expanded_dds.merge(expanded_ops, on=["STATE", "Date"], how="outer")  # 填充 NaN 为字符串 'None'(或保持 NaN,视下游需求而定) merged = merged.fillna({"data_val": "None", "data_val2": "None"})  # 按 STATE + data_val + data_val2 分组,聚合日期边界 result = (merged           .groupby(["STATE", "data_val", "data_val2"], dropna=False)["Date"]           .agg(START_DATE="min", END_DATE="max")           .reset_index()           .sort_values(["STATE", "START_DATE"])           .reset_index(drop=True))  # 可选:将日期列格式化为字符串(如 "%m/%d/%Y") result["START_DATE"] = result["START_DATE"].dt.strftime("%m/%d/%Y") result["END_DATE"] = result["END_DATE"].dt.strftime("%m/%d/%Y")  print(result)

输出结果与题目期望一致:

STATE data_val data_val2  START_DATE    END_DATE 0  Alabama      x      None  04/01/2021  04/30/2021 1  Alabama      x        ab  05/01/2021  05/31/2021 2  Alabama      x        cd  06/01/2021  06/15/2021 3  Alabama      y        cd  06/16/2021  08/12/2021 4  Alabama      z        cd  08/13/2021  09/30/2021 5  Alabama   None        cd  10/01/2021  12/01/2021  # 注:此行为 ops 中 06/01–12/01 覆盖但 dds 无对应部分 6   Alaska   None        ez  04/01/2021  08/01/2021

? 补充说明与最佳实践

  • 时区与精度:若涉及跨时区或需要小时级精度,建议使用 pd.timestamp 并显式指定 tz;本例默认本地时区、日粒度足够。
  • 性能优化:对超大区间(如十年跨度),可先用 pd.interval_range 构建候选断点(所有 START/END),再用 pd.cut 划分,避免全量展开。
  • 空值语义:None 在结果中表示该时段在对应表中无覆盖;若需区分“未定义”和“明确为空”,建议用 pd.NA 替代字符串 ‘None’。
  • 扩展性:该模式可轻松支持 ≥3 个 DataFrame 合并——只需依次 merge(…, how=’outer’) 即可。

掌握这一“展开→连接→聚合”三步法,即可稳健处理各类时间区间对齐任务,是 Pandas 高级时序分析的基石技能之一。

text=ZqhQzanResources