Towards Data Science

Beyond the Straight Line: Choosing Between OLS, Interaction Terms, and Tweedie Regression

8.5内容质量

TL;DR · AI 摘要

本文比较了OLS、交互项和Tweedie回归在处理偏态数据时的适用场景,强调了选择合适模型的重要性。

核心要点

  • OLS回归适用于线性关系且数据分布接近正态的情况。
  • 交互项能捕捉变量间的非线性关系,提升模型解释性。
  • Tweedie回归适合处理偏态分布和存在大量零值的数据。

结构提纲

按章节快速跳转。

  1. 文章探讨了在不同数据分布情况下选择合适的回归模型的重要性。

  2. 使用法国汽车第三者责任保险索赔数据集进行分析。

  3. OLS回归适用于线性关系且数据分布接近正态的情况。

  4. 交互项能捕捉变量间的非线性关系,提升模型解释性。

  5. Tweedie回归适合处理偏态分布和存在大量零值的数据。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • 回归模型选择
    • OLS回归
      • 适用于线性关系
      • 数据分布接近正态
    • 交互项
      • 捕捉变量间非线性关系
      • 提升模型解释性
    • Tweedie回归
      • 适合偏态分布
      • 处理大量零值

金句 / Highlights

值得收藏与分享的关键句。

#回归分析#数据科学#统计建模
打开原文

超越直线:在OLS、交互项和Tweedie回归之间做出选择 | Towards Data Science

数据科学

超越直线:在OLS、交互项和Tweedie回归之间做出选择

掌握广义线性模型:在偏态数据中何时使用OLS、交互项或Tweedie回归

Gustavo Santos

2026年6月25日

14分钟阅读

分享

从OLS到Tweedie回归 | 图像由AI生成。Google, 2026. https://gemini.google.com

想象一下,你正在构建一个机器学习模型,用于预测客户在未来一年内在电子商务平台上将花费多少钱。这项工作是数据科学的日常工作:加载数据、清理数据、理解数据并建模结果。

我们心中浮现的一个大问题是:你会从工具箱中拿出哪种算法?

对于回归模型,我认为我们倾向于使用经典的OLS,或者有时直接跳到复杂的集成方法,如XGBoost。但根据我的经验,我经常看到广义线性框架正是进行推断、速度和可解释性所需要的。

真正的挑战在于选择正确的模型。

  • 我们是否应该坚持使用标准的普通最小二乘(OLS)回归?
  • 我们是否需要引入交互项?
  • 或者我们的数据是否足够奇怪,以至于需要使用Tweedie回归?

选择错误的模型会导致模型输出不可能的预测,比如客户花费负数金额。

让我们分解这三种方法,以便你能自信地为你的特定数据环境选择合适的工具。

数据集

本练习使用的数据集是来自R包CASDatasets的法国汽车第三者责任索赔数据集,授权协议为GPL >2。

Christophe DutangArthur Charpentier(2026)。CASdatasets: 保险数据集,R包版本1.2-1,DOI 10.57745/P0KHAG。

code
# 基础
import pandas as pd
import numpy as np

# 数据可视化
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('darkgrid')
import plotly.express as px

# 统计
import statsmodels.api as sm
import statsmodels.formula.api as smf
import scipy.stats as stats

# 预处理与建模
from sklearn.linear_model import TweedieRegressor
from feature_engine.encoding import OneHotEncoder
import warnings
from sklearn.metrics import mean_absolute_error

import warnings
warnings.filterwarnings('ignore')

要获取数据集,你可以使用这个sklearn教程[4]中的代码片段。

code
# 加载数据
df = load_mtpl2(n_samples=250_000)

数据集的视图。作者提供的图像。

在探索数据之后,有一些有趣的转换可以进行,例如将数据截断在50万美元的最大值,并保留索赔次数低于4次的数据,因为超过这个数字的数值太疯狂(异常值),肯定会扭曲我们的预测。

code
# 截断
df = df.query('ClaimAmount < 500000')
df = df.query('ClaimNb < 4')

