일련의 데이터에서 로컬 피크 / 밸리를 찾는 방법은 무엇입니까?


16

내 실험은 다음과 같습니다.

quantmod 패키지 findPeaks에서 함수를 사용하고 있습니다 .

공차 5 내에서 "로컬"피크, 즉 시계열이 로컬 피크에서 5만큼 떨어진 후 첫 번째 위치를 감지하려고합니다.

aa=100:1
bb=sin(aa/3)
cc=aa*bb
plot(cc, type="l")
p=findPeaks(cc, 5)
points(p, cc[p])
p

출력은

[1] 3 22 41

3보다 더 많은 "로컬 피크"를 기대하고 있기 때문에 잘못된 것 같습니다 ...

이견있는 사람?


이 패키지가 없습니다. 사용되는 수치 루틴을 설명 할 수 있습니까?
AdamO

findPeaks답글 @Adam에 대한 전체 소스 코드가 나타납니다. BTW, 패키지는 "quantmod" 입니다.
whuber

R-SIG-Finance 에 게시되었습니다 .
Joshua Ulrich

답변:


8

이 코드의 소스는 R 프롬프트에서 이름을 입력하여 얻습니다. 출력은

function (x, thresh = 0) 
{
    pks <- which(diff(sign(diff(x, na.pad = FALSE)), na.pad = FALSE) < 0) + 2
    if (!missing(thresh)) {
        pks[x[pks - 1] - x[pks] > thresh]
    }
    else pks
}

이 테스트 x[pks - 1] - x[pks] > thresh는 각 피크 값을 시리즈에서 즉시 성공한 값과 비교 합니다 (시리즈의 다음 최저점은 아님). 피크 직후 함수 의 기울기 크기 추정값을 사용하고 해당 기울기가 thresh크기를 초과하는 피크 만 선택합니다 . 귀하의 경우 처음 세 개의 피크 만 테스트를 통과하기에 충분히 예리합니다. 기본값을 사용하여 모든 피크를 감지합니다.

> findPeaks(cc)
[1]  3 22 41 59 78 96

30

whuber의 응답에 동의하지만 코드의 "+2"부분을 추가하고 싶었습니다.이 부분은 새로 발견 된 피크와 실제로 일치하는 "오버 슈트"와 일치하도록 인덱스를 이동 시키려고하며 "+1"이어야합니다. 예를 들어, 본 예제에서 우리는

> findPeaks(cc)
[1]  3 22 41 59 78 96

그래프에서 이러한 피크를 강조 표시 할 때 (굵은 빨간색) : 여기에 이미지 설명을 입력하십시오

우리는 그것들이 실제 피크에서 지속적으로 1 포인트 떨어져 있음을 알 수 있습니다.

결과

pks[x[pks - 1] - x[pks] > thresh]

해야 pks[x[pks] - x[pks + 1] > thresh]하거나pks[x[pks] - x[pks - 1] > thresh]

큰 업데이트

적절한 피크 찾기 기능을 찾기위한 내 자신의 탐구에 따라 나는 이것을 썼다 :

find_peaks <- function (x, m = 3){
    shape <- diff(sign(diff(x, na.pad = FALSE)))
    pks <- sapply(which(shape < 0), FUN = function(i){
       z <- i - m + 1
       z <- ifelse(z > 0, z, 1)
       w <- i + m + 1
       w <- ifelse(w < length(x), w, length(x))
       if(all(x[c(z : i, (i + 2) : w)] <= x[i + 1])) return(i + 1) else return(numeric(0))
    })
     pks <- unlist(pks)
     pks
}

'피크'는 m양쪽의 점이 그보다 작은 로컬 최대 값으로 정의됩니다 . 따라서 매개 변수가 클수록 m최대 자금 조달 절차가 더 엄격합니다. 그래서:

find_peaks(cc, m = 1)
[1]  2 21 40 58 77 95

이 함수는를 x통해 순차 벡터의 국소 최소값을 찾는 데에도 사용할 수 있습니다 find_peaks(-x).

참고 : 누군가가 필요하면 gitHub에 함수를 넣었습니다. https://github.com/stas-g/findPeaks


6

