Bagaimana cara memuluskan kurva dengan cara yang benar?

200

Mari kita asumsikan kita memiliki dataset yang mungkin diberikan kira-kira oleh

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

Oleh karena itu kami memiliki variasi 20% dari dataset. Gagasan pertama saya adalah menggunakan fungsi UnivariateSpline dari Scipy, tetapi masalahnya adalah ini tidak mempertimbangkan kebisingan kecil dengan cara yang baik. Jika Anda mempertimbangkan frekuensinya, latar belakangnya jauh lebih kecil daripada sinyal, sehingga hanya spline dari cutoff yang mungkin merupakan ide, tetapi itu akan melibatkan transformasi bolak-balik fourier, yang mungkin mengakibatkan perilaku buruk. Cara lain akan menjadi rata-rata bergerak, tetapi ini juga akan membutuhkan pilihan penundaan yang tepat.

Adakah petunjuk / buku atau tautan bagaimana mengatasi masalah ini?

contoh

varantir
sumber
1
Apakah sinyal Anda selalu berupa gelombang sinus, atau apakah Anda hanya menggunakannya sebagai contoh?
Mark Ransom
tidak, saya akan memiliki sinyal yang berbeda, bahkan dalam contoh mudah ini jelas bahwa metode saya tidak cukup
varantir
penyaringan kalman optimal untuk kasus ini. Dan paket pykalman python berkualitas baik.
toine
Mungkin saya akan memperluas ke jawaban penuh ketika saya punya sedikit waktu, tetapi satu metode regresi kuat yang belum disebutkan adalah regresi GP (Proses Gaussian).
Ori5678

Jawaban:

262

Saya lebih suka filter Savitzky-Golay . Menggunakan kuadrat terkecil untuk mundur jendela kecil data Anda ke polinomial, lalu menggunakan polinomial untuk memperkirakan titik di tengah jendela. Akhirnya jendela digeser maju oleh satu titik data dan proses berulang. Ini berlanjut sampai setiap titik telah disesuaikan secara optimal relatif terhadap tetangganya. Ini bekerja sangat baik bahkan dengan sampel berisik dari sumber non-periodik dan non-linear.

Berikut ini adalah contoh buku masak yang menyeluruh . Lihat kode saya di bawah ini untuk mendapatkan ide betapa mudahnya menggunakannya. Catatan: Saya meninggalkan kode untuk mendefinisikan savitzky_golay()fungsi karena Anda benar-benar dapat menyalin / menempelnya dari contoh buku masak yang saya tautkan di atas.

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

secara optimal menghaluskan sinusoid yang bising

PEMBARUAN: Telah menjadi perhatian saya bahwa contoh buku resep yang saya tautkan telah dihapus. Untungnya, filter Savitzky-Golay telah dimasukkan ke dalam perpustakaan SciPy , seperti yang ditunjukkan oleh @dodohjk . Untuk mengadaptasi kode di atas dengan menggunakan sumber SciPy, ketik:

from scipy.signal import savgol_filter
yhat = savgol_filter(y, 51, 3) # window size 51, polynomial order 3
David Wurtz
sumber
Saya mendapat kesalahan Traceback (panggilan terakhir terakhir): File "hp.py", line 79, dalam <module> ysm2 = savitzky_golay (y_data, 51,3) File "hp.py", baris 42, di savitzky_golay firstvals = y [0] - np.abs (y [1: half_window + 1] [:: - 1] - y [0])
March
14
Terima kasih telah memperkenalkan filter Savitzky-Golay! Jadi pada dasarnya ini hanya seperti filter "Moving average" biasa, tetapi alih-alih hanya menghitung rata-rata, polinomial (biasanya urutan ke-2 atau ke-4) dibuat untuk setiap titik, dan hanya titik "tengah" yang dipilih. Karena informasi urutan ke-2 (atau ke-4) diperhatikan di setiap titik, bias yang diperkenalkan dalam pendekatan "moving average" di maxima atau minima lokal, diatasi. Sangat elegan.
np8
2
Hanya ingin mengucapkan terima kasih untuk ini, saya sudah gila mencoba mencari tahu dekomposisi wavelet untuk mendapatkan data yang lebih lancar, dan ini jauh lebih baik.
Eldar M.
5
Jika x data tidak teratur spasi Anda mungkin ingin menerapkan filter ke x juga: savgol_filter((x, y), ...).
Tim Kuipers
127

Cara cepat dan kotor untuk memuluskan data yang saya gunakan, berdasarkan kotak rata-rata bergerak (berdasarkan konvolusi):

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)

masukkan deskripsi gambar di sini

