132 votes

Trouver les maxima/minima locaux avec Numpy dans un tableau numpy 1D

Pouvez-vous suggérer une fonction module de numpy/scipy qui peut trouver des maxima/minima locaux dans un tableau numpy 1D ? Évidemment, l'approche la plus simple est de regarder les plus proches voisins, mais j'aimerais avoir une solution acceptée qui fasse partie de la distribution numpy.

1 votes

2 votes

Non, c'est en 2D (je parle de 1D) et cela implique des fonctions personnalisées. J'ai ma propre implémentation simple, mais je me demandais s'il en existait une meilleure, fournie avec les modules Numpy/Scipy.

0 votes

Vous pourriez peut-être mettre à jour la question en précisant que (1) vous avez un tableau 1d et (2) quel type de minimum local vous recherchez. Juste une entrée plus petite que les deux entrées adjacentes ?

239voto

danodonovan Points 5268

Dans SciPy >= 0.11

import numpy as np
from scipy.signal import argrelextrema

x = np.random.random(12)

# for local maxima
argrelextrema(x, np.greater)

# for local minima
argrelextrema(x, np.less)

Produit

>>> x
array([ 0.56660112,  0.76309473,  0.69597908,  0.38260156,  0.24346445,
    0.56021785,  0.24109326,  0.41884061,  0.35461957,  0.54398472,
    0.59572658,  0.92377974])
>>> argrelextrema(x, np.greater)
(array([1, 5, 7]),)
>>> argrelextrema(x, np.less)
(array([4, 6, 8]),)

Notez qu'il s'agit des indices de x qui sont des max/min locaux. Pour obtenir les valeurs, essayez :

>>> x[argrelextrema(x, np.greater)[0]]

scipy.signal fournit également argrelmax et argrelmin pour trouver les maxima et les minima respectivement.

1 votes

Quelle est la signification de 12 ?

7 votes

@marshmallow : np.random.random(12) génère 12 valeurs aléatoires, elles sont utilisées pour démontrer la fonction argrelextrema .

3 votes

Si l'entrée est test02=np.array([10,4,4,4,5,6,7,6]) alors il ne fonctionne pas. Il ne reconnaît pas les valeurs consécutives comme des minima locaux.

76voto

Sven Marnach Points 133943

Si vous cherchez toutes les entrées dans le tableau 1d a plus petits que leurs voisins, vous pouvez essayer

numpy.r_[True, a[1:] < a[:-1]] & numpy.r_[a[:-1] < a[1:], True]

Vous pouvez également lisse votre tableau avant cette étape en utilisant numpy.convolve() .

Je ne pense pas qu'il existe une fonction dédiée pour cela.

0 votes

Hmm, pourquoi aurais-je besoin de lisser ? Pour supprimer le bruit ? Cela semble intéressant. Il me semble que je pourrais utiliser un autre entier au lieu de 1 dans votre exemple de code. Je pensais aussi à calculer des gradients. De toute façon, s'il n'y a pas de fonction, c'est dommage.

1 votes

@Navi : Le problème est que la notion de "minimum local" varie énormément d'un cas d'utilisation à l'autre, il est donc difficile de fournir une fonction "standard" à cet effet. Le lissage permet de prendre en compte plus que le plus proche voisin. L'utilisation d'un autre entier au lieu de 1, disons 3, serait étrange car elle ne prendrait en compte que le troisième élément suivant dans les deux directions, mais pas les voisins directs.

0 votes

Je voulais dire en considérant le nombre n de voisins dans les deux sens. De sorte que min(x) = min(x[i - n] : x[i + n]).

38voto

R. C. Points 121

Pour des courbes sans trop de bruit, je recommande le petit extrait de code suivant :

from numpy import *

# example data with some peaks:
x = linspace(0,4,1e3)
data = .2*sin(10*x)+ exp(-abs(2-x)**2)

# that's the line, you need:
a = diff(sign(diff(data))).nonzero()[0] + 1 # local min+max
b = (diff(sign(diff(data))) > 0).nonzero()[0] + 1 # local min
c = (diff(sign(diff(data))) < 0).nonzero()[0] + 1 # local max

# graphical output...
from pylab import *
plot(x,data)
plot(x[b], data[b], "o", label="min")
plot(x[c], data[c], "o", label="max")
legend()
show()

Le site +1 est important, car diff réduit le numéro d'index original.

1 votes

Belle utilisation des fonctions numpy imbriquées ! mais notez que cela manque les maxima aux deux extrémités du tableau :)

2 votes

Cela se comportera également bizarrement s'il y a des valeurs répétitives. Par exemple, si vous prenez le tableau [1, 2, 2, 3, 3, 3, 2, 2, 1] le maximum local est évidemment quelque part entre les 3 du milieu. Mais si vous exécutez les fonctions que vous avez fournies, vous obtenez des maxima aux indices 2 et 6 et des minima aux indices 1, 3, 5 et 7, ce qui, pour moi, n'a pas beaucoup de sens.

6 votes

Pour éviter cela +1 au lieu de np.diff() utiliser np.gradient() .

25voto

BobC Points 383

Une autre approche (plus de mots, moins de code) qui pourrait vous aider :

Les emplacements des maxima et minima locaux sont également les emplacements des passages par zéro de la dérivée première. Il est généralement beaucoup plus facile de trouver les passages par zéro que de trouver directement les maxima et minima locaux.

