为啥当 fit_intercept=False 时 Sklearn R-squared 与 statsmodels 不同?

Posted

技术标签:

【中文标题】为啥当 fit_intercept=False 时 Sklearn R-squared 与 statsmodels 不同?【英文标题】:Why is Sklearn R-squared different from that of statsmodels when fit_intercept=False?为什么当 fit_intercept=False 时 Sklearn R-squared 与 statsmodels 不同? 【发布时间】:2022-01-07 18:07:54 【问题描述】:

我正在使用 Sklearn 和 statsmodels 执行线性回归。

我知道 Sklearn 和 statsmodels 产生相同的结果。如下图所示,Sklearn 和 statsmodels 得到了相同的结果,但是在 Sklearn 中使用fit_intercept=False 时,即使截距为零时系数相同,结果也不同。

你能解释一下原因吗?或者当我在 Sklearn 中使用fit_intercept=False 时给我任何方法。

import numpy as np
import statsmodels.api as sm
from sklearn.linear_model import LinearRegression

# dummy data:
y = np.array([1,3,4,5,2,3,4])
X = np.array(range(1,8)).reshape(-1,1) # reshape to column

# intercept is not zero : the result are the same
# scikit-learn:
lr = LinearRegression()
lr.fit(X,y)
print(lr.score(X,y))
# 0.16118421052631582

# statsmodels
X_ = sm.add_constant(X)
model = sm.OLS(y,X_)
results = model.fit()
print(results.rsquared)
# 0.16118421052631582

# intercept is zero : the result are different
# scikit-learn:
lr = LinearRegression(fit_intercept=False)
lr.fit(X,y)
print(lr.score(X,y))
# -0.4309210526315792

# statsmodels
model = sm.OLS(y,X)
results = model.fit()
print(results.rsquared)
# 0.8058035714285714

【问题讨论】:

【参考方案1】:

不一致是因为statsmodels 根据模型是否包含截距使用不同的公式计算 R 平方。如果包括截距,statsmodels 将残差平方和除以居中的总平方和,而如果不包括截距,statsmodels 将残差平方和除以未居中的总平方和。这意味着statsmodels使用以下公式计算R平方,可以在documentation中找到:

import numpy as np

def rsquared(y_true, y_pred, fit_intercept=True):
    '''
    Statsmodels R-squared, see https://www.statsmodels.org/dev/generated/statsmodels.regression.linear_model.RegressionResults.rsquared.html.
    '''
    if fit_intercept:
        return 1 - np.sum((y_true - y_pred) ** 2) / np.sum((y_true - np.mean(y_true)) ** 2)
    else:
        return 1 - np.sum((y_true - y_pred) ** 2) / np.sum(y_true ** 2)

另一方面,sklearn 总是在分母处使用居中的总平方和,而不管截距是否实际包含在模型中(即不管是 fit_intercept=True 还是 fit_intercept=False)。另见this answer。

import numpy as np
import statsmodels.api as sm
from sklearn.linear_model import LinearRegression

def rsquared(y_true, y_pred, fit_intercept=True):
    '''
    Statsmodels R-squared, see https://www.statsmodels.org/dev/generated/statsmodels.regression.linear_model.RegressionResults.rsquared.html.
    '''
    if fit_intercept:
        return 1 - np.sum((y_true - y_pred) ** 2) / np.sum((y_true - np.mean(y_true)) ** 2)
    else:
        return 1 - np.sum((y_true - y_pred) ** 2) / np.sum(y_true ** 2)

# dummy data:
y = np.array([1, 3, 4, 5, 2, 3, 4])
X = np.array(range(1, 8)).reshape(-1, 1) # reshape to column

# intercept is not zero: the result are the same
# scikit-learn:
lr = LinearRegression(fit_intercept=True)
lr.fit(X, y)
print(lr.score(X, y))
# 0.16118421052631582
print(rsquared(y, lr.predict(X), fit_intercept=True))
# 0.16118421052631582

# statsmodels
X_ = sm.add_constant(X)
model = sm.OLS(y, X_)
results = model.fit()
print(results.rsquared)
# 0.16118421052631582
print(rsquared(y, results.fittedvalues, fit_intercept=True))
# 0.16118421052631593

# intercept is zero: the result are different
# scikit-learn:
lr = LinearRegression(fit_intercept=False)
lr.fit(X, y)
print(lr.score(X, y))
# -0.4309210526315792
print(rsquared(y, lr.predict(X), fit_intercept=True))
# -0.4309210526315792
print(rsquared(y, lr.predict(X), fit_intercept=False))
# 0.8058035714285714

# statsmodels
model = sm.OLS(y, X)
results = model.fit()
print(results.rsquared)
# 0.8058035714285714
print(rsquared(y, results.fittedvalues, fit_intercept=False))
# 0.8058035714285714

【讨论】:

感谢您链接到我的答案;但我认为你在解释这个特殊情况(即两个模型都安装不带拦截)方面做得比我好得多,所以我也更新了我的答案以链接这里。干杯;) 谢谢 :) 我后来才意识到(在我发布我的答案之后)问题中包含的代码实际上是直接从你的答案中获取的,所以它可能应该首先链接到那里。 糟糕...我没注意到那个! :) 你是对的,OP 应该自己链接到那里,如果不是为了其他原因,只是为了解释为什么这个特定案例仍然没有得到很好的解释。 感谢您的详细解释:)您的好意是一个很大的帮助。 欢迎@yoon 口头感谢,但如果答案解决了您的问题,请接受 - 请参阅What should I do when someone answers my question?

以上是关于为啥当 fit_intercept=False 时 Sklearn R-squared 与 statsmodels 不同?的主要内容,如果未能解决你的问题,请参考以下文章

LassoLarsCV算法参数

线性回归中的截距

为啥当 Enlist=False 时 Sqlserver 在事务中登记?

为啥当键是 int[] 时 Dictionary.ContainsKey 返回 False? [复制]

为啥当文件确实存在时`boost::filesystem::exists`返回false?

为啥当 == 为空值返回 true 时 >= 返回 false?