在图中显示lmfit-model中心的误差

Showing the error of lmfit-model center in plot

提问人:Gargantua 提问时间:7/10/2023 更新时间:7/10/2023 访问量:77

问:

我正在使用 python lmfit 包进行数据拟合。我正在查看几个峰值,并用伪 Voigt + 常数模型拟合它们。然后,我提取拟合峰的中心位置,并编译大约一百个这样的峰位置。然后绘制一百个位置,并用正弦波模型(也使用 lmfit)进行拟合。 我的问题是,我无法找到我需要的信息来显示从伪Voigt拟合中提取的峰值位置的不确定性。在许多类似的问题中,我遇到了 lmfit 包中也包含的 eval_uncertainty 函数,但我无法使用它来提取中心位置的置信区间,只能提取峰值振幅和 sigma 值的不确定性。

理想的结果如下图所示: 我试图实现的理想情况

我的当前图表如下所示: 当前图表

显然,我需要从散点图切换到误差线图,但我目前没有任何错误间隔可供使用。

以下是我能构建的最简单可行的“迷你”案例,同时仍然包括我原始的适合模型。我的原始数据大约是 100 个数据点,而不是 4 个,因此在这种较小情况下的正弦波不是“平滑”的,但这应该不会影响引入误差线的方法。

import numpy as np
import matplotlib.pyplot as plt
from lmfit.models import PseudoVoigtModel, ConstantModel, SineModel

def Voigt_app(x,y,a,b):
    
    psvo_mod = PseudoVoigtModel(prefix='psvo_')
    pars = psvo_mod.guess(y,x=x)
    
    con_mod = ConstantModel(prefix='con_')
    pars = pars.update(con_mod.make_params(c = (y[0]+y[1])/2))
    
    mod = psvo_mod + con_mod

    out = mod.fit(y, pars, x=x)
    
    details = out.fit_report(min_correl=0.25)
    
    return out, details

def Sin_app(x,y):
    sin_mod = SineModel(prefix='sin_')
    pars = sin_mod.guess(y,x=x)
    
    con_mod = ConstantModel(prefix='con_')
    pars = pars.update(con_mod.make_params(c = np.mean(y)))
    
    mod = sin_mod + con_mod

    out = mod.fit(y,pars,x=x)
    details = out.fit_report(min_correl=0.25)
                       
    return out, details

data = np.asarray(
      [[14407.0 , 26989.0 , 31026.0 , 31194.0 , 31302.0 , 91996.0 , 112250.0 , 112204.0 , 113431.0 , 75097.0 , 55942.0 , 55826.0 , 56181.0 , 28266.0 , 14687.0],
       [12842.0 , 13275.0 , 13581.0 , 13943.0 , 14914.0 , 20463.0 , 21132.0 , 21457.0 , 23308.0 , 32017.0 , 30808.0 , 30927.0 , 30337.0 , 21025.0 , 15216.0],
       [15770.0 , 17677.0 , 19008.0 , 20911.0 , 25958.0 , 27984.0 , 28164.0 , 31161.0 , 33517.0 , 29430.0 , 28673.0 , 32725.0 , 28495.0 , 24527.0 , 25173.0],
       [16299.0 , 20067.0 , 25102.0 , 34968.0 , 37757.0 , 44871.0 , 51347.0 , 60154.0 , 54985.0 , 53383.0 , 45776.0 , 40836.0 , 30816.0 , 27922.0 , 26066.0]])
a,b = 1932, 1947
centers = []

colors = ['red','black','green','blue']
fig, ax = plt.subplots()
for i in range(4):
    ax.scatter(np.arange(15),data[i,:],label=f'data {i}',color=colors[i])
    out, details = Voigt_app(x,data[i,:],a,b)
    ax.plot(out.best_fit,label=f'fit {i}',color=colors[i])
    centers.append(out.values['psvo_center'])
ax.set_title('Data points and pseudo-Voigt fits')
ax.legend()


fig, ax = plt.subplots()
ax.scatter(np.arange(4),centers,color='red',label='data')

out, details = Sin_app(np.arange(4),np.asarray(centers))
ax.plot(np.arange(4),out.best_fit,color='black',label='fit')
ax.set_title('Peak positions and sine-wave fit')
ax.legend()

由此,我想将第二张图中的散点图与误差线图切换为伪Voigt峰值位置的置信区间(“out.values['psvo_center']”)。

python matplotlib 错误栏 lmfit

评论


答:

0赞 Noname NoSurname 7/10/2023 #1

以下是修改 Voigt_app 函数以执行此操作的方法:

def Voigt_app(x,y,a,b):
    
    psvo_mod = PseudoVoigtModel(prefix='psvo_')
    pars = psvo_mod.guess(y,x=x)
    
    con_mod = ConstantModel(prefix='con_')
    pars = pars.update(con_mod.make_params(c = (y[0]+y[1])/2))
    
    mod = psvo_mod + con_mod

    out = mod.fit(y, pars, x=x)
    
    # Calculate the confidence interval for the fitted parameters
    ci = lmfit.conf_interval(out, out)
    print(lmfit.printfuncs.report_ci(ci))

    # Extract the uncertainty for 'psvo_center'
    center_err = (ci['psvo_center'][2][1] - ci['psvo_center'][1][1])/2

    details = out.fit_report(min_correl=0.25)
    
    return out, details, center_err

然后,您可以在绘制误差线时使用这些不确定性:

center_errs = []
for i in range(4):
    out, details, center_err = Voigt_app(x, data[i,:], a, b)
    # ...
    center_errs.append(center_err)

# ...

ax.errorbar(np.arange(4), centers, yerr=center_errs, fmt='o', color='red', label='data')

评论

0赞 M Newville 7/10/2023
您可以像这样显式计算置信区间,但如果您希望在参数中使用 1-sigma 不确定性,这可能有点矫枉过正。默认情况下,对于可以确定参数值不确定性的拟合,该属性已经具有估计的 1-sigma 不确定性,并且比使用 -- 要快得多,后者要求参数具有属性,以便对每个参数的置信水平进行更慢的搜索。psvo_centerout.params['psvo_center'].stderrconf_intervalsstderr
0赞 M Newville 7/10/2023 #2

如果我正确理解了您的问题,您不仅要提取每个拟合的“psvo_center”参数的最佳拟合值,还要提取“psvo_center”的标准误差,然后将这些值用作拟合正弦振荡的数据。

如果这是正确的,我认为您要做的是通过将代码更改为stderr

centers = []
stderrs = []  # < new

colors = ['red','black','green','blue']
fig, ax = plt.subplots()
for i in range(4):
    ax.scatter(np.arange(15),data[i,:],label=f'data {i}',color=colors[i])
    out, details = Voigt_app(np.arange(15), data[i,:], a, b)
    ax.plot(out.best_fit,label=f'fit {i}',color=colors[i])
    centers.append(out.params['psvo_center'].value)       # < new 
    stderrs.append(out.params['psvo_center'].stderr)      # < new

然后,您可以使用它来制作误差线图

ax.errorbar(np.arange(4), centers, stderrs, color='red',label='data')

您可能还想使用它们来加权拟合到正弦函数,也许可以修改您的stderrsSin_App


def Sin_app(x, y, dely):       # < new
    sin_mod = SineModel(prefix='sin_')
    pars = sin_mod.guess(y,x=x)

    con_mod = ConstantModel(prefix='con_')
    pars = pars.update(con_mod.make_params(c = np.mean(y)))

    mod = sin_mod + con_mod

    out = mod.fit(y, pars, x=x, weights=1./dely)    # < new
    details = out.fit_report(min_correl=0.25)
    return out, details

并用

out, details = Sin_app(np.arange(4), np.asarray(centers), np.asarray(stderrs))

对于您给出的 4 个示例数据集,标准误差的变化不是很大,因此不会产生太大差异。但随着您添加更多数据集,它可能会变得更加重要。在使用此“生产中”之前,您可能希望添加有效的检查(例如、不是和不)。stderrNone0

对于“另一件事”,您可能还想计算正弦函数中的预测不确定性并绘制该范围。您的示例有 4 个正弦拟合变量,只有 4 个值,在这种情况下,结果是近乎完美的拟合,拟合正弦函数的不确定性小得离谱。因此,为了说明这种方法,我在下面的拟合中修复了参数。如果拟合超过4个峰曲线的结果,则没有必要这样做。但是,为了说明, 把你改成con_cSin_app

def Sin_app(x, y, dely):
    sin_mod = SineModel(prefix='sin_')
    pars = sin_mod.guess(y,x=x)

    con_mod = ConstantModel(prefix='con_')
    pars = pars.update(con_mod.make_params(c = np.mean(y)))

    pars['con_c'].vary = False  # needed with only 4 (x,y) values

    mod = sin_mod + con_mod

    out = mod.fit(y, pars, x=x, weights=1./dely)
    details = out.fit_report(min_correl=0.25)

    delta_fit = out.eval_uncertainty(sigma=1)   # < new
    return out, details, delta_fit

然后绘制使用它的结果


out, details, delta_fit = Sin_app(np.arange(4), np.asarray(centers), np.asarray(stderrs))

ax.plot(np.arange(4),out.best_fit, 'o', color='black',label='fit')

ax.fill_between(np.arange(4), 
                out.best_fit-delta_fit, out.best_fit+delta_fit,
                color="#D7D7D7")

ax.set_title('Peak positions and sine-wave fit')
ax.legend()

这导致了以下最终情节:

plot of final fit of peak centers to sinusoidal function

显示带有误差线的峰中心的“数据”、拟合值和拟合的 1-sigma 范围。

评论

0赞 Gargantua 7/11/2023
谢谢!这非常有效,我希望能够避免使用conf_interval功能,因为这为该过程增加了很多时间,这是实现这一目标的完美方法。正如您提到的,我在使用实际数据集后立即遇到了 None/0 问题。现在我只是将该值设置为任意低值作为临时解决方法,是否有使用的标准程序来避免这种情况?或者在这种情况下,数据集是问题所在吗?
0赞 Gargantua 7/11/2023
在进一步研究了这一点之后,我意识到 None-parameter 是一个数据太像峰值的问题,根本无法拟合峰值,而不是非常好的拟合,因此将其设置为任意低数字与适当数字完全相反。会进一步调查,谢谢你的帮助。
0赞 M Newville 7/12/2023
我可能会建议首先标记并忽略 None of 0(或不可用)的 stderr 的拟合。(另一个提示:您可以测试是否计算了拟合的不确定性)。如果它只是拟合的百分之几,您可能可以将它们作为“不良数据/拟合”扔掉。话又说回来,研究为什么事情有时会出错可能非常有启发性,有时(很少,但并非永远不会)变得比最初的任务或你试图看到的结果更有趣。result.errorbars