☰
用R语言实现钻石价格预测:从数据清洗到回归诊断的完整流程
2026/10/17 15:57:45 网站建设 项目流程

简介:一份基于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-D,J 最差,D 最好离散值
clarity净度等级I1-SI2-SI1-VS2-VS1-VVS2-VVS1-IF,I1 最差,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 是 num,cut 是 Ord.factor,price 是 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 的参照组是 Fair,color 的参照组是 J,clarity 的参照组是 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 的回归系数大约是 7760,p 值小于 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 值翻车的开发。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询