☰
R语言数据分析实战:用diamonds数据集完成钻石价格回归建模
2026/10/17 17:18:45 网站建设 项目流程

简介:基于R语言的钻石价格分析完整报告,面向希望掌握数据分析和回归建模的读者,以R语言绘图包自带的钻石数据集为样本,完整演示从数据清洗、探索性可视化到多元线性回归预测的流程。资源包内含1个docx文档,约2.4MB,包含分析过程、代码说明和结果图表。已有283人学习下载。报告依次进行缺失值与重复值检查、异常值剔除,绘制价格直方图与箱线图,将切割、颜色、净度等定性变量转换为因子,通过变量筛选确定克拉、切工等主要影响因素,最终建立可解释的多元线性回归模型,并给出价格预测思路。全文步骤清晰,适合R语言数据分析入门、统计建模课程设计或相关从业者参考,能够帮助读者快速复现案例,并迁移到其他回归预测问题中。

1. 钻石价格分析:一次 R 数据分析和回归建模的完整练习

第一次拿到 R 语言自带的钻石价格数据,我的反应是“这不就是拿克拉数做个回归分析吗?”可真正打开之后,这套包含 54000 条样本、10 个字段的 diamonds 数据集,很快把问题拉回到数据分析和回归分析的常识上:克拉、切工、颜色、净度、尺寸,任一因素都会扰动价格。这份基于 R 语言的钻石价格分析项目,用描述性统计、可视化和多元线性回归,回答了“钻石价格由什么决定”和“能否预测一颗特定钻石的成交价”。它适合刚学 R 数据分析的人照着跑通全流程,也适合已经能跑回归的人拆解数据清洗、哑变量编码、交互项和模型诊断这些细节。整个项目有一条清晰的分析链路:先清数据,再看价格分布,最后建模预测。

2. 数据准备工作:diamonds 结构、缺失值与异常值

2.1 读取内置数据集并查看整体结构

diamonds 是 R 生态里知名度很高的练习数据集,不需要手动下载文件,装好 ggplot2 就能直接读。用下面这段代码载入并观察:

library(ggplot2) data(diamonds) str(diamonds) dim(diamonds) summary(diamonds)

str 输出会告诉我们每个字段的类型和取值范围,dim 显示 53940 行、10 列,summary 则能一眼看到连续变量的最小值、分位数和最大值。跑完这段,基本就能判断后续该把重点放在哪些特征上。

为了后续建模时查看方便,我一般先把数据类型和范围整理成一张表。这个表的用途不只是归档,更是给回归分析做预判:哪些变量适合直接进模型,哪些需要先做因子化。

变量含义类型取值范围或等级
carat克拉重量连续值0.2 ~ 5.01
cut切工质量离散值Fair, Good, Very Good, Premium, Ideal
color钻石颜色离散值J(最差)到 D(最好)
clarity净度等级离散值I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF
depth总深度百分比连续值43 ~ 79
table钻石顶部宽度占比连续值43 ~ 95
price价格(美元)连续值326 ~ 18823
x长度(毫米)连续值0 ~ 10.74
y宽度(毫米)连续值0 ~ 58.9
z深度(毫米)连续值0 ~ 31.8

从这张表能看出一个典型问题:price 是右偏变量,最小值 326,最大值 18823,中位数和均值会差很多;x、y、z 则明显和克拉大小高度相关。建模之前不对这些变量做预处理,线性回归很容易被极端点带偏。

2.2 缺失值检查与重复值检查

拿到数据的第一个动作,不是急着画图,而是确认里面有没有空值和重复记录。R 里可以用 colSums 快速定位缺失值:

colSums(is.na(diamonds))

理想情况下每列都返回 0,说明没有缺数据。如果再细致一点,可以用 mice 包里的 md.pattern 生成缺失值模式矩阵,它能告诉我们哪些列会一起缺失,方便判断缺失机制。这个数据集很干净,跑完能看到所有记录都是完整的,连一行缺失都没有。

重复值的检查也简单,duplicated 函数配合取反就能过滤出非重复行:

diamonds <- diamonds[!duplicated(diamonds), ] dim(diamonds)

