如何以正确的方式平滑曲线?


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 2013年

不,我会有不同的信号,即使在这个简单的示例中,我的方法也很明显
varantir 2013年

卡尔曼滤波对于这种情况是最佳的。pykalman python软件包质量很好。
toine 2016年

也许我会花更多时间将其扩展为一个完整的答案,但是尚未提及的一种强大的回归方法是GP(高斯过程)回归。
Ori5678

Answers:


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

最佳平滑嘈杂的正弦曲线

更新:引起我注意的是,我链接到的食谱示例已被删除。幸运的是,正如@dodohjk所指出的,Savitzky-Golay过滤器已被合并到SciPy库中。要使用SciPy源修改上面的代码,请输入:

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

我收到错误回溯(最近一次调用最近):<module>中文件“ hp.py”,第79行ysm2 = savitzky_golay(y_data,51,3)savitzky_golay firstvals,文件“ hp.py”,第42行= y [0]-np.abs(y [1:half_window + 1] [::-1]-y [0])
3月Ho 2015年


14
感谢您介绍Savitzky-Golay滤波器!因此,基本上,这就像常规的“移动平均值”过滤器一样,但是它不只是计算平均值,而是对每个点进行多项式拟合(通常为2阶或4阶),并且仅选择“中间”点。由于在每个点都涉及到第二(或第四)阶信息,因此可以避免在“移动平均”方法中在局部最大值或最小值处引入的偏差。真的很优雅。
np8

2
只是想谢谢你,我一直在疯狂地试图找出小波分解以获得平滑的数据,这真是太好了。
Eldar M.

5
如果X数据不经常间隔您可能希望过滤器适用于X的还有: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进行操作。同样,它也不太脏---这是上述其他一些方法中最简单的情况(例如LOWESS,但是内核是一个尖锐的间隔,例如Savitzky-Golay,但是多项式为零)。
吉姆·皮瓦尔斯基

2
移动平均线的唯一问题是它落后于数据。您可以在结尾处看到更多的点,顶部的点更多,而底部的点更少,但是绿色曲线当前低于平均值,这是因为窗口功能必须向前移动才能将其考虑在内。
nurettin

而且这不适用于nd数组,只有1d。scipy.ndimage.filters.convolve1d()允许您指定nd数组的轴以进行过滤。但是我认为,两者都受到掩盖价值观念的困扰。
杰森

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

看一下这个!对于一维信号的平滑有一个明确的定义。

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以进行更平滑的处理。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.