วิธีทำให้เส้นโค้งเรียบในวิธีที่ถูกต้อง?


201

สมมติว่าเรามีชุดข้อมูลที่อาจได้รับประมาณ

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

ดังนั้นเราจึงมีรูปแบบของชุดข้อมูล 20% ความคิดแรกของฉันคือการใช้ฟังก์ชัน UnivariateSpline ของ scipy แต่ปัญหาคือว่านี่ไม่ได้พิจารณาว่าเสียงเล็ก ๆ ในทางที่ดี หากคุณพิจารณาความถี่พื้นหลังมีขนาดเล็กกว่าสัญญาณดังนั้นเส้นโค้งเพียงจุดตัดอาจเป็นแนวคิด แต่จะเกี่ยวข้องกับการแปลงฟูเรียร์ไปมาซึ่งอาจทำให้เกิดพฤติกรรมที่ไม่ดี อีกวิธีหนึ่งก็คือค่าเฉลี่ยเคลื่อนที่ แต่ก็ต้องมีทางเลือกที่เหมาะสมของความล่าช้า

คำแนะนำ / หนังสือหรือลิงก์วิธีแก้ไขปัญหานี้

ตัวอย่าง


1
สัญญาณของคุณจะเป็นคลื่นไซน์หรือคุณใช้มันเพื่อเป็นตัวอย่างเท่านั้นหรือไม่?
Mark Ransom

ไม่ฉันจะมีสัญญาณที่แตกต่างกันแม้ในตัวอย่างง่ายนี้ก็เห็นได้ชัดว่าวิธีการของเราไม่เพียงพอ
varantir

การกรองคาลมานเหมาะที่สุดสำหรับกรณีนี้ และไพ ธ อนแพ็คเกจ pykalman นั้นคุณภาพดี
toine

บางทีฉันอาจจะขยายออกเป็นคำตอบแบบเต็มเมื่อฉันใช้เวลาเพิ่มขึ้นเล็กน้อย แต่วิธีการถดถอยที่ทรงพลังวิธีหนึ่งที่ไม่ได้กล่าวถึงคือการถดถอยแบบ GP (กระบวนการแบบเกาส์เซียน)
Ori5678

คำตอบ:


264

ฉันชอบตัวกรอง 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 ได้รับการจดทะเบียนเข้าสู่ห้องสมุด SciPyเป็นแหลมออกโดย@dodohjk หากต้องการปรับโค้ดด้านบนโดยใช้ SciPy source ให้พิมพ์:

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

ฉันพบข้อผิดพลาด Traceback (การโทรล่าสุดครั้งล่าสุด): ไฟล์ "hp.py", บรรทัดที่ 79, ใน <module> 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])
มีนาคม Ho


14
ขอบคุณที่แนะนำตัวกรอง Savitzky-Golay! ดังนั้นโดยทั่วไปนี่เป็นเหมือนตัวกรอง "ค่าเฉลี่ยเคลื่อนที่" ปกติ แต่แทนที่จะคำนวณเพียงค่าเฉลี่ยการพอดีแบบพหุนาม (โดยปกติจะเป็นอันดับที่ 2 หรือ 4) ถูกสร้างขึ้นสำหรับทุก ๆ จุดและเลือกเฉพาะจุด "กลาง" เท่านั้น เนื่องจากข้อมูลเกี่ยวกับลำดับที่ 2 (หรือ 4) นั้นเกี่ยวข้องกับทุกจุดความลำเอียงที่แนะนำในแนวทาง "ค่าเฉลี่ยเคลื่อนที่" ที่ maxima หรือ minima ท้องถิ่นจะถูกหลีกเลี่ยง สง่างามจริงๆ
np8

2
แค่อยากจะบอกว่าขอบคุณสำหรับสิ่งนี้ฉันบ้าไปแล้วที่จะพยายามหาการย่อยสลายเวฟเล็ตเพื่อให้ได้ข้อมูลที่ราบรื่นขึ้นและนี่เป็นสิ่งที่ดีกว่ามาก
Eldar M.

5
หากข้อมูล x ไม่ได้เว้นระยะอย่างสม่ำเสมอคุณอาจต้องการใช้ตัวกรองกับ x's เช่นกัน: savgol_filter((x, y), ...).
ทิม Kuipers

128

วิธีที่รวดเร็วและสกปรกในการทำให้ข้อมูลราบรื่นขึ้นโดยใช้กล่องเฉลี่ยเคลื่อนที่ (โดยการบิด):

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 แต่ระดับพหุนามเป็นศูนย์)
จิม Pivarski

2
ปัญหาเดียวของค่าเฉลี่ยเคลื่อนที่คือมันล้าหลังข้อมูล คุณสามารถเห็นได้ชัดเจนที่สุดในตอนท้ายที่มีจุดมากขึ้นที่ด้านบนและด้านล่างที่ต่ำกว่า แต่ในปัจจุบันเส้นโค้งสีเขียวต่ำกว่าค่าเฉลี่ยเนื่องจากฟังก์ชั่นหน้าต่างต้องเลื่อนไปข้างหน้าเพื่อพิจารณาสิ่งเหล่านั้น
nurettin

1
และนี่ใช้ไม่ได้กับอาร์เรย์ลำดับที่ 1d เท่านั้น scipy.ndimage.filters.convolve1d()อนุญาตให้คุณระบุแกนของอาร์เรย์ที่จะทำการกรอง แต่ฉันคิดว่าทั้งคู่ต้องเจอกับปัญหาบางอย่างในค่าที่ถูกปิดบัง
เจสัน

1
@nurettin ฉันคิดว่าสิ่งที่คุณกำลังอธิบายคือเอฟเฟกต์ขอบ โดยทั่วไปตราบใดที่เคอร์เนล convolution สามารถครอบคลุมขอบเขตภายในสัญญาณมันไม่ "ล้าหลัง" ตามที่คุณพูด อย่างไรก็ตามในตอนท้ายไม่มีค่าเกินกว่า 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)

ป้อนคำอธิบายรูปภาพที่นี่

แม้ว่าสัญญาณของคุณจะไม่เป็นระยะอย่างสมบูรณ์ แต่มันจะทำงานได้ดีในการลบสัญญาณรบกวนสีขาว มีตัวกรองหลายประเภทให้ใช้ (high-pass, low-pass, ฯลฯ ... ) ตัวกรองที่เหมาะสมนั้นขึ้นอยู่กับสิ่งที่คุณกำลังมองหา


พล็อตใดเป็นตัวแปรตัวใด ฉันพยายามทำให้พิกัดของลูกเทนนิสราบรื่นขึ้นเช่น นำการตีกลับทั้งหมดที่ดูเหมือน parabolas เล็ก ๆ น้อย ๆ บนแผนของฉัน
mLstudent33

46

การปรับค่าเฉลี่ยเคลื่อนที่ให้เข้ากับข้อมูลของคุณจะช่วยขจัดเสียงรบกวนให้ดูคำตอบนี้สำหรับวิธีการดังกล่าว

หากคุณต้องการใช้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

ตัวเลือกอื่นคือใช้KernelRegใน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

ลองดู! มีคำจำกัดความที่ชัดเจนของการปรับให้เรียบของสัญญาณ 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สำหรับ smoothining มากขึ้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.