尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
房价预测数据预处理实战:缺失值、偏态与编码全解析
做这个比赛的绝大多数人都会在提交完第一版预测之后陷入同样的困惑模型换了三四个调参调得快吐了分数却始终卡在某个位置不动。很少有人意识到问题往往不在模型而在数据预处理。房价预测这类结构化比赛数据预处理好坏直接决定了模型的上限。我见过太多直接读入数据、fillna(0)、LabelEncoder一把梭哈然后丢给LightGBM的跑法效果一般在0.14到0.16之间晃荡而同样一套模型预处理做得扎实的人能稳定压到0.11以下。这篇就把我在这场比赛里做数据预处理时的完整思路和实操记录摊开来讲。数据预处理不是建模前“随便处理一下”的收尾工作它是建模的一部分。你需要对每一列缺失值问为什么缺失对每个偏态特征判断要不要变换对每类离散特征决定用哪种编码。这篇博客适合正准备上手这个比赛的新手也适合那些分数一直上不去的朋友对照检查——你可能前面的坑还没填完就急着去调模型参数了。1. 赛题与数据的底层认知预处理决定模型上限1.1 这个预测到底在预测什么房价预测就是给定一栋房子的各种属性预测它的成交价。听起来简单但真实数据根本不像教材里的示例那样干净有些字段缺失率超过一半有些特征分布严重右偏还有些特征之间相互矛盾。你处理每一个缺失值、每一个异常点其实都是在把你的业务理解注入数据。拿到数据之后不要急着写代码。先把train.csv打开看一遍train.csv有1460条样本test.csv有1459条样本一共81列79个解释特征加Id和SalePrice。先搞清楚训练集和测试集的结构差异尤其是哪些列在训练集中有缺失、哪些在测试集中有缺失以及两者分布是否一致。这个检查非常关键因为后面的所有填充策略都必须保证训练集和测试集走同一套逻辑。1.2 81列特征里真正决定价格的“骨架”81列看着很多但真正对房价起决定性作用的其实是一小撮特征。按照我的经验至少半数以上的特征属于“有则加分无则不算分”的辅助项。表格里标注的是我在这场比赛中的切分逻辑不是绝对标准但它至少能帮你建立优先级概念特征组典型字段作用权重预处理重点质量类OverallQual、OverallCond极高保持原始次序直接作为数值面积类GrLivArea、TotalBsmtSF、LotArea极高缺失填充、偏态压缩年份类YearBuilt、YearRemodAdd高构造年龄代替年份位置类Neighborhood高高基数编码谨慎处理地下室/车库GarageCars、GarageArea、BsmtQual中高无车库和无地下室要特殊处理设施类PoolQC、FireplaceQu、Fence低缺失即“无”单独做标志位这些字段的预处理优先级完全不同。面积和质量类字段预处理的每一个细节都会被模型放大而PoolQC这种缺失率高的字段填什么其实对结果影响有限重点是把“有没有”这个信息保留下来。1.3 预处理前先建立三条基线很多人在动手处理数据前没有想清楚“我做这个处理到底是为了什么”。我建议先在心里立三条基线之后每个操作都拿来对照第一目标变量SalePrice严重右偏。直接拿原始价格做回归模型会为了照顾那些几百万的豪宅而牺牲普通房子的精度。解决方案是用np.log1p把目标变量做对数变换让它接近正态分布L2损失对极端值的敏感度会下降很多。如果后面接的是深度学习模型这一步几乎就是必经之路。import numpy as np import pandas as pd train pd.read_csv(train.csv) test pd.read_csv(test.csv) y train[SalePrice] y_log np.log1p(y) # 避免log(0)同时压缩右尾第二种预处理要保证训练集和测试集用完全相同的规则。你不能在训练集里按A街区的均值填充缺失在测试集里却按B街区填。最简单的做法是把对分布敏感的填充值比如中位数、均值先在整个全量数据上计算或者把训练集和测试集concat起来做统一处理最后再切回去。第三任何特征变换都要可解释。如果不确定某个操作为什么有效就先别做。预处理阶段的每一个改动都会叠加进最终的预测结果无目的的操作只会引入噪声。这条基线能帮你抵挡“网上教程说要做特征工程所以我也做了一堆”的冲动。2. 缺失值不是“要填的洞”而是字段含义的一部分2.1 把缺失分成两类结构缺失和随机缺失这是整个预处理最关键的一步直接决定后面的填充策略。我见过很多人在缺失值上栽跟头根本原因就是没有区分缺失的类型。第一类是结构性缺失比如房子没有游泳池PoolQC字段当然就没有数据没有围栏Fence字段就空缺。这种缺失本身就在告诉你“这个属性不存在”它传递的信息量和“此处有值”一样重要。处理方式不是填一个平均数进去而是填一个专门的标记比如补0或补None让模型知道这里不是数据丢了而是本来就没有。第二类是随机缺失比如LotFrontage临街面长度有的房子确实没有量到或者MasVnrArea砖石立面面积登记的时候漏填了。这类缺失才需要真正的“填充”手段用中位数、均值或者基于其他特征的预测值补上。先跑一段代码看一下缺失分布这个行为要养成习惯missing train.isnull().sum() missing missing[missing 0].sort_values(ascendingFalse) print(missing.head(20))比赛数据里典型的结果会显示PoolQC、MiscFeature、Alley、Fence、FireplaceQu这些字段缺失率很高大概率是结构缺失而LotFrontage、GarageYrBlt、MasVnrArea这些缺失比例相对低才是随机缺失。2.2 按缺失率分组的填充策略对高缺失率字段我通常分组处理直接填一个None字符串或0并不是终点还要考虑“有/无”这个二元标志本身有没有建模价值。字段缺失率缺失类型填充策略PoolQC约99%结构缺失填None表示无泳池MiscFeature约96%结构缺失填None表示无其他设施Alley约94%结构缺失填None表示无巷子入口Fence约81%结构缺失填None表示无围栏FireplaceQu约47%结构缺失填None表示无壁炉LotFrontage约18%随机缺失按Neighborhood中位数填充GarageYrBlt约5%随机缺失配合有无车库逻辑没有车库就填0MasVnrArea约1%随机缺失配合MasVnrType填充无则0高缺失率字段除了直接填None标记之外我的习惯是再做一次判断如果一个字段90%以上的值都是同一个取值它对模型的贡献就非常有限后面可以考虑直接删掉。PoolQC就是一个典型例子绝大多数房子都没有泳池真正带泳池的样本极少这种字段留着价值不大反而可能让模型学到噪声。所以后续可以直接drop掉PoolQC、MiscFeature这类字段。2.3 LotFrontage这类连续缺失怎么补LotFrontage是临街面的长度这个字段的缺失不能简单填全局中位数。因为不同街区的房子LotFrontage的中位数差异很大。在高端街区房子普遍临街面宽在密集街区临街面窄。如果你的填充粒度不够细填出来的值就带着街区的错误信息。所以我处理LotFrontage的方法是先按Neighborhood分组计算每个街区LotFrontage的中位数然后对缺失样本用所在街区的值填充。如果某个街区在训练集里缺失太多再用全局中位数兜底。lot_frontage_median train.groupby(Neighborhood)[LotFrontage].transform(median) train[LotFrontage] train[LotFrontage].fillna(lot_frontage_median)这样填充的原理是“用相似样本的信息估计当前样本”。只填一个中位数是不够的还要理解为什么是“按街区填”而不是“全局填”——因为LotFrontage和街区带内的地块规划强相关街区比全局更接近这个样本的真实分布。这个思路你后面在GarageYrBlt、MasVnrArea上也可以复用。2.4 填充之后的验证步骤填充完了别急着往下走我习惯做两个快速验证。第一检查测试集是否也有同样的缺失列以及缺失模式是否一致。如果测试集的某个字段在训练集里没有缺失可能就是这次比赛的“隐藏差异”。第二验证填充后的分布和原始非缺失部分的分布保持一致。例如做一个简单的直方图对比如果填充后出现明显的峰值堆积说明填充逻辑可能有偏。多花这几分钟能帮你省掉后面建模时排查怪结果的时间。train[LotFrontage].describe() # 检查max、mean、std是否保持合理 # 如果max或mean因填充出现明显异常回到分组填充逻辑检查3. 连续特征的偏态、相关性与新特征取舍3.1 强偏态特征压缩为什么偏度高的特征要先做变换处理完缺失值下一个动作是检查连续特征的分布。很多特征像SalePrice一样严重右偏比如GrLivArea、LotArea、TotalBsmtSF。如果不对这些特征做变换模型对极值的敏感度会很高尤其是使用深度学习模型时输入尺度差异过大会让训练过程非常不稳定。我的通用做法是对偏度大于0.5的连续特征做对数变换加1避免0值问题。这里有个细节不是所有偏态特征都适合log变换如果正值范围很小log之后可能加剧拥堵。skewness train[cont_features].apply(lambda x: x.skew()).sort_values(ascendingFalse) skewed_cols skewness[skewness 0.5].index for col in skewed_cols: train[col] np.log1p(train[col]) test[col] np.log1p(test[col])这一步的实际收益非常大。房价预测里GrLivArea和TotalBsmtSF是权重很高的特征log变换之后不仅模型更容易拟合而且它们和目标变量的线性关系会明显增强。3.2 GrLivArea与TotalBsmtSF的“Y型关系”这个字段关系是我在比赛里印象最深的一个发现。GrLivArea是地面以上居住面积TotalBsmtSF是地下室总面积。如果分别看它们与SalePrice的相关性都很强。但把它们放在一起画散点图你会发现一个Y型的模式。怎么理解有地下室的房子TotalBsmtSF0GrLivArea和SalePrice基本呈线性正相关但没有地下室的房子TotalBsmtSF0它们的价格完全由GrLivArea决定。把这两个特征分开看模型能学到更清晰的规律混在一起模型会为了同时照顾两条线而牺牲精度。我的处理方式是给地下室相关字段单独做一个标志位HasBsmt表示是否有地下室然后后续通过组合操作把总面积拆成地上和地下两个维度分别保留。这种“拆开再组合”的思路比简单保留原始特征更有辨别力。train[HasBsmt] train[TotalBsmtSF].apply(lambda x: 1 if x 0 else 0) test[HasBsmt] test[TotalBsmtSF].apply(lambda x: 1 if x 0 else 0)3.3 年份、面积、质量怎么组合才有实际含义特征工程在这个比赛里容易做过头。很多人一股脑生成几十个组合特征最后模型反而变差。我自己的做法是只做几个有清晰业务含义的组合。年份字段建议把YearBuilt和YearRemodAdd转换成房龄。对房价来说“这个房子多少年了”比“它是哪年建的”更有解释力。房龄作为数值和SalePrice之间通常是负相关而如果直接用年份模型要自己学出这个偏移量浪费样本。train[HouseAge] 2025 - train[YearBuilt] test[HouseAge] 2025 - test[YearBuilt]面积类可以做总建筑面积地面面积加地下室面积但注意不要重复计算也可以做“地上面积/地块面积”的土地利用率。这个比值能在一定程度上反映地段密度。质量类就简单点。OverallQual本身是1到10的整数它跟房价的关系几乎是单调的直接保留原始数值就行别做哑变量。核心原则是新特征必须能用一句话解释清楚它对应房子的哪个实际属性。解释不清的特征加了只会让模型更难收敛。4. 离散特征编码有序与无序要分开处理4.1 有序类别映射成数字别交给哑变量离散特征里面有一类很特殊它有明确的等级关系。比如BsmtQual从Ex到Po分别代表优秀、良好、一般、尚可、差KitchenQual同理。这类特征如果直接做One-Hot编码就白白扔掉了等级之间的次序信息相当于把一个天然有序的变量拆成了几个毫无关系的哑变量模型的判断力反而被削弱了。我的做法是手工构造一个映射字典把等级转换成数字。这里有个小技巧转换的数字范围是可以调的。比如BsmtQual的映射我用的范围是0到5None取0没有地下室Po取1Fa取2TA取3Gd取4Ex取5。这样映射出来的数值不仅在单调性上符合直觉而且数值间隔近似表示了等级之间的真实差异。qual_map { None: 0, Po: 1, Fa: 2, TA: 3, Gd: 4, Ex: 5 } for col in [BsmtQual, KitchenQual, FireplaceQu, GarageQual, GarageCond]: train[col] train[col].map(qual_map) test[col] test[col].map(qual_map)这类映射代码有个好处它是完全解释性的后面模型调优时你可以回头检查每个等级取值对预测的影响是否符合业务直觉。如果整体出现异常也能很快定位问题。4.2 Neighborhood这类高基数特征的取舍Neighborhood是一个高基数类别特征波士顿数据集里这个字段类别很多每个类别对应一个街区而且和房价的相关性极强。把它做One-Hot编码会产生几十列稀疏特征在树模型里倒还勉强能用在深度学习模型里就很容易带来维度爆炸和稀疏问题。我在实际操作中会先看每个街区的样本量。有的街区在训练集里只有一两个样本这种类别做One-Hot就是纯粹给模型塞噪声。对这类高基数特征我通常做一次频率压缩把样本量很少的街区归并成一个Other类。这里的阈值我会根据总的样本量来定1460条训练数据近30个街区类别低于5个样本的类别合并是比较稳妥的做法。counts train[Neighborhood].value_counts() rare_neighborhoods counts[counts 5].index train[Neighborhood] train[Neighborhood].replace(rare_neighborhoods, Other)但要注意这个归并规则必须同样应用到测试集。如果测试集里有个街区在训练集里没出现过也会被归到Other否则模型训练和推理看到的特征空间不一致。4.3 用组内聚合把类别“翻译”给模型如果你舍不得丢掉Neighborhood这种强信号特征还有一种做法比One-Hot更高效就是用组内目标均值编码。简单说就是统计每个街区下的平均房价注意用对数变换后的价格把这个均值作为该街区的新特征值。这个操作的本质是“把类别翻译成模型能消费的统计信号”。但它也有明显的风险街区样本太少时组内均值会过拟合训练集。所以我会用平滑处理让组内均值和全局均值做加权样本越少权重越偏向全局。global_mean y_log.mean() stats train.groupby(Neighborhood)[SalePrice].agg([mean, count]) smooth 10 stats[smooth_mean] (stats[mean] * stats[count] global_mean * smooth) / (stats[count] smooth) train[Neighborhood_encoded] train[Neighborhood].map(stats[smooth_mean])不过这个方法要谨慎使用它本质上引入了目标泄漏的风险因为你在用目标变量来构造特征。在比赛里它是合法且高效的但在实际业务中慎用。如果只跑LightGBM我通常会保留原始Neighborhood让模型自己处理只有在深度学习场景下才考虑用组内聚合做压缩。5. 预处理中最容易翻车的三个细节5.1 异常值要结合业务判断不能只看分布按IQR箱线图一画一堆“异常值”就出来了。但房价预测这个数据里某些超出箱线图范围的值恰恰是真实存在的高价豪宅直接删掉会把珍贵的信息丢掉。我去掉数据前会做一件事把GrLivArea特别大、但SalePrice反而特别低的样本单独拉出来看业务上这种可能是带有特殊因素比如地块产权问题的低价大宅也可能是数据录入错误。只有录入错误类的异常值才需要删除真实的极端样本不但不能删反而是模型学习长尾分布的重要素材。我在这个比赛里最后只删了一个极端样本地上面积超过4000平方英尺、价格却不到市场价一半的案例这种明显不合常理去掉之后验证集的表现反而更好。其余看似“离群”的样本全部保留。5.2 重复样本不能盲目去重去重是大家最容易踩坑的地方因为大部分数据处理教程都会告诉你先检查重复值。这套流程在绝大多数数据场景下是对的但放在房价预测里有个隐藏陷阱这个比赛数据是按时间顺序排列的训练集和测试集是从同一个数据集上按先后顺序切分的它不像推荐系统那样毫无顺序意义。如果你不加思考地drop_duplicates假设测试集里有和训练集完全一样的房子行你把它直接去重删掉提交时就会少一个Id轻则行列不匹配重则提交失败。而且两栋房子即使所有字段一模一样也可能是两个真实存在、只是特征相同的样本。盲目删除会把你本就稀缺的训练样本进一步减少。我的建议是不去重或者去重之前先检查Id字段的连续性。如果你发现Id从1460直接跳到1462中间少了1那说明你刚才误删了一条赶紧回滚。5.3 训练集与测试集的对齐和差分检查最后是训练/测试集对齐这一步是预处理阶段跟建模阶段的交接点出错就是全盘皆输。我见过有人在对缺失值做填充时训练集和测试集分开操作结果中位数不一致有人做One-Hot编码时只在训练集上fit测试集里新出现的类别直接报错还有人在对离散特征做map映射时测试集里出现了一个训练集里没有的取值导致一堆NaN悄悄混进了模型。我的做法是所有需要fit的预处理步骤都在全量数据上统一计算或者至少保证测试集遵循训练集定好的规则。对于类别编码最稳妥的方式是先把训练集和测试集concat起来同时处理完毕后再按index切回去。ntrain train.shape[0] ntest test.shape[0] all_data pd.concat([train.drop([SalePrice], axis1), test], axis0) all_data pd.get_dummies(all_data) train_processed all_data[:ntrain] test_processed all_data[ntrain:]切完之后再做一个差分检查确认训练集行数1460、测试集行数1459并且每个测试集Id都在提交模板里有对应位置。这一步看起来笨但每次比赛都有人因为少了一行、多了一列而浪费一次提交机会。我做完这一整套预处理之后最深的感受是数据预处理不是一个线性流水线它是一个不断回溯、不断修正的过程。你填完缺失值可能要回去改异常值判断做完编码可能要回头改填充逻辑。所以每做完一个环节我都会跑一遍完整的数据管线确认运行状态。能把预处理做到位模型随便选个常规结构效果都不会太差。这个比赛的预处理思路放在其他房价类或者结构化的销售预测比赛里同样是可以直接迁移的一套方法论。
RELATED