1. 常规OLS:可靠的高速公路

让我们从一个基准模型开始。最简单的普通最小二乘(OLS)回归是统计建模的基石。如果数据科学有一个“默认”设置按钮,那将是OLS。

但“缺点”是它要求数据以某种方式表现。

例如,它会假设:

  • 随着自变量的变化,目标变量以恒定且可预测的速率变化。
  • 如果你将索赔数量增加 1,无论这是你第一次索赔还是不是,你的索赔金额都会增加一个固定的数额。

从数学上讲,OLS 寻找一条直线,使得实际数据点与该直线之间的平方差之和最小。

在什么情况下有效?

当你的数据满足几个核心假设时,OLS 非常强大:

  • 线性:你的特征与目标之间的关系大致是直线的。
  • 同方差性:你的残差(误差)的方差是恒定的。用通俗的话来说,模型在大值时的不准确性不会比在小值时显著更高。
  • 正态分布:误差是正态分布的。

如果你预测的是稳定且连续的事物(例如,根据树的年龄预测树的高度,或根据海拔预测温度),常规的 OLS 通常是最佳选择。它透明、快速且易于理解。

然而,对于这个问题,由于我们的数据中包含大量零值的索赔金额,我们将看到常规的 OLS 并不是最佳选择。

code
# 定义 OLS 模型公式
formula = "ClaimAmount ~ Exposure + VehPower + VehAge + DrivAge + BonusMalus + Density + C(Area) + C(VehBrand) + C(VehGas) + C(Region)"

# 拟合 OLS 模型
ols_model = smf.ols(formula=formula, data=df).fit()

# 显示模型摘要
print(ols_model.summary())

在运行了我们的基线模型后,让我们来看看结果。

OLS 结果。图片由作者提供。

如果我们查看模型的残差,如直方图和 Q-Q 图所示,OLS 模型的残差显然不是正态分布的,这在处理包含许多零值的数据集(如保险索赔金额)时是一个常见问题。

残差的直方图和 QQ-图:模型在较高值时表现不佳。图片由作者提供。

2. 带有交互项的 OLS:取决于因素

但如果现实世界变得复杂了呢?假设你根据两个特征预测房价:卧室的数量和房屋是否有游泳池。

常规的 OLS 模型会独立地看待这些因素。它会说:“多一个卧室会增加 5 万美元,而有一个游泳池会增加 3 万美元。”但这是否总是正确的?游泳池对一个一居室的公寓和一个五居室的豪华住宅的价值是否相同?

可能不是。游泳池的价值取决于房屋的大小。这就是交互项发挥作用的地方。

交互项告诉模型,一个变量的影响会随着另一个变量的水平而变化。在你的数据中,你可以通过将这两个特征相乘来表示这一点:

另一种看法是通过烹饪的类比。交互项就像烘焙。

  • 粉单独是干燥的。
  • 水单独是湿的。
  • 但当你把它们混合在一起时,你得到的不是“干湿”,而是面团,一种全新的属性。

什么时候应该使用它?

当你怀疑特征之间存在协同作用或拮抗作用时,应该用交互项升级你的 OLS 模型。

  • 协同作用:两个特征一起比它们各自的部分之和更强大(例如,营销支出 × 节假日季节)。
  • 拮抗作用:一个特征会削弱另一个特征的影响(例如,药物 A 的剂量 × 药物 B 的剂量)。

如果你的常规 OLS 模型表现不佳,并且你有很强的领域知识表明变量之间存在相互作用,不要只是给它更多的数据。尝试添加一个交互项。

好吧,让我们看看它的表现如何。

code
# 定义带有另一个交互项且不包含 'Region' 的 OLS 模型公式
formula_no_region = "ClaimAmount ~ Exposure * VehBrand + Exposure * BonusMalus + DrivAge + BonusMalus + Density + C(Area) + VehPower + C(VehGas)"

