Bir eğri doğru şekilde nasıl düzeltilir?


201

Diyelim ki yaklaşık olarak verilebilecek bir veri kümemiz var

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

Bu nedenle, veri kümesinin% 20'lik bir varyasyonuna sahibiz. İlk fikrim scipy'nin UnivariateSpline işlevini kullanmaktı, ama sorun şu ki, bu küçük gürültüyü iyi bir şekilde düşünmüyor. Frekansları göz önünde bulundurursanız, arka plan sinyalden çok daha küçüktür, bu nedenle sadece kesmenin bir spline bir fikir olabilir, ancak bu, kötü davranışa neden olabilecek ileri geri fourier dönüşümü içerebilir. Başka bir yol hareketli bir ortalama olabilir, ancak bu da gecikmenin doğru seçimini gerektirir.

Bu sorunun üstesinden gelmek için ipuçları / kitaplar veya bağlantılar var mı?

misal


1
Sinyaliniz her zaman bir sinüs dalgası mı olacak yoksa bunu sadece bir örnek için mi kullanıyordunuz?
Mark Ransom

hayır, farklı sinyaller alacağım, bu kolay örnekte bile yöntemlerimin yeterli olmadığı açık
varantir

Kalman filtreleme bu durum için idealdir. Ve pykalman python paketi iyi kalitede.
toine

Belki biraz daha fazla zamanım olduğunda tam cevabı vereceğim, ama henüz bahsedilmeyen güçlü bir regresyon yöntemi GP (Gauss Süreci) regresyonudur.
Ori5678

Yanıtlar:


264

Tercih ederim Savitzky-Golay filtresini . Verilerinizin küçük bir penceresini bir polinom üzerine gerdirmek için en az kareler kullanır, ardından pencerenin ortasındaki noktayı tahmin etmek için polinomu kullanır. Son olarak pencere bir veri noktası ileriye doğru kaydırılır ve işlem tekrarlanır. Bu, her nokta komşularına göre en uygun şekilde ayarlanana kadar devam eder. Periyodik olmayan ve doğrusal olmayan kaynaklardan gelen gürültülü örneklerle bile harika çalışır.

İşte kapsamlı bir yemek kitabı örneği . Kullanmanın ne kadar kolay olduğuna dair bir fikir edinmek için aşağıdaki koduma bakın. Not: savitzky_golay()Fonksiyonu tanımlamak için kodu dışarıda bıraktım, çünkü yukarıda bağladığım yemek kitabı örneğinden tam anlamıyla kopyalayabilir / yapıştırabilirsiniz.

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

gürültülü bir sinüzoidi en uygun şekilde yumuşatmak

GÜNCELLEME: Bağlandığım yemek kitabı örneğinin kaldırıldığı dikkatimi çekti. Neyse ki, Savitzky-Golay filtresi, @dodohjk tarafından işaret edildiği gibi SciPy kütüphanesine dahil edilmiştir . SciPy kaynağını kullanarak yukarıdaki kodu uyarlamak için şunu yazın:

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

Hata izleme aldım (en son çağrı son): Dosya "hp.py", satır 79, <module> ysm2 = savitzky_golay (y_data, 51,3) Dosya "hp.py", satır 42, savitzky_golay firstvals = y [0] - np.abs (y [1: half_window + 1] [:: - 1] - y [0])
Mart Ho


14
Savitzky-Golay filtresini tanıttığınız için teşekkür ederiz! Temelde bu normal bir "Hareketli ortalama" filtresi gibidir, ancak sadece ortalamayı hesaplamak yerine, her nokta için bir polinom (genellikle 2. veya 4. sıra) uyumu yapılır ve sadece "orta" nokta seçilir. Her noktada 2. (veya 4.) sipariş bilgisi söz konusu olduğundan, yerel maksimum veya minimumda "hareketli ortalama" yaklaşımında ortaya konan önyargı ortadan kaldırılmıştır. Gerçekten zarif.
np8

2
Bunun için teşekkür etmek istiyorum, düzgünleştirilmiş veri elde etmek için dalgacık ayrıştırmalarını anlamaya çalışıyorum ve bu çok daha güzel.
Eldar M.

5
X verileri düzenli aralıklı değilse x'ler yanı filtre uygulamak isteyebilirsiniz: savgol_filter((x, y), ...).
Tim Kuipers

128

Hareketli ortalama kutusuna (kıvrım yoluyla) dayalı olarak kullandığım verileri düzeltmenin hızlı ve kirli bir yolu:

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)

resim açıklamasını buraya girin


