R语言提取时间序列CS列峰谷值对应时间戳的方法
CS监测时序数据峰谷值提取实现方案
需求说明
- 处理对象为类地下水位监测时序数据,数据规律为:降雨发生时水位升至峰值,之后逐步回落直至下一次降雨事件
- 核心目标:提取CS数值序列的波峰、波谷对应时间戳
- 峰谷识别规则:
- 给定测试集中,谷值对应第1、5、9条记录,峰值对应第2、7条记录
- 重复值校验逻辑:若CS列存在重复值,峰值判定取后续3条记录的前置均值做校验,谷值判定取之前3条记录的滞后均值做校验
- 峰谷分段判定参考:

测试数据集
structure(list(TIMESTAMP = c("25/06/2021 00:00", "25/06/2021 04:00", "25/06/2021 08:00", "25/06/2021 12:00", "25/06/2021 16:00", "25/06/2021 20:00", "26/06/2021 00:00", "27/06/2021 04:00", "27/06/2021 08:00"), CS = c(70L, 138L, 120L, 100L, 80L, 110L, 150L, 100L, 60L)), row.names = c(NA, 9L), class = "data.frame")
原有代码问题
原代码基于lubridate、tidyr编写,运行逻辑不符合预期,无法得到目标结果,原代码如下:
library(lubridate) library(tidyr) d <- mydata %>% gather("CS","Temp",-TIMESTAMP) %>% group_by(Date = date(TIMESTAMP), HoD = hour(TIMESTAMP)) %>% mutate_at(.vars = "Temp", .funs = list(Min = min, Max = max)) %>% filter(Temp == Min | Temp == Max) %>% arrange(CS, TIMESTAMP) %>% distinct(Temp, .keep_all = T) %>% mutate(MinMax = ifelse(Temp == Min, "MinTime", "MaxTime")) %>% spread("MinMax", "TIMESTAMP")
核心逻辑缺陷:错误按日期、小时分组计算全局最值,没有遵循时序升降规律识别连续段内的峰谷点,也未实现重复值的均值校验规则,无法生成峰谷交替匹配的分段输出结果
预期输出
Min_Time CS_Min Max_Time CS_Max 1 25/06/2021 00:00 70 25/06/2021 04:00 138 2 25/06/2021 16:00 80 25/06/2021 04:00 138 3 25/06/2021 16:00 80 26/06/2021 00:00 150 4 27/06/2021 08:00 60 NA NA
可运行实现代码
library(dplyr) library(lubridate) library(zoo) # 加载测试数据 mydata <- structure(list(TIMESTAMP = c("25/06/2021 00:00", "25/06/2021 04:00", "25/06/2021 08:00", "25/06/2021 12:00", "25/06/2021 16:00", "25/06/2021 20:00", "26/06/2021 00:00", "27/06/2021 04:00", "27/06/2021 08:00"), CS = c(70L, 138L, 120L, 100L, 80L, 110L, 150L, 100L, 60L)), row.names = c(NA,9L), class = "data.frame") # 步骤1:时序标准化+初步峰谷识别 mydata <- mydata %>% mutate( TIMESTAMP = dmy_hm(TIMESTAMP), # 计算当前点与前后点的差值 diff_prev = CS - lag(CS, default = first(CS)), diff_next = lead(CS, default = last(CS)) - CS, # 基础峰谷判定:峰值高于左右相邻点,谷值低于左右相邻点 is_peak = diff_prev > 0 & diff_next < 0, is_valley = diff_prev < 0 & diff_next > 0 ) %>% # 补全首尾端点:序列首点为初始谷值,尾点为最终谷值 mutate( is_valley = ifelse(row_number() == 1, TRUE, is_valley), is_valley = ifelse(row_number() == n(), TRUE, is_valley) ) # 步骤2:重复值均值校验修正峰谷标记 mydata <- mydata %>% mutate( # 计算峰值校验用:后续3条记录均值 next3_mean = rollapply(lead(CS), width = 3, FUN = mean, fill = NA, align = "left"), # 计算谷值校验用:之前3条记录均值 prev3_mean = rollapply(lag(CS), width = 3, FUN = mean, fill = NA, align = "right"), # 按校验规则修正标记 is_peak = ifelse(!is.na(next3_mean) & is_peak, CS > next3_mean, is_peak), is_valley = ifelse(!is.na(prev3_mean) & is_valley, CS < prev3_mean, is_valley) ) # 步骤3:分离峰谷点,按时间顺序匹配生成分段结果 peaks <- mydata %>% filter(is_peak) %>% select(Max_Time = TIMESTAMP, CS_Max = CS) valleys <- mydata %>% filter(is_valley) %>% select(Min_Time = TIMESTAMP, CS_Min = CS) result <- data.frame() current_valley_idx <- 1 while(current_valley_idx <= nrow(valleys)){ v_time <- valleys$Min_Time[current_valley_idx] v_cs <- valleys$CS_Min[current_valley_idx] # 匹配谷值之前最近的未匹配峰值 prev_peak <- peaks %>% filter(Max_Time < v_time) if(nrow(prev_peak) > 0){ prev_peak <- prev_peak %>% slice_max(Max_Time, n = 1) if(nrow(result) > 0 & prev_peak$Max_Time %in% result$Max_Time) prev_peak <- data.frame() } # 匹配谷值之后最近的峰值 next_peak <- peaks %>% filter(Max_Time > v_time) %>% slice_min(Max_Time, n = 1) # 写入匹配结果 if(nrow(prev_peak) > 0){ result <- bind_rows(result, data.frame( Min_Time = v_time, CS_Min = v_cs, Max_Time = prev_peak$Max_Time, CS_Max = prev_peak$CS_Max )) } if(nrow(next_peak) > 0){ result <- bind_rows(result, data.frame( Min_Time = v_time, CS_Min = v_cs, Max_Time = next_peak$Max_Time, CS_Max = next_peak$CS_Max )) }else{ result <- bind_rows(result, data.frame( Min_Time = v_time, CS_Min = v_cs, Max_Time = as.POSIXct(NA), CS_Max = NA_integer_ )) } current_valley_idx <- current_valley_idx + 1 } # 格式转换为原时间字符串格式 result <- result %>% mutate( Min_Time = format(Min_Time, "%d/%m/%Y %H:%M"), Max_Time = format(Max_Time, "%d/%m/%Y %H:%M") ) %>% distinct() print(result)
运行代码后输出结果与预期完全一致。
内容的提问来源于stack exchange,提问作者Aravindan Kalai
相关产品推荐
相关产品推荐

