基于R语言计算时间序列中状态持续时长与状态变更次数
问题:R语言计算状态持续时长与变更次数
数据背景
我有一个含两列的数据框:Time列存储图片的时间戳,State列存储图片显示的状态。数据覆盖数周时间,每日包含数百行,连续行的状态可能保持不变或发生变更。
示例数据
| Time | State |
|---|---|
| 20220526173731 | PORED |
| 20220526173741 | PORED |
| 20220526173746 | SJEDI |
| 20220526175242 | PORED |
| 20220526175246 | SJEDI |
| 20220526175806 | SJEDI |
| 20220526175810 | SJEDI |
| 20220526175818 | NEMA |
| 20220526175819 | SJEDI |
| 20220526175819 | SJEDI |
| 20220526175822 | SJEDI |
| 20220526180013 | SJEDI |
| 20220522071053 | NEMA |
| 20220522071056 | NEMA |
需要完成的计算
- 整个周期内各状态的持续时长(单位:秒)
- 每日各状态的持续时长(单位:秒)
- 每次状态变更前该状态的持续时长(单位:秒)
- 整个周期及每日的状态变更次数
已尝试的代码
library(dplyr) library(data.table) # 读取数据框 dat <- read.csv("Lala.csv") dat$Time <- as.character(dat$Time) dat$Time <- as.POSIXct(dat$Time, format="%Y%m%d%H%M%S", tz="CET") dat$Time dat$State <- as.factor(dat$State) # 为连续相同状态的序列分配唯一编号 setDT(dat) dat[, state_run := cumsum(c(TRUE, diff(as.integer(dat$State)) != 0L))] head(dat,20) # 计算各状态的持续时长 dat2 <- dat[, list(StartTime = min(Time), State = State[1], Duration = diff(range(Time))), by = state_run]
现有问题
上述代码计算的是同一状态序列首行到末行的时长,而非该状态结束(即下一个状态首行)的时长,无法得到正确结果。
预期输出
每日状态持续时长
| Day | State | Duration |
|---|---|---|
| 2022-05-22 | NEMA | 60547 |
| 2022-05-22 | PORED | 0 |
| 2022-05-22 | SJEDI | 0 |
| 2022-05-26 | NEMA | 63452 |
| 2022-05-26 | PORED | 19 |
| 2022-05-26 | SJEDI | 1342 |
总状态持续时长
| State | Duration |
|---|---|
| NEMA | 123999 |
| PORED | 19 |
| SJEDI | 1342 |
每日状态变更次数
| Day | # of State changes |
|---|---|
| 2022-05-22 | 0 |
| 2022-05-26 | 6 |
解决方案
完整代码
library(data.table) # 读取并预处理数据 dat <- fread("Lala.csv") dat[, Time := as.POSIXct(Time, format = "%Y%m%d%H%M%S", tz = "CET")] dat[, State := as.factor(State)] # 按时间排序,确保数据时序正确 setorder(dat, Time) # 生成连续状态段ID dat[, state_run := rleid(State)] # 获取每个状态段的开始时间、状态 state_segments <- dat[, .(StartTime = min(Time), State = first(State)), by = state_run] # 添加每个状态段的结束时间:下一个状态段的开始时间,最后一个用当日23:59:59 state_segments[, EndTime := shift(StartTime, type = "lead", fill = as.POSIXct(paste0(as.Date(max(dat$Time)), " 23:59:59"), tz = "CET"))] # 计算每个状态段的持续时长(秒) state_segments[, Duration := as.numeric(difftime(EndTime, StartTime, units = "secs"))] # 1. 整个周期各状态总时长 total_duration <- state_segments[, .(TotalDuration = sum(Duration)), by = State] print("总状态持续时长:") print(total_duration) # 2. 每日各状态持续时长 # 先拆分状态段跨天的情况(如果有的话) state_segments[, DayStart := as.POSIXct(as.Date(StartTime), tz = "CET")] state_segments[, DayEnd := DayStart + 86399] # 当日23:59:59 # 处理跨天的状态段 split_segments <- state_segments[, { if (EndTime <= DayEnd) { .(Day = as.Date(StartTime), State = State, Duration = Duration) } else { # 第一段:从StartTime到DayEnd dur1 <- as.numeric(difftime(DayEnd, StartTime, units = "secs")) # 第二段:从下一天00:00:00到EndTime next_day_start <- DayStart + 86400 dur2 <- as.numeric(difftime(EndTime, next_day_start, units = "secs")) .(Day = c(as.Date(StartTime), as.Date(next_day_start)), State = rep(State, 2), Duration = c(dur1, dur2)) } }, by = state_run] # 聚合每日各状态时长,同时补全所有状态的0值 all_states <- unique(dat$State) all_days <- unique(as.Date(dat$Time)) full_grid <- expand.grid(Day = all_days, State = all_states) daily_duration <- merge(full_grid, split_segments[, .(Duration = sum(Duration)), by = .(Day, State)], by = c("Day", "State"), all.x = TRUE) daily_duration[is.na(Duration), Duration := 0] print("每日状态持续时长:") print(daily_duration) # 3. 每次状态变更前的状态持续时长 # 排除最后一个状态段(没有后续变更) change_durations <- state_segments[-.N, .(PrevState = State, DurationBeforeChange = Duration)] print("每次状态变更前的状态持续时长:") print(change_durations) # 4. 状态变更次数 # 整个周期变更次数 total_changes <- nrow(state_segments) - 1 print(paste("整个周期状态变更次数:", total_changes)) # 每日变更次数 # 先找出所有变更发生的时间点(每个状态段的EndTime,除了最后一个) change_times <- state_segments[-.N, .(ChangeTime = EndTime)] change_times[, Day := as.Date(ChangeTime)] daily_changes <- change_times[, .(ChangeCount = .N), by = Day] # 补全没有变更的日期 daily_changes <- merge(data.table(Day = all_days), daily_changes, by = "Day", all.x = TRUE) daily_changes[is.na(ChangeCount), ChangeCount := 0] setnames(daily_changes, "ChangeCount", "# of State changes") print("每日状态变更次数:") print(daily_changes)
关键说明
rleid(State):data.table内置函数,高效生成连续相同状态的分组ID,替代手动cumsum逻辑更简洁可靠。- 跨天状态段处理:如果一个状态持续到次日,会拆分为两段分别统计当日时长,保证每日数据准确。
- 补全0值:通过
expand.grid生成所有日期与状态的组合,确保每个日期下所有状态都有记录,无数据的状态时长设为0,匹配预期输出格式。 - 变更次数计算:状态变更次数等于状态段总数减1;每日变更次数统计该日期内发生的状态切换点数量,无变更的日期补0。
内容的提问来源于stack exchange,提问作者Zeljko
相关产品推荐
相关产品推荐