# 拟合带有两个交互项且不包含 'Region' 的 OLS 模型
ols_no_region_model = smf.ols(formula=formula_no_region, data=df).fit()

# 显示模型摘要
print(ols_no_region_model.summary())

带有交互项的 OLS 结果。图片由作者提供。

尽管引入了交互项,OLS 模型在 ClaimAmount 数据上的表现没有变化。这是由于零索赔的高度集中以及非零索赔的严重偏态分布。

普通最小二乘模型假设误差呈正态分布,这一假设在该零膨胀数据集中明显被违反。因此,添加交互项可能能够捕捉更复杂的关系,但无法对数据的真实分布进行建模,从而只能带来边际改进。

3. Tweedie 回归:拥抱混乱的零值

如果您的数据不是一条整洁的连续钟形曲线,又会怎样?如果您的数据看起来像是一堵巨大的零值墙,后面跟着一长串正数的拖尾,又会怎样?

正如我们在示例中看到的,每年大多数保险投保人申报的索赔金额正好是 0 美元。他们不会发生事故。但对少数发生事故的人来说,他们的索赔金额不仅仅是 5 美元或 10 美元。它们可能是数千甚至数万美元。

如果您尝试用普通的 OLS 模型拟合这些数据,模型就会崩溃。为什么?因为 OLS 假设误差服从正态分布。为了适应所有这些零值和一些巨大的异常值,OLS 可能开始预测一些低风险客户会有负的索赔金额。

这就是我们尝试使用 Tweedie 分布的原因。

Tweedie 分布是一种特殊的概率分布族,可以处理在零点处的离散质量与正值的连续、右偏分布的混合。它在泊松分布(用于计数事件)和伽马分布(用于测量连续的正数)之间起着混合的作用。

想象 Tweedie 回归就像在沙漠中测量降雨量。大多数日子的降雨量正好是零。但当它下雨时,就会倾盆大雨,您会得到一个连续且高度可变的水量。您不能用平滑的对称钟形曲线来建模沙漠的降雨量。

Tweedie 回归是以下数据的黄金标准:

  • 严格非负值(您不能有小于 0 美元的销售额或索赔金额)。
  • 在零点处有巨大的峰值(零膨胀)。
  • 非零值的高度偏态分布。

如果您在保险科技、信用风险或用户终身价值建模领域工作,且许多用户从未转化,那么 Tweedie 可能是您的潜在盟友。

code
# 对分类变量进行独热编码
ohe = OneHotEncoder(drop_last=True)
df_ohe = ohe.fit_transform(df.drop(columns=['OLS_pred', 'OLS_Int_Term_pred'], axis=1))

# X 和 Y
X = df_ohe.drop(columns=['ClaimAmount'], axis=1)
y = df_ohe['ClaimAmount']

# 在 1.75-1.8 范围内对 tweedie_powers 进行更细致的搜索
tweedie_powers = np.arange(1.7, 1.8, 0.01) # 示例:从 1.7 到 1.8,步长为 0.01
alpha_values = [0.1, 0.5, 1.0] # 测试不同的正则化强度

results = []