Eek : 사소한 업데이트. Stas_G의 기능과 동등한 수준에 도달하기 위해 두 줄의 코드 인 경계를 변경해야합니다 (-1과 +1을 추가하십시오) (실제 데이터 세트에서 너무 많은 '추가 피크'를 발견했습니다). 누군가를위한 사과는 내 원래 게시물에 의해 아주 작은 길을 잃었다.

Stas_g의 피크 찾기 알고리즘을 꽤 오랫동안 사용 해 왔습니다. 그것은 단순하기 때문에 나중의 프로젝트 중 하나에 도움이되었습니다. 그러나 계산에 수백만 번 사용해야했기 때문에 Rcpp로 다시 작성했습니다 (Rcpp 패키지 참조). 간단한 테스트에서 R 버전보다 약 6 배 빠릅니다. 관심있는 사람이 있다면 아래 코드를 추가했습니다. 잘만되면 나는 누군가를 도와줍니다, 건배!

몇 가지 사소한 경고. 이 함수는 R 코드의 역순으로 피크 인덱스를 반환합니다. 내부 C ++ Sign 함수가 필요합니다. 완전히 최적화되지는 않았지만 더 이상의 성능 향상은 기대되지 않습니다.

//This function returns the sign of a given real valued double.
// [[Rcpp::export]]
double signDblCPP (double x){
  double ret = 0;
  if(x > 0){ret = 1;}
  if(x < 0){ret = -1;}
  return(ret);
}

//Tested to be 6x faster(37 us vs 207 us). This operation is done from 200x per layer
//Original R function by Stas_G
// [[Rcpp::export]]
NumericVector findPeaksCPP( NumericVector vY, int m = 3) {
  int sze = vY.size();
  int i = 0;//generic iterator
  int q = 0;//second generic iterator

  int lb = 0;//left bound
  int rb = 0;//right bound

  bool isGreatest = true;//flag to state whether current index is greatest known value

  NumericVector ret(1);
  int pksFound = 0;

  for(i = 0; i < (sze-2); ++i){
    //Find all regions with negative laplacian between neighbors
    //following expression is identical to diff(sign(diff(xV, na.pad = FALSE)))
    if(signDblCPP( vY(i + 2)  - vY( i + 1 ) ) - signDblCPP( vY( i + 1 )  - vY( i ) ) < 0){
      //Now assess all regions with negative laplacian between neighbors...
      lb = i - m - 1;// define left bound of vector
      if(lb < 0){lb = 0;}//ensure our neighbor comparison is bounded by vector length
      rb = i + m + 1;// define right bound of vector
      if(rb >= (sze-2)){rb = (sze-3);}//ensure our neighbor comparison is bounded by vector length
      //Scan through loop and ensure that the neighbors are smaller in magnitude
      for(q = lb; q < rb; ++q){
        if(vY(q) > vY(i+1)){ isGreatest = false; }
      }

      //We have found a peak by our criterion
      if(isGreatest){
        if(pksFound > 0){//Check vector size.
         ret.insert( 0, double(i + 2) );
       }else{
         ret(0) = double(i + 2);
        }
        pksFound = pksFound + 1;
      }else{ // we did not find a peak, reset location is peak max flag.
        isGreatest = true;
      }//End if found peak
    }//End if laplace condition
  }//End loop
  return(ret);
}//End Fn

이 for 루프는 @caseyk : for(q = lb; q < rb; ++q){ if(vY(q) > vY(i+1)){ isGreatest = false; } }에 대한 마지막 실행으로 "wins"에 결함이있는 것으로 보입니다 : isGreatest = vY(rb-1) <= vY(rb). 해당 라인 주장 바로 위의 주석을 달성하려면 for 루프를 다음과 같이 변경해야합니다.for(q = lb; isGreatest && (q < rb); ++q){ isGreatest = (vY(q) <= vY(i+1)) }
Bernhard Wagner

흠. 이 코드를 작성한 이후로 오랜 시간이 걸렸습니다. IIRC는 Stas_G의 기능으로 직접 테스트되었으며 동일한 결과를 유지했습니다. 나는 당신이 무엇을 말하는지 알지만 출력에 어떤 차이가 있는지 잘 모르겠습니다. 솔루션을 내가 제안 / 적응 한 솔루션과 비교하여 조사하는 것이 좋습니다.
caseyk

