严格地将曲线平滑到原始

时间:2018-01-26 01:31:16

标签: python algorithm pandas numpy smoothing

我有一条任意输入曲线,以numpy数组给出。我想创建一个平滑的版本,类似于滚动平均值,但它严格大于原始版本并严格平滑。我可以使用滚动平均值但是如果输入曲线具有负峰值,则平滑版本将降低到该峰值周围的原始值以下。然后,我可以简单地使用这个和原始的最大值,但这将引入转换发生的非平滑点。

此外,我希望能够通过前瞻和后视来为这个结果曲线参数化算法,这样,如果给定一个大的前瞻和一个小的后视,结果曲线会更坚持对于下降边缘,并且有一个大的后视和一个小的前瞻,它宁愿接近上升的边缘。

我尝试使用pandas.Series(a).rolling()工具获取滚动方式,滚动最大值等等,但到目前为止,我发现没有办法生成输入的平滑版本,在所有情况下都保持输入。< / p>

我想有一种方法可以将滚动最大值和滚动方式结合起来以某种方式实现我想要的,所以这里有一些用于计算这些的代码:

import pandas as pd
import numpy as np

我的输入曲线:

original = np.array([ 5, 5, 5, 8, 8, 8, 2, 2, 2, 2, 2, 3, 3, 7 ])

这可以填充左(前)和右(后),边缘值作为任何滚动功能的准备:

pre = 2
post = 3
padded = np.pad(original, (pre, post), 'edge')

现在我们可以应用滚动平均值:

smoothed = pd.Series(padded).rolling(
    pre + post + 1).mean().get_values()[pre+post:]

但现在平滑版本低于原版,e。 G。在索引4

print(original[4], smoothed[4])  # 8 and 5.5

要计算滚动最大值,您可以使用:

maximum = pd.Series(padded).rolling(
    pre + post + 1).max().get_values()[pre+post:]

但在许多情况下,单独滚动最大值当然不会很平滑,并且会在原始峰值周围显示许多平顶。我希望对这些峰值采取平滑的方法。

如果你还安装了pyqtgraph,你可以很容易地绘制这样的曲线:

import pyqtgraph as pg
p = pg.plot(original)
p.plotItem.plot(smoothed, pen=(255,0,0))

(当然,其他情节库也可以。)

我希望得到的结果是e曲线。 G。就像这些价值形成的那样:

goal = np.array([ 5, 7, 7.8, 8, 8, 8, 7, 5, 3.5, 3, 4, 5.5, 6.5, 7 ])

这是曲线的图像。白线是原始(输入),红色是滚动的意思,绿色是我想要的:

curve plot

编辑:我刚刚找到名为baseline()的模块的函数envelope()peakutils。这两个函数可以计算适合较低分辨率的给定程度的多项式。输入的上峰值。对于小样品,这可能是一个很好的解决方案。我正在寻找可以应用于具有数百万个值的非常大的样本的东西;那么学位需要非常高,然后计算也需要花费相当多的时间。分段(部分部分)这样做会打开一堆新的问题和问题(比如如何正确地缝合,同时保持平滑并保证在输入之上,在处理大量碎片时的性能等),所以我&#39; d如果可能的话,尽量避免这种情况。

编辑2:我有一个很有前途的方法,重复应用一个过滤器,创建一个滚动平均值,稍微向左和向右移动,然后取这两个和原始样本的最大值。多次应用后,它会按照我想要的方式平滑曲线。但是,在深谷中可能会留下一些不平坦的斑点。以下是此代码:

pre = 30
post = 30
margin = 10
s = [ np.array(sum([[ x ] * 100 for x in
      [ 5, 5, 5, 8, 8, 8, 2, 2, 2, 2, 2, 3, 3, 7 ]], [])) ]
for _ in range(30):
  s.append(np.max([
    pd.Series(np.pad(s[-1], (margin+pre, post), 'edge')).rolling(
      1 + pre + post).mean().get_values()[pre+post:-margin],
    pd.Series(np.pad(s[-1], (pre, post+margin), 'edge')).rolling(
      1 + pre + post).mean().get_values()[pre+post+margin:],
    s[-1]], 0))

