You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用scipy.interpolate.splrep拟合线性样条出现大量NaN系数求助

问题描述

我有一组数据,手动可轻松拟合带单个内部节点的线性样条,但使用scipy.interpolate.splrep拟合时,无论设置何种平滑参数都无法正常工作,系数中出现大量NaN。编写迭代尝试不同平滑参数的代码直到抛出RuntimeWarning,结果仍存在大量NaN;为初始点设置高权重也无效。手动指定节点(如t=[75.0])时拟合可成功,但需要无需人工干预的可重复自动化方案,不限于scipy库。

相关代码:

import math
import itertools
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from scipy.interpolate import splrep, splev


def is_sorted(a):
    return np.all(a[:-1] <= a[1:])


def fit_spline(x, y):
    assert(is_sorted(x))
    
    best_n_nans = 2*len(x)
    for log_s in itertools.count(0.01, 0.0001):
        try:
            spl = splrep(x, y, s=math.exp(log_s), k=1)
        except RuntimeWarning:
            break
        
        n_nans = sum(math.isnan(t) for t in spl[1])
        if n_nans < best_n_nans:
            best_fit = spl
            best_n_nans = n_nans
            print(f"Found fit with {n_nans} nans")
            
        if len(spl[0]) <= 5:
            break
    return best_fit

matplotlib.use("Qt5Agg")
plot = plt.figure()
x = np.array([  0. ,   0. ,   0. ,   0. ,   0. ,   0.5,   1.2,   2.3,   2.6,
         3. ,   3.1,   3.1,   3.1,   5. ,   5.1,   5.2,   5.6,   6.3,
         6.5,   6.9,   9.6,   9.8,  10. ,  10.3,  10.7,  11.3,  11.3,
        12.4,  12.4,  12.4,  12.5,  12.5,  13.1,  14.2,  15.4,  15.7,
        16.1,  17.7,  18.5,  18.6,  20.5,  21.3,  22.7,  23. ,  24.3,
        26.3,  26.3,  26.6,  27. ,  27.7,  28.7,  31. ,  31. ,  31.1,
        31.1,  31.6,  32.7,  34.4,  34.5,  35.7,  35.7,  38.7,  39.8,
        43.9,  43.9,  46.9,  51. ,  52.6,  52.6,  53.3,  53.7,  54.5,
        57.7,  61.2,  62.5,  65. ,  66. ,  68.1,  70.8,  74.3,  74.6,
        76.3,  83.1,  85.7,  87.5,  90.8,  91. ,  92.2,  94.3,  94.6,
       101.1, 104.8, 109.6, 113.3, 114.8, 119. , 120.5, 121.1, 126.1,
       133.2, 136.2, 137.1, 138.5, 141.4, 144.6, 145.1, 145.3, 150.5,
       160.7, 167.4, 168.8, 170.5, 170.5, 173.3, 177. , 181.3, 185.1,
       193. , 200.3, 201.8, 204.3, 208.4, 209.9, 223.7, 230.9, 240.3,
       243.9, 250.6, 263.1])
y = np.array([ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  7,  6,
        0,  0,  0,  9,  0,  0,  0,  0,  0,  0,  9,  1,  0,  3,  0,  9,  2,
       10,  0, 14, 19,  7, 10, 18, 22, 31, 12, 29, 16, 20,  2, 38, 26, 28,
       26, 25, 17, 35, 20, 29, 39, 51, 29, 27, 43, 38, 40, 47, 41, 39, 54,
       44, 53, 65, 50, 59, 51, 62, 69, 74, 66])

spl = fit_spline(x, y)
y2 = splev(x, spl)
plt.plot(x, y, 'o', x, y2)
plot.show()

拟合得到的系数存在大量NaN:

array([nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, 35., 38.,
       40., 47., 41., 39., 54., 44., 53., 65., 50., 59., 51., 62., 69.,
       74., 66.,  0.,  0.])

解决方案

方案1:基于数据特征自动找拐点,手动构造线性样条

你的数据前半段y全为0,后半段y开始上升,拐点非常明显。可以直接通过检测第一个非零y值对应的x位置作为内部节点,然后分两段做线性拟合:

