本文介绍一种基于 pandas 的向量化方法,无需循环即可准确计算 dataframe 中每列各唯一值在时间序列中的总存在时长(按连续段累加),适用于传感器日志、状态监控等场景。
本文介绍一种基于 pandas 的向量化方法,无需循环即可准确计算 dataframe 中每列各唯一值在时间序列中的总存在时长(按连续段累加),适用于传感器日志、状态监控等场景。
在时间序列分析中,常需统计某状态(如设备运行模式、传感器读数、系统状态)的总持续时间——但关键在于:该状态可能多次出现,每次出现是连续的一段时间,需将所有连续段的时长相加,而非简单取首次与末次时间差。例如,colA 中值 2 在 Time=[1.1, 2.2] 和 [8.5, 9.8, 10.1, 11.2] 两段连续出现,其总持续时间为 (2.2−1.1) + (11.2−8.5) = 1.1 + 2.7 = 3.8 秒。
直接使用 diff().eq(0) 标记连续性虽有启发性,但难以自然关联到“连续段分组”与“跨段聚合”,易陷入索引管理与逻辑嵌套的复杂性。更优解是采用 pandas 链式向量化操作,核心思路为:
- 重塑数据结构:用 melt() 将宽表转为长格式,使每列变为可统一处理的变量;
- 识别连续段:利用 ne(shift()) 检测值变化点,再通过 cumsum() 生成连续段 ID(即“组号”);
- 分段聚合:按 (变量名, 值, 连续段ID) 三元组分组,提取每段的起始时间(first)和结束时间(last);
- 计算并累加:先算单段时长(last − first),再按 (变量名, 值) 二次分组求和,得到最终总持续时间。
以下是完整可运行代码(含示例数据与输出):
import pandas as pd
# 示例数据
df = pd.DataFrame({
'Time': [1.1, 2.2, 3.4, 4.5, 5.6, 6.2, 7.4, 8.5, 9.8, 10.1, 11.2],
'colA': [2, 2, 3, 3, 4, 4, 4, 2, 2, 2, 2],
'colB': [2, 2, 5, 5, 5, 6, 6, 6, 5, 5, 5]
})
# 核心计算逻辑
df_long = df.melt(id_vars='Time', var_name='Col_name', value_name='unique_value')
# 生成连续段标识:值变化处 cumsum +1
group_id = df_long['unique_value'].ne(df_long['unique_value'].shift()).cumsum()
df_long['segment'] = group_id
# 分段计算起止时间 → 单段时长 → 按列+值汇总总时长
result = (df_long
.groupby(['Col_name', 'unique_value', 'segment'])['Time']
.agg(['first', 'last'])
.assign(Duration=lambda x: x['last'] - x['first'])
.groupby(['Col_name', 'unique_value'])['Duration']
.sum()
.reset_index()
)
print(result)输出结果:
Col_name unique_value Duration 0 colA 2 3.8 1 colA 3 1.1 2 colA 4 1.8 3 colB 2 1.1 4 colB 5 3.6 5 colB 6 2.3
✅ 关键优势说明:
- 零循环、全向量化:避免 Python 层显式循环,性能高且代码简洁;
- 自动处理多列与多值:melt() 统一处理所有目标列,扩展性强;
- 精确连续段识别:ne(shift()).cumsum() 稳健捕获所有状态切换,不依赖排序假设(前提是原始数据按 Time 有序);
- 结果可导出为 CSV:result.to_csv('duration_report.csv', index=False) 即可保存为要求格式。
⚠️ 注意事项:
- 输入 DataFrame 的 Time 列必须已按升序排列,否则 first/last 无时间意义;若未排序,请先执行 df = df.sort_values('Time').reset_index(drop=True);
- 若存在缺失值(NaN),ne(shift()) 行为可能异常,建议提前用 dropna() 或填充策略预处理;
- 输出 Duration 为数值型(单位与 Time 列一致),如需显示 "3.8s" 字符串,可追加:result['Duration'] = result['Duration'].round(1).astype(str) + 's'。
该方法兼顾准确性、可读性与工程实用性,是处理状态持续时间统计任务的标准实践方案。


















