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.

68voto

Ben Bolker Points 50041

diff(diff(x)) (ou diff(x,differences=2) : merci à @ZheyuanLi) calcule essentiellement l'analogue discret de la dérivée seconde, et devrait donc être négatif aux maxima locaux. Les +1 ci-dessous prend en compte le fait que le résultat de l'opération diff est plus court que le vecteur d'entrée.

éditer : ajout de la correction de @Tommy pour les cas où delta-x n'est pas 1...

tt <- c(1,2,3,2,1, 1, 2, 1)
which(diff(sign(diff(tt)))==-2)+1

Ma suggestion ci-dessus ( http://statweb.stanford.edu/~tibs/PPC/Rdist/ ) est destiné au cas où les données sont plus bruyantes.

5 votes

Vous m'avez devancé de quelques secondes - et avec une meilleure solution :) Mais cela devrait être which(diff(sign(diff(x)))==-2)+1 si les valeurs ne changent pas toujours d'une unité.

0 votes

Comme l'a souligné Tommy, la solution de Ben ne fonctionne pas non plus lorsque la liste d'entrée est en ordre croissant, par exemple tt <- c(2,3,4,5,6,7) expected answer : index of last element of the list

0 votes

Le lien ...ppc.peaks.html ne fonctionne pas, utilisez le lien suivant statweb.stanford.edu/~tibs/PPC/Rdist au lieu de cela.

42voto

Tommy Points 16323

La solution de @Ben est très intéressante. Elle ne gère cependant pas les cas suivants :

# all these return numeric(0):
x <- c(1,2,9,9,2,1,1,5,5,1) # duplicated points at maxima 
which(diff(sign(diff(x)))==-2)+1 
x <- c(2,2,9,9,2,1,1,5,5,1) # duplicated points at start
which(diff(sign(diff(x)))==-2)+1 
x <- c(3,2,9,9,2,1,1,5,5,1) # start is maxima
which(diff(sign(diff(x)))==-2)+1

Voici une version plus robuste (et plus lente, plus laide) :

localMaxima <- function(x) {
  # Use -Inf instead if x is numeric (non-integer)
  y <- diff(c(-.Machine$integer.max, x)) > 0L
  rle(y)$lengths
  y <- cumsum(rle(y)$lengths)
  y <- y[seq.int(1L, length(y), 2L)]
  if (x[[1]] == x[[2]]) {
    y <- y[-1]
  }
  y
}

x <- c(1,2,9,9,2,1,1,5,5,1)
localMaxima(x) # 3, 8
x <- c(2,2,9,9,2,1,1,5,5,1)
localMaxima(x) # 3, 8
x <- c(3,2,9,9,2,1,1,5,5,1)
localMaxima(x) # 1, 3, 8

0 votes

Merci j'ai essayé ce code, et il fonctionne ! Comment modifier ce code pour le local-minima sans changer l'entrée ?

0 votes

Bonjour Tommy, j'ai voulu utiliser votre fonction localMinima dans un paquet, pourriez-vous me contacter afin que je puisse vous remercier comme il se doit ?

0 votes

@VahidMir En fait, cette fonction est une façon (intelligente !) d'obtenir les positions où la dérivée première du vecteur passe de positive à négative. Par conséquent, le minimum local serait donné là où il passe de négatif à positif : il suffit de remplacer la première ligne par y <- diff(c(.Machine$integer.max, x)) < 0L (cela permet de conserver la possibilité de détecter un minimum initial)

27voto

BondedDust Points 105234

Utilisez la fonction rollapply de la bibliothèque zoo :

x <- c(1, 2, 3, 2, 1, 1, 2, 1)
library(zoo)
 xz <- as.zoo(x)
 rollapply(xz, 3, function(x) which.min(x)==2)
#    2     3     4     5     6     7 
#FALSE FALSE FALSE  TRUE FALSE FALSE 
 rollapply(xz, 3, function(x) which.max(x)==2)
#    2     3     4     5     6     7 
#FALSE  TRUE FALSE FALSE FALSE  TRUE 

Il faut ensuite extraire l'index à l'aide des "coredata" pour les valeurs pour lesquelles "which.max" est une "valeur centrale" signalant un maximum local. Vous pouvez évidemment faire la même chose pour les minima locaux en utilisant which.min au lieu de which.max .

 rxz <- rollapply(xz, 3, function(x) which.max(x)==2)
 index(rxz)[coredata(rxz)]
#[1] 3 7

Je suppose que vous ne voulez pas les valeurs de départ ou d'arrivée, mais si c'est le cas, vous pourriez remplir les extrémités de vos vecteurs avant le traitement, un peu comme le font les télomères sur les chromosomes.

(Je mentionne le paquet ppc ("Peak Probability Contrasts" pour faire des analyses de spectrométrie de masse, simplement parce que je n'étais pas au courant de sa disponibilité avant de lire le commentaire de @BenBolker ci-dessus, et je pense que l'ajout de ces quelques mots augmentera les chances que quelqu'un qui s'intéresse à la spectrométrie de masse voie ceci lors d'une recherche).

3 votes

Il présente un avantage très important par rapport aux autres. En augmentant l'intervalle à quelque chose de plus grand que 3, nous pouvons ignorer les cas où un point se trouve être très légèrement plus élevé que ses deux voisins les plus proches, même s'il y a d'autres points proches plus grands. Cela peut s'avérer utile pour les données mesurées présentant de petites variations aléatoires.

0 votes

Merci pour le commentaire, et merci à dbaupp, et merci à @GGrothendieck et Achim Zeileis pour la rédaction. zoo si proprement que je suis en mesure de l'appliquer proprement.

2 votes

Il s'agit d'une solution fantastique, mais un avertissement s'impose : il est préférable de définir explicitement l'élément align argument. zoo:::rollapply.zoo utilise align = "center" par défaut, mais xts:::rollapply.xts utilise align = "right" .

16voto

Evan Friedland Points 2246

J'ai fait un essai aujourd'hui. Je sais que vous avez dit qu'il fallait espérer sans boucle for, mais je me suis contenté d'utiliser la fonction apply. Elle est assez compacte et rapide et permet de spécifier un seuil, de sorte que l'on peut aller au-delà de 1.

La fonction :

inflect <- function(x, threshold = 1){
  up   <- sapply(1:threshold, function(n) c(x[-(seq(n))], rep(NA, n)))
  down <-  sapply(-1:-threshold, function(n) c(rep(NA,abs(n)), x[-seq(length(x), length(x) - abs(n) + 1)]))
  a    <- cbind(x,up,down)
  list(minima = which(apply(a, 1, min) == a[,1]), maxima = which(apply(a, 1, max) == a[,1]))
}

Pour le visualiser/jouer avec les seuils, vous pouvez exécuter le code suivant :

# Pick a desired threshold # to plot up to
n <- 2
# Generate Data
randomwalk <- 100 + cumsum(rnorm(50, 0.2, 1)) # climbs upwards most of the time
bottoms <- lapply(1:n, function(x) inflect(randomwalk, threshold = x)$minima)
tops <- lapply(1:n, function(x) inflect(randomwalk, threshold = x)$maxima)
# Color functions
cf.1 <- grDevices::colorRampPalette(c("pink","red"))
cf.2 <- grDevices::colorRampPalette(c("cyan","blue"))
plot(randomwalk, type = 'l', main = "Minima & Maxima\nVariable Thresholds")
for(i in 1:n){
  points(bottoms[[i]], randomwalk[bottoms[[i]]], pch = 16, col = cf.1(n)[i], cex = i/1.5)
}
for(i in 1:n){
  points(tops[[i]], randomwalk[tops[[i]]], pch = 16, col = cf.2(n)[i], cex = i/1.5)
}
legend("topleft", legend = c("Minima",1:n,"Maxima",1:n), 
       pch = rep(c(NA, rep(16,n)), 2), col = c(1, cf.1(n),1, cf.2(n)), 
       pt.cex =  c(rep(c(1, c(1:n) / 1.5), 2)), cex = .75, ncol = 2)

enter image description here

0 votes

J'aime la fonction <inflect>. Merci Evan (y)

0 votes

Je m'en réjouis !

0 votes

Mes données comportent des valeurs répétées pour les maxima et les minima, par exemple c(1,2,3,3,2,1,1,2,3), ce qui génère plusieurs occurrences pour chaque max/min. J'expérimente avec threshold semble seulement modifier la taille des points sur le graphique, mais ne résout pas le problème. Des suggestions ?

13voto

jamie.f.olson Points 164

Il existe de bonnes solutions, mais tout dépend de ce dont vous avez besoin.

Juste diff(tt) renvoie les différences.

Vous souhaitez détecter le moment où vous passez de valeurs croissantes à des valeurs décroissantes. Une façon de le faire est fournie par @Ben :

 diff(sign(diff(tt)))==-2

Le problème est que cette méthode ne permet de détecter que les changements qui passent immédiatement d'une augmentation stricte à une diminution stricte.

Une légère modification permettra d'obtenir des valeurs répétées au niveau du pic (retour à l'état initial). TRUE pour la dernière occurrence de la valeur maximale) :

 diff(diff(x)>=0)<0

Il suffit ensuite de remplir correctement le recto et le verso si l'on veut détecter des maxima au début ou à la fin de la période de référence.

Voici tout ce qui se trouve dans une fonction (y compris la recherche des vallées) :

 which.peaks <- function(x,partial=TRUE,decreasing=FALSE){
     if (decreasing){
         if (partial){
             which(diff(c(FALSE,diff(x)>0,TRUE))>0)
         }else {
             which(diff(diff(x)>0)>0)+1
         }
     }else {
         if (partial){
             which(diff(c(TRUE,diff(x)>=0,FALSE))<0)
         }else {
             which(diff(diff(x)>=0)<0)+1
         }
     }
 }

1 votes

FAO futurs visiteurs, j'ai essayé quelques-unes des solutions suggérées ici et celle-ci a été la plus efficace pour moi.

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