Python >> Tutoriel Python >  >> Python Tag >> SciPy

Python Scipy signal.find_peaks() — Un guide utile

Cet article traite de l'analyse et du traitement des signaux, plus spécifiquement sur la manière d'identifier et de calculer les pics contenus dans un signal donné.

Motivations

Être capable d'identifier et donc de travailler avec les crêtes d'un signal est d'une importance fondamentale dans de nombreux domaines différents, de l'électronique à la science des données et à l'économie.

Quand on parle de pics , nous ne parlons pas seulement des pics d'un signal électrique, même des maxima ou minima dans une fonction mathématique sont considérées comme des pics. Gardant cela à l'esprit, nous savons tous l'importance d'avoir uneméthode rapide et fiable qui pourrait nous permettre de déterminer la position et la valeur des maxima et des minima dans une fonction ; est-ce juste pour résoudre un exercice mathématique ou pour prédire une tendance économique, le nombre d'applications est énorme.

Exemple de code Recherche et tracé des pics

Nous exploitons ici la fonction .find_peaks() du Scipy.singnal bibliothèque, pour traiter un signal/une fonction spécifique et extraire la position et l'intensité de plusieurs pics.

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import find_peaks

#defining the x and y arrays
x = np.linspace(0,10, 100)
y = x*np.random.randn(100)**2

#Find peaks
peaks = find_peaks(y, height = 1, threshold = 1, distance = 1)
height = peaks[1]['peak_heights'] #list of the heights of the peaks
peak_pos = x[peaks[0]] #list of the peaks positions

#Finding the minima
y2 = y*-1
minima = find_peaks(y2)
min_pos = x[minima[0]] #list of the minima positions
min_height = y2[minima[0]] #list of the mirrored minima heights

#Plotting
fig = plt.figure()
ax = fig.subplots()
ax.plot(x,y)
ax.scatter(peak_pos, height, color = 'r', s = 15, marker = 'D', label = 'Maxima')
ax.scatter(min_pos, min_height*-1, color = 'gold', s = 15, marker = 'X', label = 'Minima')
ax.legend()
ax.grid()
plt.show()

Plongeons-nous dans ce code étape par étape !

Importer les bibliothèques Python nécessaires

Commençons notre script en important les bibliothèques Python qui seront ensuite utilisées dans le script.

import numpy as np
from scipy.signal import find_peaks
import matplotlib.pyplot as plt 

Créer une fonction avec des pics

La première chose que nous devons faire est de créer une fonction, qui devrait présenter des pics.

Cela signifie créer les tableaux "x" et "y" qui seront ensuite traités et tracés dans notre script.

  • Nous commençons par utiliser le .linspace() fonction de Numpy, pour définir le x tableau, nous l'appelons "x" ; il se compose d'un tableau de 100 nombres équidistants.
  • Pour générer le y array, nous utilisons la fonction .randn() du au hasard package (également de Numpy), qui renvoie un échantillon d'une distribution standard (voir la documentation supplémentaire ici :https://numpy.org/devdocs/reference/random/generated/numpy.random.randn.html), il suffit de spécifier en paramètre d'entrée, la taille du tableau généré, dans ce cas, il faut faire correspondre la longueur du tableau x, donc 100.

On modifie ensuite un peu plus ce tableau, en mettant au carré ses éléments et en les multipliant par les éléments respectifs du tableau « x ». Les lignes de code suivantes décrivent ce qui a été expliqué jusqu'à présent.

#x and y arrays
x = np.linspace(0, 10, 100)
y = x*np.random.randn(100)**2

Trouver les pics de la fonction

Une fois déterminé le x et y tableaux, l'étape suivante consiste à identifier les positions des pics et leur valeur.

Pour ce faire, nous exploitons la fonction .find_peaks() , qui appartient au package .signal de la bibliothèque Scipy (une documentation supplémentaire peut être trouvée ici :https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.find_peaks.html). La seule entrée obligatoire pour cette fonction est le signal qui nous intéresse. Cependant, la fonction comporte de nombreuses options intéressantes qui peuvent nous aider à affiner notre tâche de traitement :

  • Hauteur  :il peut s'agir d'un nombre ou d'un tableau et il est utilisé pour spécifier la hauteur minimale qu'un pic doit avoir pour être identifié ;
  • Seuil :est la distance verticale requise entre un pic et son voisin, très utile dans le cas de fonctions bruitées où l'on veut éviter de sélectionner des pics à partir du bruit ;
  • Distance :est la distance horizontale minimale requise entre les sommets voisins ; cela peut être vraiment utile dans les cas où nous avons une certaine connaissance de la périodicité des pics.

Il y a alors bien d'autres options que l'on peut exploiter, par exemple pour spécifier la largeur minimale des pics etc…

La sortie du .find_peaks() La fonction est un tableau qui contient les index de chaque pic identifié. Il peut également renvoyer d'autres informations, dans le cas où nous avions précédemment spécifié des options telles que "hauteur" ou "seuil" au moment de l'appel.

