Як згладити криву правильним способом?


200

Припустимо, у нас є набір даних, який може бути заданий приблизно

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
Чи завжди ваш сигнал буде синусоїдою або ви використовували це лише для прикладу?
Марк Викуп

ні, у мене будуть різні сигнали, навіть у цьому простому прикладі очевидно, що моїх методів недостатньо
varantir

фільтрація кальмана оптимальна для цього випадку. І пакет pykalman python відрізняється хорошою якістю.
toine

Можливо, я розширю його до повної відповіді, коли у мене буде трохи більше часу, але єдиним потужним методом регресії, про який ще не було сказано, є регресія GP (Гауссового процесу).
Ori5678

Відповіді:


261

Я віддаю перевагу фільтр Савіцкого-Голай . Він використовує найменші квадрати, щоб повернути невелике вікно ваших даних на поліном, потім використовує поліном для оцінки точки в центрі вікна. Нарешті вікно зміщується вперед на одну точку даних, і процес повторюється. Це триває, поки кожен пункт не буде оптимально відрегульований щодо своїх сусідів. Він чудово працює навіть із шумними зразками з неперіодичних та нелінійних джерел.

Ось докладний приклад кулінарної книги . Перегляньте мій код нижче, щоб отримати уявлення про те, як його просто використовувати. Примітка. Я залишив код для визначення 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, введіть:

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-й) порядки стосується кожної точки, ухил, запроваджений підходом "ковзної середньої" при локальних максимумах або мінімумах, обходить. Дійсно елегантний.
np8

2
Просто хочу сказати спасибі за це, я божеволіла, намагаючись з'ясувати розкладки вейвлетів, щоб отримати згладжені дані, і це так приємніше.
Ельдар М.

5
Якщо х дані регулярно НЕ рознесені ви можете застосувати фільтр до ікси , а також: savgol_filter((x, y), ...).
Тім Куйперс

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. Крім того, він не надто брудний - це найпростіший випадок деяких інших описаних вище методів (наприклад, НИЗМЕ, але ядро ​​- це різкий інтервал і схожий на Савіцького-Голая, але ступінь полінома дорівнює нулю).
Джим Піварський

2
Єдина проблема з ковзною середньою - це те, що вона відстає від даних. Це можна побачити найбільш очевидно в кінці, де вгорі більше крапок, а внизу менше, але зелена крива на даний момент нижче середнього, оскільки функція вікна повинна рухатися вперед, щоб врахувати їх.
Нуреттін

І це не працює на другому масиві, лише 1d. scipy.ndimage.filters.convolve1d()дозволяє вказати вісь nd-масиву для фільтрації. Але я думаю, що обидва страждають від деяких питань у маскуванні цінностей.
Джейсон

1
@nurettin Я думаю, що те, що ти описуєш, є крайніми ефектами. Взагалі, поки ядро ​​згортки здатне покрити його ступінь в межах сигналу, воно не «відстає», як ви кажете. Однак у кінцевому рахунку немає значень, що перевищують 6, для включення в середнє значення, тому використовується лише "ліва" частина ядра. Ефекти кромки є у кожному згладжувальному ядрі і повинні оброблятися окремо.
Джон

4
@nurettin Ні, я намагався уточнити для інших, хто читає це, що ваш коментар "Єдина проблема зі середньою середньою - це те, що вона відстає від даних", вводить в оману. Будь-який метод фільтра вікон страждає від цієї проблеми, а не лише середньої середньої. Савіцький-голай також страждає від цієї проблеми. Отже, ваше твердження "Те, що я описую, те, що 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)

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

Навіть якщо ваш сигнал не є повністю періодичним, це зробить чудову роботу для віднімання білого шуму. Існує багато типів фільтрів (високочастотний, низькочастотний тощо), відповідний залежить від того, що ви шукаєте.


Яка ділянка для якої змінної? Я намагаюся згладити координати для тенісного м’яча в ралі, тобто. вийміть усі
відскоки,

44

Приміщення ковзних середніх даних до ваших даних згладить шум, див. Цю відповідь, як це зробити.

Якщо ви хочете використовувати LOWESS для відповідності вашим даним (це схоже на ковзну середню, але більш досконалу), ви можете зробити це, використовуючи бібліотеку статистичних моделей :

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 в статистичних моделях :

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
Посилання на рішення вітається, але будь ласка, переконайтеся, що ваша відповідь корисна без неї: додайте контекст навколо посилання, щоб ваші колеги користувачі мали уявлення про те, що це таке і чому воно є, а потім наведіть найбільш релевантну частину сторінки, яку ви ' повторне посилання на випадок, якщо цільова сторінка недоступна. Відповіді, які є трохи більше ніж посилання, можуть бути видалені.
Шрі

-4

Якщо ви плануєте графік часових рядів і якщо ви використовували mtplotlib для малювання графіків, тоді використовуйте серединний метод, щоб згладити графік

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

де timeseriesваш набір даних, які ви передаєте, ви можете змінити windowsizeдля більш детального впорядкування.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.