基于Pandas统计时间点活跃进程数(优化双重循环方案)
问题描述
我有一个包含start_time和end_time列的数据集,使用以下示例测试数据:
import pandas as pd d = {'start_time': ["2023-05-03 10:30:00", "2023-05-03 10:02:00", "2023-05-03 10:10:00", "2023-05-03 11:00:00", "2023-05-03 11:05:00"], 'end_time': ["2023-05-03 10:35:00", "2023-05-03 10:20:00", "2023-05-03 10:32:00", "2023-05-03 11:10:00", "2023-05-03 11:09:00"]} df = pd.DataFrame(data=d) df["start_time"] = pd.to_datetime(df["start_time"]) df["end_time"] = pd.to_datetime(df["end_time"])
需求是:生成一个新的DataFrame,提取原数据中的最小时间和最大时间,生成30分钟间隔的时间序列,统计每个时间点处于活跃状态(时间点落在对应行的start_time和end_time之间)的记录数。预期结果示例:
| time | count |
|---|---|
| 2023-05-03 10:02:00 | 1 |
| 2023-05-03 10:32:00 | 3 |
| 2023-05-03 11:02:00 | 1 |
我已通过以下代码生成时间序列:
idx = pd.date_range(df["start_time"].min(), df["end_time"].max(), freq="30T").to_frame(index=False, name='time')
但目前使用双重循环统计活跃数,效率极低:
count = 0 for ind, row in idx.iterrows(): for ind1, row1 in df.iterrows(): if(row["time"] >= row1["start_time"] and row["time"] <= row1["end_time"]): print("Entering here...") count += 1
请问如何优化实现该需求?
优化方案
方法1:向量化广播(中小型数据集适用)
利用pandas/numpy的广播特性,直接进行批量比较,避免显式循环,代码简洁且效率远高于双重循环:
# 生成目标时间序列 idx = pd.date_range(df["start_time"].min(), df["end_time"].max(), freq="30T").to_frame(index=False, name='time') # 向量化计算每个时间点的活跃记录数 idx['count'] = ( (idx['time'].values[:, None] >= df['start_time'].values) & (idx['time'].values[:, None] <= df['end_time'].values) ).sum(axis=1)
原理:将时间点数组转为二维结构,与start_time/end_time的一维数组进行广播比较,得到布尔型矩阵后,对每行求和即可得到对应时间点的活跃数。
方法2:事件计数法(大型数据集适用)
通过转换事件类型并计算累积活跃数,再匹配目标时间点,时间复杂度为O(n log n),适合数据量较大的场景:
# 创建事件数据集:开始事件记为+1,结束事件(结束时间+1秒)记为-1 events = pd.concat([ df['start_time'].to_frame(name='time').assign(type=1), (df['end_time'] + pd.Timedelta(seconds=1)).to_frame(name='time').assign(type=-1) ]) # 按时间排序事件 events = events.sort_values('time') # 计算累积活跃数 events['active'] = events['type'].cumsum() # 生成目标时间序列 idx = pd.date_range(df["start_time"].min(), df["end_time"].max(), freq="30T").to_frame(index=False, name='time') # 用merge_asof匹配每个时间点最近的前置事件,获取当前活跃数 result = pd.merge_asof(idx.sort_values('time'), events.sort_values('time'), on='time', direction='backward') # 处理空值并整理结果 result['count'] = result['active'].fillna(0).astype(int) result = result[['time', 'count']].sort_values('time').reset_index(drop=True)
原理:将每个时间区间的开始和结束转化为增减事件,通过累积求和得到任意时间点的活跃数,再用merge_asof快速匹配目标时间点对应的活跃状态,避免了逐行比较的低效操作。
内容的提问来源于stack exchange,提问作者weekend similar
相关产品推荐
相关产品推荐

