简介:基于R语言钻石价格分析案例资源,面向数据分析初学者、R语言用户及商科量化分析人员,以ggplot2内置diamonds数据集为素材,完整演示数据探索、缺失值与重复值检查、异常值剔除、价格分布可视化、多变量关系分析及多元线性回归建模流程。资源为单份docx文档,约2.4MB,内含变量说明表、箱线图、小提琴图、直方图及逐步筛选回归结果等关键图表,可直接用于课程设计、毕业设计或项目复现。已有283人下载学习。文档通过实证展示克拉重量、切割、颜色、净度等因素对钻石价格的影响,并给出利用回归模型预测价格走势的具体方法,能帮助读者掌握R语言在真实商业数据建模中的应用,提升数据清洗、可视化与模型解释能力,为后续学习机器学习与预测建模打下基础。
1. 不只有爱情营销:R 语言看钻石定价的 54000 条记录
提到钻石,大多数人先想到的是那句经典的广告词,但做数据分析的人更关心的是另一件事:一颗钻石凭什么从 326 美元一路标到 18823 美元?这个问题背后正好是一个典型的 R 数据分析与回归分析场景。ggplot2 内置的 diamonds 数据集收录了约 54000 颗钻石的价格、重量、切工、颜色和净度信息,用它来做一次完整的回归分析,不仅能理清价格驱动因素,还能顺手验证线性回归的边界在哪。这篇文章适合两类人:一是刚学完 R 基础、想找一份真实数据集练手的数据分析学习者,二是做市场调研或价格预测的从业者,想从数据里得到可解释的结论而不是玄学估价。
2. 数据预处理:从 diamonds 内置数据到可用建模集
分析的第一步不是急着画图,而是先把数据弄清楚。diamonds 数据集虽然干净,但它有 53940 条记录和 10 个变量,稍不小心就会在后面的建模阶段踩坑。
2.1 加载数据:ggplot2 自带的钻石品相库
diamonds 是 ggplot2 包的附赠数据,不需要单独下载,只需要确保 ggplot2 已经安装并加载。常见做法是第一次运行前先判断包是否存在,不存在就安装,然后显式调用data()函数把数据加载进工作环境。
# 检查并安装 ggplot2(首次运行时执行) if (!require(ggplot2)) install.packages("ggplot2") # 加载包并提取 diamonds 数据集 library(ggplot2) data(diamonds) # 查看数据规模与结构 str(diamonds) # 查看前六行,确认读取结果是否正确 head(diamonds)str(diamonds)会显示数据框的维度以及每个变量的类型,这一步能快速发现哪些列被读成了字符型、哪些是数值型。执行结果应该显示 53940 行、10 列,其中 cut、color、clarity 是因子型变量,carat 到 z 是数值型。接下来用summary()看每列的分布范围,比如 carat 的范围是 0.2 到 5.01,price 的范围是 326 到 18823,这些数值和后续建模的系数解释直接相关。
2.2 缺失值与重复值:干净数据里也有隐藏陷阱
缺失值检查是数据预处理的标准动作。diamonds 数据集本身质量很高,但流程上不能跳过。使用mice包中的md.pattern()函数可以输出一个矩阵,直观展示哪些列存在缺失、缺失的组合模式是什么。
# 安装并加载 mice 包 if (!require(mice)) install.packages("mice") library(mice) # 查看缺失值分布模式,0 表示无缺失,1 表示有缺失 md.pattern(diamonds) # 计算各列缺失值数量 colSums(is.na(diamonds)) # 查看重复行数量 sum(duplicated(diamonds))这段代码的关键在于md.pattern()返回的矩阵:每行代表一种缺失模式,最后一列是样本量。如果数据没有缺失值,矩阵会显示所有列都为 0,单行样本量为全量。colSums(is.na())则给出每列的缺失个数,结果全部为 0 说明数据完整。
重复值方面,duplicated()函数返回一个逻辑向量,sum()统计出重复行数。这个检查属于防患于未然——如果后面要做交叉验证或训练测试集划分,重复行会造成数据泄漏,模型评估结果虚高。
2.3 异常值定位与剔除:箱线图之外的另一层保险
箱线图是查看异常值最直接的工具。绘制 price、carat 等连续变量的箱线图,会发现上下须之外存在不少离群点,尤其是克拉数偏大的钻石,价格分布很散。
# 绘制价格箱线图,观察异常值范围 boxplot(diamonds$price, main="Price Boxplot", ylab="Price (USD)") # 用均值 ± 3 倍标准差的方法定位极端异常值 mean_price <- mean(diamonds$price) sd_price <- sd(diamonds$price) outlier_idx <- which(diamonds$price > mean_price + 3 * sd_price | diamonds$price < mean_price - 3 * sd_price) # 剔除异常值,保留剩余样本 diamonds_clean <- diamonds[-outlier_idx, ] # 对比剔除前后的样本量 nrow(diamonds) nrow(diamonds_clean)这里选择的是均值 ± 3 倍标准差作为剔除阈值,原因在于价格分布呈明显右偏,少数高价钻石会把均值拉高,而 3 倍标准差规则能过滤掉极端尾部。在实际项目中,更保守的做法是使用 IQR 规则,即超过 Q3 + 1.5 * IQR 的点才剔除;如果样本量足够大,也可以只剔除落在极端高价位且克拉数过大的记录,避免把真实的贵钻石误删。
剔除之后,建模集样本量会有小幅下降,这是可以接受的。后续所有分析和建模都基于diamonds_clean,而不是原始数据集。
3. 描述性分析:价格分布、切割与颜色维度
数据清洗完,先别急着回归。描述性统计这一步的目的,是搞清楚价格到底长什么样、不同属性维度下的价格差异是否显著,这决定了后面回归模型里交互项怎么设置。
3.1 价格直方图与分布形态:右偏市场里的峰值信号
绘制价格直方图和核密度曲线,能同时看到分布的柱状形态和密度曲线走势。
# 加载 ggplot2 并绘制价格直方图 + 核密度曲线 library(ggplot2) ggplot(diamonds_clean, aes(x = price)) + geom_histogram(aes(y = after_stat(density)), bins = 60, fill = "steelblue", alpha = 0.6) + geom_density(color = "darkred", linewidth = 1) + labs(title = "Diamond Price Distribution", x = "Price (USD)", y = "Density")从图上看,价格区间 326 到 903 美元之间的钻石数量最多,之后迅速递减;核密度曲线在 1000 美元附近出现明显峰值。2000 美元、4000 美元、8000 美元附近出现断层,说明市场需求在这几个价位区间存在明显的分层。右偏分布意味着直接用线性回归拟合价格会遇到残差非正态的问题,后面建模时要么对价格取对数,要么增加交互项。
3.2 切割、颜色和透明度对价格的影响:认知偏差被数据校正
用箱线图或小提琴图把价格按切工分组,会得到一个反直觉的结论:理想切割的钻石并不是在所有价格区间都占优势,甚至有一部分 Ideal 切割的钻石价格处于较低水平。
# 小提琴图:不同切工等级下的价格分布 ggplot(diamonds_clean, aes(x = cut, y = price, fill = cut)) + geom_violin(alpha = 0.5) + geom_boxplot(width = 0.1) + scale_y_log10() + labs(title = "Price by Cut Quality", x = "Cut", y = "Price (log scale)")对价格取 log 是为了让分布更对称,否则小提琴图会被高价尾部拉长。颜色维度从 J 到 D 逐级变好,价格总体呈上升趋势,但 G 色钻石数量最多,D 和 J 都少——这解释了为什么颜色等级不是简单的线性关系:市场上最稀缺的颜色反而因为产量少而价格高,但大部分交易集中在中等级别。
净度维度更微妙。I1 净度比 SI2 差,价格中位数却不如 SI2 高,说明净度到达一定水平后,影响价格的已经不是单一变量,而是克拉数、颜色和切工的合力。这也给后面的回归模型一个信号:交互项可能比纯主效应模型更贴合实际。
3.3 非参数检验:组间差异到底显不显著
在描述性图形之外,还需要用统计检验验证不同切工、颜色、净度下的价格差异是否显著。由于各组的分布并不均匀且不完全满足正态性假设,首选 Kruskal-Wallis 非参数检验。
# 按切工分组的价格非参数检验 kruskal.test(price ~ cut, data = diamonds_clean) # 按颜色分组的价格非参数检验 kruskal.test(price ~ color, data = diamonds_clean) # 按净度分组的价格非参数检验 kruskal.test(price ~ clarity, data = diamonds_clean)Kruskal-Wallis 检验的零假设是各组中位数无显著差异。运行结果中 p-value 远小于 0.05,三个维度全部通过显著性检验。这说明切工、颜色、净度确实会影响价格,可以作为回归模型的自变量保留。这里要注意的是,显著性差异不等于效应量大,具体影响程度要看后续回归模型里的系数。
4. 多元线性回归:从主效应到交互项的建模路径
描述性分析确认了各维度与价格的关系后,下一步是把这些变量放进回归模型里,量化每个因素的贡献。
4.1 变量转换:把因子变量变成模型能吃的哑变量
cut、color、clarity 在原始数据里是因子变量,R 的lm()函数会自动把它们转换成哑变量,但转换的基准水平是因子水平的第一个级别。为了避免默认基准引发的解释歧义,建议显式控制因子水平顺序。
# 显式设置因子水平,让基准水平更直观 diamonds_clean$cut <- factor(diamonds_clean$cut, levels = c("Fair", "Good", "Very Good", "Premium", "Ideal")) diamonds_clean$color <- factor(diamonds_clean$color, levels = c("D", "E", "F", "G", "H", "I", "J")) diamonds_clean$clarity <- factor(diamonds_clean$clarity, levels = c("IF", "VVS1", "VVS2", "VS1", "VS2", "SI1", "SI2", "I1")) # 查看转换后的结构 str(diamonds_clean[, c("cut", "color", "clarity")])这里把 cut 按 Fair 到 Ideal 的等级升序排列,color 按 D 到 J 的顺序排列,clarity 按 IF 到 I1 排列。这样设置后,回归模型输出的系数表会以第一个水平为基准,例如 cut 的系数就是相对于 Fair 的提升量,解释起来更符合直觉。
4.2 无交互多元回归:基线模型与 R² 边界
先建立一个不含交互项的基线模型,用所有属性预测价格。
# 无交互项的多元线性回归模型 model_main <- lm(price ~ carat + cut + color + clarity + depth + table + x + y + z, data = diamonds_clean) # 查看模型摘要 summary(model_main) # 查看 R² 和调整 R² summary(model_main)$r.squared summary(model_main)$adj.r.squared模型摘要输出显示 R² = 0.8798,意味着约 88% 的价格变动可以被这些变量解释。这个结果不算差,但考虑到钻石定价的高度非线性,还有提升空间。关注点有两个:一是 carat 的系数估计值较大,说明克拉重量对价格影响显著;二是 x、y、z 三个尺寸变量与 carat 存在高度相关性,可能引发多重共线性,导致个别系数不可靠。
4.3 交互项与逐步回归:捕捉克拉数和其他维度的联动
钻石价格的一个显著特征是大克拉钻石的品质溢价更明显,1 克拉的 D 色和 1 克拉的 J 色价格差异远超 0.3 克拉的同等级差异。这种联动关系需要交互项来表达。
# 建立含交互项的模型:克拉数与切工、颜色、净度的交互 model_interact <- lm(price ~ carat + cut + color + clarity + depth + table + x + y + z + carat:cut + carat:color + carat:clarity, data = diamonds_clean) # 使用向后逐步回归 + AIC 准则筛选变量 model_step <- step(model_interact, direction = "backward", trace = FALSE) # 查看逐步回归选择的最终模型 summary(model_step) # 对比两个模型的 AIC AIC(model_main) AIC(model_step)逐步回归的运行逻辑是:从包含所有交互项的完整模型出发,每一步删掉一个 AIC 贡献最小的变量,直到无法再通过删除变量来降低 AIC。结果中,carat:cut、carat:color、carat:clarity 这三个交互项全部被保留,说明克拉数与其他品质维度的交互作用确实显著。相比无交互模型,交互模型的 AIC 明显下降,R² 也有提升。
需要留心的是,交互模型的系数表变得很长,可读性下降。实际使用时,可以只解析关键交互项的系数方向和显著性,不必逐个解释。
5. 常见问题与避坑:R 数据分析里的 5 个典型翻车点
我把这次分析过程中最容易出问题的位置集中列出来,每一条都是真实会出现的问题,写代码时提前避开能省下不少调试时间。
5.1 翻车点一:缺失值检查用错函数,把 NA 当成了数据缺失
现象:用is.na(diamonds)直接判断整个数据框,结果返回几百个 TRUE,以为数据有缺失,下一步准备做插补。
原因:is.na()对整个数据框操作时会逐列判断,但因子变量的某个水平可能显示为 NA,或者读取数据时空白值被自动转成了 NA,导致误判。
解决:用colSums(is.na(diamonds))按列统计,而不是对整个数据框做汇总判断。当发现某列存在 NA 时,先用table(is.na(diamonds$xxx))查看具体分布,再决定是删除还是插补,不要盲目套用na.omit()。
5.2 翻车点二:因子水平顺序没控制,回归系数解释全乱
现象:模型输出中 cut 的系数是 1380 美元,但手头的数据里切工最高的 Ideal 反而比 Premium 便宜,怎么解释都不对。
原因:R 的factor()默认按字母顺序排列水平,lm()会把第一个水平作为基准。字母序和品质序不一致时,系数表达的含义和你预想的完全不一样。
解决:建模型前手动指定levels = c("Fair", ...),确保第一个水平是你想要的基准。如果想改变基准,还可以用relevel()函数。
5.3 翻车点三:异常值剔除一刀切,把真实高价钻石删没了
现象:按均值 ± 3 倍标准差剔除异常值后,样本量骤减,回归模型的 R² 反而下降。
原因:价格数据本身是右偏的,高端钻石虽然数量少,但它们是价格分布的重要组成部分。一刀切剔除会破坏数据的自然形态,丢失尾部信息。
解决:先看箱线图确认异常值集中在哪个方向,再决定剔除策略。如果异常值集中在高端价位,优先考虑对价格取对数再建模,而不是直接删记录。
5.4 翻车点四:交互项一股脑全塞进模型,结果系数爆炸
现象:carat:cut 加进去后,模型的系数表出现几百个变量,p 值看起来都很小,但预测效果在验证集上很差。
原因:因子变量和连续变量交互时,R 会为每个因子水平生成一个交互系数,变量数量随因子水平数线性增长。样本量不够大时,模型过拟合风险明显上升。
解决:用step()做逐步回归,让 AIC 准则决定哪些交互项值得保留。不要手动一次添加多个交互项,尤其不要在不看模型摘要的情况下直接输出系数。
5.5 翻车点五:预测时用了和新数据不匹配的因子水平
现象:训练好的模型在预测新数据时报错,提示“factor cut has new levels”,或者预测值全部变成 NA。
原因:新数据的因子变量水平里出现了训练集中不存在的值,predict.lm()无法识别。常见于把训练集和测试集分开做因子转换时,两边水平不一致。
解决:在训练集和测试集划分之前,先统一对全量数据做因子水平设置,再用索引划分数据。这样测试集的 factor 水平一定包含在训练集的水平里。
6. 模型应用与验证:从系数解读到购买决策预测
模型建完之后,最实际的问题是:这颗钻石值多少钱,以及哪些因素对价格影响最大。交互模型的系数表虽然长,但可以把预测封装成一个函数,这样任意输入一组钻石参数,就能返回预测价格。
# 封装预测函数:输入钻石属性,输出预测价格 predict_price <- function(carat_val, cut_val, color_val, clarity_val, depth_val, table_val, x_val, y_val, z_val) { 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_step, newdata = new_data) } # 示例:预测一颗 0.7 克拉、Ideal 切工、G 色、VS1 净度的钻石价格 predict_price( carat_val = 0.7, cut_val = "Ideal", color_val = "G", clarity_val = "VS1", depth_val = 61.5, table_val = 56, x_val = 5.7, y_val = 5.8, z_val = 3.6 )预测函数的要点是因子水平必须和训练集一致,所以用levels(diamonds_clean$cut)显式提取水平,防止新数据里的因子水平顺序和训练时不一致。调用时返回的数值是美元价格,可以直接作为购买决策的参考区间。
除了预测函数,回归系数本身也能给出购买建议。以 carat:clarity 的交互项为例,如果系数为正,说明克拉数越高,净度等级对价格的加成效应越明显,预算充足时优先选高净度大克拉;如果交互项系数为负,说明高克拉钻石的净度溢价反而被稀释,这时候把预算花在颜色等级上更划算。这是一个从模型系数反推购买策略的实用思路。
用回归模型做钻石估价,并不是为了得到一个精确到个位数的数字,而是为了获得一个可解释的价格区间。从那以后,我每次拿到类似的数据集,都会强制走一遍这套流程:先查缺失和重复,再做分布形态判断,最后才进入建模和交互项选择。希望这篇文章能帮你在做 R 数据分析时少踩几个坑。
本文还有配套的精品资源,点击获取