Làm thế nào để làm phẳng một đường cong đúng cách?


200

Giả sử chúng ta có một bộ dữ liệu có thể được cung cấp khoảng

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

Do đó, chúng tôi có một biến thể 20% của bộ dữ liệu. Ý tưởng đầu tiên của tôi là sử dụng chức năng UnivariateSpline của scipy, nhưng vấn đề là điều này không xem xét tiếng ồn nhỏ theo cách tốt. Nếu bạn xem xét các tần số, nền nhỏ hơn nhiều so với tín hiệu, do đó, một spline chỉ có thể bị cắt có thể là một ý tưởng, nhưng điều đó sẽ liên quan đến chuyển đổi phạm vi qua lại, có thể dẫn đến hành vi xấu. Một cách khác sẽ là một đường trung bình động, nhưng điều này cũng cần có sự lựa chọn đúng về độ trễ.

Bất kỳ gợi ý / sách hoặc liên kết làm thế nào để giải quyết vấn đề này?

thí dụ


1
Tín hiệu của bạn sẽ luôn là sóng hình sin hay bạn chỉ sử dụng nó cho một ví dụ?
Đánh dấu tiền chuộc

không, tôi sẽ có các tín hiệu khác nhau, ngay cả trong ví dụ dễ hiểu này, rõ ràng là các phương pháp của tôi không đủ
varantir 16/12/13

lọc kalman là tối ưu cho trường hợp này. Và gói python pyman có chất lượng tốt.
toine

Có thể tôi sẽ mở rộng nó thành một câu trả lời đầy đủ khi tôi có thêm một chút thời gian, nhưng phương pháp hồi quy mạnh mẽ chưa được đề cập đến là hồi quy GP (Gaussian Process).
Ori5678

Câu trả lời:


261

Tôi thích bộ lọc Savitzky-Golay . Nó sử dụng bình phương tối thiểu để hồi quy một cửa sổ nhỏ dữ liệu của bạn lên một đa thức, sau đó sử dụng đa thức để ước tính điểm ở giữa cửa sổ. Cuối cùng, cửa sổ được chuyển về phía trước bởi một điểm dữ liệu và quá trình lặp lại. Điều này tiếp tục cho đến khi mọi điểm đã được điều chỉnh tối ưu so với các nước láng giềng. Nó hoạt động tuyệt vời ngay cả với các mẫu nhiễu từ các nguồn không định kỳ và phi tuyến tính.

Dưới đây là một ví dụ nấu ăn kỹ lưỡng . Xem mã của tôi dưới đây để có được một ý tưởng về việc sử dụng nó dễ dàng như thế nào. Lưu ý: Tôi đã bỏ qua mã để xác định savitzky_golay()chức năng bởi vì bạn có thể sao chép / dán nó theo nghĩa đen từ ví dụ sách nấu ăn mà tôi đã liên kết ở trên.

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

tối ưu làm mịn một hình sin ồn ào

CẬP NHẬT: Tôi đã nhận thấy rằng ví dụ về sách dạy nấu ăn mà tôi liên kết đã bị gỡ xuống. May mắn thay, bộ lọc Savitzky-Golay đã được tích hợp vào thư viện SciPy , như được chỉ ra bởi @dodohjk . Để điều chỉnh mã trên bằng cách sử dụng nguồn SciPy, gõ:

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

Tôi đã gặp lỗi TracBack (cuộc gọi gần đây nhất): Tệp "hp.py", dòng 79, trong <module> ysm2 = savitzky_golay (y_data, 51,3) Tệp "hp.py", dòng 42, trong savitzky_golay y [0] - np.abs (y [1: Half_window + 1] [:: - 1] - y [0])
Hồ tháng ba


14
Cảm ơn bạn đã giới thiệu bộ lọc Savitzky-Golay! Vì vậy, về cơ bản, đây giống như một bộ lọc "Di chuyển trung bình" thông thường, nhưng thay vì chỉ tính trung bình, một phép tính đa thức (thường là bậc 2 hoặc bậc 4) được thực hiện cho mọi điểm và chỉ chọn điểm "giữa". Do thông tin thứ tự thứ 2 (hoặc thứ 4) được quan tâm tại mọi thời điểm, nên độ lệch được đưa ra trong phương pháp "trung bình di động" ở cực đại hoặc cực tiểu cục bộ, bị phá vỡ. Thực sự thanh lịch.
np8

2
Chỉ muốn nói lời cảm ơn vì điều này, tôi đã phát điên khi cố gắng tìm ra sự phân tách sóng con để có được dữ liệu được làm mịn, và điều này đẹp hơn rất nhiều.
Eldar M.

5
Nếu dữ liệu x không được đặt cách đều đặn, bạn cũng có thể muốn áp dụng bộ lọc cho x x : savgol_filter((x, y), ...).
Tim Kuipers

127

Một cách nhanh chóng và bẩn để làm mịn dữ liệu tôi sử dụng, dựa trên hộp trung bình di chuyển (bằng tích chập):

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)

nhập mô tả hình ảnh ở đây