Malheureusement, la dérivée première a tendance à "amplifier" le bruit. Par conséquent, lorsqu'un bruit important est présent dans les données d'origine, il est préférable d'utiliser la dérivée première uniquement après avoir appliqué un certain degré de lissage aux données d'origine.

Puisque le lissage est, au sens le plus simple, un filtre passe-bas, le lissage est souvent mieux (enfin, plus facilement) réalisé en utilisant un noyau de convolution, et la "mise en forme" de ce noyau peut fournir une quantité surprenante de capacité de préservation/amélioration des caractéristiques. Le processus de recherche d'un noyau optimal peut être automatisé par divers moyens, mais le mieux est peut-être la simple force brute (très rapide pour trouver de petits noyaux). Un bon noyau va (comme prévu) déformer massivement les données d'origine, mais il n'affectera PAS l'emplacement des pics et des vallées d'intérêt.

Heureusement, il est souvent possible de créer un noyau approprié par le biais d'un simple SWAG ("educated guess"). La largeur du noyau de lissage doit être un peu plus large que le pic "intéressant" le plus large attendu dans les données d'origine, et sa forme doit ressembler à ce pic (une ondelette à échelle unique). Pour les noyaux préservant la moyenne (ce que devrait être tout bon filtre de lissage), la somme des éléments du noyau devrait être précisément égale à 1,00 et le noyau devrait être symétrique par rapport à son centre (ce qui signifie qu'il aura un nombre impair d'éléments.

Étant donné un noyau de lissage optimal (ou un petit nombre de noyaux optimisés pour différents contenus de données), le degré de lissage devient un facteur d'échelle pour (le "gain" du) noyau de convolution.

La détermination du degré "correct" (optimal) de lissage (gain du noyau de convolution) peut même être automatisée : Comparer l'écart type des données de la dérivée première avec l'écart type des données lissées. La façon dont le rapport entre les deux écarts types change en fonction du degré de lissage peut être utilisé pour prédire les valeurs de lissage efficaces. Quelques exécutions manuelles de données (qui sont vraiment représentatives) devraient suffire.

Toutes les solutions précédentes affichées ci-dessus calculent la dérivée première, mais elles ne la traitent pas comme une mesure statistique, et ne tentent pas non plus d'effectuer un lissage de préservation/amélioration des caractéristiques (pour aider les pics subtils à "sauter" au-dessus du bruit).

Enfin, la mauvaise nouvelle : Trouver les "vrais" pics devient un véritable casse-tête lorsque le bruit présente également des caractéristiques qui ressemblent à de vrais pics (chevauchement de la bande passante). La solution suivante, plus complexe, consiste généralement à utiliser un noyau de convolution plus long (une "ouverture de noyau plus large") qui tient compte de la relation entre les "vrais" pics adjacents (comme les taux minimum ou maximum d'apparition des pics), ou à utiliser plusieurs passes de convolution en utilisant des noyaux de largeur différente (mais seulement si c'est plus rapide : c'est une vérité mathématique fondamentale que les convolutions linéaires effectuées en séquence peuvent toujours être convoluées ensemble en une seule convolution). Mais il est souvent beaucoup plus facile de trouver d'abord une séquence de noyaux utiles (de largeurs différentes) et de les convoluer ensemble que de trouver directement le noyau final en une seule étape.

J'espère que ces informations sont suffisantes pour permettre à Google (et peut-être à un bon texte de statistiques) de combler les lacunes. J'aimerais vraiment avoir le temps de fournir un exemple concret, ou un lien vers un tel exemple. Si quelqu'un en trouve un en ligne, merci de le poster ici !

7voto

Mike Vella Points 1723

Mise à jour : Je n'étais pas satisfait du gradient, j'ai donc trouvé plus fiable d'utiliser numpy.diff .

En ce qui concerne la question du bruit, le problème mathématique est de localiser les maxima/minima ; si nous voulons examiner le bruit, nous pouvons utiliser quelque chose comme la convolution qui a été mentionnée précédemment.

import numpy as np
from matplotlib import pyplot

a=np.array([10.3,2,0.9,4,5,6,7,34,2,5,25,3,-26,-20,-29],dtype=np.float)

gradients=np.diff(a)
print gradients

maxima_num=0
minima_num=0
max_locations=[]
min_locations=[]
count=0
for i in gradients[:-1]:
        count+=1

    if ((cmp(i,0)>0) & (cmp(gradients[count],0)<0) & (i != gradients[count])):
        maxima_num+=1
        max_locations.append(count)     

    if ((cmp(i,0)<0) & (cmp(gradients[count],0)>0) & (i != gradients[count])):
        minima_num+=1
        min_locations.append(count)

turning_points = {'maxima_number':maxima_num,'minima_number':minima_num,'maxima_locations':max_locations,'minima_locations':min_locations}  

print turning_points

pyplot.plot(a)
pyplot.show()

0 votes

Savez-vous comment ce gradient est calculé ? Si vous avez des données bruyantes, il est probable que le gradient change beaucoup, mais cela ne signifie pas nécessairement qu'il y a un maximum/minimum.

0 votes

Oui, je sais, mais les données bruyantes sont un problème différent. Pour cela, je pense qu'il faut utiliser le convolve.

0 votes

J'avais besoin de quelque chose de similaire pour un projet sur lequel je travaille et j'ai utilisé la méthode numpy.diff mentionnée ci-dessus. J'ai pensé qu'il pourrait être utile de mentionner que pour mes données, le code ci-dessus a manqué quelques maxima et minima, en changeant le terme moyen dans les deux déclarations if en <= et >= respectivement, j'ai pu attraper tous les points.

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