在数据分析和机器学习中,缺失值是绕不开的 “拦路虎”—— 直接删除会丢失数据,简单填充又可能引入偏差。今天就带大家用 Pandas+LinearRegression 实现 “智能填充”,通过线性回归模型预测缺失值,既保留数据完整性,又让填充结果更贴合数据规律。还会拆分代码中的核心知识点,小白也能轻松跟上!
一、先看完整代码:用线性回归填充缺失值
先把最终实现的代码放出来,帮大家建立整体认知,后面再逐个拆解知识点:
# 1. 导入必备库
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
# 2. 加载数据并拆分特征与目标(此处目标暂用"class"列,实际填充时目标是缺失列)
data = pd.read_csv(r'task3.csv') # 加载数据集
x = data.drop('class', axis=1) # 特征:删除目标列(假设"class"是建模目标)
y = data['class'] # 原始目标变量(本次填充暂不直接用)
# 3. 特征标准化(线性回归对量纲敏感,标准化是关键步骤)
scaler = StandardScaler() # 初始化标准化器
x_z = scaler.fit_transform(x) # 对特征做标准化(均值=0,标准差=1)
x = pd.DataFrame(x_z, columns=x.columns) # 转为DataFrame,保留列名便于后续操作
# 4. 核心函数:用线性回归填充缺失值
def train_lr_fill(train_data):
# 步骤1:统计各列缺失值数量,并按缺失值从少到多排序
null_sum = train_data.isnull().sum() # 计算每列缺失值个数
null_sum_sort = null_sum.sort_values(ascending=True) # 升序排序(先填缺失少的列)
# 步骤2:确定填充顺序(从缺失少的列开始,避免用大量缺失的列当特征)
index_fill = [] # 存储填充列的顺序
for col in null_sum_sort.index:
index_fill.append(col) # 依次加入列名
# 只处理有缺失值的列(无缺失的列无需填充)
if null_sum_sort[col] != 0:
# 构建回归模型的特征X和目标y:用已选列(无缺失/已填充)预测当前缺失列
X = train_data[index_fill].drop(col, axis=1) # X:除当前缺失列外的其他列
y = train_data[col] # y:当前需要填充的缺失列
# 步骤3:定位缺失行的索引(需要预测的行)和非缺失行(用于训练模型)
# 获取当前列有缺失值的行号
null_row_idx = train_data[train_data[col].isnull()].index.tolist()
# 训练数据:删除缺失行,只用完整数据训练
X_train = X.drop(null_row_idx)
y_train = y.drop(null_row_idx)
# 待预测数据:只取缺失行的特征
X_test = X.loc[null_row_idx]
# 步骤4:训练线性回归模型并预测缺失值
lr = LinearRegression() # 初始化线性回归模型
lr.fit(X_train, y_train) # 用非缺失数据训练模型
y_pred = lr.predict(X_test) # 预测缺失值
# 步骤5:用预测结果填充原数据的缺失位置
train_data.loc[null_row_idx, col] = y_pred
return train_data # 返回填充后的完整数据
# 5. 调用函数,完成缺失值填充
filled_data = train_lr_fill(x.copy()) # 传入特征副本,避免修改原始数据
print("填充前缺失值总数:", x.isnull().sum().sum())
print("填充后缺失值总数:", filled_data.isnull().sum().sum())
二、拆解核心知识点:从基础到进阶
上面的代码看似长,但核心是 “数据加载→标准化→缺失值智能填充” 三个环节,每个环节都藏着关键知识点,我们逐个吃透。
知识点 1:数据加载与特征 - 目标拆分(Pandas 基础)
机器学习中,第一步永远是 “理清数据结构”—— 哪些是特征(用于预测的变量),哪些是目标(要预测的结果)。
# 加载CSV数据:Pandas的read_csv是处理表格数据的“万能接口”
data = pd.read_csv(r'task3.csv')
# 参数说明:r''表示原始字符串,避免路径中的\被转义(如C:\data\test.csv无需写成C:\\data\\test.csv)
# 拆分特征X和目标y:假设"class"是最终要预测的目标列
x = data.drop('class', axis=1) # 特征X:删除目标列,保留所有输入变量
y = data['class'] # 目标y:单独提取需要预测的列
# 关键提醒:
# - 如果数据中没有明确的“目标列”(如纯数据清洗场景),可直接用整个dataframe处理
# - 拆分后建议用x.head()查看前5行,确认是否正确删除了目标列
知识点 2:特征标准化(LinearRegression 必备步骤)
线性回归模型对特征量纲非常敏感(比如 “收入(万元)” 和 “年龄(岁)” 数值范围差异大,会影响模型权重),因此必须先做标准化。
from sklearn.preprocessing import StandardScaler
# 1. 初始化标准化器
scaler = StandardScaler()
# 2. 对特征做标准化:fit_transform = 先计算均值/标准差(fit) + 再转换(transform)
x_z = scaler.fit_transform(x)
# 转换后的数据特点:每个特征的均值=0,标准差=1,消除量纲影响
# 3. 转回DataFrame:fit_transform返回numpy数组,需恢复成DataFrame方便后续缺失值处理
x = pd.DataFrame(x_z, columns=x.columns)
# 关键:指定columns=x.columns,保证列名和原始特征一致,避免后续列名混乱
为什么必须标准化?
举个例子:特征 A 是 “年龄(10-80)”,特征 B 是 “收入(10000-100000)”。如果不标准化,模型会认为 B 的 “数值大 = 影响大”,导致权重偏向 B,但实际两者重要性可能相同。标准化后,两者范围统一,模型能公平学习特征重要性。
知识点 3:缺失值统计与排序(Pandas 数据探查)
填充缺失值的核心逻辑是 “先填缺失少的列”—— 因为缺失少的列,可用的完整数据多,用它训练模型预测其他列更准确。
# 1. 统计每列缺失值个数:isnull()返回布尔值(True=缺失),sum()统计True的数量
null_sum = train_data.isnull().sum()
# 示例输出:age:12, income:5, score:0(age列12个缺失,score列无缺失)
# 2. 按缺失值升序排序:ascending=True表示从少到多排
null_sum_sort = null_sum.sort_values(ascending=True)
# 排序后示例:score:0, income:5, age:12
# 3. 确定填充顺序:把排序后的列名存入列表,后续按这个顺序填充
index_fill = []
for col in null_sum_sort.index:
index_fill.append(col)
# index_fill最终为:['score', 'income', 'age'](先填income,再填age)
# 关键逻辑:
# - 先处理缺失少的列(如income仅5个缺失),用无缺失的列(score)当特征训练模型
# - 填完income后,再用score+已填充的income一起预测age(缺失多的列)
# - 避免用缺失多的列当特征(比如先填age,会用大量缺失的age数据训练,结果不准)
知识点 4:定位缺失行与拆分训练 / 测试数据
要预测缺失值,需要先区分 “有完整数据的行(用于训练模型) ” 和 “有缺失值的行(需要预测) ”。
# 假设当前要填充的列是"income",先定位它的缺失行
col = "income"
# 1. 找到"income"列有缺失值的行索引:isnull()筛选缺失行,index.tolist()转成列表
null_row_idx = train_data[train_data[col].isnull()].index.tolist()
# 示例输出:[15, 28, 42, 57, 63](这5行的income是缺失的)
# 2. 拆分训练数据:只用非缺失行训练(删除缺失行)
X = train_data[index_fill].drop(col, axis=1) # 特征:除income外的其他列
y = train_data[col] # 目标:income列
X_train = X.drop(null_row_idx) # 训练特征:删除缺失行
y_train = y.drop(null_row_idx) # 训练目标:删除缺失行
# 3. 拆分待预测数据:只取缺失行的特征(用于预测缺失值)
X_test = X.loc[null_row_idx] # loc按索引取值,精准获取缺失行的特征
# 关键提醒:
# - 必须用drop()删除缺失行,不能用dropna()!因为dropna()会删除所有列的缺失行,可能误删其他列的完整数据
# - X_test必须和X_train的列数一致(都是“除当前缺失列外的其他列”),否则模型无法预测
知识点 5:线性回归预测缺失值(Scikit-learn 实战)
用完整数据训练线性回归模型,再用模型预测缺失值,这是 “智能填充” 的核心。
from sklearn.linear_model import LinearRegression
# 1. 初始化线性回归模型:LinearRegression是 sklearn 中最基础的回归模型
lr = LinearRegression()
# 2. 训练模型:用非缺失的X_train和y_train拟合模型
lr.fit(X_train, y_train)
# 模型会学习特征(如score)与目标(income)的线性关系:income = a*score + b
# 3. 预测缺失值:用X_test(缺失行的特征)预测income的缺失值
y_pred = lr.predict(X_test)
# 示例输出:[28.5, 32.1, 29.8, 35.2, 31.7](5个预测值,对应5个缺失行)
# 4. 填充缺失值:用loc精准定位缺失行,将预测值填入
train_data.loc[null_row_idx, col] = y_pred
# 关键逻辑:
# - 线性回归的核心是“找特征与目标的线性关系”,适合填充数值型缺失值(如年龄、收入)
# - 预测后必须用原数据的索引(null_row_idx)填充,确保位置不错乱
三、实战效果验证:填充前后对比
代码运行后,一定要验证填充效果,确认缺失值是否真的被补全:
# 填充前:统计所有列的缺失值总数
print("填充前缺失值总数:", x.isnull().sum().sum()) # 示例输出:17(12+5)
# 调用函数填充
filled_data = train_lr_fill(x.copy()) # 传副本!避免修改原始数据
# 填充后:再次统计缺失值总数
print("填充后缺失值总数:", filled_data.isnull().sum().sum()) # 示例输出:0
# 额外验证:查看某列填充前后的变化(以age列为例)
print("填充前age列缺失值:", x['age'].isnull().sum()) # 输出:12
print("填充后age列缺失值:", filled_data['age'].isnull().sum()) # 输出:0
注意:如果填充后仍有缺失值,可能是某列缺失值太多(如超过 50%),导致没有足够的完整数据训练模型,此时建议换用 “中位数填充” 等简单方法。
四、避坑指南:新手常犯的 3 个错误
-
直接修改原始数据
错误做法:filled_data = train_lr_fill(x)(直接传入 x,修改后原始数据被污染)
正确做法:filled_data = train_lr_fill(x.copy())(用 copy () 创建副本,保留原始数据) -
标准化后忘记恢复 DataFrame
错误做法:x = scaler.fit_transform(x)(x 变成 numpy 数组,丢失列名)
正确做法:x = pd.DataFrame(scaler.fit_transform(x), columns=x.columns)(保留列名,方便后续按列处理) -
用 dropna () 拆分训练数据
错误做法:X_train = X.dropna()(会删除所有列有缺失的行,可能误删其他列的完整数据)
正确做法:X_train = X.drop(null_row_idx)(只删除当前列的缺失行,精准控制)
五、总结:智能填充的适用场景与优势
今天讲的 “线性回归填充法” 不是万能的,但在以下场景中效果远超 “均值填充”“中位数填充”:
- 特征间存在明显线性关系(如 “收入” 和 “消费金额” 正相关);
- 缺失值比例适中(5%-30%,太少没必要用模型,太多没有足够数据训练);
- 数据是数值型(线性回归无法处理分类型数据,分类型缺失值建议用众数填充)。
掌握这个方法后,你不仅能解决缺失值问题,还能理解 “用数据预测数据” 的核心逻辑 —— 这正是机器学习的本质。下次遇到缺失值,别再简单填个均值,试试用线性回归做 “智能填充” 吧!

3933

被折叠的 条评论
为什么被折叠?



