web-dev-qa-db-fra.com

Comment lisser une courbe de la bonne manière?

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?

example

131
varantir

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()

optimally smoothing a noisy sinusoid

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
170
David Wurtz

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)

 enter image description here

87
scrx2

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)

enter image description here

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.

69
Hooked

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.

37
markmuetz

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()
13
Zichen Wang

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()
3
Herb