跑完后行数会略少于 53940,说明原数据里有少量重复记录。把重复行去掉再继续,是为了避免同一颗钻石在训练集和测试集里出现两次,造成回归评估虚高。这个习惯在真实项目里尤其重要,脏数据里最常见的两个问题就是空值和重复值。

2.3 异常值探索与剔除

连续变量做回归前,我会先看箱线图。箱线图能直观显示中位数、四分位数和异常点位置,尤其是价格这种跨度很大的变量,箱线图几乎总是会画出几个偏离主体范围的极端点。

boxplot(diamonds$price, main = "钻石价格箱线图", ylab = "price")

从箱线图观察到的现象是:中位数明显向下四分位数靠拢,上边缘延伸得很长,同时存在大量超过 1.5 倍四分位距的点。换句话说,价格分布是右偏的,少数高价钻石把箱线图的上尾巴拉得很长。

处理异常值没有绝对标准,常见的做法是基于 IQR 剔除超出上下边缘的点:

q1 <- quantile(diamonds$price, 0.25) q3 <- quantile(diamonds$price, 0.75) iqr <- IQR(diamonds$price) diamonds_clean <- subset( diamonds, price >= q1 - 1.5 * iqr & price <= q3 + 1.5 * iqr )

这段代码先算出价格的下四分位数、上四分位数和四分位距,然后只保留落在 [q1 - 1.5 * IQR, q3 + 1.5 * IQR] 区间内的样本。需要提醒的是,这里的剔除只针对价格变量的极端值,后续建模时如果要同时使用多个变量,最好对每个连续变量分别做箱线图检查,或者用多变量距离法判断,避免把所有异常点一刀切掉。

提示:IQR 剔除只适合处理明显的测量异常值,对右偏严重的价格变量,正常的高价钻石也会被划成“异常点”。我在后面避坑章节会再展开讲这个问题。

3. 描述性分析与特征工程:价格分布和品质变量的影响

3.1 价格分布直方图与核密度曲线

数据清洗完之后,第一件事通常是画价格的直方图,看目标变量的整体形状。这里的 price 是回归模型要预测的因变量,它的分布直接决定模型是否需要对目标做变换。

ggplot(diamonds_clean, aes(x = price)) + geom_histogram(bins = 50, fill = "#4C72B0", alpha = 0.7) + geom_density(aes(y = ..count..), color = "#C44E52", size = 0.8) + labs(title = "钻石价格分布直方图与核密度曲线", x = "price", y = "count")

从直方图能明显看到,钻石价格呈右偏分布:价格在 1000 美元附近的钻石数量最多,之后频次迅速下降;2000、4000、8000 美元附近出现明显的断层,这说明市场中多数成交集中在低价位段,高价位钻石数量急剧减少。核密度曲线在 1000 美元左右出现峰值,与直方图结论一致。

这种分布形态带来一个信号:直接用原始价格做线性回归,残差很可能不满足正态性假设。常见的处理办法是取对数,让价格分布更接近对称分布。实际项目中,我一般会在探索阶段就顺手把 ln_price 建出来,后面做模型对比时再决定用哪个作因变量。

3.2 切工、颜色、净度与价格的分组对比

了解价格整体分布后,要回答的问题是:哪些因素会让价格产生差异。对离散的 cut、color、clarity 三个变量,我会用箱线图和小提琴图做分组对比。比如绘制不同切工状态下的价格区间:

ggplot(diamonds_clean, aes(x = cut, y = price, fill = cut)) + geom_violin(alpha = 0.6) + geom_boxplot(width = 0.15, outlier.size = 0.3) + scale_y_log10() + labs(title = "不同切工状态下的钻石价格分布", x = "cut", y = "price(log)")

这里我把价格轴改成对数刻度,是为了在同一个坐标轴里既能看清低价区间、又不会被少数高价点把整个图撑变形。观察结果有一点反直觉:普遍认知里 Ideal 切工应该最贵,但图中可以看到 Ideal 切工的钻石里,仍有相当一部分价格很低。这说明切工只是影响价格的因素之一,颜色、净度和克拉大小会共同决定最终价格。

