
本文介绍使用 `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 + 时间范围校验
核心思路分三步:
- 统一时间类型:确保 EVENT_TIME、IN_TIME、OUT_TIME 均为 datetime64[ns];
- merge_asof 近似左连接:按 'LOT' 分组,以 EVENT_TIME(左)和 IN_TIME(右)为关键时间列,执行“向后查找最近 IN_TIME”的 merge(默认 direction='backward' 不适用,需用 'forward' 或更稳妥的 'nearest' + 后过滤);
- 严格范围过滤:因 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 是最简洁、健壮且高性能的标准解法。










