资讯动态

R语言diamonds钻石价格回归分析:清洗、建模与残差诊断

发布时间:2026/10/27 2:13:56 来源:尧图企业网站定制
简介围绕R语言展开的钻石价格分析文档面向具备基础R操作能力、希望练习数据清洗、可视化与回归建模的数据分析学习者。内容依托ggplot2自带的diamonds数据集覆盖缺失值与重复值检查、异常值识别剔除、价格分布直方图、箱线图与小提琴图、变量类型转换及新变量构造并对价格右偏分布与低价区间集中等现象作出解读同时建立线性回归模型探讨克拉重量、切工、颜色、净度等因素对价格的影响与预测思路。压缩包共1个docx文件约2.4MB以图文结合方式完整呈现数据读取、描述统计、可视化探索、模型建立与结果解释等环节便于对照复现、课程汇报和整理分析报告。已有284人学习下载适合作为R语言课程作业、数据分析入门案例或回归分析练习参考。1. R 自带的 diamonds 数据集五万四千颗钻石的价格十个变量能解释多少钻石定价这件事玄学成分不小——同样一克拉柜台上能差出三倍价格。ggplot2里躺着一份diamonds数据集53940 条记录、10 个变量克拉重量、切工、颜色、净度、深度百分比、台宽连同三维尺寸和成交价都记在里面做 R 数据分析时它几乎是回归分析的默认练手素材也是很多人接触机器学习的第一份真实分布数据。这份基于 R 语言的钻石价格分析资源围绕它走了一遍完整链路数据读取与体检、缺失值重复值异常值三连排查、定性变量因子化、一元与多元线性回归、交互项设计与向后逐步回归、显著性检验和残差诊断。适合刚上手 R、想拿一份五万条量级的数据把回归流程从头走通的人也适合已经把模型跑出来、但说不清系数该怎么解释、残差图该怎么看的熟手。2. 数据读取与体检从 data() 到箱线图先看清再动手数据体检这一步我见过太多人直接跳过。拿到diamonds就lm(price ~ ., data diamonds)跑出来 R² 零点八几感觉挺好然后系数解释一塌糊涂。问题往往不在模型在于读数据的时候没看清里面藏着什么。2.1 装包与加载三行代码拿到数据源diamonds不是独立数据集它挂在ggplot2包下面。加载ggplot2之后用data()显式取一次比依赖副作用更稳尤其是脚本里同时 library 了好几个包、存在同名对象覆盖风险的时候。# 首次运行才需要装过就跳过这一行 install.packages(c(ggplot2, dplyr, mice, car)) library(ggplot2) library(dplyr) # 显式加载内置数据集避免依赖 package attach 的副作用 data(diamonds, package ggplot2) # 行数、列数、字段名 dim(diamonds) names(diamonds)dim()返回53940 10这个数字要记住后面所有清洗操作都要能解释行数怎么变的。names()输出的十个字段里cut、color、clarity是名义变量carat、depth、table、price、x、y、z是数值变量。字段含义和取值范围如下表建模前建议对着表过一遍因为后面判断异常值时全靠它。字段含义典型范围类型carat克拉重量0.20 – 5.01连续cut切工等级Fair / Good / Very Good / Premium / Ideal有序分类color颜色等级J差→ D好有序分类clarity净度等级I1 → IF有序分类depth总深度百分比 2z/(xy)×10043 – 79连续table台宽百分比43 – 95连续price成交价美元326 – 18823连续x / y / z长 / 宽 / 深毫米0 – 10.74 / 0 – 58.9 / 0 – 31.8连续2.2 结构体检str、缺失值与重复值三连str()给出字段类型和前后几条样例比head()信息密度高。紧接着做缺失值排查colSums(is.na())是最直接的一行mice::md.pattern()会把缺失模式画成矩阵图字段多了以后一眼能看出是不是成块缺失。str(diamonds) # 各列缺失数量 colSums(is.na(diamonds)) # 缺失模式可视化0 表示缺失1 表示有值 library(mice) md.pattern(diamonds, rotate.names TRUE)这份数据的结果是干净的十列全零缺失。很多人看到这里就收工了其实diamonds的缺失是隐性的——x、y、z里存在取值为 0 的记录长度为零的钻石在物理上不存在这属于用 0 冒充缺失值。这一条后面单独讲。重复值排查同样简单duplicated()返回逻辑向量sum()汇总。要注意这里判的是整行完全一致不是主键重复——钻石没有唯一编号两颗参数完全相同的钻石在数据里就会被判成重复处理策略要自己定。# 完全重复的行数 sum(duplicated(diamonds)) # 去重后保留原行名方便回溯 diamonds_uniq - diamonds[!duplicated(diamonds), ] nrow(diamonds_uniq)去重之后行数会掉一点具体掉多少取决于你判重的字段范围。我的习惯是先按全字段去重如果掉得异常多比如超过 1%再回头逐字段核对是不是某几列精度截断导致的伪重复。2.3 异常值判定1.5 倍 IQR 只是第一道筛子price是右偏严重的分布箱线图上须外点一大片。标准做法是算四分位距上下各留 1.5 倍 IQR 作为围栏。这套规则对近似对称的分布很好用对价格这种长尾分布就会误杀——高价位本来就该有长尾那不算错误那是市场结构。# 画 price 的箱线图先看清尾巴有多长 boxplot(diamonds_uniq$price, horizontal TRUE, main price 箱线图, xlab 美元) q1 - quantile(diamonds_uniq$price, 0.25) q3 - quantile(diamonds_uniq$price, 0.75) iqr - q3 - q1 lower - q1 - 1.5 * iqr # 约 -1262负数无意义 upper - q3 1.5 * iqr # 约 11894 c(lower lower, upper upper)算出来的下围栏是负数说明价格分布压根不对称机械套 1.5 倍 IQR 会把上千条真实高价记录删掉。所以我在这个项目里把异常值拆成两类处理物理上不可能的记录直接删统计上的长尾保留或者做对数变换。物理判断就三条——y、z不能为 0y不能超过 20 毫米z不能超过 20 毫米。数据集里y最大 58.9、z最大 31.8明显是录入错误这种才是真正的脏数据。diamonds_clean - diamonds_uniq %% filter(y 0, z 0, x 0) %% # 尺寸为 0 的物理上不存在 filter(y 20, z 20, x 20) %% # 超过 20mm 的属于录入错误 filter(depth 40, depth 80) # 总深度百分比超出常识区间 nrow(diamonds_clean)每一步 filter 后面我都会nrow()打一次确认删了多少条、占比多少。删掉超过 3% 就要停下来重新想这是血泪经验有一次顺手加了个price 15000的条件结果把整条高价长尾砍了模型 R² 从 0.88 涨到 0.94看着漂亮实际上模型再也不会预测超过一万五的钻石。2.4 清洗后的基线统计清洗完先把summary()打出来把清洗前后的均值中位数对一遍。这一步能暴露不少问题如果清洗后均值掉得厉害说明删掉的主要是高价位如果中位数几乎没变说明删除是均匀的。summary(diamonds_clean[, c(carat, depth, table, price)]) # 清洗前后对比 rbind( 清洗前 c(n nrow(diamonds_uniq), mean_price mean(diamonds_uniq$price), median_price median(diamonds_uniq$price)), 清洗后 c(n nrow(diamonds_clean), mean_price mean(diamonds_clean$price), median_price median(diamonds_clean$price)) )这份数据清洗后的均值在 3900 美元上下中位数 2400 美元左右均值明显高于中位数右偏特性保留下来了——这是个好信号说明没有把长尾删干净。这份清洗后仍右偏的基线是后面判断模型残差是否正常的锚点。3. 变量编码与特征工程有序因子、派生比率和可视化取舍模型能不能解释七成看变量编码。cut、color、clarity这三个字段在原始数据里是字符型直接扔进lm()会被当成三水平以上的分类变量处理R 默认按字母序排水平结果就是Fair变成基准组所有系数都是相对一般切工而言的——这个基准在业务上毫无意义。3.1 有序因子level 顺序直接决定系数符号cut的五档从 Fair 到 Ideal 是有序的color从 J 到 D、clarity从 I1 到 IF 也是。用factor(levels ...)显式指定顺序再考虑ordered TRUE。区别在于无序因子进模型会生成 k-1 个哑变量有序因子默认生成线性对比.L、二次.Q这类正交多项式项解释起来更接近每升一级价格变化多少。diamonds_clean - diamonds_clean %% mutate( # 切工从差到好 cut factor(cut, levels c(Fair, Good, Very Good, Premium, Ideal), ordered TRUE), # 颜色 J 最差、D 最好注意方向别写反 color factor(color, levels c(J, I, H, G, F, E, D), ordered TRUE), # 净度 I1 最差、IF 最好 clarity factor(clarity, levels c(I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF), ordered TRUE) ) # 检查水平顺序是否生效 levels(diamonds_clean$cut) levels(diamonds_clean$color)这里最容易翻车的是color。数据集的取值是 D 到 JD 最好、J 最差而字母序恰好是 D 在前。如果偷懒不指定 levelsR 会把 D 当基准组回归系数全变成比 D 差多少方向全反。我一般在建模前固定跑一次levels()检查这个习惯救过不止一次。3.2 派生变量体积、台宽比和那个不能用的每克拉单价原始字段之外工程上常补几个派生量。x*y*z近似体积比单看某一维更能刻画看起来多大depth字段本身已经是 2z/(xy)×100原始数据里直接可用但清洗后可以重算一遍做交叉校验差值大的记录说明原始字段有误。diamonds_clean - diamonds_clean %% mutate( volume x * y * z, # 近似体积立方毫米 depth_calc 2 * z / (x y) * 100, # 重算总深度百分比 depth_diff abs(depth - depth_calc) # 与原字段的偏差用于校验 ) # 偏差超过 1 个百分点的记录有多少 sum(diamonds_clean$depth_diff 1, na.rm TRUE)还有一个特征必须点名price / carat也就是每克拉单价。它跟目标变量price直接相关放进模型等于把答案提前告诉模型R² 能瞬间冲到 0.99——这是典型的数据泄露在机器学习里属于最低级的错误之一但拿它做描述性分析、看不同切工的溢价水平是合理的。用之前先问一句这个变量在预测时拿得到吗拿不到就不能进特征集。3.3 描述性可视化直方图看形状核密度看峰小提琴看分组可视化这一步不是画着好看是给建模定方向。price的直方图决定要不要做对数变换分组小提琴图决定交互项要不要加。library(ggplot2) # 价格分布直方图叠加核密度 ggplot(diamonds_clean, aes(x price)) geom_histogram(aes(y after_stat(density)), bins 60, fill grey75, colour white) geom_density(colour #c0392b, linewidth 1) labs(x 价格美元, y 密度, title 钻石价格分布) # 切工 × 价格纵轴取对数才能看清组间差异 ggplot(diamonds_clean, aes(x cut, y price, fill cut)) geom_violin(trim FALSE, alpha 0.7) scale_y_log10() theme(legend.position none) labs(x 切工等级, y 价格对数刻度)直方图出来能看见明显的单峰右偏峰值在 1000 美元附近2000、4000、8000 附近有几处断层。小提琴图纵轴必须取对数否则五个组的箱体全被压扁在底部什么也看不出来——这是新手最常犯的绘图错误不取对数就断言各组没差异纯属误判。从分组图能读出的信息是切工越好价格中位数越高但 Ideal 组的下须拖得很长说明理想切工里也有一批小克拉的低价货。这个切工与价格不是单调关系的现象正是后面要加carat:cut交互项的直接依据。4. 回归建模从 carat 单变量到交互项 AIC 逐步回归建模的推进顺序很重要一上来就全变量加交互跑出来的东西自己都解释不了。我的习惯是先跑单变量建立基准再逐步加变量每加一步都看调整 R² 和残差标准误的变化心里有一本账。4.1 一元线性回归7760 美元/克拉这个系数怎么读carat和price的正相关肉眼可见先用它单独建一个模型作为后续所有模型的比较基准。fit1 - lm(price ~ carat, data diamonds_clean) summary(fit1) # 取出系数和置信区间 coef(fit1) confint(fit1, carat, level 0.95)系数约 7760p 值远小于 0.001字面解释是克拉重量每增加 1 克拉价格平均上涨约 7760 美元。但这个解释只在 0.2 到 5 克拉这个区间内成立而且模型强行假设了线性关系。实际上钻石定价是阶梯式的整克拉和 0.99 克拉能差出一大截线性模型捕捉不到。confint()给出的是系数的 95% 置信区间用来判断这个估计稳不稳区间越窄越可靠。一元模型的调整 R² 只有 0.85 左右残差标准误一千多美元。作为基准够用但说明还有大量信息没被利用——切工、颜色、净度这三维度一颗都没进模型。4.2 无交互项多元模型R²0.8798 卡在什么地方把三个定性变量和三个尺寸变量全加进来先不加任何交互项。fit2 - lm(price ~ carat cut color clarity depth table x y z, data diamonds_clean) summary(fit2) AIC(fit2) # 方差膨胀因子看共线性 car::vif(fit2)调整 R² 大约到 0.88比一元模型涨了一点但没到预期。问题出在两个地方。一是x、y、z和carat高度相关三个尺寸基本都是克拉重量的函数VIF 很容易超过 10共线性把系数估计的方差撑大了。二是模型假设切工对价格的影响是固定加项可现实中切工的价值恰恰是通过放大克拉重量的溢价来实现的——一克拉的 Ideal 和三克拉的 Ideal切工带来的绝对差价完全不同。VIF 那块要具体看数值。carat的 VIF 通常在 10 以上x、y、z也在 5 到 15 之间浮动。处理方式不是无脑删而是想清楚如果目标是预测共线性不影响预测精度可以留着如果目标是解释系数就得删掉一组。我在解释型任务里一般保留carat把x、y、z拿掉或者只保留volume。4.3 交互项设计与向后逐步回归AIC 到底选了什么交互项的加法有讲究。全上cut*color*clarity会生成几百个参数样本再多也撑不住。我的做法是先只给carat配交互——carat:cut、carat:color、carat:clarity因为克拉重量是价格的乘性驱动因子其余变量更像调节项。fit3 - lm(price ~ carat * cut carat * color carat * clarity depth table volume, data diamonds_clean) summary(fit3)$adj.r.squared length(coef(fit3)) # 参数个数确认没有爆炸参数个数会到几十个比fit2多不少调整 R² 也有提升。接下来用step()做向后逐步回归按 AIC 剔除贡献不足的项。# k 2 是 AICk log(n) 是 BIC惩罚更重、模型更精简 fit4 - step(fit3, direction backward, k 2, trace 1) summary(fit4) AIC(fit3, fit4)step()的direction参数选backward是从全模型开始逐个删both允许增删交替、搜索空间更大但更慢。k参数是关键k 2对应 AIC偏向预测精度k log(nrow)对应 BIC惩罚项随样本量增长更偏向精简模型。五万多条样本log(n)约等于 10.8比 2 大得多BIC 会砍掉更多交互项。我用哪个取决于目的——做预测用 AIC做解释用 BIC。模型变量构成调整 R²参数量用途fit1carat≈0.852基准fit2carat 三个定性 depth/table/xyz≈0.88约 25可解释性对照fit3carat 与三个定性全交互 尺寸≈0.93约 60预测主力候选fit4step 向后筛选后≈0.93视 k 而定最终模型trace 1会把每一步删掉的变量和对应的 AIC 变化打出来这个输出别关它记录了模型是怎么一步步瘦下来的。某一步 AIC 只降了 0.3说明那个变量删不删都行属于可解释性和精度的权衡点。4.4 系数怎么念给非技术同事听模型跑完最难的往往不是跑是解释。有序因子在模型里以.L、.Q、.C这类正交多项式项出现系数没法直接读成升一级涨多少钱。要给出人能听懂的口径可以固定其他变量做一个预测网格。# 固定其余变量在中位数水平看切工从 Fair 到 Ideal 的预测价格 grid - data.frame( carat median(diamonds_clean$carat), cut factor(levels(diamonds_clean$cut), levels levels(diamonds_clean$cut), ordered TRUE), color factor(G, levels levels(diamonds_clean$color), ordered TRUE), clarity factor(VS2, levels levels(diamonds_clean$clarity), ordered TRUE), depth median(diamonds_clean$depth), table median(diamonds_clean$table), volume median(diamonds_clean$volume) ) # 用最终模型预测 grid$pred - predict(fit4, newdata grid) grid[, c(cut, pred)]这个网格的预测差值就是这个数据集里切工升一档的平均溢价比读系数直观得多拿去跟业务方沟通也更容易被接受。注意网格里的color、clarity必须用 factor 且 levels 跟训练集一致否则predict()直接报错——这是新手最常撞的一堵墙。5. 常见问题排查模型跑通但结论不能信的五种情况跑出summary()不等于做出分析。下面五条是我在这份数据上反复踩过的坑按现象 → 原因 → 解决列出来中一条就值得回头查。5.1 现象R² 突然从 0.88 跳到 0.99原因特征里混进了price / carat或者任何由price推导出来的量。这是数据泄露模型在训练时已经看到了答案。另一种隐蔽变体是先把全量数据做了标准化或者 PCA 再切分训练测试集测试集的信息渗进了训练过程。解决建模前把所有字段过一遍凡是目标变量的函数一律剔除。标准化、降维这类操作只在训练集上fit再用同一组参数去transform测试集。这条不分语言、不分框架是所有机器学习的通用红线。5.2 现象高价钻石永远预测偏低残差在尾部堆成一片原因为了清洗异常值把price的上围栏之外全删了。统计意义上的长尾被当成错误删除模型压根没见过一万五千美元以上的样本自然预测不出来。解决区分物理不可能和统计上少见。y0、z58.9这类违反常识的删掉价格长尾保留或者对price取对数后建模log变换能把右偏压平残差表现通常明显改善。取了对数记得在预测结果上做exp()还原否则报出来的数字会小一大截。5.3 现象切工越差系数越高跟常识完全相反原因factor()没指定levelsR 按字母序排水平基准组变成了Fair所有系数都是相对一般而言符号自然反了。color同理字母序 D 在前不指定就会把最好的 D 当基准。解决三个有序变量一律显式写levels建模前跑一次levels()核对顺序。我现在的习惯是把这三个变量的 levels 定义抽成一个常量列表在脚本顶部统一定义全流程复用避免不同脚本里顺序不一致。5.4 现象交互项全上之后VIF 飙到几十系数标准误大得离谱原因carat * cut * color * clarity展开后参数几百个各交互项之间高度共线加上主效应设计矩阵接近奇异。样本量再大也压不住标准误。解决交互项只加有业务含义的且优先跟主驱动变量carat配。加完跑car::vif()超过 10 的项回头查共线性来源。还可以先做中心化scale(carat, scale FALSE)把主效应和交互项的相关性降下来系数会稳定不少。5.5 现象模型在训练集上 R² 0.93换一批数据就掉到 0.7原因五万多条数据里有很多近乎重复的样本参数雷同、只有价格差几十美元随机切分时训练集和测试集里同时出现同一颗钻石等于变相泄漏。另外如果在全量数据上做完清洗再切分切分边界也会受清洗规则影响。解决先切分再清洗训练集得到的清洗规则四分位阈值、上下围栏直接套到测试集上不要各自算各自的分位数。有近似重复的样本按参数组合做分组切分保证同一组只出现在一边。# 先切分再在训练集上算阈值 set.seed(2024) idx_train - sample(seq_len(nrow(diamonds_clean)), size 0.7 * nrow(diamonds_clean)) train - diamonds_clean[idx_train, ] test - diamonds_clean[-idx_train, ] # 阈值从 train 算套用到 test q1_tr - quantile(train$price, 0.25) q3_tr - quantile(train$price, 0.75) upper_tr - q3_tr 1.5 * (q3_tr - q1_tr) test_f - test[test$price upper_tr, ] nrow(test_f)代码本身很短重点在顺序切分在前阈值计算在后且只算一次。反过来做评估结果会虚高而且这种虚高在数据量越大、重复样本越多的时候越明显五万条这个量级已经足够让偏差显形。6. 残差四图与显著性检验给模型做一次完整检查模型跑完不算完残差图是必看项。par(mfrow c(2,2))之后plot(fit4)一次性出四张诊断图每一张对应一个假设。par(mfrow c(2, 2), mar c(4, 4, 2, 1)) plot(fit4, which 1:4) par(mfrow c(1, 1))第一张残差对拟合值理想状态是点在零线附近均匀散布、没有喇叭口。价格数据上常见的是右端开口说明高价位方差更大是异方差可以试log(price)或者加权最小二乘。第二张 QQ 图看正态性两端翘起说明残差厚尾样本量五万多的情况下轻微偏离可以接受只要不是 S 形。第三张看标准化残差方差的稳定性第四张看杠杆值和 Cook 距离挑出影响力过大的个别点——diamonds里那几个尺寸录入错误的记录往往就是在这里露头的。|im_start|注意五万多条样本做shapiro.test()会直接报错样本量上限是 5000。要看正态性改用 QQ 图或者对残差做ks.test()、ad.test()别硬上。分组差异的显著性检验也要跟上。不同切工组的价格不满足正态性教科书式的方差分析前提不成立用 Kruskal-Wallis 非参数检验更稳妥。# 不同切工组的价格分布是否存在显著差异 kruskal.test(price ~ cut, data diamonds_clean) # 两组比较就用 Wilcoxon wilcox.test(price ~ cut, data subset(diamonds_clean, cut %in% c(Fair, Ideal)))kruskal.test()的零假设是各组分布位置相同p 值极小说明组间有差异这跟模型里cut项显著是互相印证的。要注意 p 值小只说明有差异不说明差异多大效应量还得靠前面那份预测网格去量化。五万条样本下几乎任何组间差异都会显著这是大样本检验的通病别把统计显著当成业务重要。最后补一句predict()做预测一定要在newdata里把三个因子变量的 levels 对齐缺一个水平就会报factor has new levels。我吃过一次亏清洗后的测试集里刚好没有Fair这一档模型直接拒绝预测。从那以后我每次做完清洗都先跑一遍sapply(diamonds_clean[, c(cut,color,clarity)], levels)确认水平没塌再往下走建模流程。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