Supposons que nous ayons un jeu de données qui pourrait être donné approximativement par
import numpy as np
x = np.linspace(0,2*np.pi,100)
y = np.sin(x) + np.random.random(100) * 0.2
Nous avons donc une variation de 20% de l'ensemble de données. Ma première idée était d'utiliser la fonction UnivariateSpline de scipy, mais le problème est que cela ne tient pas compte du faible bruit. Si vous considérez les fréquences, l’arrière-plan est beaucoup plus petit que le signal, donc une spline uniquement de la valeur de coupure pourrait être une idée, mais cela impliquerait une transformation de Fourier en arrière, ce qui pourrait entraîner un mauvais comportement. moyenne mobile, mais cela nécessiterait également le bon choix du délai.
Des astuces/livres ou des liens pour résoudre ce problème?
Je préfère un filtre de Savitzky-Golay . Il utilise les moindres carrés pour régresser une petite fenêtre de vos données sur un polynôme, puis utilise le polynôme pour estimer le point situé au centre de la fenêtre. Enfin, la fenêtre est décalée d’un point de données et le processus se répète. Cela continue jusqu'à ce que chaque point ait été ajusté de manière optimale par rapport à ses voisins. Cela fonctionne très bien même avec des échantillons bruyants provenant de sources non périodiques et non linéaires.
Voici un exemple complet de livre de recettes . Voir mon code ci-dessous pour avoir une idée de la facilité d'utilisation. Remarque: j'ai omis le code permettant de définir la fonction savitzky_golay()
car vous pouvez littéralement le copier/coller à partir de l'exemple de livre de recettes que j'ai lié ci-dessus.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0,2*np.pi,100)
y = np.sin(x) + np.random.random(100) * 0.2
yhat = savitzky_golay(y, 51, 3) # window size 51, polynomial order 3
plt.plot(x,y)
plt.plot(x,yhat, color='red')
plt.show()
UPDATE: Il a été porté à mon attention que l'exemple de livre de cuisine auquel j'ai lié avait été supprimé. Heureusement, le filtre Savitzky-Golay a été incorporé dans la bibliothèque SciPy , comme indiqué par @dodohjk . Pour adapter le code ci-dessus à l'aide du code source SciPy, tapez:
from scipy.signal import savgol_filter
yhat = savgol_filter(y, 51, 3) # window size 51, polynomial order 3
Un moyen rapide et sale pour lisser les données que j'utilise, basé sur une boîte moyenne mobile (par convolution):
x = np.linspace(0,2*np.pi,100)
y = np.sin(x) + np.random.random(100) * 0.8
def smooth(y, box_pts):
box = np.ones(box_pts)/box_pts
y_smooth = np.convolve(y, box, mode='same')
return y_smooth
plot(x, y,'o')
plot(x, smooth(y,3), 'r-', lw=2)
plot(x, smooth(y,19), 'g-', lw=2)
Si vous êtes intéressé par une version "lisse" d'un signal périodique (comme dans votre exemple), alors une FFT est la bonne solution. Prenez la transformée de Fourier et soustrayez les fréquences à faible contribution:
import numpy as np
import scipy.fftpack
N = 100
x = np.linspace(0,2*np.pi,N)
y = np.sin(x) + np.random.random(N) * 0.2
w = scipy.fftpack.rfft(y)
f = scipy.fftpack.rfftfreq(N, x[1]-x[0])
spectrum = w**2
cutoff_idx = spectrum < (spectrum.max()/5)
w2 = w.copy()
w2[cutoff_idx] = 0
y2 = scipy.fftpack.irfft(w2)
Même si votre signal n'est pas complètement périodique, vous réussirez à soustraire le bruit blanc. Il existe de nombreux types de filtres à utiliser (passe-haut, passe-bas, etc.), le filtre approprié dépend de ce que vous recherchez.
Ajuster une moyenne mobile à vos données lisserait le bruit, voir ceci cette réponse pour savoir comment faire.
Si vous souhaitez utiliser LOWESS pour adapter vos données (similaire à une moyenne mobile mais plus sophistiquée), vous pouvez le faire en utilisant le statsmodels library:
import numpy as np
import pylab as plt
import statsmodels.api as sm
x = np.linspace(0,2*np.pi,100)
y = np.sin(x) + np.random.random(100) * 0.2
lowess = sm.nonparametric.lowess(y, x, frac=0.1)
plt.plot(x, y, '+')
plt.plot(lowess[:, 0], lowess[:, 1])
plt.show()
Enfin, si vous connaissez la forme fonctionnelle de votre signal, vous pourriez adapter une courbe à vos données, ce qui serait probablement la meilleure chose à faire.
Une autre option consiste à utiliser KernelReg in statsmodel :
from statsmodels.nonparametric.kernel_regression import KernelReg
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0,2*np.pi,100)
y = np.sin(x) + np.random.random(100) * 0.2
# The third parameter specifies the type of the variable x;
# 'c' stands for continuous
kr = KernelReg(y,x,'c')
plt.plot(x, y, '+')
y_pred, y_std = kr.fit(x)
plt.plot(x, y_pred)
plt.show()
Regarde ça! Il existe une définition claire du lissage d'un signal 1D.
http://scipy-cookbook.readthedocs.io/items/SignalSmooth.html
Raccourci:
import numpy
def smooth(x,window_len=11,window='hanning'):
"""smooth the data using a window with requested size.
This method is based on the convolution of a scaled window with the signal.
The signal is prepared by introducing reflected copies of the signal
(with the window size) in both ends so that transient parts are minimized
in the begining and end part of the output signal.
input:
x: the input signal
window_len: the dimension of the smoothing window; should be an odd integer
window: the type of window from 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'
flat window will produce a moving average smoothing.
output:
the smoothed signal
example:
t=linspace(-2,2,0.1)
x=sin(t)+randn(len(t))*0.1
y=smooth(x)
see also:
numpy.hanning, numpy.hamming, numpy.bartlett, numpy.blackman, numpy.convolve
scipy.signal.lfilter
TODO: the window parameter could be the window itself if an array instead of a string
NOTE: length(output) != length(input), to correct this: return y[(window_len/2-1):-(window_len/2)] instead of just y.
"""
if x.ndim != 1:
raise ValueError, "smooth only accepts 1 dimension arrays."
if x.size < window_len:
raise ValueError, "Input vector needs to be bigger than window size."
if window_len<3:
return x
if not window in ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']:
raise ValueError, "Window is on of 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'"
s=numpy.r_[x[window_len-1:0:-1],x,x[-2:-window_len-1:-1]]
#print(len(s))
if window == 'flat': #moving average
w=numpy.ones(window_len,'d')
else:
w=eval('numpy.'+window+'(window_len)')
y=numpy.convolve(w/w.sum(),s,mode='valid')
return y
from numpy import *
from pylab import *
def smooth_demo():
t=linspace(-4,4,100)
x=sin(t)
xn=x+randn(len(t))*0.1
y=smooth(x)
ws=31
subplot(211)
plot(ones(ws))
windows=['flat', 'hanning', 'hamming', 'bartlett', 'blackman']
hold(True)
for w in windows[1:]:
eval('plot('+w+'(ws) )')
axis([0,30,0,1.1])
legend(windows)
title("The smoothing windows")
subplot(212)
plot(x)
plot(xn)
for w in windows:
plot(smooth(xn,10,w))
l=['original signal', 'signal with noise']
l.extend(windows)
legend(l)
title("Smoothing a noisy signal")
show()
if __name__=='__main__':
smooth_demo()