这创建了30次迭代应用过滤器,可以使用pyqtplot绘制这些迭代,所以:

p = pg.plot(original)
for q in s:
  p.plotItem.plot(q, pen=(255, 100, 100))

生成的图像如下所示: enter image description here

我不喜欢这种方法有两个方面:①需要花费大量时间(这使我慢下来),②它仍然在山谷中有不平滑的部分(虽然在我的用例中这可能是可以接受的)。

3 个答案:

答案 0 :(得分:1)

我现在玩了很多,我想我找到了两个主要答案,解决了我的直接需要。我会在下面给他们。

import numpy as np
import pandas as pd
from scipy import signal
import pyqtgraph as pg

这些只是必要的导入,用于所有代码打击。 pyqtgraph当然只用于显示内容,所以你真的不需要它。

对称平滑

这可用于创建始终高于信号的平滑线,但无法区分上升沿和下降沿,因此单个峰值周围的曲线看起来是对称的。在许多情况下,这可能是非常好的,因为它不如下面的非对称解决方案复杂(并且也没有任何我想知道的怪癖)。

s = np.repeat([5, 5, 5, 8, 8, 8, 2, 2, 2, 2, 2, 3, 3, 7], 400) + 0.1
s *= np.random.random(len(s))
pre = post = 400
x = pd.Series(np.pad(s, (pre, post), 'edge')).rolling(
    pre + 1 + post).max().get_values()[pre+post:]
y = pd.Series(np.pad(x, (pre, post), 'edge')).rolling(
    pre + 1 + post, win_type='blackman').mean().get_values()[pre+post:]
p = pg.plot(s, pen=(100,100,100))
for c, pen in ((x, (0, 200, 200)),
               (y, pg.mkPen((255, 255, 255), width=3, style=3))):
    p.plotItem.plot(c, pen=pen)

enter image description here

  • 创建滚动最大值(x,青色)和
  • 创建一个窗口滚动平均值(z,白色虚线)。

不对称平滑

我的用例需要一个允许区分上升沿和下降沿的版本。下降或上升时输出速度应该不同。

评论:用作压缩器/扩展器的包络线,快速上升的曲线意味着几乎完全抑制突然响亮的噪音的影响,而缓慢上升的曲线意味着缓慢压缩在响亮的噪音之前很长一段时间的信号,在爆炸出现时保持动态。另一方面,如果在嘈杂的声音之后曲线快速下降,这会在听到一声巨响之后立即产生安静的声音,而缓慢下降的曲线也会保持动态,并且只会缓慢地将信号恢复到正常水平。

s = np.repeat([5, 5, 5, 8, 8, 8, 2, 2, 2, 2, 2, 3, 3, 7], 400) + 0.1
s *= np.random.random(len(s))
pre, post = 100, 1000
t = pd.Series(np.pad(s, (post, pre), 'edge')).rolling(
    pre + 1 + post).max().get_values()[pre+post:]
g = signal.get_window('boxcar', pre*2)[pre:]
g /= g.sum()
u = np.convolve(np.pad(t, (pre, 0), 'edge'), g)[pre:]
g = signal.get_window('boxcar', post*2)[:post]
g /= g.sum()
v = np.convolve(np.pad(t, (0, post), 'edge'), g)[post:]
u, v = u[:len(v)], v[:len(u)]
w = np.min(np.array([ u, v ]),0)
pre = post = max(100, min(pre, post)*3)
x = pd.Series(np.pad(w, (pre, post), 'edge')).rolling(
    pre + 1 + post).max().get_values()[pre+post:]
y = pd.Series(np.pad(x, (pre, post), 'edge')).rolling(
    pre + 1 + post, win_type='blackman').mean().get_values()[pre+post:]
p = pg.plot(s, pen=(100,100,100))
for c, pen in ((t, (200, 0, 0)),
               (u, (200, 200, 0)),
               (v, (0, 200, 0)),
               (w, (200, 0, 200)),
               (x, (0, 200, 200)),
               (y, pg.mkPen((255, 255, 255), width=3))):
    p.plotItem.plot(c, pen=pen)