scrx2
sumber
9
Ini memiliki beberapa keuntungan yang bagus: (1) berfungsi untuk fungsi apa pun, tidak hanya periodik, dan (2) tidak ada ketergantungan atau fungsi besar untuk menyalin-menempel. Anda dapat melakukannya langsung dengan Numpy murni. Juga, itu tidak terlalu kotor --- ini adalah kasus paling sederhana dari beberapa metode lain yang dijelaskan di atas (seperti LOWESS tetapi kernel adalah interval yang tajam dan seperti Savitzky-Golay tetapi derajat polinom adalah nol).
Jim Pivarski
2
satu-satunya masalah dengan moving average adalah bahwa ia tertinggal di belakang data. Anda dapat melihat ini paling jelas di ujung di mana ada lebih banyak titik di bagian atas dan lebih sedikit di bagian bawah, tetapi kurva hijau saat ini di bawah rata-rata karena fungsi jendela harus bergerak maju untuk memperhitungkannya.
nurettin
Dan ini tidak bekerja pada array nd, hanya 1d. scipy.ndimage.filters.convolve1d()memungkinkan Anda menentukan sumbu array-nd untuk melakukan penyaringan. Tapi saya pikir keduanya menderita beberapa masalah dalam nilai-nilai yang di-mask.
Jason
1
@nurettin Saya pikir yang Anda gambarkan adalah efek tepi. Secara umum, selama kernel konvolusi mampu menutupi jangkauannya dalam sinyal, konvolusi tidak "tertinggal" seperti yang Anda katakan. Namun, pada akhirnya, tidak ada nilai di luar 6 yang termasuk dalam rata-rata, jadi hanya bagian "kiri" dari kernel yang digunakan. Efek tepi ada di setiap kernel smoothing dan harus ditangani secara terpisah.
Jon
4
@nurettin Tidak, saya mencoba menjelaskan kepada orang lain yang membaca ini bahwa komentar Anda "satu-satunya masalah dengan moving average adalah bahwa ia tertinggal di belakang data" menyesatkan. Metode filter jendela apa pun menderita masalah ini, bukan hanya rata-rata bergerak. Savitzky-golay juga menderita masalah ini. Jadi pernyataan Anda "Apa yang saya jelaskan adalah apa yang savitzky_golay selesaikan dengan estimasi" adalah salah. Metode penghalusan mana pun membutuhkan cara untuk menangani bagian tepi yang tidak tergantung dari metode penghalusan itu sendiri.
Jon
79

Jika Anda tertarik pada versi "halus" dari sinyal yang periodik (seperti contoh Anda), maka FFT adalah cara yang tepat. Ambil transformasi fourier dan kurangi frekuensi dengan kontribusi rendah:

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)

masukkan deskripsi gambar di sini

Bahkan jika sinyal Anda tidak sepenuhnya periodik, ini akan sangat membantu mengurangi white noise. Ada banyak jenis filter untuk digunakan (high-pass, low-pass, dll ...), yang sesuai tergantung pada apa yang Anda cari.

Doyan
sumber
Plot mana untuk variabel yang mana? Saya mencoba memuluskan koordinat bola tenis dalam sebuah reli, yaitu. keluarkan semua pantulan yang terlihat seperti parabola kecil di plot saya
mLstudent33
44

Memasukkan rata-rata bergerak ke data Anda akan memuluskan kebisingan, lihat jawaban ini untuk cara melakukannya.

Jika Anda ingin menggunakan LOWESS agar sesuai dengan data Anda (ini mirip dengan moving average tetapi lebih canggih), Anda bisa melakukannya menggunakan perpustakaan 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()

Akhirnya, jika Anda tahu bentuk fungsional sinyal Anda, Anda bisa memasukkan kurva ke data Anda, yang mungkin merupakan hal terbaik untuk dilakukan.

markmuetz
sumber
Kalau saja sudah loessdilaksanakan.
scrutari
18

Pilihan lain adalah menggunakan KernelReg di 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()
Zichen Wang
sumber
7

Lihat ini! Ada definisi yang jelas tentang penghalusan sinyal 1D.

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

Jalan pintas:

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()
IPhysResearch
sumber
3
Tautan ke suatu solusi disambut baik, tetapi harap pastikan jawaban Anda bermanfaat tanpanya: tambahkan konteks di sekitar tautan sehingga sesama pengguna Anda akan mengetahui apa itu dan mengapa ada, lalu kutip bagian yang paling relevan dari halaman yang Anda tuju. menautkan kembali jika seandainya halaman target tidak tersedia. Jawaban yang sedikit lebih dari sebuah tautan dapat dihapus.
Shree
-4

Jika Anda merencanakan grafik deret waktu dan jika Anda telah menggunakan mtplotlib untuk menggambar grafik maka gunakan metode median untuk menghaluskan grafik

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

di mana timeseriesset data Anda berlalu, Anda dapat mengubah windowsizeagar lebih halus.

Pavan Purohit
sumber