Dans ce cas, la fonction renvoie un tableau de tableaux, le premier sous-tableau contient toujours les index des pics, les autres peuvent présenter les hauteurs des pics trouvés ou leurs seuils gauche et droit (et toutes les autres informations qui étaient précédemment spécifiées comme saisie facultative lors de l'appel de la fonction), sous forme de dictionnaire.

Après cette brève explication, voyons dans les lignes de code suivantes comment appeler la fonction et ainsi trouver les pics.

#Find peaks
peaks = find_peaks(y, height = 1, threshold = 1, distance = 1)
height = peaks[1]['peak_heights'] #list containing the height of the peaks
peak_pos = x[peaks[0]]   #list containing the positions of the peaks  

Comme on peut le voir dans les lignes de code ci-dessus, nous avons donné en entrée le tableau "y" puis nous avons spécifié d'autres paramètres optionnels (je les ai tous mis égaux à 1 car je ne savais pas quel était l'aspect de ma fonction; j'ai seulement savait que tous les nombres étaient positifs, puisque la fonction est au carré).

Puisque nous avons spécifié les paramètres optionnels "height", la sortie de la fonction ("peaks"), consiste en un tableau, le premier élément est un sous-tableau contenant les positions des pics, le deuxième sous-tableau est un dictionnaire qui contient toutes les informations spécifié dans les paramètres d'entrée optionnels donnés au moment de l'appel.

Nous pouvons exploiter cette fonctionnalité puissante pour extraire les hauteurs des pics ; la seule chose à faire est de définir un tableau, "height", qui sera égal au tableau contenu dans la clé du dictionnaire "peak_heights".

On peut alors créer un tableau contenant les positions des pics le long du tableau x en exploitant le premier sous-tableau du tableau « pics », soit peaks[0] et utilisez-le comme index de notre tableau "x". De cette manière, nous pouvons stocker dans un tableau appelé "peak_pos", uniquement les positions des points, le long du tableau "x", correspondant aux pics. Les tableaux "height" et "peak_pos" sont ceux qui seront utilisés pour tracer les pics sur la fonction initiale.

Qu'en est-il des minima ?

Jusqu'à présent, nous avons vu comment identifier la position et calculer la hauteur de nos pics. Pour certaines applications, nous pourrions être intéressés à analyser également les minima (ou le creux) de nos signaux. Les lignes suivantes illustreront une stratégie simple pour accomplir cette tâche.

La fonction .find_peaks() est seulement capable de repérer et d'analyser les pics d'une fonction ; pour résoudre ce problème, nous devons "tromper" la fonction en modifiant le signal d'entrée.

Une façon pratique de le faire est de refléter notre signal ; si on réfléchit une fonction par rapport à l'axe horizontal, les points qui correspondaient à ses minima seront alors convertis en ses nouveaux maxima ou pics.

Après cela, nous pouvons simplement répéter la procédure expliquée dans le paragraphe précédent. Pour refléter la fonction, nous pouvons simplement multiplier le tableau "y" par -1 et stocker sa valeur dans un nouveau tableau appelé "y2". Cette fois, lors de l'appel de la fonction .find_peaks() , nous ne spécifierons pas l'option "hauteur", car la hauteur de ces pics peut correspondre à des nombres négatifs (en principe, nous ne savons pas à quoi ressembleront les minima en miroir). On peut laisser tous les autres paramètres optionnels, si on veut affiner l'analyse (je les ai laissé tous égaux à 1).

#Find minima
y2 = y*-1
minima = find_peaks(y2, threshold = 1, distance = 1)
min_pos = x[minima[0]]   #list containing the positions of the minima
min_height = y2[minima[0]]   #list containing the height of the minima

Comme vous pouvez le voir, cette fois pour obtenir les hauteurs des minima, nous avons juste indexé le tableau "y2" avec le tableau contenant les index des pics (les vrais minima en miroir de la fonction originale "y") et les avons stockés dans le tableau "min_height". À ce stade, nous avons également les informations sur les minima de la fonction d'origine, nous devons juste nous rappeler de les refléter à nouveau au moment de tracer leur valeur.

Tracé des pics

Pour voir le résultat de notre analyse des pics, nous traçons maintenant la fonction d'origine, les pics et les minima. La fonction est tracée comme une ligne continue tandis que les pics et les minima comme des points uniques (d'où un nuage de points). Les maxima/pics seront tracés en rouge, en utilisant un losange comme marqueur ; d'autre part, les minima sont tracés en jaune, avec un symbole de croix. Nous terminons notre tracé en ajoutant la légende et la grille. Les lignes de code suivantes décrivent la procédure qui vient d'être expliquée.

#Plotting the function + peaks and minima
fig = plt.figure()
ax = fig.subplots()
ax.plot(x,y)
ax.scatter(peak_pos, height, color = 'r', s = 10, marker = 'D', label = 'maxima')
ax.scatter(min_pos, min_height*-1, color = 'gold', s = 10, marker = 'X', label = 'minima')
ax.legend()
ax.grid()
plt.show()

Le résultat final est plutôt affiché dans la figure 1.

Illustration 1 : Fonction initiale (courbe bleue) avec les pics identifiés (les maxima, losanges rouges) et les minima (croix jaunes).

Comme on peut le voir sur la figure 1, nous avons identifié avec succès la plupart des maxima/pics et des minima de la fonction initiale. Certains pics mineurs n'ont pas été pris en compte dans l'analyse; si nous étions également intéressés par ceux-là, nous devrions ajuster les paramètres optionnels comme le seuil et la hauteur et répéter plusieurs fois la même procédure.