如何将样条拟合转换为分段函数?

Cle*_*leb 5 python numpy scipy piecewise

假设我有

import numpy as np
from scipy.interpolate import UnivariateSpline

# "true" data; I don't know this function
x = np.linspace(0, 100, 1000)
d = np.sin(x * 0.5) + 2 + np.cos(x * 0.1)

# sample data; that's what I actually measured
x_sample = x[::20]
d_sample = d[::20]

# fit spline
s = UnivariateSpline(x_sample, d_sample, k=3, s=0.005)

plt.plot(x, d)
plt.plot(x_sample, d_sample, 'o')
plt.plot(x, s(x))
plt.show()
Run Code Online (Sandbox Code Playgroud)

我得到

在此处输入图片说明

我现在想要的是所有橙色点之间的功能,所以像

knots = s.get_knots()
f0 = <some expression> for knots[0] <= x < knots[1]
f1 = <some expression> for knots[1] <= x < knots[2]
...
Run Code Online (Sandbox Code Playgroud)

因此,fi应该以再现样条拟合形状的方式进行选择。

我在这里找到了这篇文章,但是在那里生成的样条线对于上面的示例似乎不正确,而且它也不完全是我需要的,因为它不返回表达式。

我怎么能把样条变成分段函数?是否有(直接)方式来表达每个区间,例如作为多项式?

ste*_*emo 6

简短的回答是,如果您对标准幂基多项式的系数感兴趣,最好使用CubicSpline(并参阅此讨论):

cu = scipy.interpolate.CubicSpline(x_sample, d_sample)

plt.plot(x_sample, y_sample, 'ko')
for i in range(len(cu.x)-1):
    xs = np.linspace(cu.x[i], cu.x[i+1], 100)
    plt.plot(xs, np.polyval(cu.c[:,i], xs - cu.x[i]))
Run Code Online (Sandbox Code Playgroud)

三次样条分段

为了回答您的问题,您可以从这里使用numpy.piecewise、 中的断点cu.x和 中的系数创建分段函数cu.c,然后直接自己编写多项式表达式或使用numpy.polyval。例如,

cu.c[:,0]  # coeffs for 0th segment
# array([-0.01316353, -0.02680068,  0.51629024,  3.        ])

# equivalent ways to code polynomial for this segment
f0 = lambda x: cu.c[0,0]*(x-x[0])**3 + cu.c[1,0]*(x-x[0])**2 + cu.c[2,0]*(x-x[0]) + cu.c[3,0]
f0 = lambda x: [cu.c[i,0]*(x-x[0])**(3-i) for i in range(4)]

# ... or getting values directly from x's
y0 = np.polyval(cu.c[:,0], xs - cu.x[0])
Run Code Online (Sandbox Code Playgroud)

更长的答案:

这里有几个潜在的混淆点:

  • UnivariateSpline拟合B 样条基,因此系数与标准多项式幂基不同
  • 为了从 B-spline 转换,我们可以使用PPoly.from_spline,但不幸的是UnivariateSpline返回一个截断的节点和系数列表,这些节点和系数不会与这个函数一起使用。我们可以通过访问 spline 对象的内部数据来解决这个问题,这有点忌讳。
  • 此外,系数矩阵c(无论是 fromUnivariateSpline还是CubicSpline)的度数顺序相反,并假设您“居中”自己,例如系数 atc[k,i]属于c[k,i]*(x-x[i])^(3-k)

根据您的设置,请注意,如果我们不使用 UnivariateSpline 包装器,而是直接拟合而不使用splrep平滑 ( s=0),我们可以获取tck(knots-coefficients-degree) 元组并将其发送到PPoly.from_spline函数并获得我们想要的系数:

tck = scipy.interpolate.splrep(x_sample, d_sample, s=0)
tck
# (array([0.        , 0.        , 0.        , 0.        , 2.68456376,
#        4.02684564, 5.36912752, 6.7114094 , 9.39597315, 9.39597315,
#        9.39597315, 9.39597315]),
# array([3.        , 3.46200469, 4.05843704, 3.89649312, 3.33792889,
#        2.29435138, 1.65015175, 1.59021688, 0.        , 0.        ,
#        0.        , 0.        ]),
# 3)

p = scipy.interpolate.PPoly.from_spline(tck)
p.x.shape  # breakpoints in unexpected shape
# (12,)

p.c.shape  # polynomial coeffs in unexpected shape
# (4, 11)
Run Code Online (Sandbox Code Playgroud)

注意怪异重复断点tck,又在p.x:这是一个FITPACK事情(算法运行这一切)。

如果我们尝试tck从 UnivariateSpline发送一个带有 的元组(s.get_knots(), s.get_coeffs(), 3),我们就会错过那些重复,所以from_spline不起作用。尽管看起来完整的向量存储在 中,但检查self._data,所以我们可以

s = scipy.interpolate.UnivariateSpline(x_sample, d_sample, s=0)
tck = (s._data[8], s._data[9], 3)
p = scipy.interpolate.PPoly.from_spline(tck)
Run Code Online (Sandbox Code Playgroud)

并获得与以前相同的结果。要检查这些系数是否有效:

plt.plot(x_sample, d_sample, 'o')

for i in range(len(p.x)-1):
    xs = np.linspace(p.x[i], p.x[i+1], 10)
    plt.plot(xs, np.polyval(p.c[:,i], xs - p.x[i]))
Run Code Online (Sandbox Code Playgroud)

分段样条

Notenumpy.polyval希望 coeffs 的顺序相反,因此我们可以按原样传递p.c