Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/283.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 严格在原始曲线上方平滑曲线的算法_Python_Algorithm_Pandas_Numpy_Smoothing - Fatal编程技术网

Python 严格在原始曲线上方平滑曲线的算法

Python 严格在原始曲线上方平滑曲线的算法,python,algorithm,pandas,numpy,smoothing,Python,Algorithm,Pandas,Numpy,Smoothing,我有一个任意的输入曲线,以numpy数组的形式给出。我想创建一个平滑的版本,类似于滚动平均值,但它严格大于原始值,并且严格平滑。我可以使用滚动平均值,但如果输入曲线有一个负峰值,平滑后的版本将下降到该峰值附近的原始值以下。然后,我可以简单地使用这个和原始的最大值,但这会在过渡发生的地方引入非光滑点 此外,我希望能够通过向前看和向后看来对算法进行参数化,这样,如果向前看和向后看较大,则生成的曲线更倾向于保持下降边缘,向前看较大和较小,则更倾向于接近上升边缘 我尝试使用pandas.Series(a

我有一个任意的输入曲线,以numpy数组的形式给出。我想创建一个平滑的版本,类似于滚动平均值,但它严格大于原始值,并且严格平滑。我可以使用滚动平均值,但如果输入曲线有一个负峰值,平滑后的版本将下降到该峰值附近的原始值以下。然后,我可以简单地使用这个和原始的最大值,但这会在过渡发生的地方引入非光滑点

此外,我希望能够通过向前看和向后看来对算法进行参数化,这样,如果向前看和向后看较大,则生成的曲线更倾向于保持下降边缘,向前看较大和较小,则更倾向于接近上升边缘

我尝试使用
pandas.Series(a).rolling()
工具获得滚动平均值、滚动最大值等,但到目前为止,我还没有找到方法生成平滑版本的输入,在所有情况下都保持在上面的输入

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

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:]
但是现在平滑的版本低于原始版本,即。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 ])
这是曲线的图像。白线是原始的(输入),红色是滚动的意思,绿色是我想要的:

编辑:我刚刚找到名为
peakutils
的模块的函数
baseline()
envelope()
。这两个函数可以计算一个给定阶的多项式来拟合较低的阶。输入的上峰值。对于小样本,这是一个很好的解决方案。我正在寻找一些也可以应用于具有数百万值的非常大的样本的东西;然后,学位需要非常高,计算也需要相当长的时间。分段操作(一节接一节)会带来一系列新的问题和问题(比如如何在保持平滑的同时保证输入,处理大量工件时的性能等),因此如果可能的话,我希望避免这种情况

编辑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))
生成的图像如下所示:


这种方法有两个方面我不喜欢:① 它需要大量的迭代时间(这会减慢我的速度),② 山谷中仍然有不光滑的部分(尽管在我的使用案例中,这可能是可以接受的)。

正如我在注释中指出的,在八高高原前后,绿线的行为是不一致的。如果左区域行为是您想要的,您可以执行以下操作:

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)
这将产生:

更新:

这里有一个更适合你的选择。如果使用以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)


一个缺点是,由于集成内核通常大于1(这确保了输出曲线平滑且不低于输入),因此输出曲线总是大于输入曲线,例如,在绘制时位于大峰值的顶部,而不是正好位于顶部。

作为问题的一部分,我已经生成了一个函数,它通过最小化积分,使多项式严格高于点,从而使多项式与数据相匹配。我怀疑如果你对你的数据进行分段处理,它可能对你有用

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

我现在已经玩了很多,我想我找到了两个主要的答案,解决了我的直接需求。我将在下面给出它们

import numpy as np
import pandas as pd
from scipy import signal
import pyqtgraph as pg
这些只是必要的导入,用于所有代码清理<当然,code>pyqtgraph仅用于显示内容,因此您并不真正需要它

对称平滑 这可以用来创建一条始终位于信号上方的平滑线,但它无法区分上升沿和下降沿,因此单峰周围的曲线看起来是对称的。在许多情况下,这可能是很好的,因为它比下面的不对称解决方案要简单得多(而且我也不知道有什么怪癖)

  • 创建滚动最大值(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 = 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)
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)