曲線を正しい方法で滑らかにする方法は?


200

およそ次のように与えられるデータセットがあるとしましょう

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

したがって、データセットの20%の変動があります。私の最初のアイデアはscipyのUnivariateSpline関数を使用することでしたが、問題は小さなノイズを適切に考慮していないことです。周波数を考慮すると、背景は信号よりもはるかに小さいため、カットオフのみのスプラインが考えられるかもしれませんが、前後にフーリエ変換を行うため、動作が悪くなる可能性があります。別の方法は移動平均ですが、これには遅延の正しい選択も必要です。

この問題に取り組むためのヒント/本またはリンクはありますか?

例


1
信号は常に正弦波ですか、それとも例としてのみ使用しましたか?
Mark Ransom

いいえ、私はそれが私の方法が十分でないことは明らかだとしても、この簡単な例では、異なる信号を持つことになります
varantir

この場合、カルマンフィルター処理が最適です。そして、pykalman pythonパッケージは良質です。
2016

もう少し時間があれば、それを完全な答えに拡張するかもしれませんが、まだ言及されていない強力な回帰方法の1つは、GP(ガウスプロセス)回帰です。
Ori5678

回答:


262

私はSavitzky-Golayフィルターを好みます。最小二乗法を使用してデータの小さなウィンドウを多項式に回帰し、多項式を使用してウィンドウの中心の点を推定します。最後に、ウィンドウが1データポイント前にシフトされ、プロセスが繰り返されます。これは、すべてのポイントがその近傍に対して最適に調整されるまで続きます。非周期的で非線形のソースからのノイズの多いサンプルでもうまく機能します。

これがクックブック完全な例です。使用がいかに簡単かについては、以下のコードを参照してください。注:savitzky_golay()上記でリンクしたクックブックの例から文字通りコピー/貼り付けできるため、関数を定義するためのコードは省略しました。

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

ノイズの多い正弦波を最適に平滑化

更新:私がリンクしたクックブックの例が削除されたことが私の注意になりました。幸い、@ dodohjkで指摘されているように、Savitzky-GolayフィルターがSciPyライブラリに組み込まれています。SciPyソースを使用して上記のコードを適合させるには、次のように入力します。

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

エラー「トレースバック(最新の呼び出しは最後)」:ファイル「hp.py」、79行目、<モジュール> ysm2 = savitzky_golay(y_data、51,3)ファイル「hp.py」、42行目、savitzky_golay firstvals = y [0]-np.abs(y [1:half_window + 1] [::-1]-y [0])
March Ho


14
Savitzky-Golayフィルターをご紹介いただきありがとうございます。つまり、これは基本的に通常の「移動平均」フィルターと同じですが、平均を計算するだけでなく、すべての点に対して多項式(通常は2次または4次)の近似が行われ、「中間」点のみが選択されます。すべての点で2次(または4次)の情報が関係しているため、極大または極小での「移動平均」アプローチで導入されるバイアスは回避されます。本当にエレガント。
np8 2016年

2
これをありがとうと言いたいだけですが、ウェーブレット分解を理解して平滑化されたデータを取得しようとすることに夢中になっており、これは非常に優れています。
Eldar

5
xデータが規則的に配置されていない場合は、xにもフィルターを適用することができますsavgol_filter((x, y), ...)
Tim Kuipers、2018

127

(畳み込みによる)移動平均ボックスに基づいて、使用するデータを平滑化する迅速で汚れた方法:

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)

ここに画像の説明を入力してください


9
これには、いくつかの優れた利点があります。(1)定期的なだけでなく、任意の関数で機能し、(2)コピーして貼り付ける依存関係や大きな関数がない。あなたは純粋なNumpyですぐにそれを行うことができます。また、あまり汚くない-それは上記の他の方法のいくつかの最も単純なケースです(LOWESSのようにカーネルは鋭い間隔でSavitzky-Golayのようですが多項式次数はゼロです)。
Jim Pivarski、2015

2
移動平均の唯一の問題は、データに遅れが生じることです。これは、一番上に点があり、下に点が少ない端で最も明白に見ることができますが、ウィンドウ関数はそれらを考慮に入れるために前方に移動する必要があるため、緑の曲線は現在平均を下回っています。
nurettin 2017

そして、これはnd配列では機能せず、1dでのみ機能します。scipy.ndimage.filters.convolve1d()nd-arrayの軸を指定してフィルタリングを行うことができます。しかし、両方ともマスクされた値のいくつかの問題に苦しんでいると思います。
Jason

1
@nurettinあなたが説明しているのはエッジ効果だと思います。一般的に、畳み込みカーネルが信号内でその範囲をカバーできる限り、あなたが言うように「遅れ」はありません。ただし、最後に、平均に含める値は6を超えないため、カーネルの「左側」の部分のみが使用されています。エッジ効果はすべてのスムージングカーネルに存在し、個別に処理する必要があります。
Jon

4
@nurettinいいえ、私はこれを読んでいる他の人たちに、「移動平均の唯一の問題はデータより遅れている」というコメントは誤解を招くものであることを明確にしようとしていました。移動平均だけでなく、どのウィンドウフィルターメソッドにもこの問題があります。Savitzky-golayもこの問題を抱えています。したがって、「私が説明しているのは、savitzky_golayが推定によって解決するものです」という文は間違っています。どちらのスムージング方法でも、スムージング方法自体に依存しない、エッジを処理する方法が必要です。
ジョン

79

(例のように)周期的な信号の「滑らかな」バージョンに興味がある場合は、FFTが適しています。フーリエ変換を取り、寄与度の低い周波数を差し引きます。

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)

ここに画像の説明を入力してください

信号が完全に周期的ではない場合でも、これはホワイトノイズを取り除くのに非常に役立ちます。使用するフィルターには多くの種類があります(ハイパス、ローパスなど)。適切なフィルターは、探しているものによって異なります。


どのプロットがどの変数に対応していますか?私はラリーでテニスボールの座標を滑らかにしようとしています。私のプロットで小さな放物線のように見えるすべてのバウンスを取り出します
mLstudent33

44

データに移動平均を当てはめると、ノイズが滑らかになります。その方法については、この回答を参照してください。

LOWESSを使用してデータを適合させたい場合(移動平均に似ていますが、より高度です)、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()

最後に、信号の関数形式がわかっている場合は、データに曲線を当てはめることができます。これがおそらく最善の方法です。


のみloess実装されていた場合。
scrutari

18

別のオプションはstatsmodelsKernelRegを使用することです

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

これをチェック!1D信号の平滑化の明確な定義があります。

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

ショートカット:

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
ソリューションへのリンクは歓迎しますが、それがなくても回答が役立つことを確認してください。リンクの周りにコンテキストを追加して、他のユーザーがそれが何であるか、なぜそこにあるのかを理解し、ページの最も関連性の高い部分を引用してくださいターゲットページが利用できない場合にリンクし直します。リンクに過ぎない回答は削除される場合があります。
Shree

-4

時系列グラフをプロットしていて、グラフの描画にmtplotlibを使用している場合は、メジアン法を使用してグラフを滑らかにします

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

timeseries渡されたデータのセットはどこにあるか、windowsizeよりスムーズにするために変更できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.