9
Bunun birkaç güzel avantajı vardır: (1) sadece periyodik olarak değil, herhangi bir işlev için çalışır ve (2) kopyala yapıştırmaya bağımlılık veya büyük işlevler yoktur. Saf Numpy ile hemen yapabilirsiniz. Ayrıca, çok kirli değil - bu yukarıda açıklanan diğer yöntemlerin en basit örneğidir (DÜŞÜK gibi ama çekirdek keskin bir aralıktır ve Savitzky-Golay gibi ama polinom derecesi sıfırdır).
Jim Pivarski

2
hareketli ortalama ile ilgili tek sorun, verilerin gerisinde kalmasıdır. Bunu en üstte daha fazla ve altta daha az noktanın olduğu sonunda görebilirsiniz, ancak yeşil eğri şu anda ortalamanın altındadır, çünkü pencere işlevi bunları dikkate almak için ilerlemelidir.
nurettin

1
Ve bu nd dizisinde çalışmıyor, sadece 1d. scipy.ndimage.filters.convolve1d()filtrelemeyi yapmak için nd dizisinin bir eksenini belirtmenize olanak tanır. Ama her ikisinin de maskelenmiş değerlerde bazı sorunlardan muzdarip olduğunu düşünüyorum.
Jason

1
@nurettin Sanırım tanımladığınız şey kenar efektleridir. Genel olarak, evrişim çekirdeği sinyal içindeki boyutunu kapsayabildiği sürece, dediğin gibi "geride kalmaz". Bununla birlikte, sonuçta, ortalamaya dahil edilecek 6'nın ötesinde hiçbir değer yoktur, bu nedenle çekirdeğin sadece "sol" kısmı kullanılmaktadır. Kenar efektleri her yumuşatma çekirdeğinde bulunur ve ayrı ayrı ele alınmalıdır.
Jon

4
@nurettin Hayır, başkaları için "hareketli ortalama ile ilgili tek sorun, verilerin gerisinde kalması" yorumunuzun yanıltıcı olduğunu açıklamaya çalışıyordum. Herhangi bir pencere filtresi yöntemi, sadece hareketli ortalama değil, bu sorunu yaşar. Savitzky-golay de bu sorunu yaşıyor. Yani "savitzky_golay'ın tahminle çözdüğü şey" ifadeniz yanlış. Her iki düzeltme yöntemi de, düzeltme yönteminin kendisinden bağımsız olan kenarları işlemek için bir yol gerektirir.
Jon

79

Bir sinyalin periyodik (örneğin örneğiniz gibi) “pürüzsüz” bir versiyonuyla ilgileniyorsanız, FFT doğru yoludur. Fourier dönüşümünü alın ve düşük katkıda bulunan frekansları çıkarın:

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)

resim açıklamasını buraya girin

Sinyaliniz tamamen periyodik olmasa bile, bu beyaz gürültüyü çıkarmak için harika bir iş çıkarır. Kullanılacak birçok filtre türü (high-pass, low-pass, vb ...), uygun olanı aradığınıza bağlıdır.


Hangi değişken için hangi grafik? Mitingde tenis topu koordinatlarını düzeltmeye çalışıyorum. benim komplo üzerinde küçük parabol gibi görünen tüm sıçramalar çıkar
mLstudent33

46

Verilerinize hareketli bir ortalama yerleştirmek gürültüyü düzeltir, bunun nasıl yapılacağı için bu cevaba bakın .

Verilerinize uyacak şekilde DÜŞÜK kullanmak istiyorsanız (hareketli bir ortama benzer, ancak daha karmaşıktır), bunu istatistik modelleri kitaplığını kullanarak yapabilirsiniz :

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

Son olarak, sinyalinizin fonksiyonel formunu biliyorsanız, verilerinize bir eğri sığdırabilirsiniz, bu muhtemelen yapılacak en iyi şey olacaktır.


Sadece loessuygulanmış olsaydı .
scrutari

18

Başka bir seçenek kullanmaktır KernelReg içinde 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

Şuna bir bak! 1D sinyalinin düzgünleştirilmesinin açık bir tanımı vardır.

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

Kısayol:

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
Bir çözümün bağlantısı hoş karşılanır, ancak lütfen cevabınızın onsuz faydalı olduğundan emin olun: bağlantınızın çevresine bağlam ekleyin, böylece diğer kullanıcılarınız ne olduğu ve neden orada olduğu hakkında bir fikir sahibi olacak, ardından sayfanın en alakalı bölümünü alıntılayacağız ' hedef sayfanın kullanılamaması durumunda bağlantı kuruluyor. Bir bağlantıdan biraz daha fazlası olan yanıtlar silinebilir.
Shree

-4

Zaman serisi grafiği çiziyorsanız ve grafik çizmek için mtplotlib kullandıysanız, grafiği düzgünleştirmek için medyan yöntemini kullanın

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

timeseriesveri kümeniz nerede geçiyorsa windowsize, daha fazla düzeltme için değiştirebilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.