9
Điều này có một vài lợi thế tốt: (1) hoạt động cho bất kỳ chức năng nào, không chỉ định kỳ và (2) không phụ thuộc hoặc chức năng lớn để sao chép-dán. Bạn có thể làm điều đó ngay lập tức với Numpy thuần túy. Ngoài ra, nó không quá bẩn --- đó là trường hợp đơn giản nhất trong số các phương pháp khác được mô tả ở trên (như LỚN nhưng hạt nhân là một khoảng sắc nét và giống như Savitzky-Golay nhưng mức độ đa thức bằng không).
Jim Pivarski 16/2/2015

2
vấn đề duy nhất với di chuyển trung bình là nó bị tụt lại phía sau dữ liệu. Bạn có thể thấy điều này rõ ràng nhất ở cuối, nơi có nhiều chấm ở phía trên và ít hơn ở phía dưới, nhưng đường cong màu xanh lá cây hiện đang ở dưới mức trung bình vì chức năng cửa sổ phải di chuyển về phía trước để đưa chúng vào tài khoản.
Nurettin

Và điều này không hoạt động trên mảng nd, chỉ 1d. scipy.ndimage.filters.convolve1d()cho phép bạn chỉ định một trục của mảng thứ hai để thực hiện quá trình lọc. Nhưng tôi nghĩ cả hai đều chịu một số vấn đề trong các giá trị đeo mặt nạ.
Jason

1
@nurettin Tôi nghĩ những gì bạn mô tả là hiệu ứng cạnh. Nói chung, miễn là hạt nhân chập có thể bao phủ phạm vi của nó trong tín hiệu, nó không "tụt lại phía sau" như bạn nói. Tuy nhiên, cuối cùng, không có giá trị nào vượt quá 6 để đưa vào trung bình, do đó, chỉ có phần "bên trái" của hạt nhân đang được sử dụng. Hiệu ứng cạnh có mặt trong mọi hạt nhân làm mịn và phải được xử lý riêng.
Jon

4
@nurettin Không, tôi đã cố gắng làm rõ cho những người khác đọc nhận xét của bạn rằng "vấn đề duy nhất với di chuyển trung bình là nó bị tụt hậu so với dữ liệu" là sai lệch. Bất kỳ phương pháp bộ lọc cửa sổ nào cũng gặp phải vấn đề này, không chỉ di chuyển trung bình. Savitzky-golay cũng bị vấn đề này. Vì vậy, tuyên bố của bạn "Những gì tôi đang mô tả là những gì savitzky_golay giải quyết bằng ước tính" là sai. Phương pháp làm mịn đòi hỏi một cách để xử lý các cạnh độc lập với chính phương pháp làm mịn.
Jon

79

Nếu bạn quan tâm đến phiên bản "mượt" của tín hiệu định kỳ (như ví dụ của bạn), thì FFT là cách phù hợp. Thực hiện chuyển đổi fourier và trừ đi các tần số đóng góp thấp:

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)

nhập mô tả hình ảnh ở đây

Ngay cả khi tín hiệu của bạn không hoàn toàn định kỳ, điều này sẽ làm rất tốt việc loại bỏ nhiễu trắng. Có nhiều loại bộ lọc để sử dụng (high-pass, low-pass, v.v ...), loại phù hợp phụ thuộc vào những gì bạn đang tìm kiếm.


Âm mưu nào cho biến nào? Tôi đang cố gắng làm dịu tọa độ cho quả bóng tennis trong một cuộc biểu tình, tức là. lấy ra tất cả các trang bị trả lại trông giống như những parabolas nhỏ trên cốt truyện của tôi
mLstudent33

44

Gắn trung bình di động vào dữ liệu của bạn sẽ làm giảm nhiễu, hãy xem câu trả lời này để biết cách thực hiện.

Nếu bạn muốn sử dụng THẤP để phù hợp với dữ liệu của mình (tương tự như mức trung bình di động nhưng tinh vi hơn), bạn có thể thực hiện điều đó bằng thư viện 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()

Cuối cùng, nếu bạn biết dạng chức năng của tín hiệu của mình, bạn có thể điều chỉnh đường cong cho dữ liệu của mình, đây có thể là điều tốt nhất để làm.


Nếu chỉ có đã loessthực hiện.
Scrutari

18

Một tùy chọn khác là sử dụng KernelReg trong 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

Kiểm tra này! Có một định nghĩa rõ ràng về làm mịn tín hiệu 1D.

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

Đường tắt:

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
Liên kết đến một giải pháp được hoan nghênh, nhưng vui lòng đảm bảo câu trả lời của bạn hữu ích mà không cần đến nó: thêm ngữ cảnh xung quanh liên kết để người dùng của bạn sẽ có ý tưởng về nó là gì và tại sao lại có, sau đó trích dẫn phần có liên quan nhất của trang bạn ' liên kết lại trong trường hợp trang đích không có sẵn. Câu trả lời ít hơn một liên kết có thể bị xóa.
Shree

-4

Nếu bạn đang vẽ đồ thị chuỗi thời gian và nếu bạn đã sử dụng mtplotlib để vẽ biểu đồ thì hãy sử dụng phương pháp trung bình để làm mịn biểu đồ

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

nơi timeserieslà bộ dữ liệu của bạn thông qua bạn có thể thay đổi windowsizecho smoothining hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.