同样的逻辑也适用于 color 和 clarity。把 color 按 J 到 D 排序后,会看到随着颜色等级提升,价格中位数总体向上走;而 clarity 分组里,VVS1、VVS2 的中位数往往高于肉眼可见内含物的 I1 和 SI 级别。需要注意的是,单个分组图只能告诉我们“相关方向”,不能直接推断“因果强度”,真正的系数估计要留到回归模型里做。

3.3 变量类型变换与新特征构造

diamonds 数据里 cut、color、clarity 是文本型离散值,不能直接扔进 lm。R 处理这类变量,标准做法是先转成 factor,让模型自动生成哑变量:

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") )

这里手动指定 levels,是为了让哑变量的参照组固定为“最差”等级。比如 cut 以 Fair 为基准,color 以 J 为基准,clarity 以 I1 为基准,那么回归系数就能直接读出“相比最差等级,提升到某等级带来多少价格增量”。如果不显式指定 levels,R 会按字母序自动设置参照组,结果里的对比基准可能不符合业务直觉。

特征构造方面,我习惯把数据里已有的 x、y、z 组合成新的几何指标,让模型有机会捕捉尺寸交互效应:

diamonds_clean$total_size <- diamonds_clean$x + diamonds_clean$y + diamonds_clean$z diamonds_clean$volume <- diamonds_clean$x * diamonds_clean$y * diamonds_clean$z diamonds_clean$depth_ratio <- 2 * diamonds_clean$z / (diamonds_clean$x + diamonds_clean$y)

total_size 是从长宽深三个方向合成的整体尺寸,volume 是体积的粗粒度量,depth_ratio 则是深度比例。这样做的目的是给模型提供更丰富的几何信息,避免只依赖 carat 这个单一变量去解释价格。实际建模时,x、y、z 和 carat 之间往往存在较强的相关关系,放进模型前需要结合后面的回归诊断做筛选,否则会出现共线性导致的符号反转。

4. 回归分析:从一元线性回归到多元交互模型

4.1 一元线性回归:克拉与价格的关系

先看最简单的情况:只用 carat 预测 price。我很少在模型阶段直接写lm(price ~ carat)而不看散点图,因为 carat 和 price 的关系往往不是严格直线,而是随着克拉数增大,价格离散程度越来越高。

lm1 <- lm(price ~ carat, data = diamonds_clean) summary(lm1)

summary 输出里首先看系数的 p 值,carat 的系数远小于 0.001,说明克拉和价格有显著正向关系。项目里拟合出的斜率约在 7760 美元每克拉,直观理解就是克拉数每增加 1,价格预期增加约 7760 美元。再看 R 方,单变量模型通常能解释 85% 左右的价格变异,但残差图会显示明显的异方差性:克拉数越大,残差分布越宽。这说明其他品质变量仍然在影响价格,只是它们被淹没在残差里了。

这个阶段的关键结论并不是“克拉很重要”,而是“只用克拉预测还不够稳定”。高克拉钻石的稀缺溢价会放大价格波动,所以后面必须引入切工、颜色、净度这些定性变量。

4.2 多元回归与哑变量处理

加入 cut、color、clarity 后,模型从简单的一元回归变成多元回归。R 在 lm 里看到 factor 类型变量,会自动按 dummy 方式展开。

lm_multi <- lm( price ~ carat + cut + color + clarity + depth + table + total_size, data = diamonds_clean ) summary(lm_multi)

这个模型对应的是没有交互项的多元线性回归,拟合完可以发现 R 方上升到了约 0.88。也就是说,加上品质等级和深度、台宽比例后,价格解释能力提升了,但没有想象中那么大。原因是 cut、color、clarity 对价格的影响并不只是简单的线性叠加,一颗颜色较差但克拉数很大、切工又很普通的钻石,其价格可能会受到多因素同时拖累。

在解读多元回归结果时,要注意系数含义:在控制其他变量不变的前提下,color 从 J 提升到 D,价格预期增加多少;clarity 从 I1 提升到 IF,价格预期增加多少。由于 R 自动生成的参照组不同,具体系数会和我手动指定的 levels 一致。

