Как правильно сгладить кривую?

Предположим, что у нас есть набор данных, который может быть задан приблизительно на

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, но проблема в том, что это не учитывает небольшой шум в хорошем смысле. Если вы рассматриваете частоты, фон намного меньше сигнала, поэтому сплайн только обрезания может быть идеей, но это будет включать в себя преобразование Фурье назад и вперед, что может привести к плохому поведению. Другим способом будет скользящее среднее, но для этого также потребуется правильный выбор задержки.

Любые подсказки/книги или ссылки, как решить эту проблему?

example

Ответ 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()

optimally smoothing a noisy sinusoid

ОБНОВЛЕНИЕ: До меня дошло, что пример поваренной книги, на который я ссылался, был удален. К счастью, фильтр Савицкого-Голея был включен в библиотеку SciPy, на что указывает @dodohjk. Чтобы адаптировать приведенный выше код с использованием исходного кода SciPy, введите:

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

Ответ 2

Быстрый и грязный способ сглаживания данных, которые я использую, на основе скользящего среднего (сверткой):

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)

введите описание изображения здесь

Ответ 3

Если вас интересует "плавная" версия периодического сигнала (например, вашего примера), то 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)

enter image description here

Даже если ваш сигнал не является полностью периодическим, это отлично справится с вычитанием белого шума. Там используется множество типов фильтров (high-pass, low-pass и т.д.), Соответствующий зависит от того, что вы ищете.

Ответ 4

Подгонка скользящего среднего к вашим данным сгладит шум, см. этот этот ответ, чтобы узнать, как это сделать.

Если вы хотите использовать 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()

Наконец, если вы знаете функциональную форму вашего сигнала, вы можете подогнать кривую к вашим данным, что, вероятно, будет лучшим решением.

Ответ 5

Другой вариант - использовать 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()

Ответ 6

Проверь это! Существует четкое определение сглаживания 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()

Ответ 7

Если вы строите график временных рядов и используете mtplotlib для рисования графиков, используйте срединный метод сглаживания графика

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

где timeseries - ваш переданный набор данных, вы можете изменить windowsize для более сглаживания.