用R语言实现钻石价格预测:从数据清洗到回归诊断的完整流程

发布时间:2026/10/17 15:58:05
用R语言实现钻石价格预测:从数据清洗到回归诊断的完整流程
简介一份基于R语言的钻石价格分析文档面向数据分析初学者与R语言使用者完整呈现从数据探索、清洗到多元线性回归建模的流程。资源内为1个docx文档压缩包大小约2.4MB内容以diamonds内置数据集为基础涵盖变量说明、缺失值与异常值处理、描述性统计、直方图与箱线图可视化以及克拉重量、切割、颜色、净度对价格影响的回归预测适合用于学习回归分析、特征探索与R实战案例。文档结构清晰包含关键代码结果与图表解读可直接参考复现。已有283人学习下载适合希望快速上手R数据分析、撰写课程报告或了解钻石定价逻辑的读者。1. 从一颗钻石的价格说起这份 R 数据分析项目到底在解决什么问题我身边有个朋友准备买钻戒两颗钻石克拉数差不多价格却差了快一倍。他问我到底差在哪我直接把这套基于 R 语言的钻石价格分析跑了一遍。它用 ggplot2 自带的 diamonds 数据集五万多颗钻石、十个字段把切工、颜色、净度这些变量全部拉出来最终落在多元线性回归模型上既能解释价格差异也能对新钻石做价格预测。这个项目不是只贴几张图的水文而是从数据清洗、异常值剔除、描述性统计、显著性检验一路做到回归诊断的完整流程。适合刚学 R 回归分析的人照着跑一遍也适合做商品价格预测的开发者拿来做模板。2. 项目结构与技术选型一条从数据清洗到回归预测的 R 分析流水线拿到这份资源后我先把脚本从头到尾读了一遍发现主线非常清晰数据读取、数据体检、可视化探索、简单线性回归、多元线性回归、显著性检验、回归诊断。这个顺序也是价格预测类 R 项目里最常见的骨架后面我拿到任何类似数据集都会先按这个流程套一遍。2.1 diamonds 数据集字段解读10 个变量里哪些是连续值哪些是名义变量建模之前最该做的事是把数据集的字段性质摸清楚。diamonds 是 ggplot2 包自带的数据记录了大约 54000 颗钻石的克拉、切工、颜色、净度以及价格等信息。变量说明如下属性含义取值范围类型carat克拉重量0.2 到 5.01连续值cut切割质量Fair、Good、Very Good、Premium、Ideal离散值color颜色等级J-I-H-G-F-E-DJ 最差D 最好离散值clarity净度等级I1-SI2-SI1-VS2-VS1-VVS2-VVS1-IFI1 最差IF 最好离散值depth总深度百分比43 到 79连续值table钻石顶部相对最宽点的宽度43 到 95连续值price美元价格326 到 18823连续值x钻石长度毫米0 到 10.74连续值y钻石宽度毫米0 到 58.9连续值z钻石深度毫米0 到 31.8连续值这里面最需要注意的一点是cut、color、clarity 三个名义变量不是数字放进回归模型之前必须转成 factor否则 R 会把它当连续值处理结果完全没法解释。我一般会在读入数据后立刻用str()看一遍类型确认这三个变量已经被识别成 factor。2.2 分析链路设计清洗、探索、建模、诊断、预测五步走这个项目的分析链路可以拆成五个阶段。第一阶段是数据体检看缺失值、重复值、异常值第二阶段是描述性统计分析用直方图和箱线图看价格分布以及各变量和价格的关系第三阶段是特征工程把定性变量转成 factor并构造 depth、table 相关的派生指标第四阶段是建立简单线性回归和多元线性回归再通过 AIC 准则做逐步回归选变量第五阶段是显著性检验和回归诊断确认模型不是只在当前数据集上成立。每一步都有对应的 R 代码这也是我推荐照着跑的原因它不是给你一个黑匣子模型而是把从数据到结论的每个决定都摊开了。比如价格直方图里能看到价格在 1000 美元附近出现峰值2000、4000、8000 美元附近出现断层这种信息在建模前就已经影响变量处理方式。2.3 环境准备安装并加载 ggplot2、mice、car、dplyr运行环境不复杂装好 R 之后在控制台执行下面这段安装代码就行。install.packages() 会从在线仓库拉取包第一次装可能要等一会。# 安装依赖包已经装过可以跳过 install.packages(c(ggplot2, mice, car, dplyr)) # 加载包 library(ggplot2) library(mice) library(car) library(dplyr) # 加载 ggplot2 内置的钻石数据集 data(diamonds)这段代码里ggplot2 负责数据集和可视化mice 用来生成缺失值模式表car 用来做共线性诊断dplyr 负责数据清洗和去重。data(diamonds)执行后diamonds 会变成一个 data.frame后面所有分析都基于它。如果环境中已经有同名对象建议先rm(diamonds)再加载避免读到旧数据。3. 数据探索与预处理缺失值、重复值、异常值一个都不能放过数据分析的绝大部分时间其实花在预处理上。这个项目里 diamonds 数据本身质量不错没有缺失值重复值也很少但异常值是真的有而且会直接影响回归系数所以这一步不能省。3.1 数据概览与类型检查head、str、summary 三件套我拿到任何数据集都会先跑三行代码head 看长什么样str 看类型summary 看分布。这一步能快速发现变量类型是否读对了也能看出价格是否存在极端值。# 查看前 6 行 head(diamonds) # 查看数据结构包括每个变量的类型 str(diamonds) # 查看描述性统计包括最小值、分位数、最大值 summary(diamonds)str(diamonds)会输出每个变量的类型比如 carat 是 numcut 是 Ord.factorprice 是 int。如果 cut、color、clarity 不是 factor后面回归时就要手动as.factor()。summary(diamonds)里 price 的第三四分位数和最大值差距很大说明价格分布右偏这为后面做对数变换或者异常值剔除埋了伏笔。3.2 缺失值与重复值排查mice 可视化与去重这一步是数据体检的核心。diamonds 数据集在 ggplot2 里已经是清洗过的但换到自己的项目时缺失值和重复值往往比想象中要多。用 mice 包的 md.pattern 可以快速看到缺失值模式。# 生成缺失值模式矩阵行名代表缺失模式最后一列是样本数 md.pattern(diamonds) # 检查重复行数量 sum(duplicated(diamonds)) # 如果有重复行用 dplyr 的 distinct 去重 diamonds - distinct(diamonds)md.pattern()返回的矩阵里0 表示该行没有缺失1 表示缺失。排在最前面的那一行如果全为 0并且样本数等于总行数说明没有缺失值。duplicated()返回逻辑向量sum()统计重复行数如果大于 0用distinct()去重。注意 distinct 默认保留每条记录第一次出现的行不会改变原始列顺序。3.3 箱线图与三倍标准差法则异常值剔除从输入的数据探索结果看箱线图中位数接近下四分位数且存在偏离三个标准差之外的点说明数据里有异常值。异常值会导致回归系数被拉偏尤其是价格这种右偏变量。# 用箱线图观察价格和几个连续变量的离群点 ggplot(stack(diamonds[, c(price, carat, x, y, z)]), aes(x ind, y values)) geom_boxplot() # 以 price 为例用三倍标准差法标记异常值 price_mean - mean(diamonds$price) price_sd - sd(diamonds$price) diamonds$is_outlier - diamonds$price price_mean - 3 * price_sd | diamonds$price price_mean 3 * price_sd # 剔除异常值 diamonds_clean - diamonds[!diamonds$is_outlier, ]箱线图用来定性观察三倍标准差用来定量剔除。stack()把多个连续变量转成长格式方便一次画出多个箱线图price_mean - 3 * price_sd和price_mean 3 * price_sd构成上下界落在界外的样本标记为异常。我一般会在剔除前列出异常值数量如果超过总样本的 1%就会考虑用对数变换而不是直接删除避免丢掉高价钻石的信息。提示三倍标准差法适用于近似正态分布的数据。price 明显右偏严格来说先取对数再判异常更合理但这个项目为了展示完整流程直接在原值上做了处理。4. 核心建模多元线性回归从零到交互项选变量的完整链路这个项目真正的核心在建模。前面的数据清洗都是为这一步服务的后面的显著性检验和回归诊断也都是围绕模型展开。建模过程不是一次到位而是从单变量、无交互项多元回归一路升级到带交互项的逐步回归。4.1 变量类型变换把 cut、color、clarity 转成 factor回归模型要求数值输入所以三个名义变量必须先做类型变换。R 里的 factor 类型会自动生成哑变量不需要手动构造 matrix。关键是因子水平的顺序因为第一个水平会成为参照组。# 指定因子水平第一个水平作为回归中的参照组 diamonds_clean$cut - factor(diamonds_clean$cut, levels c(Fair, Good, Very Good, Premium, Ideal)) diamonds_clean$color - factor(diamonds_clean$color, levels c(J, I, H, G, F, E, D)) diamonds_clean$clarity - factor(diamonds_clean$clarity, levels c(I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF))这里 cut 的参照组是 Faircolor 的参照组是 Jclarity 的参照组是 I1。后续回归结果里出现的每一个分类系数都是相对参照组的差值。比如 Ideal 的回归系数如果是 1500意思是同等克拉和其他条件下Ideal 切工比 Fair 切工平均贵 1500 美元。水平顺序影响解读方式但不影响模型拟合效果。4.2 简单线性回归克拉每增加 1 克拉价格预期增加多少先看价格直方图再做一个最简单的单变量回归。价格分布右偏数量最多的钻石集中在 326 到 1200 美元附近这和普通商品的价格规律一样越贵越稀有。# 价格直方图观察右偏分布 ggplot(diamonds_clean, aes(x price)) geom_histogram(bins 50, fill steelblue, color white) # 用克拉数预测价格 lm_carat - lm(price ~ carat, data diamonds_clean) summary(lm_carat)lm(price ~ carat)是最简单的回归写法summary 会输出回归系数、标准误、t 值和 R 方。从项目的结果看carat 的回归系数大约是 7760p 值小于 0.001说明每增加 1 克拉价格预期增加 7760 美元左右。单变量模型的 R 方大概在 0.85 附近已经不错但真实市场里同样克拉的钻石价格可能差很多因为切工、颜色、净度还没进来。4.3 无交互项多元回归为什么 R²0.8798 还不够把所有变量都塞进回归是一个很自然的下一步。carat、cut、color、clarity、depth、table、x、y、z 全部作为自变量price 作为因变量。# 无交互项的多元线性回归 lm_full - lm(price ~ carat cut color clarity depth table x y z, data diamonds_clean) summary(lm_full)这个模型的 R 方是 0.8798比单变量模型高了一些但和后面交互项模型相比还是差了一口气。问题在于它假设 carat 对价格的影响是固定的不管钻石切工是 Fair 还是 Ideal涨一克拉都涨同样的价格。现实显然不是这样高切工、高颜色钻石的溢价在克拉数变大时会更夸张。这个假设限制正是交互项要解决的问题。4.4 交互项与 AIC 逐步回归把切工、颜色、净度都送进模型为了捕捉不同类别下克拉数的差异化影响项目里加入了三个交互项carat:cut、carat:color、carat:clarity。交互项的含义是克拉数对价格的影响会随着切工、颜色、净度的不同而变化。# 带交互项的模型 lm_interact - lm(price ~ carat * cut carat * color carat * clarity depth table x y z, data diamonds_clean) # 向后逐步回归按 AIC 最小化选择变量 lm_step - step(lm_interact, direction backward, trace 1) # 查看逐步回归选出的最终模型 summary(lm_step)direction backward表示从全模型开始每次尝试删掉一个变量选择 AIC 下降最多的删法一直删到 AIC 不再下降为止。AIC 是拟合优度和模型复杂度的折中数值越小越好。项目运行结果是所有交互项都被保留说明切工、颜色、净度和克拉数之间的交互效应确实是显著的。这里要提醒一句step()的输出会很长trace 1 会把每一步删除的变量列出来。真正建模时不要只看最后的 summary还要看中间过程确认被删掉的变量是否在业务上有意义。如果 AIC 差不多的两个模型我会优先选择更容易解释的那个。5. 显著性检验与回归诊断别让 p 值骗了你附避坑指南模型建完之后不能直接拿去用。这个项目后面做了显著性检验和回归诊断目的是回答两个问题不同切工、颜色、净度的钻石价格差异是真的吗模型残差是否稳定、变量之间是否存在共线性5.1 分组显著性检验非参数检验与正态性检验价格是连续值分组变量是 cut、color、clarity最常见的思路是先看各组价格均值再做组间差异检验。项目里先做了正态性检验发现各组分布并不一样不能直接套用参数检验所以改用 Kruskal-Wallis 非参数检验。# 切工、颜色、净度分别做 Kruskal-Wallis 检验 kruskal.test(price ~ cut, data diamonds_clean) kruskal.test(price ~ color, data diamonds_clean) kruskal.test(price ~ clarity, data diamonds_clean) # 抽样后用 shapiro.test 看价格是否正态数据量太大时全量会跑不动 set.seed(42) d_sample - diamonds_clean[sample(nrow(diamonds_clean), 2000), ] shapiro.test(d_sample$price)Kruskal-Wallis 检验的零假设是各分组的中位数相同。输出结果里 p-value 很小说明至少有一组的价格中位数和其他组显著不同。这印证了前面可视化的结论颜色从 J 到 D价格整体上升净度也一样越干净越贵。注意 shapiro.test 对样本量很敏感5 万条数据全量跑会非常慢而且几乎必然拒绝正态所以我一般随机抽 2000 条做参考而不是把它当成结论。5.2 回归诊断残差、共线性与模型稳定性检查回归诊断是建模后最容易跳过的一步但恰恰是最有价值的。它告诉你模型在哪里翻了车。# 残差图看是否存在异方差 plot(lm_step, which 1) # 方差膨胀因子看多重共线性 vif(lm_step)第一张残差图把拟合值放在 x 轴残差放在 y 轴。如果残差在零线附近随机分布说明模型还算健康如果出现明显的漏斗形说明方差随着价格增大而增大。这个数据集里价格跨度从 326 到 18823残差图通常会出现异方差趋势我一般会再跑一个lm(log(price) ~ ...)对比。vif()输出方差膨胀因子通常认为超过 10 就存在明显共线性。这里 x、y、z 和 carat、depth、table 高度相关VIF 值可能会比较高。遇到这种情况我不会把所有变量都删掉而是先看业务关系保留对价格解释力最强的一组删掉冗余尺寸变量。5.3 常见问题排查这个项目里的四个踩坑记录第一个坑跑md.pattern(diamonds)时看到输出里全是数字以为数据缺失很严重。原因是 md.pattern 返回的是缺失模式矩阵不是缺失数量统计表。解决方法看矩阵的行名行名为 0 表示该模式无缺失再对照最后一列的样本数如果加起来等于总行数就没有缺失。第二个坑把 x、y、z 全部放进模型后发现某个系数的符号反直觉比如钻石长度 x 对价格是负影响。原因是 x、y、z 和 carat 高度相关多重共线性把系数方向扭曲了。解决方法用cor()看变量间的相关系数再用vif()诊断必要时只保留 x、y、z 中的一两个或者干脆用它们的平均值构造新变量。第三个坑异常值剔除后 R 方反而下降。原因是我把价格最高的一批钻石直接删掉了而这些高价样本承载了大量由切工、颜色、净度带来的信息。解决方法先看箱线图确认离群点占比小于 1% 再考虑剔除如果占比大就改用对数变换这样既能保留高端样本又能压低极端值对回归的影响。第四个坑交互项模型里某个分类变量的主效应系数是负数看起来像切工越好价格越低。原因是 R 把 factor 的第一个水平当参照组其他系数是相对参照组的差值加入交互项后主效应只代表参照组下的影响解释起来很容易误伤。解决方法不要只看主效应系数要看交互项的总效应或者用relevel()换一个参照组重新拟合。6. 二次开发把钻石价格预测脚本改造成你自己的分析工具跑通整个项目之后最有价值的不是那张回归表而是可以把模型封装成预测函数变成自己手里的一个估价小工具。6.1 把回归方程封装成 predict 函数模型对象本身就能用来 predict但直接调用 predict 需要构造一个包含所有变量的 data.frame太容易漏字段。我一般会先封装一个函数把钻石属性作为参数传进去。# 封装预测函数输入钻石属性输出预测价格 predict_diamond_price - function(carat_val, cut_val, color_val, clarity_val, depth_val, table_val, x_val, y_val, z_val, model lm_step) { new_data - data.frame( carat carat_val, cut factor(cut_val, levels levels(diamonds_clean$cut)), color factor(color_val, levels levels(diamonds_clean$color)), clarity factor(clarity_val, levels levels(diamonds_clean$clarity)), depth depth_val, table table_val, x x_val, y y_val, z z_val ) predict(model, newdata new_data) } # 预测 1.5 克拉、Very Good 切工、G 色、VS2 净度钻石的价格 predict_diamond_price(1.5, Very Good, G, VS2, 61.5, 57, 7.2, 7.1, 4.4)这里最关键的细节是 factor 的水平必须和建模时完全一致。如果 new_data 里的 cut 没有按训练时的 levels 设置predict 会报错或者把未知水平变成 NA。封装成函数之后你可以把预测脚本直接 source 进来在一个 R 脚本里批量给多颗钻石估价。每次改造完我习惯把数据随机切 80% 训练、20% 测试用均方根误差 RMSE 对比新旧模型验证预测误差有没有变大。这样改完心里才有底。6.2 接入自己的钻石数据集如果手头有自己的钻石数据替换起来很简单。只要保证列名一致类型一致就可以把 diamonds_clean 换成你自己的 data.frame。# 读取自己的数据 my_data - read.csv(my_diamond_data.csv) # 检查类型重点看 cut、color、clarity 是不是 factor str(my_data) # 如果列名不一致先重命名 my_data - my_data %% rename(carat carat, price price, cut cut)接入新数据后先重复一遍缺失值、重复值、异常值检查再跑模型。我不建议直接把 lm_step 用在完全不同的数据集上因为这里的交互项关系是从 diamonds 数据学出来的换一个市场或者换一种定价体系系数很可能不适用。6.3 把模型结果导出和持久化模型训练一次不容易可以把摘要输出到文本文件也可以把模型对象存成 RDS 文件下次直接加载省得重新拟合。# 导出模型摘要 sink(diamond_price_model_summary.txt) summary(lm_step) sink() # 保存模型对象下次直接 load saveRDS(lm_step, file diamond_price_model.rds) # 新会话里加载模型 lm_step - readRDS(diamond_price_model.rds)sink 会把输出重定向到文件跑完记得再调用 sink() 恢复控制台输出。saveRDS 保存的是完整模型对象包含系数、残差、诊断信息比只存系数要实用得多。从那以后我每次拿到新的价格数据都会先画直方图和箱线图再用 vif 检查共线性强制把数据体检和回归诊断走一遍等模型稳定了才敢拿预测结果去说事。这套流程救了我不少次也让我少当几次只会看 p 值翻车的开发。希望帮到你。本文还有配套的精品资源点击获取