相关推荐

agent-skills:面向智能体的可插拔能力契约设计

agent-skills:面向智能体的可插拔能力契约设计

1. 项目概述:一个被严重低估的“技能容器”概念“agent-skills”这个词组最近在技术社区和开发者讨论中频繁浮现,但它既不是某个具体开源库的官方命名,也不是某家大厂刚发布的API产品名——它本质上是一种面向智能体(Agent&#x…

📅 2026/10/21 8:47:21
DeepSeekAPI智能用例生成实战:自动化测试完整方案

DeepSeekAPI智能用例生成实战:自动化测试完整方案

简介:面向软件测试工程师与自动化测试开发者的23页PDF文档,系统讲解如何借助DeepSeek API构建智能用例生成方案。文档从自动化测试框架的定义与常见类型讲起,依次介绍DeepSeek API的基本概念、技术特点、应用场景和密钥获取方式,然…

📅 2026/10/21 8:42:10
Superset 4.1.1 中文版 Docker 离线部署实战:内网环境安装与避坑指南

Superset 4.1.1 中文版 Docker 离线部署实战:内网环境安装与避坑指南

简介:这份资源面向需要在无外网环境下搭建数据可视化平台的企业运维与开发者,提供 Superset 4.1.1 中文版的 Docker 离线部署方案,解决内网环境无法拉取镜像、部署流程繁琐的问题。压缩包共 6 个文件,约 524.2MB,包含 …

📅 2026/10/21 8:42:10
MORE NEWS

更多资讯

📰

图表设计实战指南:从信息梳理到视觉编码的完整方法论

1. 从“diagram-design”这个标题里能读出什么第一次看到“diagram-design”这个标题,我脑子里蹦出来的不是某个具体工具,而是一类非常典型的工作场景:有人需要把脑子里那团乱麻一样的逻辑,变成一张别人一眼能看懂的图。这件事听起…

📰

创意工作者必备:素材管理与项目归档体系搭建指南

1. 为什么我一直在折腾"artcraft"这件事先说清楚,"artcraft"不是某个官方产品,也不是什么特定软件的名字。这个词拆开就是 art 加上 craft,艺术与工艺的组合。我最早接触这个概念,是在整理一批手工艺作品素材…

📰

RuoYi-Cloud微服务分离版启动避坑指南:Nacos配置与网关路由排查

1. 为什么微服务分离版值得单独写一篇启动避坑指南若依(RuoYi)这个开源快速开发框架,国内做企业级管理系统的开发者应该都不陌生。单体版(RuoYi-Vue)和微服务版(RuoYi-Cloud)是两条主线&#xf…

📰

换流器高占比系统最低惯量需求评估模型与实现

简介:面向电力系统规划、运行与控制研究人员,这套资料聚焦换流器高占比场景下的最低惯量需求评估。内容涵盖惯量需求定义与影响因素分析、频率稳定约束下的评估模型、惯量安全域概念及分级预警机制,并提供从单机频率响应到多机系统惯量评估、…

📰

ZXCA证书实战:自签名、CA签发与Nginx/IIS/Tomcat配置

简介:这套ZXCA自信数字证书制作工具面向需要自建证书体系的个人开发者和小型组织,用于解决证书申请、签发、管理以及数据加密等安全需求。资料包共八个文件,大小约四点八四兆,主体是两个可执行程序,另配有文本格式的参…

📰

FPGA实现FFT的工程实践:架构设计、定点策略与RTL调试

直接开场。做了快十年FPGA,算下来真正让人头疼的不是逻辑写不出来,而是算法和架构配合不上。FFT又是这里面最典型的一个坑,因为它在教科书里是一套优雅的数学,在硬件里却是一堆时序、资源、定点、数据流的问题组成的泥潭。标题就四…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