올바른 방법으로 커브를 부드럽게하는 방법?


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 패키지는 좋은 품질입니다.
toine

어쩌면 조금 더 시간이 지나면 전체 답변으로 확장 할 수도 있지만 아직 언급되지 않은 강력한 회귀 방법은 GP (Gaussian Process) 회귀입니다.
Ori5678

답변:


261

나는 Savitzky-Golay 필터를 선호합니다 . 최소 제곱을 사용하여 데이터의 작은 창을 다항식으로 되 돌린 다음 다항식을 사용하여 창 중앙의 점을 추정합니다. 마지막으로 창은 하나의 데이터 포인트만큼 앞으로 이동하고 프로세스가 반복됩니다. 이것은 모든 점이 이웃에 대해 최적으로 조정될 때까지 계속됩니다. 비 주기적 및 비선형 소스의 노이즈 샘플에서도 효과적입니다.

다음은 철저한 요리 책 예제 입니다. 사용하기 쉬운 방법에 대한 아이디어를 얻으려면 아래 코드를 참조하십시오. 참고 : 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()

시끄러운 정현파를 최적으로 부드럽게

업데이트 : 내가 연결된 요리 책 예제가 중단되었습니다. 다행히도 Savitzky-Golay 필터는 @dodohjk가 지적한 것처럼 SciPy 라이브러리 에 통합 되었습니다 . SciPy 소스를 사용하여 위의 코드를 수정하려면 다음을 입력하십시오.

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

오류 추적이 발생했습니다 (가장 최근의 마지막 호출). y [0]-np.abs (y [1 : half_window + 1] [::-1]-y [0])
March Ho


14
Savitzky-Golay 필터를 소개해 주셔서 감사합니다! 기본적으로 이것은 일반적인 "이동 평균"필터와 비슷하지만 평균을 계산하는 대신 모든 점에 대해 다항식 (보통 2 차 또는 4 차)이 적합하고 "중간"점만 선택됩니다. 2 차 (또는 4 차) 주문 정보는 모든 시점에서 관련이 있기 때문에, 국부 최대 또는 최소에서 "이동 평균"접근법에 도입 된 편향은 회피된다. 정말 우아합니다.
np8

2
그냥 고맙다고 말하고 싶습니다. 매끄러운 데이터를 얻기 위해 웨이블릿 분해를 알아 내려고 미쳤습니다. 이것이 훨씬 좋습니다.
Eldar M.

5
x 데이터의 간격이 일정하지 않으면 x에 필터를 적용 할 수도 있습니다 savgol_filter((x, y), ...).
Tim Kuipers

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와 비슷하지만 다항도는 0입니다).
Jim Pivarski

2
이동 평균의 유일한 문제는 데이터보다 뒤쳐진다는 것입니다. 맨 위에 더 많은 점이 있고 맨 아래에 더 적은 점이있을 때 가장 분명하게 알 수 있지만 창 함수를 고려하여 앞으로 이동해야하기 때문에 녹색 곡선이 현재 평균보다 낮습니다.
nurettin

그리고 이것은 nd 배열에서는 작동하지 않으며 1d에서만 작동합니다. scipy.ndimage.filters.convolve1d()필터링을 수행하기 위해 nd-array의 축을 지정할 수 있습니다. 그러나 나는 둘 다 마스크 값의 일부 문제로 고통 받고 있다고 생각합니다.
Jason

1
@ nurettin 나는 당신이 묘사하는 것이 가장자리 효과라고 생각합니다. 일반적으로 컨볼 루션 커널이 신호 내에서 범위를 커버 할 수있는 한, "뒤로"지연되지 않습니다. 그러나 결국에는 평균에 포함 할 6보다 큰 값이 없으므로 커널의 "왼쪽"부분 만 사용됩니다. 가장자리 효과는 모든 스무딩 커널에 존재하며 별도로 처리해야합니다.
Jon

4
@ nurettin 아니오, 나는 다른 사람들이 이것을 읽는 것을 분명히하려고 노력했습니다. 모든 윈도우 필터 방법은 이동 평균뿐만 아니라이 문제를 겪습니다. Savitzky-golay도이 문제를 겪고 있습니다. 따라서 "내가 설명하는 것은 savitzky_golay가 추정으로 해결하는 것"이라는 진술은 잘못되었습니다. 스무딩 방법 중 하나는 스무딩 방법 자체와 독립적 인 가장자리를 처리 할 수있는 방법이 필요합니다.
Jon

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

또 다른 옵션은 statsmodels 에서 KernelReg 를 사용하는 입니다 .

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.