import numpy as np
import matplotlib.pyplot as plt

# 原始数据
x = np.array([  0. ,   0. ,   0. ,   0. ,   0. ,   0.5,   1.2,   2.3,   2.6,
         3. ,   3.1,   3.1,   3.1,   5. ,   5.1,   5.2,   5.6,   6.3,
         6.5,   6.9,   9.6,   9.8,  10. ,  10.3,  10.7,  11.3,  11.3,
        12.4,  12.4,  12.4,  12.5,  12.5,  13.1,  14.2,  15.4,  15.7,
        16.1,  17.7,  18.5,  18.6,  20.5,  21.3,  22.7,  23. ,  24.3,
        26.3,  26.3,  26.6,  27. ,  27.7,  28.7,  31. ,  31. ,  31.1,
        31.1,  31.6,  32.7,  34.4,  34.5,  35.7,  35.7,  38.7,  39.8,
        43.9,  43.9,  46.9,  51. ,  52.6,  52.6,  53.3,  53.7,  54.5,
        57.7,  61.2,  62.5,  65. ,  66. ,  68.1,  70.8,  74.3,  74.6,
        76.3,  83.1,  85.7,  87.5,  90.8,  91. ,  92.2,  94.3,  94.6,
       101.1, 104.8, 109.6, 113.3, 114.8, 119. , 120.5, 121.1, 126.1,
       133.2, 136.2, 137.1, 138.5, 141.4, 144.6, 145.1, 145.3, 150.5,
       160.7, 167.4, 168.8, 170.5, 170.5, 173.3, 177. , 181.3, 185.1,
       193. , 200.3, 201.8, 204.3, 208.4, 209.9, 223.7, 230.9, 240.3,
       243.9, 250.6, 263.1])
y = np.array([ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  7,  6,
        0,  0,  0,  9,  0,  0,  0,  0,  0,  0,  9,  1,  0,  3,  0,  9,  2,
       10,  0, 14, 19,  7, 10, 18, 22, 31, 12, 29, 16, 20,  2, 38, 26, 28,
       26, 25, 17, 35, 20, 29, 39, 51, 29, 27, 43, 38, 40, 47, 41, 39, 54,
       44, 53, 65, 50, 59, 51, 62, 69, 74, 66])

# 自动定位拐点:第一个y>0的位置对应的x值
non_zero_idx = np.argmax(y > 0)
knot_x = x[non_zero_idx]

# 拆分数据为两段
x_segment1 = x[x <= knot_x]
y_segment1 = y[x <= knot_x]
x_segment2 = x[x >= knot_x]
y_segment2 = y[x >= knot_x]

# 分别拟合线性模型
coeffs1 = np.polyfit(x_segment1, y_segment1, 1)
coeffs2 = np.polyfit(x_segment2, y_segment2, 1)

# 生成拟合曲线
x_fit = np.linspace(x.min(), x.max(), 500)
y_fit = np.where(x_fit <= knot_x,
                 np.polyval(coeffs1, x_fit),
                 np.polyval(coeffs2, x_fit))

# 可视化
plt.figure()
plt.plot(x, y, 'o', label='原始数据')
plt.plot(x_fit, y_fit, '-', linewidth=2, label='自动拟合线性样条')
plt.legend()
plt.show()

这个方案完全自动化,利用数据的特征直接定位节点,拟合结果和手动操作一致,不会出现NaN问题。

方案2:使用scipy.interpolate.LSQUnivariateSpline约束节点数量

如果偏好使用scipy的样条工具,LSQUnivariateSpline可以指定固定数量的内部节点,避免splrep的自动节点选择导致的NaN问题。通过一阶差分自动定位初始节点,再让算法优化到最优位置:

from scipy.interpolate import LSQUnivariateSpline
import numpy as np
import matplotlib.pyplot as plt

# 原始数据
x = np.array([  0. ,   0. ,   0. ,   0. ,   0. ,   0.5,   1.2,   2.3,   2.6,
         3. ,   3.1,   3.1,   3.1,   5. ,   5.1,   5.2,   5.6,   6.3,
         6.5,   6.9,   9.6,   9.8,  10. ,  10.3,
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 23:45:36