首页 > 解决方案 > 为什么 statsmodels 的 OLS 中的四次线性回归与 LibreOffice Calc 不匹配?

问题描述

我正在使用 statsmodels 的 OLS 线性回归和 Patsy 四次公式y ~ x + I(x**2) + I(x**3) + I(x**4),但与 LibreOffice Calc 相比,所得回归与数据的拟合度较差。为什么这与 LibreOffice Calc 生成的不匹配?

统计模型代码:

import io
import numpy
import pandas
import matplotlib
import matplotlib.offsetbox
import statsmodels.tools
import statsmodels.formula.api

csv_data = """Year,CrudeRate
1999,197.0
2000,196.5
2001,194.3
2002,193.7
2003,192.0
2004,189.2
2005,189.3
2006,187.6
2007,186.9
2008,186.0
2009,185.0
2010,186.2
2011,185.1
2012,185.6
2013,185.0
2014,185.6
2015,185.4
2016,185.1
2017,183.9
"""

df = pandas.read_csv(io.StringIO(csv_data))

cause = "Malignant neoplasms"
x = df["Year"].values
y = df["CrudeRate"].values

olsdata = {"x": x, "y": y}
formula = "y ~ x + I(x**2) + I(x**3) + I(x**4)"
model = statsmodels.formula.api.ols(formula, olsdata).fit()

print(model.params)

df.plot("Year", "CrudeRate", kind="scatter", grid=True, title="Deaths from {}".format(cause))

func = numpy.poly1d(model.params.values[::-1])
matplotlib.pyplot.plot(df["Year"], func(df["Year"]))

matplotlib.pyplot.show()

产生以下系数:

Intercept    9.091650e-08
x            9.127904e-05
I(x ** 2)    6.109623e-02
I(x ** 3)   -6.059164e-05
I(x ** 4)    1.503399e-08

以及下图:

图1

但是,如果我将数据带入 LibreOffice Calc,单击绘图并选择“插入趋势线...”,选择“多项式”,输入“度数”=4,然后选择“显示方程”,得到的趋势线是与 statsmodels 不同,似乎更适合:

图2

系数是:

Intercept = 1.35e10
x =          2.69e7
x^2 =       -2.01e4
x^3 =          6.69
x^4 =      -0.83e-3

统计模型版本:

$ pip3 list | grep statsmodels
statsmodels                  0.9.0

编辑:三次也不匹配,但二次匹配。

编辑:缩小Year(并在 LibreOffice 中做同样的事情)匹配:

df = pandas.read_csv(io.StringIO(csv_data))
df["Year"] = df["Year"] - 1998

缩小后的系数和绘图:

Intercept    197.762384
x             -0.311548
I(x ** 2)     -0.315944
I(x ** 3)      0.031304
I(x ** 4)     -0.000833

图3

标签: pythonpandascurve-fittingstatsmodels

解决方案


根据@Josef 的评论,问题在于大数不适用于高阶多项式,并且 statsmodels 不能自动缩放域。另外,我在原始问题中没有提到这一点,因为我没想到需要转换域,但我还需要根据年份预测样本外值,所以我将其设为范围结束:

predict_x = +5
min_scaled_domain = -1
max_scaled_domain = +1
df["Year"] = df["Year"].transform(lambda x: numpy.interp(x, (x.min(), x.max() + predict_x), (min_scaled_domain, max_scaled_domain)))

这种转换创建了一个拟合良好的回归:

图4

如果在 LibreOffice Calc 中应用相同的域变换,则系数匹配。

最后,打印预测值:

func = numpy.polynomial.Polynomial(model.params)
print(func(max_scaled_domain))

推荐阅读