Come levigare una curva nel modo giusto?


200

Supponiamo che abbiamo un set di dati che potrebbe essere fornito approssimativamente da

import numpy as np
x = np.linspace(0,2*np.pi,100)
y = np.sin(x) + np.random.random(100) * 0.2

Pertanto abbiamo una variazione del 20% del set di dati. La mia prima idea è stata quella di utilizzare la funzione UnivariateSpline di scipy, ma il problema è che questo non considera bene il piccolo rumore. Se si considerano le frequenze, lo sfondo è molto più piccolo del segnale, quindi una spline solo del cutoff potrebbe essere un'idea, ma ciò implicherebbe una trasformazione di Fourier avanti e indietro, che potrebbe comportare un comportamento scorretto. Un altro modo sarebbe una media mobile, ma ciò richiederebbe anche la giusta scelta del ritardo.

Suggerimenti / libri o link su come affrontare questo problema?

esempio


1
Il tuo segnale sarà sempre un'onda sinusoidale o lo stavi usando solo per un esempio?
Mark Ransom,

no, avrò segnali diversi, anche in questo semplice esempio è ovvio che i miei metodi non sono sufficienti
varantir

il filtro kalman è ottimale per questo caso. E il pacchetto pykalman python è di buona qualità.
Toine

Forse lo espanderò a una risposta completa quando avrò un po 'più di tempo, ma l'unico potente metodo di regressione che non è stato ancora menzionato è la regressione GP (Gaussian Process).
Ori5678,

Risposte:


262

Preferisco un filtro Savitzky-Golay . Utilizza i minimi quadrati per regredire una piccola finestra dei dati su un polinomio, quindi utilizza il polinomio per stimare il punto al centro della finestra. Alla fine la finestra viene spostata in avanti di un punto dati e il processo si ripete. Questo continua fino a quando ogni punto è stato regolato in modo ottimale rispetto ai suoi vicini. Funziona alla grande anche con campioni rumorosi da fonti non periodiche e non lineari.

Ecco un esempio completo del libro di cucina . Vedi il mio codice qui sotto per avere un'idea di quanto sia facile da usare. Nota: ho lasciato fuori il codice per la definizione della savitzky_golay()funzione perché puoi letteralmente copiarlo / incollarlo dall'esempio del libro di cucina che ho collegato sopra.

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

levigare in modo ottimale una sinusoide rumorosa

AGGIORNAMENTO: È venuto alla mia attenzione che l'esempio del libro di cucina a cui ho collegato è stato rimosso. Fortunatamente, il filtro Savitzky-Golay è stato incorporato nella libreria SciPy , come sottolineato da @dodohjk . Per adattare il codice sopra usando il sorgente SciPy, digitare:

from scipy.signal import savgol_filter
yhat = savgol_filter(y, 51, 3) # window size 51, polynomial order 3

Ho ricevuto l'errore Traceback (ultima chiamata più recente): file "hp.py", linea 79, in <modulo> ysm2 = savitzky_golay (y_data, 51,3) File "hp.py", linea 42, in savitzky_golay firstvals = y [0] - np.abs (y [1: half_window + 1] [:: - 1] - y [0])
March Ho


14
Grazie per aver introdotto il filtro Savitzky-Golay! Quindi fondamentalmente questo è proprio come un normale filtro "Media mobile", ma invece di calcolare semplicemente la media, viene adattato un adattamento polinomiale (di solito secondo o quarto ordine) per ogni punto, e viene scelto solo il punto "medio". Poiché le informazioni del 2o (o 4o) ordine riguardano ogni punto, l'elusione introdotta nell'approccio della "media mobile" ai massimi o minimi locali viene elusa. Davvero elegante.
np8,

2
Voglio solo ringraziarti per questo, sono impazzito cercando di capire le scomposizioni wavelet per ottenere dati più fluidi, e questo è molto più bello.
Eldar M.

5
Se i dati x non è distanziato regolarmente si potrebbe desiderare di applicare il filtro di The x pure: savgol_filter((x, y), ...).
Tim Kuipers,

127

Un modo rapido e sporco per lisciare i dati che uso, basato su un riquadro a media mobile (per convoluzione):

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)

inserisci qui la descrizione dell'immagine


