模型数据最小二乘拟合

这是一篇针对物理学家 (例如物理学家、天文学家) 或工程师的 statsmodels 快速入门。

为什么需要它?

因为大多数 statsmodels 是由统计学家编写的,他们使用不同的术语,有时还使用不同的方法,因此很难知道哪些类和函数是相关的,以及它们的输入和输出含义。

[1]:
import numpy as np
import pandas as pd
import statsmodels.api as sm

线性模型

假设您有数据点,测量值为 y,位置为 x,测量误差为 y_err

如何使用 statsmodels 将直线模型拟合到此数据?

有关详细讨论,请参见 Hogg 等人 (2010),“数据分析食谱:将模型拟合到数据” …… 我们将使用他们在表 1 中给出的示例数据。

因此模型为 f(x) = a * x + b,在图 1 中,他们打印了我们想要复制的结果 …… 此数据的“标准加权最小二乘拟合”的最佳拟合参数和参数误差为:* a = 2.24 +- 0.11 * b = 34 +- 18

[2]:
data = """
  x   y y_err
201 592    61
244 401    25
 47 583    38
287 402    15
203 495    21
 58 173    15
210 479    27
202 504    14
198 510    30
158 416    16
165 393    14
201 442    25
157 317    52
131 311    16
166 400    34
160 337    31
186 423    42
125 334    26
218 533    16
146 344    22
"""
try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO
data = pd.read_csv(StringIO(data), delim_whitespace=True).astype(float)

# Note: for the results we compare with the paper here, they drop the first four points
data.head()
/tmp/ipykernel_4678/2751177292.py:28: FutureWarning: The 'delim_whitespace' keyword in pd.read_csv is deprecated and will be removed in a future version. Use ``sep='\s+'`` instead
  data = pd.read_csv(StringIO(data), delim_whitespace=True).astype(float)
[2]:
x y y_err
0 201.0 592.0 61.0
1 244.0 401.0 25.0
2 47.0 583.0 38.0
3 287.0 402.0 15.0
4 203.0 495.0 21.0

要拟合直线,请使用加权最小二乘类 WLS …… 参数称为:* exog = sm.add_constant(x) * endog = y * weights = 1 / sqrt(y_err)

请注意,exog 必须是一个二维数组,其中 x 作为一列,以及一列额外的 1。添加此列 1 表示您要拟合模型 y = a * x + b,将其省略表示您要拟合模型 y = a * x

您必须使用选项 cov_type='fixed scale' 来告诉 statsmodels 您确实有绝对尺度的测量误差。如果您没有,statsmodels 会将权重视为数据点之间的相对权重,并在内部对其进行重新缩放,以便最佳拟合模型的 chi**2 / ndf = 1

[3]:
exog = sm.add_constant(data["x"])
endog = data["y"]
weights = 1.0 / (data["y_err"] ** 2)
wls = sm.WLS(endog, exog, weights)
results = wls.fit(cov_type="fixed scale")
print(results.summary())
                            WLS Regression Results
==============================================================================
Dep. Variable:                      y   R-squared:                       0.400
Model:                            WLS   Adj. R-squared:                  0.367
Method:                 Least Squares   F-statistic:                     193.5
Date:                Thu, 03 Oct 2024   Prob (F-statistic):           4.52e-11
Time:                        15:50:45   Log-Likelihood:                -119.06
No. Observations:                  20   AIC:                             242.1
Df Residuals:                      18   BIC:                             244.1
Df Model:                           1
Covariance Type:          fixed scale
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const        213.2735     14.394     14.817      0.000     185.062     241.485
x              1.0767      0.077     13.910      0.000       0.925       1.228
==============================================================================
Omnibus:                        0.943   Durbin-Watson:                   2.901
Prob(Omnibus):                  0.624   Jarque-Bera (JB):                0.181
Skew:                          -0.205   Prob(JB):                        0.914
Kurtosis:                       3.220   Cond. No.                         575.
==============================================================================

Notes:
[1] Standard Errors are based on fixed scale

与 scipy.optimize.curve_fit 对比

[4]:
# You can use `scipy.optimize.curve_fit` to get the best-fit parameters and parameter errors.
from scipy.optimize import curve_fit


def f(x, a, b):
    return a * x + b


xdata = data["x"]
ydata = data["y"]
p0 = [0, 0]  # initial parameter estimate
sigma = data["y_err"]
popt, pcov = curve_fit(f, xdata, ydata, p0, sigma, absolute_sigma=True)
perr = np.sqrt(np.diag(pcov))
print("a = {0:10.3f} +- {1:10.3f}".format(popt[0], perr[0]))
print("b = {0:10.3f} +- {1:10.3f}".format(popt[1], perr[1]))
a =      1.077 +-      0.077
b =    213.273 +-     14.394

与自写成本函数对比

[5]:
# You can also use `scipy.optimize.minimize` and write your own cost function.
# This does not give you the parameter errors though ... you'd have
# to estimate the HESSE matrix separately ...
from scipy.optimize import minimize


def chi2(pars):
    """Cost function."""
    y_model = pars[0] * data["x"] + pars[1]
    chi = (data["y"] - y_model) / data["y_err"]
    return np.sum(chi ** 2)


result = minimize(fun=chi2, x0=[0, 0])
popt = result.x
print("a = {0:10.3f}".format(popt[0]))
print("b = {0:10.3f}".format(popt[1]))
a =      1.077
b =    213.274

非线性模型

[6]:
# TODO: we could use the examples from here:
# http://probfit.readthedocs.org/en/latest/api.html#probfit.costfunc.Chi2Regression

最后更新:2024 年 10 月 3 日