4.3 交互项与逐步回归:AIC 和变量选择

由于不同品质变量之间可能存在协同作用,项目里进一步加入 carat:cut、carat:color、carat:clarity 三个交互项。交互项的含义是:克拉数对价格的影响,会随着切工、颜色、净度等级变化而变化。

lm_interact <- lm( price ~ carat + cut + color + clarity + depth + table + carat:cut + carat:color + carat:clarity, data = diamonds_clean ) lm_step <- step(lm_interact, direction = "backward", k = 2) summary(lm_step)

step 函数默认使用 AIC 准则,k = 2 表示每增加一个参数要付出对应的惩罚。向后逐步回归的做法是先把所有变量全放进去,然后每一步尝试删掉一个变量,如果删除后 AIC 没有显著变差,就保留删除结果,重复直到没有变量可删。

我在实际跑这个数据集时发现,AIC 下的逐步回归几乎把所有交互项都保留了,模型列表非常长。原因在于样本量超过 5 万,AIC 对大样本模型倾向于选择更复杂的结构,所以“所有变量都被选入模型”并不稀奇。

提示:AIC 适合中等样本下的变量筛选,样本量很大时建议改用 BIC(k = log(nrow(diamonds_clean))),BIC 对复杂模型惩罚更重,结果更容易解释。

4.4 回归诊断与多重共线性检验

模型建完之后不能只看 R 方,还需要做回归诊断。最常用的诊断包括残差正态性、残差 vs 拟合值的散点图,以及多重共线性检查。

par(mfrow = c(2, 2)) plot(lm_step) library(car) vif(lm_step)

plot.lm 会一次性输出残差与拟合值关系图、正态 Q-Q 图、标准化残差和 Cook's distance。观察这些图时,如果残差与拟合值之间呈喇叭状分布,说明方差不稳定;Q-Q 图的两端明显偏离直线,说明残差正态性不好。VIF 如果超过 5,就该警惕共线性问题,尤其是 x、y、z、volume 和 carat 这几个变量往往纠缠在一起。

在这个项目里,加入交互项后模型解释力会进一步提高,R 方可以接近 0.92。但代价是系数非常多,业务解释变复杂。如果目的是理解价格机制,我会保留部分交互项;如果目的是预测,交互模型在测试集上的表现通常也比无交互模型更稳。之后再结合验证集的预测误差,决定最终用哪个版本。

5. 避坑指南:钻石价格建模时容易踩的五个坑

5.1 数据清洗与特征构造的坑

第一坑:看到 1.5 倍 IQR 就直接批量删除异常值。现象是剔除后训练集 R 方上升,但用真实市场数据做验证时,预测结果普遍偏低。原因很简单,价格右偏分布里的“高价点”不是测量错误,而是真实存在的极品钻石;按 IQR 一刀切会把大量高价值样本删掉。解决方法是先区分异常值和极端值,对 price 做对数变换,或者用稳健回归处理极端点,不要轻易删行。

第二坑:手动把 cut、color、clarity 编码成 1、2、3、4、5 再塞进 lm。现象是模型系数看起来整齐,但预测结果对等级排序特别敏感,一旦实际数据里出现等级跨越就会坏掉。原因是这种编码强制假设“提升一个等级带来的价格增量固定”,而真实市场里 Ideal 和 Very Good 的价差通常远大于 Fair 到 Good 的价差。解决方案是用有序因子或普通 factor 生成哑变量,让每个等级都有独立的系数。

第三坑:在构造新变量时把强相关变量一股脑全放进去。现象是回归系数出现反直觉符号,比如 x 变量增加,价格反而下降。原因就是 x、y、z、total_size、volume 和 carat 之间存在严重共线性,多重共线性会让系数估计变得极不稳定。解决方法是先用相关性矩阵和 VIF 检查,再决定保留 carat、depth_ratio 这样的独立特征,而不是把原始尺寸全部保留。

5.2 建模与评估的坑

第四坑:只看 summary 里的 R 方,忽略残差分布。现象是模型 R 方接近 0.9,但真实预测误差却大得离谱。原因是价格变量本身右偏,平方和权重被极端值主导,模型可能只在低价区间表现好。解决方法是把 R 方、RMSE、中位绝对误差放在一起看,并且按克拉分段分别评估预测误差,观察模型是否在高低价位段一致稳定。

