You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 11:48:26