enter image description here

这个序列无情地结合了几种信号处理方法。

  • 输入信号显示为灰色。这是上面提到的输入的嘈杂版本。
  • 对此(t,红色)应用滚动最大值。
  • 然后创建一个专门设计的下降沿卷积曲线(g)并计算卷积(u,黄色)。
  • 对于具有不同卷积曲线(g再次)的上升沿重复此过程,并计算卷积(v,绿色)。
  • u和v的最小值是具有所需斜率但不是很光滑的曲线;特别是当下降和上升的斜坡相互碰到时(w,紫色),它有尖锐的尖峰。
  • 在此基础上应用上述对称方法:
    • 创建此曲线的滚动最大值(x,青色)。
    • 创建此曲线的窗口滚动平均值(y,白色虚线)。

答案 1 :(得分:0)

正如我在笔记中指出的那样,绿线的行为在八高原之前和之后的区域是不一致的。如果左区域行为符合您的要求,您可以执行以下操作:

import numpy as np 
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from scipy.spatial import ConvexHull 

# %matplotlib inline # for interactive notebooks

y=np.array([ 5, 5, 5, 8, 8, 8, 2, 2, 2, 2, 2, 3, 3, 7])
x=np.array(range(len(y)))
#######
# This essentially selects the vertices that you'd touch streatching a 
# rubber band over the top of the function
vs = ConvexHull(np.asarray([x,y]).transpose()).vertices 
indices_of_upper_hull_verts = list(reversed(np.concatenate([vs[np.where(vs == len(x)-1)[0][0]: ],vs[0:1]])))

newX = x[indices_of_upper_hull_verts]
newY = y[indices_of_upper_hull_verts]
#########

x_smooth = np.linspace(newX.min(), newX.max(),500)
f = interp1d(newX, newY, kind='quadratic')
y_smooth=f(x_smooth)

plt.plot (x,y)
plt.plot (x_smooth,y_smooth)
plt.scatter (x, y)

产生:

enter image description here

更新:

这里有一个可能更适合你的选择。如果使用以1为中心的简单卷积而不是滚动平均值,则生成的曲线将始终大于输入。可以调整卷积内核的翅膀以进行前瞻/后视。

像这样:

import numpy as np 
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from scipy.ndimage.filters import convolve

## For interactive notebooks
#%matplotlib inline 

y=np.array([ 5, 5, 5, 8, 8, 8, 2, 2, 2, 2, 2, 3, 3, 7]).astype(np.float)

preLength = 1
postLength = 1
preWeight = 0.2
postWeight = 0.2

kernal = [preWeight/preLength for i in range(preLength)] + [1] + [postWeight/postLength for i in range(postLength)]

output = convolve(y,kernal)

x=np.array(range(len(y)))

plt.plot (x,y)
plt.plot (x,output)

plt.scatter (x, y)

enter image description here

缺点是因为集成内核通常大于1(这确保输出曲线平滑且从不低于输入),输出曲线将始终大于输入曲线,例如输入曲线。在大峰顶上,并没有像你画的那样坐在上面。

答案 2 :(得分:0)

作为对问题的一部分的初步尝试,我已经产生了一种函数,该函数通过最小化受到多项式严格高于点的约束的积分,使多项式拟合数据。我怀疑如果你对数据进行分段处理,它可能适合你。

import scipy.optimize

def upperpoly(xdata, ydata, order):
    def objective(p):
        """Minimize integral"""
        pint = np.polyint(p)
        integral = np.polyval(pint, xdata[-1]) - np.polyval(pint, xdata[0])
        return integral

    def constraints(p):
        """Polynomial values be > data at every point"""
        return np.polyval(p, xdata) - ydata

    p0 = np.polyfit(xdata, ydata, order)
    y0 = np.polyval(p0, xdata)
    shift = (ydata - y0).max()
    p0[-1] += shift

    result = scipy.optimize.minimize(objective, p0, 
                                     constraints={'type':'ineq', 
                                                  'fun': constraints})

    return result.x