82 votes

Recherche de maxima et minima locaux

Je cherche un moyen efficace de trouver les maxima/minima locaux pour une grande liste de nombres dans R. J'espère qu'il n'y aura pas de for boucles...

Par exemple, si j'ai un fichier de données comme 1 2 3 2 1 1 2 1 Je veux que la fonction renvoie 3 et 7, qui sont les positions des maxima locaux.

11voto

symbolrush Points 2291

J'arrive en retard à la fête, mais cela pourrait intéresser d'autres personnes. Vous pouvez aujourd'hui utiliser la fonction (interne) find_peaks de ggpmisc l'emballage. Vous pouvez le paramétrer en utilisant threshold , span y strict arguments. En effet, depuis ggpmisc est destiné à être utilisé avec ggplot2 vous pouvez directement tracer minima y maxima en utilisant le stat_peaks y stat_valleys fonctions :

set.seed(1)
x <- 1:10
y <- runif(10)
# Maxima
x[ggpmisc:::find_peaks(y)]
[1] 4 7
y[ggpmisc:::find_peaks(y)]
[1] 0.9082078 0.9446753
# Minima
x[ggpmisc:::find_peaks(-y)]
[1] 5
y[ggpmisc:::find_peaks(-y)]
[1] 0.2016819    
# Plot
ggplot(data = data.frame(x, y), aes(x = x, y = y)) + geom_line() + stat_peaks(col = "red") + stat_valleys(col = "green")

enter image description here

8voto

mikeck Points 1827

La réponse de @42- est excellente, mais j'avais un cas d'utilisation où je ne voulais pas utiliser zoo . Il est facile de mettre cela en œuvre avec dplyr en utilisant lag y lead :

library(dplyr)
test = data_frame(x = sample(1:10, 20, replace = TRUE))
mutate(test, local.minima = if_else(lag(x) > x & lead(x) > x, TRUE, FALSE)

Comme le rollapply vous pouvez contrôler la taille de la fenêtre et les cas limites à l'aide de la fonction lag / lead arguments n y default respectivement.

0 votes

Une différence entre cette solution et la solution rollapply apparaît lorsque la fenêtre est plus grande que 1. Disons que nous voulons regarder à l'intérieur de 3 positions dans n'importe quelle direction. Avec la solution rollapply, nous pouvons regarder 7 valeurs, et elle nous dira si celle du milieu est le minima. Dans cette solution, l'utilisation de if_else(lag(x, 3) > x & lead(x, 3) > x ne regarderait que la troisième position, et non les 1 et 2 également. J'aime l'idée d'utiliser une solution dplyr, mais écrire 6 & conditions semble un peu fastidieux

0 votes

Voir ma réponse pour une solution qui s'appuie sur ce principe.

4voto

Jeffery Petit Points 138

Dans le cas sur lequel je travaille, les doublons sont fréquents. J'ai donc implémenté une fonction qui permet de trouver le premier ou le dernier extrema (min ou max) :

locate_xtrem <- function (x, last = FALSE)
{
  # use rle to deal with duplicates
  x_rle <- rle(x)

  # force the first value to be identified as an extrema
  first_value <- x_rle$values[1] - x_rle$values[2]

  # differentiate the series, keep only the sign, and use 'rle' function to
  # locate increase or decrease concerning multiple successive values.
  # The result values is a series of (only) -1 and 1.
  #
  # ! NOTE: with this method, last value will be considered as an extrema
  diff_sign_rle <- c(first_value, diff(x_rle$values)) %>% sign() %>% rle()

  # this vector will be used to get the initial positions
  diff_idx <- cumsum(diff_sign_rle$lengths)

  # find min and max
  diff_min <- diff_idx[diff_sign_rle$values < 0]
  diff_max <- diff_idx[diff_sign_rle$values > 0]

  # get the min and max indexes in the original series
  x_idx <- cumsum(x_rle$lengths)
  if (last) {
    min <- x_idx[diff_min]
    max <- x_idx[diff_max]
  } else {
    min <- x_idx[diff_min] - x_rle$lengths[diff_min] + 1
    max <- x_idx[diff_max] - x_rle$lengths[diff_max] + 1
  }
  # just get number of occurences
  min_nb <- x_rle$lengths[diff_min]
  max_nb <- x_rle$lengths[diff_max]

  # format the result as a tibble
  bind_rows(
    tibble(Idx = min, Values = x[min], NB = min_nb, Status = "min"),
    tibble(Idx = max, Values = x[max], NB = max_nb, Status = "max")) %>%
    arrange(.data$Idx) %>%
    mutate(Last = last) %>%
    mutate_at(vars(.data$Idx, .data$NB), as.integer)
}

La réponse à la question initiale est la suivante :

> x <- c(1, 2, 3, 2, 1, 1, 2, 1)
> locate_xtrem(x)
# A tibble: 5 x 5
    Idx Values    NB Status Last 
  <int>  <dbl> <int> <chr>  <lgl>
1     1      1     1 min    FALSE
2     3      3     1 max    FALSE
3     5      1     2 min    FALSE
4     7      2     1 max    FALSE
5     8      1     1 min    FALSE

Le résultat indique que le deuxième minimum est égal à 1 et que cette valeur est répétée deux fois à partir de l'indice 5. On pourrait donc obtenir un résultat différent en indiquant cette fois à la fonction de trouver les dernières occurrences des extrêmes locaux :

> locate_xtrem(x, last = TRUE)
# A tibble: 5 x 5
    Idx Values    NB Status Last 
  <int>  <dbl> <int> <chr>  <lgl>
1     1      1     1 min    TRUE 
2     3      3     1 max    TRUE 
3     6      1     2 min    TRUE 
4     7      2     1 max    TRUE 
5     8      1     1 min    TRUE 

En fonction de l'objectif, il est alors possible de basculer entre la première et la dernière valeur d'un extrême local. Le second résultat avec last = TRUE pourrait également être obtenue par une opération entre les colonnes "Idx" et "NB"...

Enfin, pour traiter le bruit dans les données, une fonction pourrait être mise en œuvre pour supprimer les fluctuations inférieures à un seuil donné. Le code n'est pas exposé car il va au-delà de la question initiale. Je l'ai intégré dans un package (principalement pour automatiser le processus de test) et je donne ci-dessous un exemple de résultat :

x_series %>% xtrem::locate_xtrem()

enter image description here

x_series %>% xtrem::locate_xtrem() %>% remove_noise()

enter image description here

0 votes

C'est très bien ! J'avais un graphique de valeurs cumulées, et donc des plages plates. Votre solution est la seule qui ait fonctionné. Ce serait bien d'avoir inclus le code de traçage aussi.

0 votes

C'est une fonction géniale ! Pouvoir contrôler le premier par rapport au dernier dans une séquence de doublons est très pratique.

2voto

Ehren Points 21

J'ai eu quelques difficultés à faire fonctionner les emplacements dans les solutions précédentes et j'ai trouvé un moyen de saisir directement les minima et les maxima. Le code ci-dessous permet de le faire et de le tracer, en marquant les minima en vert et les maxima en rouge. Contrairement à la méthode which.max() permet d'extraire tous les indices des minima/maxima d'un cadre de données. La valeur zéro est ajoutée dans le premier diff() pour tenir compte de la diminution de la longueur du résultat qui se produit chaque fois que vous utilisez la fonction. En l'insérant dans l'élément diff() évite d'avoir à ajouter un décalage en dehors de l'expression logique. Cela n'a pas beaucoup d'importance, mais je pense que c'est une façon plus propre de procéder.

# create example data called stockData
stockData = data.frame(x = 1:30, y=rnorm(30,7))

# get the location of the minima/maxima. note the added zero offsets  
# the location to get the correct indices
min_indexes = which(diff(  sign(diff( c(0,stockData$y)))) == 2)
max_indexes = which(diff(  sign(diff( c(0,stockData$y)))) == -2)

# get the actual values where the minima/maxima are located
min_locs = stockData[min_indexes,]
max_locs = stockData[max_indexes,]

# plot the data and mark minima with red and maxima with green
plot(stockData$y, type="l")
points( min_locs, col="red", pch=19, cex=1  )
points( max_locs, col="green", pch=19, cex=1  )

0 votes

Presque très bien - cela ne semble pas fonctionner avec un maximum à la fin> histData$counts [1] 18000 0 0 0 0 0 0 0 0 0 0 0 0 [217] 0 0 0 0 0 0 0 0 0 5992

0 votes

max_indexes = sign(diff( c(0,histData$counts,0)))) fonctionne, mais je ne sais pas si elle casse autre chose.

