楼主: dfour
236 0

【机器学习】实战1.2——保险花销预测进阶——特征工程、多项式升维 [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

小学生

14%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
40 点
帖子
3
精华
0
在线时间
0 小时
注册时间
2018-10-20
最后登录
2018-10-20

楼主
dfour 发表于 2025-12-3 16:37:14 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币
# 导入核心数据处理库
import numpy as np  # 用于数值计算(对数变换、数组操作、误差计算)
import pandas as pd  # 用于数据读取、清洗、特征工程

# 读取保险数据集(CSV格式,默认逗号分隔)
# 数据集包含:年龄(age)、性别(sex)、BMI指数(bmi)、子女数(children)、是否吸烟(smoker)、地区(region)、保险费用(charges)
data = pd.read_csv('./data/insurance.csv')
# 查看前5行数据,快速了解数据结构(列名、数据类型、样本取值)
print("原始数据集前5行:")
print(data.head())
print("-" * 50)

# # EDA(探索性数据分析):分析目标变量分布及各特征对保费的影响
# 导入绘图库
import matplotlib.pyplot as plt
import seaborn as sns  # 基于matplotlib的高级绘图库,更适合统计可视化

# 设置中文显示(解决图表中文乱码问题,适配PyCharm)
plt.rcParams['font.sans-serif'] = ['SimHei']  # Windows系统
# plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']  # Mac系统
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示异常

# -------------------------- 1. 目标变量(保险费用)原始分布 --------------------------
plt.figure(figsize=(10, 6))
plt.hist(data['charges'], bins=30, edgecolor='black', alpha=0.7, color='#4CAF50')
plt.title('保险费用原始分布直方图', fontsize=14, fontweight='bold')
plt.xlabel('保险费用(元)', fontsize=12)
plt.ylabel('客户数量(频数)', fontsize=12)
plt.grid(axis='y', alpha=0.3)  # 添加水平网格线,提升可读性
plt.show()  # PyCharm中需调用show()显示图表
# 观察结论:保费呈明显右偏分布(多数客户保费较低,少数高风险客户保费极高)

# -------------------------- 2. 目标变量对数变换后分布 --------------------------
plt.figure(figsize=(10, 6))
# np.log1p = log(1+x),避免x=0时log(0)的无穷大问题,且逆变换可逆(expm1)
plt.hist(np.log1p(data['charges']), bins=30, edgecolor='black', alpha=0.7, color='#FF9800')
plt.title('保险费用对数变换(log1p)后分布直方图', fontsize=14, fontweight='bold')
plt.xlabel('对数变换后的保险费用', fontsize=12)
plt.ylabel('客户数量(频数)', fontsize=12)
plt.grid(axis='y', alpha=0.3)
plt.show()
# 观察结论:对数变换后分布更接近正态分布,可提升模型预测精度

# -------------------------- 3. 性别(sex)对保费的影响 --------------------------
plt.figure(figsize=(10, 6))
# 核密度图(KDE):展示不同性别的保费概率分布
sns.kdeplot(data.loc[data.sex == 'male', 'charges'], shade=True, label='男性', color='#2196F3')
sns.kdeplot(data.loc[data.sex == 'female', 'charges'], shade=True, label='女性', color='#E91E63')
plt.title('不同性别的保险费用分布对比', fontsize=14, fontweight='bold')
plt.xlabel('保险费用(元)', fontsize=12)
plt.ylabel('概率密度', fontsize=12)
plt.legend(fontsize=11)
plt.grid(alpha=0.3)
plt.show()
# 观察结论:男女保费分布差异极小,性别对保费影响不显著

# -------------------------- 4. 地区(region)对保费的影响 --------------------------
plt.figure(figsize=(12, 7))
sns.kdeplot(data.loc[data.region == 'northwest', 'charges'], shade=True, label='西北地区', color='#9C27B0')
sns.kdeplot(data.loc[data.region == 'southwest', 'charges'], shade=True, label='西南地区', color='#00BCD4')
sns.kdeplot(data.loc[data.region == 'northeast', 'charges'], shade=True, label='东北地区', color='#FF5722')
sns.kdeplot(data.loc[data.region == 'southeast', 'charges'], shade=True, label='东南地区', color='#795548')
plt.title('不同地区的保险费用分布对比', fontsize=14, fontweight='bold')
plt.xlabel('保险费用(元)', fontsize=12)
plt.ylabel('概率密度', fontsize=12)
plt.legend(fontsize=11)
plt.grid(alpha=0.3)
plt.show()
# 观察结论:四个地区的保费分布重叠度高,地区对保费影响较弱

# -------------------------- 5. 是否吸烟(smoker)对保费的影响 --------------------------
plt.figure(figsize=(10, 6))
sns.kdeplot(data.loc[data.smoker == 'yes', 'charges'], shade=True, label='吸烟', color='#F44336')
sns.kdeplot(data.loc[data.smoker == 'no', 'charges'], shade=True, label='不吸烟', color='#4CAF50')
plt.title('吸烟状态对保险费用分布的影响', fontsize=14, fontweight='bold')
plt.xlabel('保险费用(元)', fontsize=12)
plt.ylabel('概率密度', fontsize=12)
plt.legend(fontsize=11)
plt.grid(alpha=0.3)
plt.show()
# 观察结论:吸烟客户的保费显著高于不吸烟客户,是保费的关键影响因素

# -------------------------- 6. 子女数(children)对保费的影响 --------------------------
plt.figure(figsize=(12, 7))
# 分别绘制0-5个子女的保费分布
sns.kdeplot(data.loc[data.children == 0, 'charges'], shade=True, label='0个子女', color='#3F51B5')
sns.kdeplot(data.loc[data.children == 1, 'charges'], shade=True, label='1个子女', color='#2196F3')
sns.kdeplot(data.loc[data.children == 2, 'charges'], shade=True, label='2个子女', color='#00BCD4')
sns.kdeplot(data.loc[data.children == 3, 'charges'], shade=True, label='3个子女', color='#4CAF50')
sns.kdeplot(data.loc[data.children == 4, 'charges'], shade=True, label='4个子女', color='#FFC107')
sns.kdeplot(data.loc[data.children == 5, 'charges'], shade=True, label='5个子女', color='#FF9800')
plt.title('不同子女数的保险费用分布对比', fontsize=14, fontweight='bold')
plt.xlabel('保险费用(元)', fontsize=12)
plt.ylabel('概率密度', fontsize=12)
plt.legend(fontsize=10)
plt.grid(alpha=0.3)
plt.show()
# 观察结论:子女数对保费有一定影响,但差异不如吸烟状态显著

# # 特征工程:简化特征维度、转换特征类型、编码分类变量
# -------------------------- 1. 删除弱影响特征 --------------------------
# 基于EDA结论:性别(sex)和地区(region)对保费影响极小,删除以简化模型
data = data.drop(['region', 'sex'], axis=1)
print("删除弱影响特征后的数据集前5行:")
print(data.head())
print("-" * 50)


# -------------------------- 2. 特征离散化:连续/多分类特征转为二分类 --------------------------
def feature_discretize(df, bmi_threshold, child_threshold):
    """
    自定义函数:将连续特征BMI和多分类特征子女数离散化为二分类
    参数:
        df: 每行数据(apply按行处理)
        bmi_threshold: BMI分类阈值(这里30是肥胖标准)
        child_threshold: 子女数分类阈值(这里0区分"无子女"和"有子女")
    返回:
        处理后的单行数据
    """
    # BMI≥30标记为"over"(肥胖),否则为"under"(非肥胖)
    df['bmi'] = 'over' if df['bmi'] >= bmi_threshold else 'under'
    # 子女数==0标记为"no"(无子女),否则为"yes"(有子女)
    df['children'] = 'no' if df['children'] == child_threshold else 'yes'
    return df


# 按行应用离散化函数,指定BMI阈值30、子女数阈值0
data = data.apply(feature_discretize, axis=1, args=(30, 0))
print("特征离散化后的数据集前5行:")
print(data.head())
print("-" * 50)

# -------------------------- 3. 分类变量独热编码(One-Hot Encoding) --------------------------
# 离散化后的特征(bmi、children、smoker)均为分类变量,需编码为模型可识别的数值
data = pd.get_dummies(data, drop_first=False)  # drop_first=False:保留所有分类(避免信息丢失)
print("独热编码后的数据集前5行:")
print(data.head())
print("-" * 50)

# -------------------------- 4. 分离特征矩阵(X)和目标变量(y) --------------------------
X = data.drop('charges', axis=1)  # X:所有输入特征(排除目标变量)
y = data['charges']  # y:目标变量(需预测的保险费用)

# -------------------------- 5. 缺失值填充 --------------------------
# 假设缺失值为"无该特征",用0填充(实际项目可根据特征含义优化,如均值/中位数)
X.fillna(0, inplace=True)
y.fillna(0, inplace=True)

print("最终特征矩阵前5行:")
print(X.head())
print("-" * 50)

# # 模型训练:划分数据集、构造多项式特征、训练三种回归模型
# -------------------------- 1. 划分训练集和测试集 --------------------------
from sklearn.model_selection import train_test_split

# test_size=0.3:30%数据为测试集(评估泛化能力),70%为训练集(训练参数)
# random_state=42:固定随机种子,保证结果可复现(原代码未设置,补充优化)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# -------------------------- 2. 构造多项式特征(捕捉非线性关系) --------------------------
from sklearn.preprocessing import PolynomialFeatures

# degree=2:构造二次多项式特征(含特征平方项和交叉项)
# include_bias=False:不添加偏置项(模型会自动处理截距)
poly_features = PolynomialFeatures(degree=2, include_bias=False)
X_train_poly = poly_features.fit_transform(X_train)  # 训练集:拟合+转换(仅用训练集数据,避免泄露)
# 原代码错误:测试集用了fit_transform(会重新拟合测试集数据,导致数据泄露),修正为transform
X_test_poly = poly_features.transform(X_test)  # 测试集:仅转换(复用训练集的多项式规则)

print(f"多项式特征构造后:训练集维度 {X_train_poly.shape},测试集维度 {X_test_poly.shape}")
print("-" * 50)

# -------------------------- 3. 导入三种回归模型 --------------------------
from sklearn.linear_model import LinearRegression  # 普通线性回归(基准模型)
from sklearn.linear_model import Ridge  # 岭回归(L2正则化,缓解过拟合)
from sklearn.ensemble import GradientBoostingRegressor  # 梯度提升回归(集成学习,捕捉复杂关系)

# -------------------------- 4. 训练普通线性回归 --------------------------
reg = LinearRegression()
# 用对数变换后的目标变量训练(修正右偏分布,提升模型效果)
reg.fit(X_train_poly, np.log1p(y_train))
# 补充原代码缺失的测试集预测(原代码未定义y_predict,导致评估报错)
y_predict = reg.predict(X_test_poly)

# -------------------------- 5. 训练岭回归 --------------------------
ridge = Ridge(alpha=1.0)  # alpha:正则化强度(默认1.0,可通过调参优化)
ridge.fit(X_train_poly, np.log1p(y_train))
y_predict_ridge = ridge.predict(X_test_poly)

# -------------------------- 6. 训练梯度提升回归 --------------------------
booster = GradientBoostingRegressor(random_state=42)  # random_state保证可复现
booster.fit(X_train_poly, np.log1p(y_train))
y_predict_boost = booster.predict(X_test_poly)

# # 模型评估:使用RMSE(均方根误差)评估性能
# RMSE越小,预测精度越高;对比训练集/测试集RMSE,判断过拟合/欠拟合
from sklearn.metrics import mean_squared_error


# 定义评估函数:统一计算对数尺度和原始尺度的RMSE(避免重复代码)
def evaluate_model(model_name, y_train_true, y_train_pred, y_test_true, y_test_pred):
    """
    计算模型的RMSE评估指标
    参数:
        model_name: 模型名称(用于打印)
        y_train_true: 训练集真实值(原始尺度)
        y_train_pred: 训练集预测值(对数尺度)
        y_test_true: 测试集真实值(原始尺度)
        y_test_pred: 测试集预测值(对数尺度)
    返回:
        对数尺度训练/测试RMSE、原始尺度训练/测试RMSE
    """
    # 对数尺度RMSE(反映相对误差,不受数据尺度影响)
    log_rmse_train = np.sqrt(mean_squared_error(y_true=np.log1p(y_train_true), y_pred=y_train_pred))
    log_rmse_test = np.sqrt(mean_squared_error(y_true=np.log1p(y_test_true), y_pred=y_test_pred))

    # 原始尺度RMSE(反映绝对误差,单位为元,更直观)
    # 用expm1逆转log1p变换(避免直接exp导致的误差:exp(log1p(x))=x+1)
    rmse_train = np.sqrt(mean_squared_error(y_true=y_train_true, y_pred=np.expm1(y_train_pred)))
    rmse_test = np.sqrt(mean_squared_error(y_true=y_test_true, y_pred=np.expm1(y_test_pred)))

    # 格式化输出结果
    print("=" * 60)
    print(f"{model_name} 评估结果:")
    print(f"对数尺度 - 训练集RMSE:{log_rmse_train:.4f} | 测试集RMSE:{log_rmse_test:.4f}")
    print(f"原始尺度 - 训练集RMSE:{rmse_train:.2f} 元 | 测试集RMSE:{rmse_test:.2f} 元")
    print("=" * 60 + "\n")

    return log_rmse_train, log_rmse_test, rmse_train, rmse_test


# -------------------------- 1. 评估普通线性回归 --------------------------
evaluate_model(
    model_name="普通线性回归(二次多项式特征)",
    y_train_true=y_train,
    y_train_pred=reg.predict(X_train_poly),
    y_test_true=y_test,
    y_test_pred=y_predict
)

# -------------------------- 2. 评估岭回归 --------------------------
evaluate_model(
    model_name="岭回归(L2正则化+二次多项式特征)",
    y_train_true=y_train,
    y_train_pred=ridge.predict(X_train_poly),
    y_test_true=y_test,
    y_test_pred=y_predict_ridge
)

# -------------------------- 3. 评估梯度提升回归 --------------------------
evaluate_model(
    model_name="梯度提升回归(集成学习+二次多项式特征)",
    y_train_true=y_train,
    y_train_pred=booster.predict(X_train_poly),
    y_test_true=y_test,
    y_test_pred=y_predict_boost
)
运行结果:
'''
原始数据集前5行:
   age     sex     bmi  children smoker     region      charges
0   19  female  27.900         0    yes  southwest  16884.92400
1   18    male  33.770         1     no  southeast   1725.55230
2   28    male  33.000         3     no  southeast   4449.46200
3   33    male  22.705         0     no  northwest  21984.47061
4   32    male  28.880         0     no  northwest   3866.85520
--------------------------------------------------
删除弱影响特征后的数据集前5行:
   age     bmi  children smoker      charges
0   19  27.900         0    yes  16884.92400
1   18  33.770         1     no   1725.55230
2   28  33.000         3     no   4449.46200
3   33  22.705         0     no  21984.47061
4   32  28.880         0     no   3866.85520
--------------------------------------------------
特征离散化后的数据集前5行:
   age    bmi children smoker      charges
0   19  under       no    yes  16884.92400
1   18   over      yes     no   1725.55230
2   28   over      yes     no   4449.46200
3   33  under       no     no  21984.47061
4   32  under       no     no   3866.85520
--------------------------------------------------
独热编码后的数据集前5行:
   age      charges  bmi_over  ...  children_yes  smoker_no  smoker_yes
0   19  16884.92400         0  ...             0          0           1
1   18   1725.55230         1  ...             1          1           0
2   28   4449.46200         1  ...             1          1           0
3   33  21984.47061         0  ...             0          1           0
4   32   3866.85520         0  ...             0          1           0

[5 rows x 8 columns]
--------------------------------------------------
最终特征矩阵前5行:
   age  bmi_over  bmi_under  children_no  children_yes  smoker_no  smoker_yes
0   19         0          1            1             0          0           1
1   18         1          0            0             1          1           0
2   28         1          0            0             1          1           0
3   33         0          1            1             0          1           0
4   32         0          1            1             0          1           0
--------------------------------------------------
多项式特征构造后:训练集维度 (936, 35),测试集维度 (402, 35)
--------------------------------------------------
============================================================
普通线性回归(二次多项式特征) 评估结果:
对数尺度 - 训练集RMSE:0.3825 | 测试集RMSE:0.3740
原始尺度 - 训练集RMSE:4707.56 元 | 测试集RMSE:4523.22 元
============================================================

============================================================
岭回归(L2正则化+二次多项式特征) 评估结果:
对数尺度 - 训练集RMSE:0.3825 | 测试集RMSE:0.3740
原始尺度 - 训练集RMSE:4710.78 元 | 测试集RMSE:4523.84 元
============================================================

============================================================
梯度提升回归(集成学习+二次多项式特征) 评估结果:
对数尺度 - 训练集RMSE:0.3506 | 测试集RMSE:0.3988
原始尺度 - 训练集RMSE:4279.33 元 | 测试集RMSE:4599.79 元
============================================================


进程已结束,退出代码为 0

'''
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:机器学习 多项式 Polynomial regression Matplotlib

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
加好友,备注jr
拉您进交流群
GMT+8, 2026-1-7 06:20