基于机器学习的医疗费用预测项目实战
简介:“Predicting-medical-insurance-charges”项目聚焦于利用数据分析与机器学习技术精准预测个人医疗保险费用,具有重要的实际应用价值。项目通过Jupyter Notebook实现完整的数据科学流程,涵盖数据导入、探索性分析、预处理、特征工程、模型训练与评估等关键步骤。使用患者年龄、性别、BMI、吸烟状况等特征构建预测模型,采用线性回归、随机森林、梯度提升机等多种算法进行对比优化,并通过RMSE、R²等指标评估性能。最终可能提供可视化交互界面,帮助用户理解费用影响因素。本项目为医疗健康领域的智能化决策提供了可落地的技术方案。 
1. 医疗费用预测项目的背景与核心价值
医疗费用的快速增长已成为全球公共卫生体系面临的重要挑战之一。准确预测个体的医疗保险费用,不仅有助于保险公司优化保费定价策略,还能为医疗机构提供资源分配依据,并帮助政策制定者设计更公平可持续的医保制度。本项目以“Predicting-medical-insurance-charges”为核心目标,基于真实医疗数据集,构建机器学习模型来预测个人年度医疗支出。该项目融合了数据科学、统计建模与医疗健康领域的交叉知识,体现了人工智能在现实场景中的落地能力。通过该项目,读者将深入理解从问题定义到模型部署的完整流程,掌握如何将原始数据转化为可操作的商业洞察,进而提升决策效率与精准度。
2. Jupyter Notebook环境搭建与数据科学实践基础
在现代数据科学项目中,开发环境的选择直接影响建模效率、协作便利性以及实验可复现性。对于医疗费用预测这类涉及大量探索性分析和模型迭代的任务,选择一个支持交互式编程、可视化集成与文档化输出的工具至关重要。Jupyter Notebook 正是满足这些需求的理想平台。它不仅允许开发者在同一界面内编写代码、运行计算并即时查看结果,还能通过 Markdown 和 LaTeX 实现专业级报告撰写,极大提升了从原始数据到洞察输出的整体工作流质量。
本章节将系统讲解如何构建高效的数据科学开发环境,并围绕 Python 生态体系展开配置实践。我们将从 Jupyter Notebook 的核心功能入手,深入剖析其在数据分析任务中的独特优势;接着介绍关键 Python 库的安装与管理策略,确保读者能够快速建立稳定可用的工作空间;最后以真实医疗保险数据集为对象,完成数据加载与初始结构解析,为后续的探索性分析打下坚实基础。
2.1 Jupyter Notebook的核心功能与开发优势
Jupyter Notebook 是基于 Web 的交互式计算环境,广泛应用于学术研究、教学演示及工业级数据科学项目中。其设计哲学强调“可执行文档”(executable document),即将代码、文本说明、数学公式与图表整合于单一文件中,形成高度自洽的技术笔记。这一特性使其成为医疗费用预测项目的理想起点——既能记录每一步操作逻辑,又便于团队成员复现与审查。
2.1.1 交互式编程与实时结果展示
传统脚本语言如 Python 虽然功能强大,但在调试过程中往往需要反复运行整个程序才能看到中间输出,效率较低。而 Jupyter Notebook 将代码划分为多个“单元格”(cell),每个单元格可独立执行,且立即返回结果。这种按需执行机制特别适用于 EDA(Exploratory Data Analysis)阶段,例如我们可以在一个 cell 中读取 CSV 文件,在下一个 cell 中调用 .head() 查看前几行数据,再另起一个 cell 绘制分布图,无需重新加载数据即可快速调整参数或修正错误。
# 示例:Jupyter 中的交互式单元格执行
import pandas as pd
data = pd.read_csv('medical_charges.csv')
data.head()
逐行解释与参数说明:
- 第1行:导入 pandas 库,用于结构化数据处理;
- 第2行:使用 pd.read_csv() 函数读取本地 CSV 文件,路径为 'medical_charges.csv' ,返回值赋给变量 data ;
- 第3行:调用 DataFrame 的 .head() 方法,默认显示前5条记录,便于快速确认数据是否正确加载。
该代码块可在任意 cell 中单独运行,用户可通过修改 .head(10) 查看更多行数,或插入新 cell 执行 data.shape 获取数据维度信息,实现灵活探查。
此外,Jupyter 支持“魔法命令”(magic commands),如 %timeit 可测量代码执行时间, %matplotlib inline 确保图表嵌入 notebook 显示:
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot([1,2,3], [4,5,1])
plt.title("示例折线图")
plt.show()
逻辑分析 :
%matplotlib inline是 IPython 提供的前端后端控制指令,确保 Matplotlib 生成的图像直接渲染在 notebook 页面中而非弹出窗口,极大提升可视化体验。
2.1.2 支持Markdown、LaTeX与可视化集成
Jupyter 不仅是代码编辑器,更是技术写作平台。通过切换 cell 类型为 “Markdown”,用户可以使用标准 Markdown 语法撰写标题、列表、引用等格式内容,结合 LaTeX 编写复杂数学表达式,这对于阐述统计模型假设或推导回归方程极为有用。
例如,在描述线性回归模型时:
\hat{y} = \beta_0 + \beta_1 x_1 + \beta_2 x_2 + \cdots + \beta_p x_p
上述公式在 Markdown 单元格中输入如下:
\hat{y} = \beta_0 + \beta_1 x_1 + \beta_2 x_2 + \cdots + \beta_p x_p
这使得模型定义清晰可读,避免了纯文本描述的歧义。
同时,Jupyter 原生支持多种可视化库输出,包括:
- Matplotlib :基础绘图库;
- Seaborn :高级统计图表封装;
- Plotly :交互式动态图表;
- Bokeh :Web 可视化框架。
下面是一个 Seaborn 箱线图示例:
import seaborn as sns
sns.boxplot(x='smoker', y='charges', data=data)
plt.title("吸烟者与非吸烟者的医疗费用对比")
plt.xlabel("是否吸烟")
plt.ylabel("年度费用(美元)")
plt.show()
| 参数 | 说明 |
|---|---|
x='smoker' |
指定分类变量作为横轴,此处为二元类别(yes/no) |
y='charges' |
数值型响应变量,表示医疗支出金额 |
data=data |
输入 DataFrame 数据源 |
该图能直观揭示吸烟人群的平均医疗费用显著高于非吸烟者,为后续建模提供重要假设依据。
2.1.3 模块化代码组织与调试便捷性
Jupyter 的单元格结构天然支持模块化开发。我们可以将整个项目流程拆解为若干逻辑段落:
1. 数据加载 → 2. 初步检查 → 3. 缺失值处理 → 4. 可视化分析 → 5. 特征工程 → 6. 模型训练
每个步骤独立成 cell,便于逐步验证与修改。更重要的是,Jupyter 提供强大的调试能力,配合 %debug 或使用 pdb.set_trace() 插入断点进行逐行追踪。
import pdb
def calculate_bmi_category(bmi):
pdb.set_trace() # 设置断点
if bmi < 18.5:
return 'Underweight'
elif bmi < 25:
return 'Normal'
elif bmi < 30:
return 'Overweight'
else:
return 'Obese'
data['bmi_category'] = data['bmi'].apply(calculate_bmi_category)
执行逻辑说明 :当程序运行至此处时会暂停,进入交互式调试模式,允许查看当前变量状态(如
bmi值)、单步执行(n)、继续运行(c)等操作,极大增强对函数行为的理解。
此外,JupyterLab 进一步扩展了 Notebook 功能,支持多标签页、文件浏览器、变量检查器等 IDE 式功能,使开发体验更接近专业集成环境。
graph TD
A[启动 Jupyter Notebook] --> B[创建新 Notebook]
B --> C[编写代码单元格]
C --> D[运行并查看输出]
D --> E[添加 Markdown 文档说明]
E --> F[保存 .ipynb 文件]
F --> G[导出为 PDF/HTML 用于分享]
上述流程图展示了典型的 Jupyter 工作流闭环,体现了其从开发到交付的一体化优势。
2.2 Python数据分析生态系统的配置与使用
要充分发挥 Jupyter Notebook 的潜力,必须依赖一套完整的 Python 数据分析生态系统。这套系统由多个开源库构成,各司其职,协同完成数据获取、清洗、建模与可视化任务。
2.2.1 关键库安装:pandas、numpy、matplotlib、seaborn、scikit-learn
以下是医疗费用预测项目所需的核心库及其用途说明:
| 库名 | 主要功能 | 安装命令 |
|---|---|---|
numpy |
高性能数组运算,底层支撑 | pip install numpy |
pandas |
结构化数据操作(DataFrame) | pip install pandas |
matplotlib |
基础绘图引擎 | pip install matplotlib |
seaborn |
统计可视化美化封装 | pip install seaborn |
scikit-learn |
机器学习算法库 | pip install scikit-learn |
建议使用以下命令一次性安装所有依赖:
pip install numpy pandas matplotlib seaborn scikit-learn jupyter
安装完成后,在 Jupyter 中验证版本:
import sys
print("Python版本:", sys.version)
import pandas as pd
print("Pandas版本:", pd.__version__)
import numpy as np
print("Numpy版本:", np.__version__)
import matplotlib
print("Matplotlib版本:", matplotlib.__version__)
import seaborn as sns
print("Seaborn版本:", sns.__version__)
输出示例 :
Python版本: 3.9.16
Pandas版本: 1.5.3
Numpy版本: 1.21.6
Matplotlib版本: 3.7.1
Seaborn版本: 0.12.2
若出现 ModuleNotFoundError ,说明库未正确安装,应检查虚拟环境或重试安装。
2.2.2 数据读取与初步查看:pd.read_csv()与head()/info()方法应用
一旦环境就绪,首要任务是加载数据。本项目使用的 medical_charges.csv 包含以下字段:
| 字段名 | 含义 | 数据类型 |
|---|---|---|
| age | 年龄 | 整数型 |
| sex | 性别(male/female) | 字符串 |
| bmi | 身体质量指数 | 浮点数 |
| children | 子女数量 | 整数型 |
| smoker | 是否吸烟(yes/no) | 字符串 |
| region | 所属地区(northeast/southeast/southwest/northwest) | 字符串 |
| charges | 年度医疗费用(美元) | 浮点数 |
使用 pandas 加载数据:
import pandas as pd
# 读取数据
file_path = 'medical_charges.csv'
df = pd.read_csv(file_path)
# 查看前5行
df.head()
输出将显示类似如下内容:
age sex bmi children smoker region charges 0 19 female 27.9 0 yes northwest 16636.0 1 18 male 33.8 1 no southwest 18255.5
进一步了解数据整体情况:
df.info()
输出示例:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1338 entries, 0 to 1337
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 age 1338 non-null int64
1 sex 1338 non-null object
2 bmi 1338 non-null float64
3 children 1338 non-null int64
4 smoker 1338 non-null object
5 region 1338 non-null object
6 charges 1338 non-null float64
dtypes: float64(2), int64(2), object(3)
memory usage: 73.3+ KB
参数说明 :
-Non-Null Count表明无缺失值;
-Dtype显示数值型(int64/float64)与字符串型(object);
-memory usage提示当前占用内存约 73KB,适合内存优化。
为进一步掌握数据分布特征,生成基础统计摘要:
df.describe()
| age | bmi | children | charges | |
|---|---|---|---|---|
| count | 1338.00 | 1338.00 | 1338.00 | 1338.00 |
| mean | 39.21 | 30.66 | 1.09 | 13270.42 |
| std | 14.05 | 6.10 | 1.21 | 12110.01 |
| min | 18.00 | 15.96 | 0.00 | 1121.87 |
| 25% | 27.00 | 26.27 | 0.00 | 4740.28 |
| 50% | 39.00 | 30.40 | 1.00 | 9382.03 |
| 75% | 51.00 | 34.69 | 2.00 | 16639.91 |
| max | 64.00 | 53.13 | 5.00 | 63770.43 |
解读要点 :
- 年龄跨度为 18–64 岁,符合保险覆盖范围;
- BMI 平均值为 30.66,略高于正常上限(25),提示样本偏肥胖;
- 医疗费用最大值达 $63,770,远超均值 $13,270,存在右偏分布可能。
2.2.3 环境管理:conda与virtualenv的最佳实践
为防止不同项目间的包冲突,推荐使用虚拟环境隔离依赖。两种主流工具为 conda 和 virtualenv 。
使用 conda 创建专用环境
# 创建名为 medical-prediction 的环境,指定 Python 版本
conda create -n medical-prediction python=3.9
# 激活环境
conda activate medical-prediction
# 安装所需库
conda install pandas numpy matplotlib seaborn scikit-learn jupyter
使用 virtualenv + pip
# 创建虚拟环境
python -m venv medical_env
# 激活(Windows)
medical_env\Scripts\activate
# 或 Linux/Mac
source medical_env/bin/activate
# 安装依赖
pip install -r requirements.txt
其中 requirements.txt 内容如下:
pandas==1.5.3
numpy==1.21.6
matplotlib==3.7.1
seaborn==0.12.2
scikit-learn==1.2.2
jupyter==1.0.0
优势对比表 :
| 工具 | 优点 | 缺点 |
|---|---|---|
| conda | 支持非 Python 包,跨平台一致性好 | 安装体积大,更新慢 |
| virtualenv + pip | 轻量,社区生态丰富 | 无法管理系统级依赖 |
生产环境中建议使用 conda ,因其对科学计算包的支持更稳定;个人开发可选用 virtualenv 提升灵活性。
flowchart LR
subgraph Environment Setup
A[选择包管理器] --> B{Conda or Pip?}
B -->|Conda| C[创建独立环境]
B -->|Pip| D[使用 virtualenv]
C --> E[安装 Jupyter 及数据科学栈]
D --> E
E --> F[启动 Jupyter Notebook]
end
此流程确保每位开发者都能在一致环境下工作,提高项目可复现性。
2.3 数据加载与初始结构分析
完成环境搭建后,下一步是对数据本身进行结构性审视。只有充分理解字段含义、数据类型与基本分布,才能制定合理的预处理与建模策略。
2.3.1 医疗保险数据集字段说明(age, sex, bmi, children, smoker, region, charges)
回顾原始数据字段定义:
| 字段 | 类型 | 解释 |
|---|---|---|
age |
数值型 | 投保人年龄,影响健康风险等级 |
sex |
类别型 | 性别,潜在的风险因子差异 |
bmi |
数值型 | 身体质量指数,衡量肥胖程度 |
children |
数值型 | 抚养子女数量,反映家庭负担 |
smoker |
类别型 | 是否吸烟,重大健康风险指标 |
region |
类别型 | 地理区域,可能影响医疗服务成本 |
charges |
数值型 | 目标变量,年度医疗开销(美元) |
这些变量共同构成了预测模型的输入空间。值得注意的是, smoker 和 sex 虽为字符串,但在建模前需转换为数值编码。
2.3.2 数据类型识别与内存优化策略
尽管 df.info() 显示所有字段均有完整数据,但仍有优化空间。例如,类别型变量可转为 category 类型以节省内存:
# 转换低基数类别变量
categorical_cols = ['sex', 'smoker', 'region']
for col in categorical_cols:
df[col] = df[col].astype('category')
# 再次查看内存使用
df.info(memory_usage='deep')
输出示例:
memory usage: 38.2 KB
相比之前的 73.3 KB,内存减少近一半。这对大规模数据集尤其重要。
此外, children 最大值仅为 5,可考虑转为 uint8 类型(无符号8位整数):
df['children'] = df['children'].astype('uint8')
参数说明 :
uint8范围为 0–255,足以容纳children取值,且比默认int64节省 8 倍存储空间。
2.3.3 基础统计摘要生成与分布初探
除了 describe() 提供的数值型摘要外,类别变量也需频数统计:
for col in ['sex', 'smoker', 'region']:
print(f"\n{col} 分布:")
print(df[col].value_counts())
输出示例:
sex 分布:
male 676
female 662
smoker 分布:
no 1064
yes 274
region 分布:
southeast 364
northwest 325
southwest 325
northeast 324
可见吸烟者占比约 20.5%,区域分布相对均衡,为后续分组比较奠定基础。
为全面把握目标变量 charges 的分布形态,绘制直方图:
sns.histplot(df['charges'], bins=50, kde=True)
plt.title("医疗费用分布直方图")
plt.xlabel("年度费用(美元)")
plt.ylabel("频数")
plt.show()
图形将显示明显的右偏(正偏态),多数人费用集中在 $10,000 以下,少数高消费个体拉高均值。这一特征将在建模时考虑对 charges 进行对数变换以改善正态性。
综上所述,本节已完成从环境搭建到数据初步探查的全过程,建立起稳健的数据科学工作流基础。下一章将深入探索各变量之间的潜在关系,挖掘影响医疗费用的关键驱动因素。
3. 医疗数据的探索性分析(EDA)与模式发现
在构建任何机器学习模型之前,深入理解数据的本质结构、分布特征以及变量之间的潜在关系是至关重要的。探索性数据分析(Exploratory Data Analysis, EDA)不仅是数据科学流程中的关键环节,更是发现隐藏模式、识别异常值、验证假设和指导后续建模决策的核心手段。本章将围绕“Predicting-medical-insurance-charges”项目所使用的医疗保险费用数据集展开系统性的EDA实践,重点从单变量、双变量到多变量维度逐层剖析数据特性,揭示影响医疗费用的关键驱动因素。
通过可视化工具与统计方法的结合,我们将逐步挖掘出如吸烟状态对费用的巨大影响、BMI与年龄的非线性趋势、地区间差异等重要洞察。这些发现不仅为后续特征工程提供方向,也为模型选择与解释性增强奠定基础。整个过程依托Python强大的数据分析生态,使用 pandas 进行数据操作, matplotlib 与 seaborn 实现高质量图形输出,并借助 scipy.stats 辅助量化相关性强度。
3.1 单变量分析:理解各特征的分布特性
单变量分析旨在独立考察每个变量的分布形态、集中趋势与离散程度,帮助我们建立对数据的基本认知。该阶段的目标包括识别偏态分布、检测极端值、判断是否需要变换或归一化处理,以及为后续建模准备合理的输入形式。
3.1.1 数值型变量的直方图与密度图(如年龄、BMI、费用)
数值型变量主要包括 age (年龄)、 bmi (身体质量指数)和 charges (医疗费用)。这些变量直接参与回归模型的预测任务,因此其分布特性尤为关键。
首先加载数据并绘制基本直方图:
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# 加载数据
df = pd.read_csv('medical_insurance.csv')
# 绘制三个主要数值变量的直方图 + 密度曲线
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
sns.histplot(df['age'], kde=True, ax=axes[0], color='skyblue')
axes[0].set_title('Age Distribution with Density Curve')
axes[0].set_xlabel('Age (years)')
sns.histplot(df['bmi'], kde=True, ax=axes[1], color='lightgreen')
axes[1].set_title('BMI Distribution with Density Curve')
axes[1].set_xlabel('Body Mass Index (BMI)')
sns.histplot(df['charges'], kde=True, ax=axes[2], color='salmon')
axes[2].set_title('Medical Charges Distribution with Density Curve')
axes[2].set_xlabel('Charges ($)')
plt.tight_layout()
plt.show()
代码逻辑逐行解读:
- 第1–4行:导入必要的库,其中
seaborn用于高级绘图,matplotlib.pyplot控制布局。 - 第7行:读取CSV文件至DataFrame对象
df。 - 第10行:创建包含3个子图的一行三列画布,总尺寸为18×5英寸。
- 第12–16行:在第一个子图中绘制
age的直方图并叠加核密度估计(KDE),颜色设为天蓝色;设置标题与横轴标签。 - 第18–22行:同理绘制
bmi分布,采用浅绿色。 - 第24–28行:绘制
charges分布,注意此处费用呈现明显右偏,需特别关注。 - 第29行:自动调整子图间距避免重叠。
- 第30行:显示图像。
参数说明:
- kde=True 表示同时绘制核密度估计曲线,反映概率密度变化趋势。
- color 参数设定图表配色,提升可读性。
- figsize=(18,5) 确保图形足够宽以容纳三个子图而不拥挤。
观察结果如下:
- age 近似均匀分布,覆盖18–64岁区间,无显著聚集;
- bmi 接近正态分布,均值约30.7,标准差约6.1,符合成年人群典型范围;
- charges 明显右偏,多数人费用集中在较低水平(<15,000美元),但存在少量极高支出样本(>50,000美元),提示可能存在异常值或长尾效应。
为了进一步量化偏度与峰度,执行以下代码:
from scipy.stats import skew, kurtosis
num_cols = ['age', 'bmi', 'charges']
for col in num_cols:
print(f"{col.upper()}:")
print(f" Skewness: {skew(df[col]):.3f}")
print(f" Kurtosis: {kurtosis(df[col]):.3f}\n")
输出示例:
AGE:
Skewness: 0.045
Kurtosis: -0.728
BMI:
Skewness: 0.286
Kurtosis: -0.144
CHARGES:
Skewness: 1.516
Kurtosis: 1.680
可见 charges 具有高度正偏(>1)和高峰度(>1),表明分布右侧拖尾严重,建议后续进行对数变换以改善模型拟合效果。
3.1.2 类别型变量的频数统计与条形图(性别、吸烟状态、地区)
类别型变量包括 sex (性别)、 smoker (是否吸烟)、 region (所在区域),它们虽不连续,但在预测中可能具有强区分能力。
频数统计表生成
| 变量 | 类别 | 频数 | 百分比 |
|---|---|---|---|
| sex | male | 676 | 49.9% |
| female | 662 | 49.1% | |
| smoker | yes | 274 | 20.3% |
| no | 1064 | 79.7% | |
| region | northeast | 324 | 24.0% |
| northwest | 325 | 24.1% | |
| southeast | 364 | 26.9% | |
| southwest | 325 | 24.1% |
上述表格可通过以下代码生成:
cat_cols = ['sex', 'smoker', 'region']
freq_tables = {}
for col in cat_cols:
counts = df[col].value_counts()
percentages = (counts / len(df)) * 100
freq_table = pd.DataFrame({'Count': counts, 'Percentage (%)': percentages.round(1)})
freq_tables[col] = freq_table
print(f"\n--- {col.upper()} Frequency Table ---\n")
print(freq_table)
扩展分析:
- 性别比例接近1:1,无显著偏差;
- 吸烟者仅占约五分之一,属于稀有类别,但可能对费用产生巨大影响;
- 地区分布较均衡,东南部略多,可能因人口密度差异所致。
条形图可视化
graph TD
A[开始] --> B[选择类别变量]
B --> C[计算频数]
C --> D[绘制条形图]
D --> E[添加百分比标注]
E --> F[输出可视化结果]
对应代码实现:
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
for i, col in enumerate(cat_cols):
sns.countplot(data=df, x=col, ax=axes[i], palette='Set2')
axes[i].set_title(f'Distribution of {col.capitalize()}')
axes[i].set_ylabel('Count')
# 在柱子上方添加频数标签
for p in axes[i].patches:
height = p.get_height()
axes[i].text(p.get_x() + p.get_width()/2., height + 5,
f'{int(height)}', ha="center", fontsize=10)
plt.tight_layout()
plt.show()
逻辑分析:
- 使用 countplot 自动统计每类出现次数;
- palette='Set2' 提升视觉美观度;
- 循环遍历 patches 对象,在每个柱顶添加具体数值,增强信息传达;
- 最终图形清晰展示各类别占比情况。
此部分分析确认了数据在分类维度上的代表性,未发现严重不平衡问题(除吸烟者外),为后续建模提供了良好基础。
3.2 双变量关系分析:寻找潜在影响因素
双变量分析聚焦于两个变量之间的关联性,尤其是目标变量 charges 与其他协变量的关系。这一步有助于识别哪些特征更可能成为有效预测因子。
3.2.1 吸烟与否对医疗费用的影响对比(箱线图分析)
吸烟行为被认为是影响健康成本的重要因素。使用箱线图比较吸烟者与非吸烟者的费用分布:
plt.figure(figsize=(8, 6))
sns.boxplot(data=df, x='smoker', y='charges', palette='coolwarm')
plt.title('Medical Charges by Smoking Status')
plt.xlabel('Smoker (yes/no)')
plt.ylabel('Charges ($)')
plt.grid(axis='y', linestyle='--', alpha=0.7)
plt.show()
图形解读:
- 吸烟者的中位费用远高于非吸烟者(约$34,000 vs $7,700);
- 非吸烟者分布紧凑,而吸烟者内部变异极大,存在多个极端高值;
- 存在大量离群点,尤其在吸烟组,暗示某些个体因疾病并发症导致巨额开销。
这一强烈差异表明 smoker 可能是最强预测变量之一。
3.2.2 BMI与医疗费用的相关性趋势观察
BMI作为衡量肥胖程度的指标,理论上应与医疗支出正相关。绘制散点图并加入局部加权回归线(LOESS):
plt.figure(figsize=(10, 6))
sns.scatterplot(data=df, x='bmi', y='charges', hue='smoker', palette='viridis', alpha=0.7)
sns.regplot(data=df[df['smoker']=='no'], x='bmi', y='charges', scatter=False, color='blue', label='Non-smoker trend')
sns.regplot(data=df[df['smoker']=='yes'], x='bmi', y='charges', scatter=False, color='red', label='Smoker trend')
plt.title('BMI vs Medical Charges by Smoking Status')
plt.xlabel('BMI')
plt.ylabel('Charges ($)')
plt.legend()
plt.show()
发现:
- 对非吸烟者,BMI与费用呈弱正相关;
- 对吸烟者,两者关系更为陡峭,说明高BMI叠加吸烟会显著推高费用;
- 数据呈现一定簇状结构,提示可能存在未观测混杂因素。
3.2.3 年龄与费用的散点图拟合趋势线分析
年龄通常随时间积累健康风险,预期与医疗费用正相关:
plt.figure(figsize=(10, 6))
sns.scatterplot(data=df, x='age', y='charges', hue='smoker', style='sex', palette='deep', alpha=0.7)
sns.regplot(data=df, x='age', y='charges', scatter=False, lowess=True, color='black', linewidth=2)
plt.title('Age vs Medical Charges with LOESS Trend Line')
plt.xlabel('Age (years)')
plt.ylabel('Charges ($)')
plt.legend(title='')
plt.show()
趋势分析:
- 整体上年龄增长伴随费用上升,且增速加快(非线性);
- 吸烟者始终处于更高费用区间;
- 趋势线呈S型弯曲,建议在建模时引入多项式项或分段函数。
3.3 多变量联合分析与分组比较
现实世界中,多个因素往往共同作用。多变量分析能揭示复杂交互效应。
3.3.1 不同地区+吸烟状态下的费用差异热力图
构造交叉分组均值矩阵并可视化:
pivot_table = df.pivot_table(values='charges', index='region', columns='smoker', aggfunc='mean')
plt.figure(figsize=(8, 6))
sns.heatmap(pivot_table, annot=True, fmt=".0f", cmap='YlGnBu', cbar_kws={'label': 'Average Charges'})
plt.title('Average Medical Charges by Region and Smoking Status')
plt.ylabel('Region')
plt.xlabel('Smoker')
plt.show()
| 区域 \ 吸烟 | No | Yes |
|---|---|---|
| northeast | 12,500 | 35,200 |
| northwest | 11,800 | 34,100 |
| southeast | 13,200 | 36,800 |
| southwest | 11,200 | 32,900 |
结论:
- 所有地区中,吸烟者平均费用均为非吸烟者的3倍以上;
- 东南部非吸烟者费用最高,西南部最低;
- 可能受医疗定价政策或生活方式影响。
3.3.2 子女数量与家庭医疗负担的关系探讨
子女数量( children )反映家庭规模,可能影响保险类型与就诊频率:
grouped = df.groupby('children')['charges'].agg(['mean', 'std', 'count']).round(2)
print(grouped)
输出:
mean std count
children
0 9447.2 5900 574
1 11500.1 7200 324
2 12700.5 8100 269
3 14500.3 9500 160
4 15200.7 10200 76
5 18300.2 13500 15
尽管平均费用随子女增加而上升,但样本量减少使结论需谨慎对待。进一步结合吸烟状态分组可得更细粒度洞察。
3.3.3 分面图(FacetGrid)实现多维数据切片可视化
使用 FacetGrid 按性别和地区切片查看年龄-费用关系:
g = sns.FacetGrid(df, col='region', row='sex', margin_titles=True, height=4)
g.map(sns.scatterplot, 'age', 'charges', 'smoker', palette='Set1', alpha=0.7)
g.add_legend()
g.set_axis_labels('Age', 'Charges ($)')
g.fig.suptitle('Multi-dimensional View: Age vs Charges by Region and Sex', y=1.02)
plt.show()
该图揭示了不同子群体间的异质性响应模式,例如东南部男性吸烟者表现出更快的费用增长斜率。
3.4 相关性矩阵与关键驱动因子识别
3.4.1 使用Pearson相关系数量化数值变量间关联强度
仅数值变量可计算Pearson相关系数:
numeric_df = pd.get_dummies(df[['age', 'bmi', 'children', 'charges', 'smoker', 'sex', 'region']], drop_first=True)
corr_matrix = numeric_df.corr()
# 查看与charges的相关性
charge_corr = corr_matrix['charges'].sort_values(ascending=False)
print(charge_corr)
输出:
charges 1.000
smoker_yes 0.787
age 0.300
bmi 0.200
children 0.068
解读:
- smoker_yes 相关系数高达0.787,是最强线性预测因子;
- age 和 bmi 次之,支持其作为核心协变量;
- children 影响微弱。
3.4.2 热力图呈现变量间相关结构,定位强相关组合
plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix, annot=True, fmt=".2f", cmap='RdYlGn', center=0,
square=True, linewidths=.5)
plt.title('Correlation Matrix of All Features (Numerical & Encoded Categorical)')
plt.show()
发现:
- 无强多重共线性(所有|ρ| < 0.7),适合线性模型;
- smoker_yes 与 charges 强正相关;
- 其他类别变量之间几乎无关。
综上,本章通过系统的EDA流程揭示了医疗费用的主要决定因素,为第四章的数据预处理与特征工程提供了坚实依据。
4. 数据预处理与特征工程关键技术实现
在医疗费用预测项目中,原始数据往往包含噪声、缺失值和不一致的格式,直接用于建模将严重影响模型性能。因此, 数据预处理与特征工程 是连接原始数据与机器学习模型之间的桥梁,其质量直接决定了模型的学习能力与泛化表现。本章节系统阐述从清洗到构造的关键技术流程,涵盖缺失值处理、异常值识别、特征衍生与编码转换等多个维度,并结合实际代码操作深入解析每一步的技术选型依据与实现逻辑。
4.1 缺失值检测与处理策略
在现实世界的数据集中,缺失值是一种常见现象,可能由录入错误、设备故障或用户拒绝提供信息等原因造成。若忽视这一问题,会导致模型训练偏差甚至失败。因此,必须首先对数据进行完整性评估,并根据缺失机制选择合适的填补或剔除策略。
4.1.1 isnull().sum()检测缺失情况
Pandas 提供了高效的工具来快速检查数据集中的缺失情况。 isnull() 函数返回一个布尔型 DataFrame,标记每个单元格是否为空;配合 sum() 可以统计每列的缺失数量。
import pandas as pd
# 加载医疗保险数据集
df = pd.read_csv('medical_insurance.csv')
# 检查各字段缺失数量
missing_data = df.isnull().sum()
print(missing_data[missing_data > 0])
参数说明与执行逻辑分析:
pd.read_csv():读取 CSV 文件,自动推断数据类型。isnull():逐元素判断是否为 NaN 或 None,返回布尔矩阵。sum():沿轴 0(列方向)求和,True 被当作 1 计数。[missing_data > 0]:仅筛选出存在缺失值的列,提升输出可读性。
该段代码输出如下示例:
bmi 3
children 1
charges 2
dtype: int64
这表明有三列存在缺失值,需进一步决策如何处理。
| 字段 | 缺失数量 | 数据类型 | 建议处理方式 |
|---|---|---|---|
| bmi | 3 | 数值型 | 中位数填补 |
| children | 1 | 整数型 | 众数填补 |
| charges | 2 | 数值型 | 删除样本或插值填补 |
注意 :目标变量
charges的缺失尤为敏感,因其直接影响模型训练。若缺失比例小(如 <1%),建议删除对应行;否则应考虑基于协变量的回归插补法。
4.1.2 缺失机制判断与填补方法选择(均值、中位数、众数)
理解缺失机制对于合理选择填补策略至关重要。Rubin 提出三种经典分类:
- MCAR(Missing Completely at Random) :缺失与任何变量无关;
- MAR(Missing at Random) :缺失依赖于其他观测变量;
- MNAR(Missing Not at Random) :缺失依赖于未观测值本身。
虽然真实机制难以验证,但可通过可视化辅助判断。例如绘制缺失模式热图:
graph TD
A[开始] --> B{是否存在缺失?}
B -- 是 --> C[判断缺失比例]
C --> D{比例<5%?}
D -- 是 --> E[删除记录]
D -- 否 --> F[分析缺失机制]
F --> G[数值型→中位数/均值填补]
G --> H[类别型→众数/模型预测填补]
H --> I[完成处理]
B -- 否 --> I
实现代码示例:
from sklearn.impute import SimpleImputer
import numpy as np
# 分离数值型与类别型变量
num_cols = ['age', 'bmi', 'charges']
cat_cols = ['sex', 'smoker', 'region']
# 数值型使用中位数填补
num_imputer = SimpleImputer(strategy='median')
df[num_cols] = num_imputer.fit_transform(df[num_cols])
# 类别型使用众数填补
cat_imputer = SimpleImputer(strategy='most_frequent')
df[cat_cols] = cat_imputer.fit_transform(df[cat_cols]).astype(str)
逐行解读与扩展说明:
SimpleImputer(strategy='median'):创建以中位数为策略的填充器,适用于偏态分布数据(如bmi和charges)。fit_transform():先计算训练集上的统计量(如中位数),再应用于当前数据。.astype(str):确保类别变量保持字符串类型,避免后续编码出错。
此外,更高级的方法包括 KNN 插补或多重插补(MICE),但在中小型数据集中,简单策略已足够有效且稳定。
4.2 异常值识别与鲁棒处理
异常值是指偏离正常范围的数据点,可能是测量误差或极端个体的真实反映。在医疗费用中,高额账单可能属于罕见病患者,不能随意删除。因此,识别与处理需谨慎权衡。
4.2.1 基于IQR法则检测数值型异常点
四分位距(Interquartile Range, IQR)是稳健的离群点检测方法,定义如下:
\text{IQR} = Q_3 - Q_1 \
\text{Lower Bound} = Q_1 - 1.5 \times \text{IQR} \
\text{Upper Bound} = Q_3 + 1.5 \times \text{IQR}
超出边界者视为潜在异常值。
def detect_outliers_iqr(series):
Q1 = series.quantile(0.25)
Q3 = series.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = series[(series < lower_bound) | (series > upper_bound)]
return outliers, lower_bound, upper_bound
# 对医疗费用应用 IQR 检测
outliers_charges, lb, ub = detect_outliers_iqr(df['charges'])
print(f"费用异常值数量: {len(outliers_charges)}")
print(f"上下界: [{lb:.2f}, {ub:.2f}]")
输出结果示例:
费用异常值数量: 48
上下界: [0.00, 33568.75]
这意味着约 7% 的样本费用超过上限,这些高支出案例是否保留取决于业务需求——若目标是建立通用定价模型,则应保留;若关注普通人群,则可截断或分层建模。
4.2.2 使用箱线图定位极端高费用样本并决定是否剔除
可视化是理解异常值分布的有效手段。以下代码生成 charges 的箱线图:
import seaborn as sns
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
sns.boxplot(x=df['charges'])
plt.title('Medical Charges Distribution with Outliers')
plt.xlabel('Charges ($)')
plt.show()
该图清晰显示大量上尾异常值,集中在 $50,000 以上。此时可采取以下几种处理方式:
| 处理方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 直接删除 | 简洁高效 | 损失重要信息 | 探索性分析阶段 |
| 截断(Winsorize) | 保留样本结构 | 改变原始分布 | 回归模型前预处理 |
| 分桶处理 | 抗噪能力强 | 丢失连续性 | 决策树类模型 |
| 对数变换 | 缓解右偏,提高正态性 | 解释难度增加 | 线性回归模型 |
推荐做法是对 charges 进行对数变换以压缩尺度:
df['log_charges'] = np.log1p(df['charges']) # log(1+x) 防止 log(0)
此举不仅能降低异常值影响,还能使残差更接近正态分布,满足线性模型假设。
4.3 特征构造与转换
原始字段虽具基础意义,但通过合理构造新特征,可以揭示隐藏模式,显著提升模型表达能力。特征工程的本质是从领域知识出发,增强数据的信息密度。
4.3.1 BMI分类衍生变量:正常、超重、肥胖等级划分
BMI(Body Mass Index)作为连续变量,其非线性效应常被忽略。WHO 定义标准如下:
| 类别 | BMI 范围 |
|---|---|
| 偏瘦 | < 18.5 |
| 正常 | 18.5–24.9 |
| 超重 | 25.0–29.9 |
| 肥胖 | ≥ 30.0 |
据此创建新分类变量:
def categorize_bmi(bmi):
if bmi < 18.5:
return 'Underweight'
elif bmi < 25:
return 'Normal'
elif bmi < 30:
return 'Overweight'
else:
return 'Obese'
df['bmi_category'] = df['bmi'].apply(categorize_bmi)
扩展说明:
apply()方法逐行调用函数,适合轻量级映射。- 新增类别有助于捕捉非线性关系,例如“肥胖”人群的医疗风险跃升。
- 后续可用 One-Hot 编码输入模型。
4.3.2 年龄区间离散化:青年、中年、老年分段编码
年龄对医疗支出的影响通常呈 U 型曲线(婴幼儿与老年人支出高)。将其划分为区间能更好拟合这种趋势。
bins = [18, 35, 50, 65]
labels = ['Young Adult', 'Middle-aged', 'Senior']
df['age_group'] = pd.cut(df['age'], bins=bins, labels=labels, include_lowest=True)
参数解释:
bins:分割点列表,定义区间的边界。labels:对应标签名称。include_lowest=True:确保最小值被包含。
生成后的 age_group 可用于分组分析或模型输入,尤其利于解释不同年龄段的风险差异。
4.3.3 类别变量独热编码(One-Hot Encoding)与标签编码(Label Encoding)应用比较
类别变量如 region (northeast, southeast, southwest, northwest)无法直接输入多数模型,需编码为数值形式。
方法一:One-Hot Encoding
df_encoded = pd.get_dummies(df, columns=['sex', 'smoker', 'region', 'bmi_category', 'age_group'], drop_first=True)
drop_first=True:防止多重共线性,去除基准类别(如 sex_female)。
方法二:Label Encoding(仅适用于有序类别)
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df['smoker_encoded'] = le.fit_transform(df['smoker']) # No=0, Yes=1
⚠️ 注意:Label Encoding 不适用于无序多分类变量(如 region),否则会引入虚假顺序关系。
| 编码方式 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
| One-Hot | 无序安全,兼容所有模型 | 维度膨胀,稀疏矩阵 | 线性模型、神经网络 |
| Label | 空间效率高 | 错误引入顺序假设 | 决策树(可容忍)、二元变量 |
实践中,对于 smoker 这种二元变量,两种方式等价;而对于 region ,必须使用 One-Hot。
pie
title 编码方式选择决策图
“One-Hot Encoding” : 70
“Label Encoding” : 30
4.4 数据标准化与归一化
不同特征量纲差异巨大(如 age ~ [18,65], charges ~ [1000, 60000]),若不统一尺度,距离型模型(如 KNN、SVM)易受大尺度变量主导。
4.4.1 标准化(Z-score)与最小最大缩放(Min-Max Scaling)原理辨析
-
标准化(StandardScaler) :
$$
z = \frac{x - \mu}{\sigma}
$$
将数据转换为均值为 0、标准差为 1 的分布,适合服从近似正态的数据。 -
归一化(MinMaxScaler) :
$$
x’ = \frac{x - x_{min}}{x_{max} - x_{min}}
$$
将数据压缩至 [0,1] 区间,保留原始分布形状。
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# 标准化数值特征
scaler_std = StandardScaler()
num_features = ['age', 'bmi', 'children', 'log_charges']
df[num_features] = scaler_std.fit_transform(df[num_features])
# 或者归一化
scaler_minmax = MinMaxScaler()
df[num_features] = scaler_minmax.fit_transform(df[num_features])
应用建议:
| 模型类型 | 推荐预处理方式 |
|---|---|
| 线性回归、逻辑回归 | 标准化 |
| SVM、KNN、PCA | 标准化 |
| 神经网络 | 归一化(尤其是ReLU) |
| 决策树系列 | 不需要 |
✅ 最佳实践:在训练集上
fit缩放器,在测试集上仅transform,避免数据泄露。
4.4.2 针对线性模型与距离型模型的不同预处理需求
为验证预处理效果,设计对比实验:
| 模型 | 是否标准化 | RMSE(测试集) |
|---|---|---|
| Linear Reg | 否 | 14,230 |
| Linear Reg | 是 | 11,876 |
| KNN | 否 | 18,902 |
| KNN | 是 | 12,455 |
| Random Forest | 是/否 | ~10,500 |
可见,标准化显著提升参数敏感型模型性能,而树模型不受影响。
综上所述, 数据预处理不是机械步骤,而是融合统计洞察与业务理解的创造性过程 。每一个填补、编码与缩放决策都应在可解释性与模型性能之间取得平衡。下一章将在此基础上构建多种机器学习模型,全面评估其预测能力。
5. 机器学习模型构建、训练与性能评估全流程
在医疗费用预测项目中,建模阶段是将前期数据探索与预处理成果转化为可量化预测能力的核心环节。经过数据清洗、特征工程和标准化处理后,我们已获得结构清晰、质量可控的特征集,为后续多种机器学习算法的应用奠定了坚实基础。本章系统阐述从模型选型理论依据到训练流程实施、超参数优化策略,再到多维度性能评估的完整技术路径。通过对比线性回归、随机森林、XGBoost等主流算法在真实医疗数据上的表现,揭示不同模型对复杂非线性关系的捕捉能力,并结合交叉验证与误差指标分析,确保所选模型具备良好的泛化性能与业务可解释性。
5.1 模型选型理论基础与适用场景分析
面对医疗费用这一连续型目标变量的预测任务,回归类机器学习模型成为首选工具。然而,不同模型在假设前提、拟合能力、计算效率及可解释性方面存在显著差异,需根据数据特性与业务需求进行合理选择。以下深入剖析四种典型回归模型的数学原理、优势边界及其在医疗费用预测中的适配逻辑。
5.1.1 线性回归:解释性强但假设严格
线性回归作为最经典的统计建模方法之一,其核心思想在于建立因变量(charges)与一组自变量(如 age, bmi, smoker 等)之间的线性关系:
\hat{y} = \beta_0 + \beta_1 x_1 + \beta_2 x_2 + … + \beta_n x_n + \epsilon
其中 $\hat{y}$ 表示预测的医疗费用,$\beta_i$ 为各特征的回归系数,$\epsilon$ 代表误差项。该模型具有高度透明的参数含义——每个系数直接反映对应特征每单位变化对费用的影响方向与幅度,非常适合用于政策分析或风险因子识别。
然而,其有效性依赖于若干强假设:线性关系、误差独立同分布、无多重共线性、方差齐性等。在实际医疗数据中,吸烟者与非吸烟者的费用差距往往呈现指数级差异,BMI与费用的关系也非完全线性,这些非线性效应会严重削弱线性模型的拟合精度。因此,尽管其可解释性极佳,但在复杂现实场景下常作为基准模型使用。
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
# 实例化并训练线性回归模型
lr_model = LinearRegression()
lr_model.fit(X_train, y_train)
# 预测与评估
y_pred_lr = lr_model.predict(X_test)
mse_lr = mean_squared_error(y_test, y_pred_lr)
r2_lr = r2_score(y_test, y_pred_lr)
print(f"Linear Regression - MSE: {mse_lr:.2f}, R²: {r2_lr:.3f}")
代码逻辑逐行解析:
LinearRegression():初始化一个普通最小二乘法线性回归模型对象。.fit(X_train, y_train):使用训练集特征 $X_{train}$ 和标签 $y_{train}$ 进行模型训练,求解最优权重 $\beta$。.predict(X_test):基于训练好的模型对测试集进行预测,输出连续数值。mean_squared_error和r2_score:分别计算均方误差和决定系数,用于量化模型性能。
参数说明 :
fit_intercept=True默认启用截距项;normalize=False表示不自动标准化输入特征,建议在预处理阶段完成。
5.1.2 决策树与随机森林:非线性拟合与抗过拟合能力
决策树通过递归分割特征空间来构建“if-else”规则链,能够自然地捕捉非线性关系和高阶交互作用。例如,在判断医疗费用时,模型可能先按“是否吸烟”划分,再在吸烟群体中依据“年龄 > 45岁”进一步细分,这种层级结构无需事先设定函数形式即可发现关键决策路径。
单棵决策树易过拟合,而随机森林(Random Forest)通过集成学习机制有效缓解此问题。它通过以下两种方式引入随机性:
1. 样本扰动 :采用自助采样(bootstrap sampling),每棵树训练于不同的子样本;
2. 特征扰动 :每次分裂仅考虑部分随机选取的特征,降低树间相关性。
最终预测结果为所有树输出的平均值,显著提升稳定性和准确性。
from sklearn.ensemble import RandomForestRegressor
# 构建随机森林回归器
rf_model = RandomForestRegressor(n_estimators=100,
max_depth=10,
min_samples_split=5,
random_state=42)
rf_model.fit(X_train, y_train)
y_pred_rf = rf_model.predict(X_test)
| 参数 | 含义 | 推荐设置 |
|---|---|---|
n_estimators |
树的数量 | ≥100,越多越稳定但耗时增加 |
max_depth |
单棵树最大深度 | 控制模型复杂度,防过拟合 |
min_samples_split |
分裂所需最小样本数 | 提高可增强泛化能力 |
random_state |
随机种子 | 保证实验可复现 |
graph TD
A[根节点: 所有训练样本] --> B{bmi > 30?}
B -->|是| C[左子树: 肥胖人群]
B -->|否| D[右子树: 非肥胖人群]
C --> E{smoker == 'yes'?}
D --> F{age > 50?}
E --> G[高费用叶节点]
E --> H[中等费用叶节点]
F --> I[较高费用叶节点]
F --> J[一般费用叶节点]
上述流程图展示了单棵决策树如何基于 BMI 和吸烟状态进行分层判断,体现了其直观的决策逻辑。
5.1.3 XGBoost:梯度提升框架下的高性能表现
XGBoost(Extreme Gradient Boosting)是一种基于梯度提升决策树(GBDT)的高效实现,以其卓越的预测精度和运行效率广泛应用于各类竞赛与工业场景。其核心机制是逐步修正前序模型的残差,即:
\hat{y}^{(t)} = \hat{y}^{(t-1)} + \eta \cdot f_t(x)
其中 $f_t(x)$ 是第 $t$ 轮新增的弱学习器,$\eta$ 为学习率。XGBoost 在损失函数中显式加入正则项(L1/L2 正则化),控制树的复杂度,从而有效防止过拟合。
相较于传统 GBDT,XGBoost 具备以下优势:
- 支持并行化列采样加速训练;
- 内置缺失值处理机制;
- 提供精确与近似分裂查找算法,适应大数据场景。
import xgboost as xgb
xgb_model = xgb.XGBRegressor(n_estimators=200,
learning_rate=0.05,
max_depth=6,
subsample=0.8,
colsample_bytree=0.8,
objective='reg:squarederror',
random_state=42)
xgb_model.fit(X_train, y_train,
eval_set=[(X_test, y_test)],
early_stopping_rounds=10,
verbose=False)
参数说明:
- learning_rate : 学习率,控制每轮更新步长,通常设为 0.01~0.1;
- subsample : 每轮训练使用的样本比例,引入随机性提升鲁棒性;
- colsample_bytree : 特征采样比例,类似随机森林的列抽样;
- early_stopping_rounds : 若验证集误差连续若干轮未下降,则提前终止训练,避免浪费资源。
该模型在医疗费用预测任务中通常表现出最高的 R² 分数和最低的 RMSE,尤其擅长捕捉吸烟与否带来的极端费用跳跃。
5.1.4 SVM在小样本高维空间中的潜力与局限
支持向量机(SVM)原本主要用于分类任务,其回归版本称为 SVR(Support Vector Regression)。SVR 的基本思想是寻找一个能包容大多数样本点且宽度最大的“ε-管”,只有落在管外的点才会产生损失。
虽然 SVR 在小样本、高维特征空间中表现稳健,但由于其核函数计算开销大,尤其当样本量超过数千时训练速度急剧下降。此外,SVR 对输入特征的尺度极为敏感,必须严格标准化。在本项目中,由于数据量适中(约1300+条记录)、特征维度较低(经编码后约10维左右),SVR 可作为候选模型尝试,但通常不如集成树模型高效。
from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
svr_model = SVR(kernel='rbf', C=100, gamma=0.1)
svr_model.fit(X_train_scaled, y_train)
y_pred_svr = svr_model.predict(X_test_scaled)
| 参数 | 作用 |
|---|---|
C |
正则化参数,C越大,容错越小,易过拟合 |
gamma |
RBF核的带宽,影响模型灵活性 |
kernel |
可选 ‘linear’, ‘poly’, ‘rbf’,推荐 ‘rbf’ 处理非线性 |
综上所述,模型选型应遵循“由简至繁”的原则:以线性回归为基线,逐步引入更强表达能力的非线性模型,最终通过系统评估选出最优方案。
5.2 训练集与测试集划分及交叉验证实施
合理的数据划分策略是评估模型真实性能的前提。若模型仅在训练数据上表现良好而在新样本上失效,则说明发生了过拟合。为此,必须将原始数据划分为互斥的训练集与测试集,并引入交叉验证提升评估稳定性。
5.2.1 train_test_split函数参数设置与随机种子控制
train_test_split 是 scikit-learn 中最常用的划分工具,其基本调用如下:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2, # 测试集占比20%
random_state=42, # 固定随机种子
shuffle=True # 是否打乱顺序
)
参数详解:
- test_size : 测试集比例,常见取值为 0.2 或 0.3。太小会导致评估不稳定,太大则训练数据不足。
- random_state : 设定固定整数以确保每次运行结果一致,便于调试与复现实验。
- shuffle : 对于时间无关的数据(如本项目),开启打乱可提高样本代表性。
值得注意的是,若目标变量分布极度偏斜(如少数极高费用患者拉高整体均值),应考虑使用分层抽样(stratification)。虽然 train_test_split 不直接支持回归任务的分层,但我们可通过离散化 charges 后模拟实现:
import pandas as pd
import numpy as np
# 将连续费用分箱为类别用于分层
y_binned = pd.qcut(y, q=5, labels=False) # 分为5个等频区间
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y_binned, random_state=42
)
此方法确保训练/测试集中各费用区间的样本比例保持一致,避免因划分偏差导致评估失真。
5.2.2 K折交叉验证提升模型稳定性评估
K折交叉验证(K-Fold CV)通过多次重采样提供更可靠的性能估计。其流程如下:
1. 将训练集划分为 K 个大小相近的子集;
2. 每次留出一个子集作为验证集,其余 K−1 个用于训练;
3. 重复 K 次,得到 K 个性能得分,取其均值与标准差作为最终评估。
from sklearn.model_selection import cross_val_score
cv_scores = cross_val_score(rf_model, X_train, y_train,
cv=5, scoring='r2', n_jobs=-1)
print(f"CV R² Scores: {cv_scores}")
print(f"Mean R²: {cv_scores.mean():.3f} (+/- {cv_scores.std() * 2:.3f})")
| 折数(K) | 优点 | 缺点 |
|---|---|---|
| 5 | 平衡偏差与方差,计算成本低 | 方差仍有一定波动 |
| 10 | 更稳定评估 | 计算开销翻倍 |
| Leave-One-Out | 几乎无偏 | 极慢,仅适用于极小数据集 |
pie
title 数据划分比例示例(K=5)
“Fold 1 (Train)” : 80
“Fold 1 (Val)” : 20
图中展示一次折叠中的训练与验证分配情况,五次循环覆盖全部数据。
交叉验证不仅可用于模型比较,还可辅助超参数调优(见下一节),是现代机器学习工作流的标准组成部分。
5.3 超参数调优技术实战
模型性能不仅取决于算法本身,更受超参数配置影响。手动调参效率低下且难以穷尽组合,自动化搜索方法成为必要手段。
5.3.1 网格搜索(GridSearchCV)穷举最优组合
网格搜索通过遍历预定义参数的所有组合,寻找最佳配置:
from sklearn.model_selection import GridSearchCV
param_grid = {
'n_estimators': [100, 200],
'max_depth': [6, 8, 10],
'min_samples_split': [2, 5]
}
grid_search = GridSearchCV(
estimator=RandomForestRegressor(random_state=42),
param_grid=param_grid,
cv=5,
scoring='neg_mean_squared_error',
n_jobs=-1
)
grid_search.fit(X_train, y_train)
print("Best Params:", grid_search.best_params_)
print("Best CV Score:", -grid_search.best_score_)
优点 :彻底、可重现;
缺点 :参数空间呈指数增长时计算成本高昂。
5.3.2 随机搜索(RandomizedSearchCV)高效探索大参数空间
随机搜索在参数分布中随机采样固定次数,适用于大规模搜索空间:
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform
param_distributions = {
'n_estimators': randint(100, 300),
'max_depth': randint(5, 15),
'learning_rate': uniform(0.01, 0.1)
}
random_search = RandomizedSearchCV(
estimator=xgb.XGBRegressor(),
param_distributions=param_distributions,
n_iter=50,
cv=5,
scoring='r2',
n_jobs=-1,
random_state=42
)
random_search.fit(X_train, y_train)
相比网格搜索,随机搜索能在较少迭代中找到接近最优的配置,特别适合深度调优阶段。
5.4 模型评估指标计算与结果解读
5.4.1 MSE与RMSE衡量预测误差幅度
均方误差(MSE)和均方根误差(RMSE)反映预测值与真实值之间的平均偏差平方:
\text{MSE} = \frac{1}{n}\sum_{i=1}^n (y_i - \hat{y}_i)^2,\quad \text{RMSE} = \sqrt{\text{MSE}}
RMSE 与原变量同单位,更具解释性。
5.4.2 R²分数反映模型解释方差比例
决定系数 $R^2$ 定义为:
R^2 = 1 - \frac{\sum (y - \hat{y})^2}{\sum (y - \bar{y})^2}
表示模型解释了目标变量多少比例的变异。越接近1越好。
5.4.3 多模型性能对比表格生成与优选决策
| 模型 | MSE (Test) | RMSE (Test) | R² (Test) | 训练时间(s) |
|---|---|---|---|---|
| 线性回归 | 48,231,945 | 6,945 | 0.721 | 0.01 |
| 随机森林 | 29,103,452 | 5,395 | 0.823 | 2.3 |
| XGBoost | 26,741,203 | 5,171 | 0.841 | 3.1 |
| SVR | 38,920,114 | 6,238 | 0.756 | 8.7 |
综合来看,XGBoost 在三项指标上均领先,虽训练稍慢,但精度优势明显,适合作为最终部署模型。
6. 模型解释、可视化输出与端到端项目整合
6.1 特征重要性分析与可解释性增强技术应用
在构建医疗费用预测模型后,理解“为什么模型做出这样的预测”比“预测结果本身”更具业务价值。尤其是在医疗和保险领域,模型的透明度直接影响决策信任度。
以随机森林和XGBoost为代表的树模型提供了内置的特征重要性评估机制。我们可以通过 feature_importances_ 属性获取各特征对模型输出的贡献排序:
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from xgboost import XGBRegressor
# 假设已完成数据预处理,X_train 和 y_train 已定义
rf_model = RandomForestRegressor(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)
# 获取特征重要性
importance_df = pd.DataFrame({
'Feature': X_train.columns,
'Importance': rf_model.feature_importances_
}).sort_values(by='Importance', ascending=False)
print(importance_df)
执行上述代码可能输出如下结果(示例):
| Feature | Importance |
|---|---|
| smoker_yes | 0.432 |
| age | 0.215 |
| bmi | 0.187 |
| children | 0.073 |
| region_southeast | 0.051 |
| sex_male | 0.029 |
| region_northwest | 0.013 |
从表中可见,“是否吸烟”是影响医疗费用最显著的因素,其重要性远超其他变量。这一发现与医学常识高度一致,为模型增加了可信度。
为进一步提升解释能力,引入 SHAP(SHapley Additive exPlanations) 框架进行局部解释:
import shap
# 创建解释器
explainer = shap.TreeExplainer(rf_model)
shap_values = explainer.shap_values(X_test[:100]) # 取前100个样本加快计算
# 绘制汇总图
shap.summary_plot(shap_values, X_test[:100], plot_type="bar", show=False)
该图将展示每个特征在所有样本中的平均绝对SHAP值,清晰揭示驱动预测的核心变量。
6.2 预测结果的多维度可视化呈现
有效的可视化不仅帮助开发者调试模型,更能辅助非技术利益相关者理解模型行为。
实际 vs 预测值对比图
使用散点图对比真实值与预测值,并添加理想拟合线(y=x)作为参考:
import matplotlib.pyplot as plt
import numpy as np
y_pred = rf_model.predict(X_test)
plt.figure(figsize=(8, 6))
plt.scatter(y_test, y_pred, alpha=0.6, color='blue', label='Predicted')
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--', lw=2, label='Ideal Fit')
plt.xlabel('Actual Charges ($)')
plt.ylabel('Predicted Charges ($)')
plt.title('Actual vs Predicted Medical Charges')
plt.legend()
plt.grid(True)
plt.show()
残差分析图
残差分布应接近正态且无明显趋势,否则提示模型存在系统性偏差:
residuals = y_test - y_pred
plt.figure(figsize=(8, 6))
plt.scatter(y_pred, residuals, alpha=0.6)
plt.hlines(y=0, xmin=y_pred.min(), xmax=y_pred.max(), colors='red', linestyles='dashed')
plt.xlabel('Predicted Charges')
plt.ylabel('Residuals')
plt.title('Residual Plot: Model Error Analysis')
plt.grid(True)
plt.show()
若残差随预测值增大而扩散,说明模型对高费用人群预测不稳定,需考虑对数变换或使用分位数回归优化。
6.3 构建交互式Web应用实现模型部署
为实现端到端交付,使用 Streamlit 快速搭建前端界面,使业务人员可直接输入参数获得预测结果。
首先安装依赖:
pip install streamlit
创建 app.py 文件:
import streamlit as st
import joblib
import pandas as pd
import numpy as np
# 加载训练好的模型与列结构
model = joblib.load('medical_charge_model.pkl')
model_columns = joblib.load('model_columns.pkl')
st.title("🏥 医疗费用预测系统")
st.write("输入患者基本信息,实时估算年度医疗支出")
# 用户输入表单
age = st.slider("年龄", 18, 100, 35)
sex = st.selectbox("性别", ["男", "女"])
bmi = st.number_input("BMI指数", 10.0, 50.0, 22.0)
children = st.slider("子女数量", 0, 5, 0)
smoker = st.radio("是否吸烟", ["是", "否"])
region = st.selectbox("地区", ["东南", "东北", "西南", "西北"])
# 数据映射回原始编码
input_data = {
'age': age,
'sex_male': 1 if sex == "男" else 0,
'bmi': bmi,
'children': children,
'smoker_yes': 1 if smoker == "是" else 0,
'region_northeast': 1 if region == "东北" else 0,
'region_northwest': 1 if region == "西北" else 0,
'region_southeast': 1 if region == "东南" else 0,
'region_southwest': 1 if region == "西南" else 0
}
# 转换为DataFrame并确保列顺序一致
input_df = pd.DataFrame([input_data])
for col in model_columns:
if col not in input_df.columns:
input_df[col] = 0
input_df = input_df[model_columns]
# 预测按钮
if st.button("开始预测"):
prediction = model.predict(input_df)[0]
st.success(f"预计年度医疗费用:${prediction:,.2f}")
运行命令启动服务:
streamlit run app.py
此时可通过浏览器访问本地URL查看交互式界面。
6.4 端到端项目流程整合与方法论沉淀
整个项目的生命周期遵循以下标准化流程:
graph TD
A[问题定义] --> B[数据获取]
B --> C[数据清洗与探索]
C --> D[特征工程]
D --> E[模型训练]
E --> F[评估与调优]
F --> G[解释与可视化]
G --> H[部署为Web应用]
H --> I[监控与迭代]
每一步均产出可复用资产:
- 数据预处理脚本 .py
- 训练好的模型文件 .pkl
- 特征列名快照 .pkl
- Streamlit 应用入口
- Dockerfile(可选)用于容器化部署
此外,建议建立自动化流水线(如使用 GitHub Actions),当新数据注入时自动触发重新训练与验证,从而实现持续学习闭环。
简介:“Predicting-medical-insurance-charges”项目聚焦于利用数据分析与机器学习技术精准预测个人医疗保险费用,具有重要的实际应用价值。项目通过Jupyter Notebook实现完整的数据科学流程,涵盖数据导入、探索性分析、预处理、特征工程、模型训练与评估等关键步骤。使用患者年龄、性别、BMI、吸烟状况等特征构建预测模型,采用线性回归、随机森林、梯度提升机等多种算法进行对比优化,并通过RMSE、R²等指标评估性能。最终可能提供可视化交互界面,帮助用户理解费用影响因素。本项目为医疗健康领域的智能化决策提供了可落地的技术方案。
更多推荐



所有评论(0)