0 votes

@idontgetoutmuch... la méthode utilise essentiellement des calculs de dérivée première des données et ne trouvera pas de maxima ou de minima relatifs à un point final de la série évaluée. elle fonctionnerait pour l'avant-dernière valeur de la série s'il s'agissait d'un maximum/minimum relatif parce que la dérivée peut être approximée à cet endroit. si vous recherchez le maximum dans une série, la fonction max() devrait fonctionner correctement. en combinant cela avec le code ci-dessus, vous devriez obtenir les informations dont vous avez besoin sur les maxima/minimums.

2voto

zx8754 Points 13573

Cette fonction de Timothée Poisot est pratique pour les séries bruyantes :

3 mai 2009
Un algorithme pour trouver des extremums locaux dans un vecteur
Classé sous : Algorithme - Tags : Extrema, Série temporelle - Timothée Poisot @ 6:46pm

J'ai passé un certain temps à chercher un algorithme pour trouver des extrema locaux dans un vecteur (série temporelle). La solution que j'ai utilisée consiste à "parcourir" le vecteur vecteur par pas plus grand que 1, afin de ne retenir qu'une seule valeur même même lorsque les valeurs sont très bruitées (voir l'image à la fin de l'article). post).

Voici comment cela se passe :

findpeaks <- function(vec,bw=1,x.coo=c(1:length(vec)))
{
    pos.x.max <- NULL
    pos.y.max <- NULL
    pos.x.min <- NULL
    pos.y.min <- NULL   for(i in 1:(length(vec)-1))     {       if((i+1+bw)>length(vec)){
                sup.stop <- length(vec)}else{sup.stop <- i+1+bw
                }
        if((i-bw)<1){inf.stop <- 1}else{inf.stop <- i-bw}
        subset.sup <- vec[(i+1):sup.stop]
        subset.inf <- vec[inf.stop:(i-1)]

        is.max   <- sum(subset.inf > vec[i]) == 0
        is.nomin <- sum(subset.sup > vec[i]) == 0

        no.max   <- sum(subset.inf > vec[i]) == length(subset.inf)
        no.nomin <- sum(subset.sup > vec[i]) == length(subset.sup)

        if(is.max & is.nomin){
            pos.x.max <- c(pos.x.max,x.coo[i])
            pos.y.max <- c(pos.y.max,vec[i])
        }
        if(no.max & no.nomin){
            pos.x.min <- c(pos.x.min,x.coo[i])
            pos.y.min <- c(pos.y.min,vec[i])
        }
    }
    return(list(pos.x.max,pos.y.max,pos.x.min,pos.y.min))
}

enter image description here

Lien vers l'article de blog original

0 votes

J'aime cette fonction. Elle permet de déterminer facilement les enveloppes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X