如何高效地基于时间区间匹配为 DataFrame 添加新列

2次阅读

如何高效地基于时间区间匹配为 DataFrame 添加新列

本文介绍使用 `pandas.merge_asof()` 高效实现两表按分组键(如 lot)和时间范围(event_time ∈ [in_time, out_time])关联,并添加对应 value 列,避免低效嵌套循环,适用于数十万行规模数据。

在实际数据分析中,常需将一个事件表(如 df1)与一个时段标签表(如 df2)进行“时间区间匹配”:即对 df1 中每条记录,查找 df2 中满足 同组(LOT 相等)且 EVENT_TIME 落在 [IN_TIME, OUT_TIME] 内 的唯一 VALUE,并将其作为新列(如 ‘DATA’)加入结果。若采用双重 for 循环逐行判断,时间复杂度为 O(n×m),面对 10 万+ 行数据极易超时(如原文所述 >10 分钟)。幸运的是,Pandas 提供了专为此类场景优化的 merge_asof(),配合合理预处理,可在毫秒级完成。

✅ 正确做法:merge_asof + 时间范围校验

核心思路分三步:

  1. 统一时间类型:确保 EVENT_TIME、IN_TIME、OUT_TIME 均为 datetime64[ns];
  2. merge_asof 近似左连接:按 ‘LOT’ 分组,以 EVENT_TIME(左)和 IN_TIME(右)为关键时间列,执行“向后查找最近 IN_TIME”的 merge(默认 direction=’backward’ 不适用,需用 ‘forward’ 或更稳妥的 ‘nearest’ + 后过滤);
  3. 严格范围过滤:因 merge_asof 仅保证 EVENT_TIME ≥ IN_TIME,还需显式校验 EVENT_TIME ≤ OUT_TIME,并用 .where() 清洗无效匹配。

以下是完整、可复现的优化代码:

import pandas as pd  # 构造示例数据 df1 = pd.DataFrame({     'LOT': ['A', 'A', 'A', 'A', 'A', 'A'],     'SLOT': [1, 2, 3, 4, 5, 6],     'EVENT_TIME': ['2024-01-20 13:30', '2024-01-20 13:36',                    '2024-01-21 14:28', '2024-01-21 14:30',                    '2024-01-21 14:32', '2024-01-21 14:34'] })  df2 = pd.DataFrame({     'LOT': ['A', 'A'],     'IN_TIME': ['2024-01-20 13:20', '2024-01-21 14:25'],     'OUT_TIME': ['2024-01-20 13:40', '2024-01-21 14:50'],     'VALUE': [13, 15] })  # 步骤1:转为 datetime(必需!) df1['EVENT_TIME'] = pd.to_datetime(df1['EVENT_TIME']) df2['IN_TIME'] = pd.to_datetime(df2['IN_TIME']) df2['OUT_TIME'] = pd.to_datetime(df2['OUT_TIME'])  # 步骤2 & 3:merge_asof + 范围校验(推荐方案) result = (     pd.merge_asof(         df1.sort_values(['LOT', 'EVENT_TIME']),          df2.sort_values(['LOT', 'IN_TIME']),         by='LOT',         left_on='EVENT_TIME',         right_on='IN_TIME',         allow_exact_matches=True,   # 允许 EVENT_TIME == IN_TIME         direction='backward'         # 找 ≤ EVENT_TIME 的最大 IN_TIME(最常用)     )     .assign(DATA=lambda x: x['VALUE'].where(x['EVENT_TIME'] <= x['OUT_TIME']))     .drop(['IN_TIME', 'OUT_TIME', 'VALUE'], axis=1)     .reset_index(drop=True) )  print(result)

输出:

LOT  SLOT        EVENT_TIME  DATA 0   A     1 2024-01-20 13:30:00 13.0 1   A     2 2024-01-20 13:36:00 13.0 2   A     3 2024-01-21 14:28:00 15.0 3   A     4 2024-01-21 14:30:00 15.0 4   A     5 2024-01-21 14:32:00 15.0 5   A     6 2024-01-21 14:34:00 15.0

⚠️ 关键注意事项

  • 必须排序:merge_asof 要求左右 DataFrame 按连接时间列升序排列(sort_values 不可省略);
  • direction 选择
    • 'backward'(默认):找 ≤ EVENT_TIME 的最大 IN_TIME → 适合“事件发生在某时段开始之后”;
    • 'forward':找 ≥ EVENT_TIME 的最小 IN_TIME → 适合“事件触发时段开始”;
    • 'nearest':找绝对时间差最小者 → 需额外校验 EVENT_TIME 是否真在 [IN_TIME, OUT_TIME] 内;
  • 边界处理:设 allow_exact_matches=True 确保 EVENT_TIME == IN_TIME 被正确匹配;
  • 缺失值处理:不满足 EVENT_TIME ≤ OUT_TIME 的行,DATA 将为 NaN,可根据业务需求 .fillna() 或过滤;
  • 性能优势:merge_asof 时间复杂度接近 O(n + m),比 O(n×m) 循环快数百至数千倍。

✅ 替代方案(备选)

若数据逻辑更复杂(如多对多匹配),可考虑:

  • 使用 pd.IntervalIndex 构建时段索引,再用 df1['EVENT_TIME'].map() 查找;
  • 借助 numpy.searchsorted 手动实现二分查找(需先按 LOT 分组处理)。

但对绝大多数“单值区间匹配”场景,merge_asof 是最简洁、健壮且高性能的标准解法。

text=ZqhQzanResources