또한 필자는이 스크립트를 100x 정도의 순서로 개인적으로 테스트했으며 (이것이 내 프로젝트에서 하나라고 가정) 백만 번 이상 사용되었으며 문헌 결과와 완전히 일치하는 간접 결과를 제공했다고 덧붙여 야합니다. 특정 테스트 사례. 따라서 '결함이있는'경우 '결함이 아닙니다';)
caseyk

1

첫째, 알고리즘은 또한 평평한 고원의 오른쪽으로 하락을 잘못 호출하여 sign(diff(x, na.pad = FALSE)) 0 부터 -1이되므로 diff도 -1이됩니다. 간단한 수정은 음수 항목 앞의 부호 차이가 0이 아니라 양수인지 확인하는 것입니다.

    n <- length(x)
    dx.1 <- sign(diff(x, na.pad = FALSE))
    pks <- which(diff(dx.1, na.pad = FALSE) < 0 & dx.1[-(n-1)] > 0) + 1

둘째 :이 알고리즘은 시퀀스에서 3 개의 연속 된 항의 실행에서 '위로'와 '아래로'와 같이 매우 지역적인 결과를 제공 합니다. 노이즈가있는 연속 함수의 로컬 최대 값에 관심이 있다면 아마도 더 나은 것들이있을 것입니다. 그러나 이것은 저의 저렴한 해결책입니다.


  1. 데이터를 약간 매끄럽게하기 위해 연속 3 점의 연속 평균을 사용하여 피크를 먼저 식별합니다 . 또한 위에서 언급 한 플랫-드롭-오프에 대한 제어를 사용하십시오.
  2. 황토 평활 버전의 경우 각 피크를 중심으로 한 창 내부 평균과 외부 지역 평균 평균을 비교하여 이러한 후보를 필터링합니다.

    "myfindPeaks" <- 
    function (x, thresh=0.05, span=0.25, lspan=0.05, noisey=TRUE)
    {
      n <- length(x)
      y <- x
      mu.y.loc <- y
      if(noisey)
      {
        mu.y.loc <- (x[1:(n-2)] + x[2:(n-1)] + x[3:n])/3
        mu.y.loc <- c(mu.y.loc[1], mu.y.loc, mu.y.loc[n-2])
      }
      y.loess <- loess(x~I(1:n), span=span)
      y <- y.loess[[2]]
      sig.y <- var(y.loess$resid, na.rm=TRUE)^0.5
      DX.1 <- sign(diff(mu.y.loc, na.pad = FALSE))
      pks <- which(diff(DX.1, na.pad = FALSE) < 0 & DX.1[-(n-1)] > 0) + 1
      out <- pks
      if(noisey)
      {
        n.w <- floor(lspan*n/2)
        out <- NULL
        for(pk in pks)
        {
          inner <- (pk-n.w):(pk+n.w)
          outer <- c((pk-2*n.w):(pk-n.w),(pk+2*n.w):(pk+n.w))
          mu.y.outer <- mean(y[outer])
          if(!is.na(mu.y.outer)) 
            if (mean(y[inner])-mu.y.outer > thresh*sig.y) out <- c(out, pk)
        }
      }
      out
    }

0

이 함수는 또한 고지의 끝을 식별하는 것이 사실이지만, 또 다른 쉬운 수정이 있다고 생각합니다. 실제 피크의 첫 번째 차이는 '1'과 '-1'이되고 두 번째 차이는 '-2'입니다. 우리는 직접 확인할 수 있습니다

    pks <- which(diff(sign(diff(x, na.pad = FALSE)), na.pad = FALSE) < 1) + 1

이것은 질문에 대답하지 않는 것 같습니다.
Michael R. Chernick

0

Numpy 사용

ser = np.random.randint(-40, 40, 100) # 100 points
peak = np.where(np.diff(ser) < 0)[0]

또는

double_difference = np.diff(np.sign(np.diff(ser)))
peak = np.where(double_difference == -2)[0]

판다 사용하기

ser = pd.Series(np.random.randint(2, 5, 100))
peak_df = ser[(ser.shift(1) < ser) & (ser.shift(-1) < ser)]
peak = peak_df.index
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.