如何在不使用df.iterrows()的情况下向量化标记时间序列连续事件
问题描述
给定如下时间序列DataFrame:
| 时间(time) | 事件(event) |
|---|---|
| 2020-01-01 12:00:00 | 1 |
| 2020-01-01 12:00:01 | NaN |
| 2020-01-01 12:00:02 | 1 |
| 2020-01-01 12:00:03 | 1 |
| 2020-01-01 12:00:04 | NaN |
| 2020-01-01 12:00:05 | NaN |
| 2020-01-01 12:00:06 | 1 |
| 2020-01-01 12:00:07 | NaN |
希望生成如下汇总DataFrame:
| 事件ID(event_id) | 开始时间(time_start) | 结束时间(time_stop) |
|---|---|---|
| 1 | 2020-01-01 12:00:00 | 2020-01-01 12:00:01 |
| 2 | 2020-01-01 12:00:02 | 2020-01-01 12:00:04 |
| 3 | 2020-01-01 12:00:06 | 2020-01-01 12:00:07 |
思路是先添加event_i列,为每个连续事件组填充序号(1、2、3……),再生成汇总表,但卡在事件序号标记步骤。可以用df.iterrows()实现,但该方法不被推荐,请问如何通过向量化操作完成标记?
示例代码:
import pandas as pd import numpy as np # define mini-dataset as an example data= {'time': ['2020-01-01 12:00:00', '2020-01-01 12:00:01', '2020-01-01 12:00:02','2020-01-01 12:00:03', '2020-01-01 12:00:04','2020-01-01 12:00:05', '2020-01-01 12:00:06', '2020-01-01 12:00:07', '2020-01-01 12:00:08', '2020-01-01 12:00:09','2020-01-01 12:00:10'], 'event': [1,np.nan,1,1,np.nan,np.nan,1,np.nan,1,1,np.nan]} df = pd.DataFrame(data) df['time']=pd.to_datetime((df['time'])) # give a sequential number to each event df['event_i'] = np.nan # for each event-number, group by and stack: event_id, time_start time_stop # ...
解决方案
可以通过向量化的分组标记完成事件序号的生成,核心思路是识别连续事件组的起始点,再通过累加起始点的数量来生成序号,具体步骤如下:
1. 标记事件组的起始点
首先,判断每行是否是一个新事件组的开始:当当前行event为1,且上一行event为NaN时,标记为1(起始点),否则为0。用shift()方法实现:
# 标记事件组的起始位置:当前event为1且前一行event为NaN时,视为新组开始 df['is_start'] = (df['event'] == 1) & (df['event'].shift(1).isna())
2. 生成事件组序号
对起始点的标记进行累加,得到每个事件组的初始序号,再通过向前填充补全非事件行(event为NaN)的序号:
# 累加起始点得到初始序号,再向前填充补全非事件行的序号 df['event_i'] = df['is_start'].cumsum() df['event_i'] = df['event_i'].where(df['event'].notna(), np.nan).ffill()
这一步完成后,event_i列会为每个连续事件组(包括后续的NaN终止行)分配对应的序号。
3. 生成汇总DataFrame
按event_i分组,提取每组的第一个时间作为开始时间,最后一个时间作为结束时间,再重置索引得到事件ID:
# 按事件组分组,提取起始和结束时间 summary_df = df.groupby('event_i').agg( time_start=('time', 'first'), time_stop=('time', 'last') ).reset_index() # 重命名列并调整顺序 summary_df = summary_df.rename(columns={'event_i': '事件ID(event_id)'}) summary_df = summary_df[['事件ID(event_id)', 'time_start', 'time_stop']] summary_df.columns = ['事件ID(event_id)', '开始时间(time_start)', '结束时间(time_stop)']
完整代码
import pandas as pd import numpy as np # 定义数据集 data= {'time': ['2020-01-01 12:00:00', '2020-01-01 12:00:01', '2020-01-01 12:00:02','2020-01-01 12:00:03', '2020-01-01 12:00:04','2020-01-01 12:00:05', '2020-01-01 12:00:06', '2020-01-01 12:00:07', '2020-01-01 12:00:08', '2020-01-01 12:00:09','2020-01-01 12:00:10'], 'event': [1,np.nan,1,1,np.nan,np.nan,1,np.nan,1,1,np.nan]} df = pd.DataFrame(data) df['time']=pd.to_datetime(df['time']) # 1. 标记事件组起始点 df['is_start'] = (df['event'] == 1) & (df['event'].shift(1).isna()) # 2. 生成事件组序号 df['event_i'] = df['is_start'].cumsum() df['event_i'] = df['event_i'].where(df['event'].notna(), np.nan).ffill() # 3. 生成汇总表 summary_df = df.groupby('event_i').agg( time_start=('time', 'first'), time_stop=('time', 'last') ).reset_index() summary_df = summary_df.rename(columns={'event_i': '事件ID(event_id)'}) summary_df = summary_df[['事件ID(event_id)', 'time_start', 'time_stop']] summary_df.columns = ['事件ID(event_id)', '开始时间(time_start)', '结束时间(time_stop)'] print(summary_df)
运行后输出的汇总表与需求一致,全程使用向量化操作,避免了iterrows()的低效问题。
内容的提问来源于stack exchange,提问作者Kris De Beleir
相关产品推荐
相关产品推荐

