Sklearn与Statsmodels多元线性回归结果差异疑问(含分类变量)
问题背景
你用OneHotEncoder将2个分类变量(floorLevel、buildingType)转换成了全部虚拟变量,结合3个连续变量构建多元线性回归模型,但分别用sklearn和statsmodels实现后,发现仅截距和分类变量的系数存在差异,连续变量的系数完全一致。
你的自变量与因变量
y = df['price'] x = df[['age', 'totalRooms', 'elevator', 'floorLevel_bottom', 'floorLevel_high', 'floorLevel_low', 'floorLevel_medium','floorLevel_top', 'buildingType_bungalow', 'buildingType_plate', 'buildingType_plate_tower', 'buildingType_tower']]
sklearn实现与输出
from sklearn.linear_model import LinearRegression mlr = LinearRegression() mlr.fit(x, y) print('Intercept: \n', mlr.intercept_) print("Coefficients:") list(zip(x, mlr.coef_))
输出:
Intercept:
35228.96453917408
Coefficients:
[('age', 1046.5347118942063),
('totalRooms', -797.7667275033103),
('elevator', 11940.629576736419),
('floorLevel_bottom', 1011.5929167549165),
('floorLevel_high', 157.60625500592502),
('floorLevel_low', 483.89164772666277),
('floorLevel_medium', 630.9547280568961),
('floorLevel_top', -2284.0455475443687),
('buildingType_bungalow', 31610.88176756009),
('buildingType_plate', -9649.087529585862),
('buildingType_plate_tower', -8813.187607409624),
('buildingType_tower', -13148.606630564624)]
statsmodels实现与输出
import statsmodels.api as sm x_in = sm.add_constant(x) model = sm.OLS(y, x_in).fit() print(model.summary())
输出(关键参数):
Intercept 2.43e+04
age 1046.5347
totalRooms -797.7667
elevator 1.194e+04
floorLevel_bottom 5870.7604
floorLevel_high 5016.7738
floorLevel_low 5343.0592
floorLevel_medium 5490.1223
floorLevel_top 2575.1220
buildingType_bungalow 3.768e+04
buildingType_plate -3575.1281
buildingType_plate_tower -2739.2282
buildingType_tower -7074.6472
核心原因:虚拟变量陷阱的不同处理逻辑
你将每个分类变量的全部虚拟变量都纳入了模型,这会引发完全多重共线性(虚拟变量陷阱):比如floorLevel的5个虚拟变量中,任意4个都能推导第5个的值(因为每个样本必然属于其中一个类别,5个变量的和恒为1)。此时模型参数是不可唯一识别的,sklearn和statsmodels选择了不同的求解策略,导致参数表现不同,但预测结果完全一致。
1. sklearn LinearRegression的处理
sklearn会自动通过约束条件消除共线性:它会默认将某一个虚拟变量设为参考类别(系数隐含为0,实际表现为截距包含该类别的基准值),其他虚拟变量的系数是相对于该参考类别的差值。
- 从你的输出看,它把
floorLevel_top和buildingType_tower作为参考类别:floorLevel_bottom的系数1011.59 → 底层比顶层的价格高1011.59buildingType_plate的系数-9649.09 → 板楼比塔楼的价格低9649.09- 截距35228.96 → 代表“顶层+塔楼”组合的基准价格
2. statsmodels OLS的处理
statsmodels不会自动移除变量,而是使用广义逆矩阵求解最小范数解。此时的参数解释完全不同:
- 截距2.43e+04 → 代表全局平均价格的基准值
- 每个虚拟变量的系数 → 代表该类别价格相对于全局平均的差值
floorLevel_bottom的系数5870.76 → 底层价格比全局平均高5870.76
验证:两个模型的预测结果完全一致
尽管参数数值不同,但你可以用同一组输入数据测试两个模型的预测值,结果会完全相同——因为它们本质上拟合的是同一个线性模型,只是参数的解释方式不同。
解决方法:统一参考类别,避免虚拟变量陷阱
要让两个库的参数结果完全对应,你需要在生成虚拟变量时,为每个分类变量去掉一个类别,作为参考基准:
from sklearn.preprocessing import OneHotEncoder from sklearn.compose import ColumnTransformer # 定义分类变量列 cat_cols = ['floorLevel', 'buildingType'] # 初始化OneHotEncoder,drop='first'表示去掉每个分类的第一个类别作为参考 encoder = OneHotEncoder(drop='first', sparse_output=False) # 构建转换器,只对分类变量编码 ct = ColumnTransformer( transformers=[('onehot', encoder, cat_cols)], remainder='passthrough' # 保留连续变量 ) # 转换得到处理后的特征矩阵 x_processed = ct.fit_transform(df[['age', 'totalRooms', 'elevator'] + cat_cols])
此时再分别用sklearn和statsmodels建模,截距和系数会完全一致,都是以被去掉的类别为参考基准。
内容的提问来源于stack exchange,提问作者Jleeca