9
Questo ha alcuni vantaggi: (1) funziona per qualsiasi funzione, non solo periodica, e (2) nessuna dipendenza o grande funzione da copiare e incollare. Puoi farlo subito con Numpy puro. Inoltre, non è troppo sporco --- è un caso più semplice di alcuni degli altri metodi sopra descritti (come LOWESS ma il kernel è un intervallo nitido e come Savitzky-Golay ma il grado polinomiale è zero).
Jim Pivarski,

2
l'unico problema con la media mobile è che è in ritardo rispetto ai dati. Puoi vederlo apparentemente alla fine dove ci sono più punti nella parte superiore e meno nella parte inferiore, ma la curva verde è attualmente sotto la media perché la funzione della finestra deve spostarsi in avanti per tenerne conto.
Nurettin,

E questo non funziona su nd array, solo 1d. scipy.ndimage.filters.convolve1d()consente di specificare un asse di un nd-array per eseguire il filtro. Ma penso che entrambi soffrano di alcuni problemi nei valori mascherati.
Jason,

1
@nurettin Penso che ciò che stai descrivendo siano effetti collaterali. In generale, fintanto che il kernel di convoluzione è in grado di coprire la sua estensione all'interno del segnale, non "resta indietro" come dici tu. Alla fine, tuttavia, non ci sono valori oltre 6 da includere nella media, quindi viene utilizzata solo la parte "sinistra" del kernel. Gli effetti bordo sono presenti in ogni kernel di smoothing e devono essere gestiti separatamente.
Jon,

4
@nurettin No, stavo cercando di chiarire per gli altri leggendo questo che il tuo commento "l'unico problema con la media mobile è che è in ritardo rispetto ai dati" è fuorviante. Qualsiasi metodo di filtro per finestre soffre di questo problema, non solo della media mobile. Anche Savitzky-Golay soffre di questo problema. Quindi la tua affermazione "Quello che sto descrivendo è ciò che savitzky_golay risolve per stima" è semplicemente sbagliato. Entrambi i metodi di levigatura richiedono un modo per gestire i bordi che è indipendente dal metodo di levigatura stesso.
Jon,

79

Se sei interessato a una versione "liscia" di un segnale periodico (come il tuo esempio), una FFT è la strada giusta da percorrere. Prendi la trasformata di Fourier e sottrai le frequenze che contribuiscono in basso:

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)

inserisci qui la descrizione dell'immagine

Anche se il tuo segnale non è completamente periodico, questo farà un ottimo lavoro per sottrarre il rumore bianco. Esistono molti tipi di filtri da utilizzare (passa-alto, passa-basso, ecc ...), quello appropriato dipende da ciò che stai cercando.


Quale trama è per quale variabile? Sto cercando di appianare le coordinate per la pallina da tennis in una manifestazione, vale a dire.
elimina

44

Applicare una media mobile ai tuoi dati eliminerebbe il rumore, vedi questa risposta per come farlo.

Se desideri utilizzare LOWESS per adattare i tuoi dati (è simile a una media mobile ma più sofisticato), puoi farlo utilizzando la libreria statsmodels :

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

Infine, se conosci la forma funzionale del tuo segnale, potresti adattare una curva ai tuoi dati, che sarebbe probabilmente la cosa migliore da fare.


Se solo lo avesse loessimplementato.
scrutari,

18

Un'altra opzione è usare KernelReg in statsmodels :

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

7

Controllalo! Esiste una chiara definizione di livellamento di un segnale 1D.

http://scipy-cookbook.readthedocs.io/items/SignalSmooth.html

scorciatoia:

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
Un link a una soluzione è il benvenuto, ma assicurati che la tua risposta sia utile senza di essa: aggiungi un contesto attorno al link in modo che i tuoi colleghi utenti abbiano qualche idea di cosa sia e perché sia ​​lì, quindi cita la parte più pertinente della pagina che ' re collegamento a nel caso in cui la pagina di destinazione non sia disponibile. Le risposte che sono poco più di un collegamento possono essere eliminate.
Shree,

-4

Se si sta disegnando un grafico delle serie temporali e se si è utilizzato mtplotlib per disegnare grafici, utilizzare il metodo mediano per uniformare il grafico

smotDeriv = timeseries.rolling(window=20, min_periods=5, center=True).median()

dove si timeseriestrova il tuo set di dati che puoi modificare windowsizeper renderlo più fluido.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.