print("正在搜索最佳 Tweedie 指数和 alpha 值...") for power in tweedie_powers: for alpha in alpha_values: sklearn_tweedie_model = TweedieRegressor( power=power, link='auto', solver='newton-cholesky', max_iter=500, alpha=alpha # 添加正则化 ) try:

使用 Exposure 作为偏移量拟合模型

sklearn_tweedie_model.fit(X, y, sample_weight=df['Exposure']) mae = mean_absolute_error(y, sklearn_tweedie_model.predict(X)) results.append((power, alpha, mae)) except Exception as e:

捕获某些参数组合可能遇到的收敛问题

results.append((power, alpha, float('inf'))) # 对拟合失败的情况分配一个较高的 MAE 值

对结果进行排序,找到最佳模型

best_result = min(results, key=lambda x: x[2]) best_power, best_alpha, best_mae = best_result

print(f"最佳 Tweedie 指数: {best_power:.2f}, 最佳 Alpha: {best_alpha:.1f}, 最佳 MAE: {best_mae:.2f}")

现在,使用最佳参数拟合模型

sklearn_tweedie_model = TweedieRegressor( power=best_power, link='auto', solver='newton-cholesky', max_iter=500, alpha=best_alpha ) sklearn_tweedie_model.fit(X, y, sample_weight=df['Exposure'])

print("使用优化后的参数成功拟合了 Sklearn Tweedie 回归模型!") print(results)

code

正在搜索最佳 Tweedie 指数和 alpha 值... 最佳 Tweedie 指数: 1.76, 最佳 Alpha: 1.0, 最佳 MAE: 110.31 Sklearn Tweedie 回归模型使用优化后的参数成功拟合!

code

让我们使用这个模型进行预测,并接着检查 QQ 图的外观。

使用优化后的模型进行预测

df['Tweedie_pred'] = sklearn_tweedie_model.predict(X)

code

这是实际值与该模型预测值的对比。

Tweedie 模型预测值与实际值的对比。图片由作者提供。

请注意,误差依然非常明显,尤其是在 ClaimNb == 1 的情况下。虽然 Tweedie 模型的残差仍然显示出与正态分布的偏差,但该模型遵循的是 Tweedie 分布。因此,真正的改进体现在 MAE 指标上,该指标的值降低了约 35%。

## 模型对比

cols=['ClaimNb','ClaimAmount', 'OLS_pred', 'OLS_Int_Term_pred', 'Tweedie_pred'] df[cols].sample(10).round()

code

模型对比。图片由作者提供。

我们可以看到,Tweedie 回归模型对零索赔的预测值更接近于零,与 OLS 估计相比,结果明显更好,因为 OLS 估计会将所有 ClaimNb 值的预测值都接近平均值。Tweedie 回归模型可以理解零膨胀现象,但它不会预测零,而是给出一个较小的值。

from sklearn.metrics import mean_absolute_error

计算 OLS 的 MAE

mae_ols = mean_absolute_error(df['ClaimAmount'], df['OLS_pred']) print(f"OLS 模型的 MAE: {mae_ols:.2f}")

计算 OLS 交互项的 MAE

mae_ols = mean_absolute_error(df['ClaimAmount'], df['OLS_Int_Term_pred']) print(f"带交互项的 OLS 模型的 MAE: {mae_ols:.2f}")

计算 Statsmodels Tweedie 的 MAE

mae_sm_tweedie = mean_absolute_error(df['ClaimAmount'], df['Tweedie_pred']) print(f"Tweedie 模型的 MAE: {mae_sm_tweedie:.2f}")

计算零膨胀模型的 MAE

mae_zero_inflated = mean_absolute_error(df['ClaimAmount'], df['ZeroInflated_pred']) print(f"零膨胀模型的 MAE: {mae_zero_inflated:.2f}")

code

OLS模型的MAE:174.17 包含交互项的OLS模型的MAE:172.24 Tweedie模型的MAE:111.97

code

## 决策矩阵:选择你的路径

当你面对数据集,试图决定采取哪条路径时,请使用下表来指导你的决策:

| 模型 | 目标变量类型 | 关键假设/优势 | 最适合用于 |
|------|----------------|----------------|----------------|
| 普通OLS | 连续、对称、钟形分布 | 变量间的关系是线性且独立的 | 稳定的物理测量、基础预测 |
| 带交互项的OLS | 连续、对称 | 特征A的影响会根据特征B的变化而变化 | 营销归因、房地产定价、经济学 |
| Tweedie回归 | 高度偏斜、非负、大部分为零 | 能够处理在零点处的显著峰值以及随后的长尾 | 保险索赔、客户生命周期价值、罕见事件成本 |

## 附加模型

但我不满意这个模型。显然,这个模型在实践中结果并不理想。我们看到,Tweedie模型确实可以显著减少误差,但它仍然通过控制非常高的和非常低的数值(0)并使所有预测值更接近平均值来实现这一点。

唯一剩下的解决方案是创建一个两步模型,其中我们将建模一个零膨胀分布:

- 运行一个分类器,告诉我们是否有索赔金额。它返回有索赔金额的概率。

- 使用第一个模型的结果,我们可以使用Tweedie分布来建模只有那些有索赔金额的观测值。否则,模型将只返回零。

让我们来编写代码。

我们首先使用One Hot Encoding来准备数据集,以去除类别。

import lightgbm as lgb from sklearn.metrics import classification_report

从df中删除之前的预测

cols = ['ClaimNb', 'Exposure', 'Area', 'VehPower', 'VehAge', 'DrivAge', 'BonusMalus', 'VehBrand', 'VehGas', 'Density', 'Region', 'ClaimAmount'] df2 = df[cols].copy()

1. 创建二元结果变量,表示是否有索赔

df2['has_claim'] = (df2['ClaimAmount'] > 0).astype(int)

准备“零索赔”分类的数据

df_ohe = ohe.fit_transform(df2) X = df_ohe.drop(columns=['ClaimAmount', 'has_claim']) y = df_ohe['has_claim']

code

接下来是一大段代码。

- 我们将数据分为训练集和验证集用于LightGBM。

- 计算权重以处理类别不平衡。

- 设置调优后的LightGBM模型参数。

- 使用早停训练LightGBM模型。

from sklearn.model_selection import train_test_split

print("正在为P(Claim > 0)调整LightGBM参数...")

将训练数据拆分为训练集和验证集用于早停

X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

train_data_split = lgb.Dataset(X_train, label=y_train) val_data_split = lgb.Dataset(X_val, label=y_val)

计算scale_pos_weight以更好地处理类别不平衡

scale_pos_weight_value = (y_train == 0).sum() / (y_train == 1).sum()

code

# 为更好地控制并缓解警告信息,定义更新后的参数
params_tuned = {
    'objective': 'binary',
    'metric': 'auc',
    'learning_rate': 0.05,  # 降低学习率
    'num_leaves': 31,       # 增加 num_leaves(默认为 31),允许更高的复杂度
    'max_depth': 7,         # 增加树的深度,允许更高的复杂度
    'min_child_samples': 15, # 稍微降低叶节点的最小数据量
    'min_gain_to_split': 0.01, # 降低分裂所需的最小增益
    'force_col_wise': True, # 保留以兼容数据集
    'scale_pos_weight': scale_pos_weight_value, # 显式处理类别不平衡
    'verbose': -1           # 隐藏训练过程中的详细输出
}

# 使用调整后的参数重新训练 LightGBM 模型
# 由于学习率降低且复杂度增加,增加 num_boost_round
model_tuned = lgb.train(
    params_tuned,
    train_data_split,
    num_boost_round=300, # 增加提升轮数
    valid_sets=[val_data_split], # 提供验证集用于提前停止
    callbacks=[lgb.early_stopping(20, verbose=False)] # 添加提前停止并隐藏详细输出,增加耐心值
)
  • 预测索赔发生的概率。将其转换为 0 或 1 以计算分类报告。
  • 使用分类报告评估模型。
  • 将预测概率存储在数据框中。
  • 将概率低于 0.5 的设为 0。
code
# 使用调整后的模型进行预测和评估
y_pred = (model_tuned.predict(X) > 0.5).astype(int)
print("\n调整后的 LightGBM 模型分类报告:")
print(classification_report(y, y_pred))

# 将概率存储到下一步的零膨胀模型中
df2['prob_has_claim'] = model_tuned.predict(X)

# 如果 prob_has_claim 低于 0.5,设为 0
df2.loc[df2['prob_has_claim'] < 0.5, 'prob_has_claim'] = 0

现在,模型的第二部分是仅筛选那些有金额关联的索赔,并使用 Tweedie 回归来估计金额。

code
# 第二部分:使用 Tweedie 回归对索赔金额(ClaimAmount > 0)进行建模
# 仅筛选正索赔数据
df_positive = df_ohe[df_ohe['has_claim'] == 1].copy()
X_positive = df_ohe.drop(columns=['ClaimAmount', 'has_claim'], axis=1)
y_positive = df_ohe['ClaimAmount']

# 使用 Exposure 作为严重性模型的偏移量
print("\n拟合 Tweedie 模型 E[ClaimAmount | Claim > 0]...")
sklearn_tweedie_model = TweedieRegressor(
    power=1.85,
    link='auto',
    solver='newton-cholesky',
    max_iter=500,
    alpha=1
)
sklearn_tweedie_model.fit(X_positive, y_positive, sample_weight=df['Exposure'])

最后,该模型的预测结果将是第一个模型估计的概率乘以 Tweedie 回归器估计的金额。

code
# 对所有观测值预测条件严重性(包括没有索赔的观测值)
# 假设严重性模型从正索赔数据中学到的可以泛化。
df['pred_severity_if_claim'] = sklearn_tweedie_model.predict(df_ohe.drop(columns=['ClaimAmount', 'has_claim'], axis=1))

# 计算零膨胀模型的最终预测
df['ZeroInflated_pred'] = df2['prob_has_claim'] * df['pred_severity_if_claim']

# 显示预测结果和 MAE
print("\n零膨胀模型预测结果(前 5 行):")
print(df[['ClaimAmount', 'ZeroInflated_pred']].sample(5))

mae_zero_inflated = mean_absolute_error(df['ClaimAmount'], df['ZeroInflated_pred'])
print(f"\n零膨胀模型的 MAE: {mae_zero_inflated:.2f}")

这是结果。

code
拟合 Tweedie 模型 E[ClaimAmount | Claim > 0]...

零膨胀模型预测(前 5 行):
         ClaimAmount  ZeroInflated_pred
IDpol                                  
1179635          0.0           0.000000
108738           0.0           0.000000
2066923          0.0           0.000000
1007186          0.0           0.000000
1117109       1128.0         729.428288

零膨胀模型的 MAE:87.79

太棒了,我们在 MAE 上又获得了 21% 的提升。这令人惊叹!

零膨胀模型结果:实际值与预测值。图片由作者提供。

结论

数据科学很少是关于找到一个“完美”的算法来解决所有问题。相反,它是关于将模型的数学假设与数据的实际行为相匹配。

从简单开始。查看你的目标变量的分布。如果它看起来像一个钟形曲线,从常规的 OLS 开始。如果你知道你的特征相互影响,引入交互项。但如果你面对的是一堆零和一个长尾的大量数值,那就完全跳过 OLS 的头痛,利用 Tweedie 回归的强大功能。

在建模之前花几分钟分析目标变量的分布,可以节省你日后调整错误架构的数天时间。

GitHub 仓库

https://github.com/gurezende/Zero-Inflated-Tweedie-Regression

参考文献

[1. 数据集] ( https://www.openml.org/search?type=data&sort=runs&id=43593&status=active )

[2. 数据许可和引用] ( https://dutangc.github.io/CASdatasets/ )

Christophe Dutang 和 Arthur Charpentier(2026)。CASdatasets:保险数据集,R 包版本 1.2-1,DOI 10.57745/P0KHAG。

[3. Tweedie 回归文档] ( https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.TweedieRegressor.html )

[4. Tweedie 回归教程] ( https://scikit-learn.org/stable/auto_examples/linear_model/plot_tweedie_regression_insurance_claims.html )

作者

查看 Gustavo Santos 的所有文章

,

深入分析

机器学习

Python

回归

分享这篇文章

  • 在 Facebook 上分享
  • 在 LinkedIn 上分享
  • 在 X 上分享

Towards Data Science 是一个社区出版物。提交你的见解,以触达我们的全球受众,并通过 TDS 作者支付计划获得报酬。

将 href 更新为你的实际提交 URL

为 TDS 写作

✦ 结束 CTA ✦