第五坑:直接拿全部数据拟合后,又用同一批数据评估模型。现象是训练集误差比测试集误差好看很多,甚至“预测结果”看起来完美。原因是模型记住了训练数据里的噪声,而不是真正学到的规律。解决方法是把数据集按 7:3 拆成训练集和测试集,或者用交叉验证,每次都在不同子集上评估,才能得到可信的预测性能。

我把这五条经验写进自己的分析流程后,踩坑次数明显少了。每次建模前先问三个问题:异常值是否已被业务解释、分类变量是否真的有序、验证方式是否复用了训练集。这三个问题对得上号,模型基本不会翻车。

6. 二次开发:把静态分析改造成可预测价格的回归模块

6.1 改用对数价格并做训练集、测试集验证

如果想让这份分析从一个演示性质的报告变成真正能投入使用的价格预测模块,我建议第一步就把因变量从 price 换成 ln_price。对数变换能缓解右侧极端值对模型的拉扯,预测出来的价格也更稳定。

set.seed(123) train_idx <- sample(nrow(diamonds_clean), size = floor(0.7 * nrow(diamonds_clean))) train <- diamonds_clean[train_idx, ] test <- diamonds_clean[-train_idx, ] lm_log <- lm( ln_price ~ carat + cut + color + clarity + depth + table, data = train ) test$pred <- exp(predict(lm_log, newdata = test)) test$error <- test$pred - test$price rmse <- sqrt(mean(test$error^2)) mape <- mean(abs(test$error / test$price))

这里先随机抽 70% 样本作为训练集,30% 作为测试集;在训练集上拟合对数价格模型,再用 predict 对测试集做预测,最后通过 exp 还原成美元价格。RMSE 和 MAPE 综合起来可以判断模型是否可靠。

实际调试时,我会把训练和测试的误差分别打印出来对比。如果训练误差远小于测试误差,说明过拟合;如果两者都很大,说明特征或模型结构还不够。通过这样反复对比,最终留下的模型会在不同数据集上都保持稳定。

6.2 输出价格预测与批量评估

工程化改造的第二步是写一个可复用的预测函数,输入钻石属性,输出预测价格区间。这样不仅自己能用,还可以接成一个简单接口给业务方用。

predict_diamond_price <- function(carat, cut, color, clarity, depth, table) { new <- data.frame( carat = carat, cut = factor(cut, levels = levels(train$cut)), color = factor(color, levels = levels(train$color)), clarity = factor(clarity, levels = levels(train$clarity)), depth = depth, table = table ) pred_val <- predict(lm_log, newdata = new, interval = "prediction") exp(pred_val) } predict_diamond_price( carat = 0.7, cut = "Very Good", color = "G", clarity = "VS1", depth = 62.2, table = 57.5 )

predict 函数里的 interval = "prediction" 会返回拟合值上下界,经过 exp 变换后就是预测价格的一个大致区间。这里要注意新输入的分类变量必须和训练集保持完全相同的 levels,否则 R 会因为“非对比参照组”直接报错。为了让这个函数足够健壮,我还会额外检查传入的 cut、color、clarity 是否合法,不合法的值直接给出提示,而不是让模型输出一个毫无意义的价格。

到这一步,原来的分析报告就不再只是静态的图表和回归摘要,而是一个能对新钻石样本做价格预测的小模块。如果后续收集到更多市场数据,只需替换训练集重新拟合一版模型,函数和评估流程可以原样复用。

这套钻石价格分析最让我受益的并不是回归系数的绝对值,而是“先怀疑数据,再怀疑模型,最后才相信结论”的流程。从那以后,我每拿到一个陌生数据集,都会强迫自己先走一遍缺失值和异常值检查,再做特征类型确认,最后才进入建模阶段。这次用 R 语言跑完整条分析链路,最大的收获是学会在模型复杂度与业务解释之间找平衡。希望这份拆解对你跑通这个项目也有帮助。

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

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

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

立即咨询