☰
R语言钻石价格分析:多元回归、交互项与回归诊断实战
2026/10/26 2:01:50 网站建设 项目流程

简介:围绕R语言钻石价格分析的学习文档,面向希望掌握数据清洗、可视化与回归建模的初学者及数据分析爱好者,借助ggplot2自带、约5.4万条记录的diamonds数据集,完整走通从数据概览、缺失值与重复值检查、异常值识别剔除,到价格分布直方图、箱线图和小提琴图探索,再到简单线性回归与多元回归建模、价格预测的分析链路。文档重点讨论克拉重量、切工、颜色、净度等变量对价格的影响,呈现价格分布的右偏特征与千美元附近峰值,并通过逐步筛选梳理主要影响因素,兼顾方法理解与结果解读。资源包仅含1个docx文件,约2.4MB,篇幅集中,便于按分析步骤阅读和复盘,适合课堂示例、课程作业或自学练手时参考。已有284人学习,可作为R语言回归分析与机器学习入门实践的配套材料。

1. 反直觉开场:Ideal 切工的钻石价格为何不占顶

先抛一个我拆这份 R 语言钻石价格分析时没预料到的结果:把 ggplot2 自带 diamonds 数据集的 price 画成小提琴图,切工等级标到 Ideal(理想)的那一组,价格中位数并没有甩开 Premium,甚至分布下沿还压着一大片低价钻石。这跟"切工越好越贵"的常识是拧着的。这份基于 R 语言的数据分析项目,做的就是把这层拧巴拆开——用 53940 条钻石记录、10 个变量,从缺失值清洗、异常值剔除、描述性可视化一路走到多元线性回归、交互项筛选和回归诊断,最后回答一个问题:到底哪些因素在真正推高钻石价格。适合手里有 R 环境、想练一遍完整回归分析链路的人,也适合拿它当课程设计或数据岗面试作品底稿的开发者。

2. 数据底座与工程骨架:54000 行 diamonds 怎么摆进一个能跑的 R 项目

diamonds 是 ggplot2 包自带的数据集,省掉了找数据、爬数据、对齐字段这些前置脏活。但"数据白送"不等于"项目能跑",很多人第一次打开 RStudio 就是一行data(diamonds)然后在一个 .R 文件里从头写到尾,写到第 300 行想改个异常值阈值,发现前面所有图都得重跑一遍。我习惯先把脚本按职责切开,再谈代码。

2.1 目录结构:把一次性脚本拆成可重跑的流水线

一个能反复迭代的分析项目,目录不需要复杂,但必须让"清洗"和"建模"物理隔离。清洗规则一改,只需要重跑01_clean.R,模型脚本读的是落盘的中间结果,不用连带重画十几张图。

diamond-price-analysis/ ├── R/ │ ├── 00_setup.R # 依赖包安装与全局参数(对比度、随机种子) │ ├── 01_clean.R # 结构检查、缺失值、重复值、异常值、特征构造 │ ├── 02_eda.R # 直方图、核密度、小提琴图、箱线图 │ ├── 03_model.R # 一元回归、多元回归、交互项、逐步回归 │ └── 04_diagnose.R # 残差图、QQ 图、VIF、显著性检验 ├── data/ │ └── diamonds_clean.rds # 清洗后的中间结果,建模脚本只认这个 ├── output/ │ ├── fig_price_hist.png │ └── model_summary.txt └── report.Rmd # 汇总成可 knit 的报告

data/diamonds_clean.rds是整个结构的枢纽。用saveRDS()而不是write.csv(),是因为因子变量的 levels 顺序会随 CSV 读写丢失,而 levels 顺序直接决定回归里谁是基准组——这个坑后面单独讲。

2.2 十个变量的类型判定:哪些能直接进模型,哪些要先转

diamonds 的 10 个变量里,只有 price 是目标变量,其余 9 个是特征。R 读进来时 cut、color、clarity 是 character 类型,不转 factor 直接丢进lm(),模型会把它当连续变量算,系数含义全错。

变量名含义取值范围原始类型建模前处理
carat克拉重量0.2 - 5.01numeric直接入模
cut切工质量Fair / Good / Very Good / Premium / Idealcharacter转有序因子
color颜色等级J(最差)到 D(最好)character转有序因子,注意方向
clarity净度I1 到 IF(最差到最好)character转有序因子
depth总深度百分比43 - 79numeric直接入模
table台面宽度比43 - 95numeric直接入模
price美元价格326 - 18823numeric目标变量,常取对数
x / y / z长 / 宽 / 深(毫米)x: 0-10.74, y: 0-58.9, z: 0-31.8numeric需先处理 0 值再入模

表里 color 这一行要特别留心:J 是最差、D 是最好的,但字符排序里 D 排在 J 前面。如果直接as.factor(),R 会按字母序把 D 设成基准组,回归系数就变成"相对最好的颜色,其他颜色便宜多少",方向恰好反了。这是这类项目里最常见的解释性翻车点。

2.3 依赖与初始化:四行代码定全局

# 00_setup.R pkgs <- c("ggplot2", "dplyr", "mice", "car", "lmtest") for (p in pkgs) if (!requireNamespace(p, quietly = TRUE)) install.packages(p) library(ggplot2) library(dplyr) # 关键:把无序对比度设为 treatment,保证因子系数是"相对基准组"的解释 options(contrasts = c("contr.treatment", "contr.poly")) set.seed(2024) # 读数据:diamonds 随 ggplot2 一起加载,不需要外部文件 data("diamonds", package = "ggplot2")

options(contrasts = ...)这一行是整份脚本里性价比最高的一行。R 对有序因子(ordered factor)默认用contr.poly多项式对比,输出的是线性、二次、三次趋势项,系数名叫.L、.Q、.C,业务方看了完全不知道在说什么。改成contr.treatment后,系数就是"该水平相对基准组的价格差异",能直接写进报告。

set.seed(2024)在这个项目里主要给后续的抽样检验用——正态性检验对全量 53940 行跑不动,必须抽样,抽样就得固定种子,否则每次 knit 报告结论都在抖。

3. 清洗链路:缺失值、重复行与箱线图异常值的处理顺序

清洗顺序是有讲究的:先看结构,再查缺失,再查重复,最后处理异常值。顺序反了会出问题——比如先去重再查缺失,你会误以为数据本来就没有缺失,其实缺失行已经被去重顺手删掉了。

3.1 结构、缺失值与重复行的三连检查

# 01_clean.R str(diamonds) # 看每个变量的类型和前几个取值 summary(diamonds) # 看数值变量的五数概括和因子水平计数 head(diamonds, 6) # 前六行 tail(diamonds, 6) # 后六行 colSums(is.na(diamonds)) # 每列缺失值个数 sum(duplicated(diamonds)) # 完全重复的行数

str()输出里能看到num和chr两种类型标注,这是判断"要不要转因子"的第一手依据。summary()更有信息量:如果某个数值变量的 Max 明显偏离中位数几个数量级,基本就能锁定异常值区间,diamonds 里 y 的最大值 58.9 毫米对一颗最大 5 克拉的钻石来说显然不合理。

colSums(is.na())返回全 0,说明这个数据集没有缺失值,原文里用mice::md.pattern()画缺失模式图也是同样的结论。没有缺失值就不需要插补,但这一步不能省——真实业务数据里缺失率 30% 以上是常态,养成先查的习惯比记住"这个数据集没缺失"有用得多。

sum(duplicated())返回 0,说明没有完全重复行。注意这里是"完全重复"——同一颗钻石被录入两次、但价格写错了,duplicated()是发现不了的,那是业务主键层面的去重,跟统计意义上的重复行不是一回事。

3.2 箱线图异常值:剔还是不剔,先想清楚

# 画价格箱线图,肉眼确认异常点规模 boxplot(diamonds$price, main = "钻石价格箱线图", ylab = "price (USD)") # 用 IQR 规则定位异常区间 q1 <- quantile(diamonds$price, 0.25) q3 <- quantile(diamonds$price, 0.75) iqr <- q3 - q1 lower <- q1 - 1.5 * iqr upper <- q3 + 1.5 * iqr cat("下界:", lower, " 上界:", upper, "\n") # 剔除并落盘 diamonds_clean <- diamonds %>% filter(price >= lower & price <= upper) saveRDS(diamonds_clean, "data/diamonds_clean.rds")

箱线图跑出来你会看到中位数紧贴下四分位数,上须以外拖出一长串点——这就是原文说的"存在偏离三个标准差之外的点"。IQR 规则(1.5 倍四分位距)和 3σ 规则在这里结论不一样:价格分布严重右偏,3σ 的边界会被少数极高价拉得很宽,剔掉的点反而少;IQR 更严格。我一般先用 IQR 看规模,再决定是用 1.5 倍还是 3 倍。

这里必须提醒一句:在价格分析里"剔异常值"是个需要犹豫的动作。价格 18823 美元那颗钻石不是录入错误,它是一颗真实的大克拉高净度钻石。按 IQR 一刀切下去,高端样本几乎被清空,模型学到的是"价格上限大概 5000 美元",拿去预测大钻会系统性低估。原文的做法是剔了,课程作业里没问题,落到真实业务上我一般改成"winsorize 缩尾"或者"只在诊断阶段标记、不参与训练"。

3.3 变量类型转换与衍生特征构造

diamonds_clean <- diamonds_clean %>% mutate( # 有序因子:levels 从差到好,基准组自动落在第一个 cut = factor(cut, levels = c("Fair","Good","Very Good","Premium","Ideal"), ordered = TRUE), color = factor(color, levels = c("J","I","H","G","F","E","D"), ordered = TRUE), clarity = factor(clarity, levels = c("I1","SI2","SI1","VS2","VS1","VVS2","VVS1","IF"), ordered = TRUE), # 衍生特征:体积近似、克拉单价、深度比值 volume = x * y * z, price_per_carat = price / carat, depth_ratio = 2 * z / (x + y) )

levels的顺序决定了回归输出里谁是基准(Intercept 吸收的那一组)。cut 的基准是 Fair,color 的基准是 J,clarity 的基准是 I1——都是最差档,这样每个系数都是"比最差档贵多少",正负号一看就懂。

三个衍生变量各有用途:volume用 x·y·z 近似体积,比单看 carat 更能反映"看起来大不大";price_per_carat是克拉单价,做分档对比时比总价更公平;depth_ratio用 2z/(x+y) 复算深度百分比,可以跟原始 depth 列做一致性校验,差得多的行往往是尺寸录错了。

注意volume = x * y * z这一行有隐患:diamonds 里存在 x 或 y 或 z 为 0 的记录,乘出来 volume 就是 0,后面取对数直接变 -Inf。处理办法在避坑那章展开。

3.4 价格分布:直方图、核密度与条形图

# 直方图 + 核密度曲线 + 正态参考线 ggplot(diamonds_clean, aes(x = price)) + geom_histogram(aes(y = after_stat(density)), bins = 50, fill = "steelblue", color = "white", alpha = 0.7) + geom_density(color = "darkred", linewidth = 1) + stat_function(fun = dnorm, args = list(mean = mean(diamonds_clean$price), sd = sd(diamonds_clean$price)), linetype = "dashed") + labs(title = "钻石价格分布", x = "价格(美元)", y = "密度") + theme_minimal()

after_stat(density)是把直方图的纵轴从计数换成密度,否则直方图和核密度曲线的量纲对不上,画在一起没法看。bins = 50是按 53940 条样本量估的,样本越多分箱可以越细。

跑出来三个特征很明显:整体右偏、1000 美元附近有个尖峰、2000 / 4000 / 8000 美元附近出现断层。右偏符合奢侈品定价规律——低价钻石数量多,高价钻石稀缺。1000 美元那个峰是市场心理价位聚集,price_per_carat在这个区间成交最密集。

stat_function(fun = dnorm, ...)叠的正态参考线在 8000 美元之前跟核密度曲线几乎不重合,之后两条线才贴近——这说明价格不能用正态分布假设去建模,后面做显著性检验时选非参数方法,根子就在这张图上。

3.5 分类型变量与价格的关系图

# 小提琴图:切工 vs 价格(价格取 log 便于观察低值区) ggplot(diamonds_clean, aes(x = cut, y = price, fill = cut)) + geom_violin(trim = FALSE, alpha = 0.6) + scale_y_log10() + labs(title = "不同切工等级的价格分布", x = "切工", y = "价格(log10)") + theme_minimal() + theme(legend.position = "none") # 条形图:各切工等级的数量 ggplot(diamonds_clean, aes(x = cut, fill = cut)) + geom_bar() + geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5) + theme_minimal() + theme(legend.position = "none")

scale_y_log10()是关键。价格跨度从 326 到 18823,线性轴上低价区全被压成一条线,小提琴的形体根本看不出来,取对数后分布形状才展开。原文说"此处价格取开值(log)便于观察",就是这个意思。

小提琴图给出的结论跟条形图要合起来读:Ideal 切工数量 21488 颗、Premium 13748 颗、Very Good 12069 颗、Good 4891 颗、Fair 1598 颗,Ideal + Premium 占比过半。数量最多的 Ideal 组价格分布最宽、下沿最低,说明"好切工"在这个数据集里不是稀缺品,而是标配——正因为供给充足,它反而不构成价格溢价的主要来源。这个结论要等多元回归把 carat 控制住之后才能确认。

color 和 clarity 的箱线图更规整:颜色从 J 到 D、净度从 I1 到 IF,价格中位数基本单调上升,但 clarity 的 I1 组中位数反而比 SI2 高——样本量小加上个别大钻拉高了中位数,这种小样本组的图不要过度解读。

4. 建模主线:从一元回归到带交互项的多元回归与回归诊断

清洗完的数据落在data/diamonds_clean.rds,建模脚本只读这个文件。整条建模主线是递进的:先钉死单个最强特征,再堆全变量看 R² 天花板,再加交互项抠剩余方差,最后用诊断图判断模型假设成不成立。

4.1 一元线性回归:先把克拉和价格的关系钉死

# 03_model.R diamonds_clean <- readRDS("data/diamonds_clean.rds") fit1 <- lm(price ~ carat, data = diamonds_clean) summary(fit1) confint(fit1, level = 0.95) # 系数的 95% 置信区间

summary()输出里 carat 的系数约为 7760,p 值远小于 0.001,含义是:克拉重量每增加 1 克拉,钻石价格平均上涨约 7760 美元(在 95% 置信区间内)。这是全项目里解释力最强的一个单变量——Multiple R-squared大概在 0.85 上下,一颗克拉数就吃掉了大部分方差。

但一元回归的残差图会暴露问题:残差随 fitted value 增大呈喇叭口张开,典型的异方差。原因很直白——1 克拉的钻石价格波动几千美元,3 克拉的钻石波动几万美元,误差尺度本身就跟价格量级绑在一起。这也是后面要给 price 取对数的动机。

confint()这行别省。只报点估计在业务沟通里是耍流氓,区间宽窄直接反映这个系数可不可信,宽度超过系数本身一半的时候,我会回去检查样本量或者共线性。

4.2 哑变量与多元回归:R²=0.88 之后怎么办

fit2 <- lm(price ~ carat + cut + color + clarity + depth + table + x + y + z, data = diamonds_clean) summary(fit2)

因子变量进lm()时 R 自动展开成哑变量,7 个水平的 clarity 展开成 7 列(基准组 I1 被 Intercept 吸收)。这一步不需要手动model.matrix(),手动展开反而容易漏掉基准组导致完全共线。

fit2的R-squared大约 0.8798。从 0.85 到 0.88,加了 8 个变量只换来 3 个百分点,说明两个事:一是 carat 已经吃掉了主要信号,二是剩余方差里可能藏着非线性结构,线性加变量加不出来。

这时候最容易被误用的判断是"R² 0.88 已经很高了,模型可用"。R² 高只说明拟合样本好,不说明残差满足同方差、独立性假设。0.88 的模型如果残差图是喇叭形,预测区间仍然不可信——这是后面 4.5 节要处理的事。

另外注意 x、y、z 和 carat、volume 之间的共线性。carat 和 volume 的相关系数通常在 0.95 以上,同时入模会让两个系数的标准误同时膨胀,VIF 会直接报警。我在fit2里保留原始 x/y/z 而不放 volume,就是为了避开这个。

4.3 交互项与逐步回归:AIC 怎么用

# 构造带一阶交互项的候选模型 fit3 <- lm(price ~ (carat + cut + color + clarity)^2 + depth + table + x + y + z, data = diamonds_clean) # 向后逐步回归,按 AIC 删变量 fit3_step <- step(fit3, direction = "backward", trace = 1) summary(fit3_step) AIC(fit2, fit3_step) # 两个模型横向比较

(carat + cut + color + clarity)^2是 R 的公式语法糖,展开后等于主效应加上两两交互项,不包含平方项。这里只让四个核心变量交互,depth、table、x/y/z 保持主效应——全量交互会产生上百个项,跑得慢还容易过拟合。

direction = "backward"是从全模型开始逐个删,trace = 1会把每一步删了哪个变量、AIC 降了多少打印出来。原文说"所有变量都被选入了模型",说明在这个数据规模下交互项确实带来了拟合提升,AIC 没有给出删除信号。

AIC(fit2, fit3_step)这行是横向对比的唯一正确姿势。AIC 只能比同一份数据、同一个因变量上的模型,fit2和fit3_step满足条件,可以比;但如果把lm(price ~ ...)和lm(log(price) ~ ...)放一起比 AIC,那是无效比较——因变量变了,似然不可比。

4.4 显著性检验:非参数检验在这类数据上更稳

# 不同切工类型之间价格是否有显著差异 kruskal.test(price ~ cut, data = diamonds_clean) # 不同颜色、不同净度同理 kruskal.test(price ~ color, data = diamonds_clean) kruskal.test(price ~ clarity, data = diamonds_clean) # 正态性检验前先抽样,全量会直接报错 set.seed(2024) sample_price <- sample(diamonds_clean$price, 5000) shapiro.test(sample_price)

选kruskal.test()而不是aov(),理由是 3.4 节那张直方图已经证明了价格分布严重右偏,方差分析的 normality + 方差齐性两个前提都不满足。Kruskal-Wallis 只要求各组独立,对分布形态没要求,在这类偏态数据上是更稳的选择。

三个检验的 p 值都远小于 0.01,结论一致:不同切工、不同颜色、不同净度组之间的价格差异是统计显著的。但显著不等于重要——样本量五万多的时候,任何微小差异都会被检验成显著,效应量(比如各组中位数差)才是判断业务意义的东西,这一点在写报告时要说清楚。

shapiro.test()那一行的 5000 不是随便写的,这个函数的硬限制是样本量不能超过 5000,超了直接抛错。原文里对全量分组做正态性检验,如果没抽样是跑不通的。

4.5 回归诊断:残差图、QQ 图与 VIF

# 04_diagnose.R par(mfrow = c(2, 2)) plot(fit3_step) # 多重共线性 car::vif(fit3_step) # 残差自相关(按行号顺序的 Durbin-Watson) lmtest::dwtest(fit3_step)

四张诊断图各有分工:Residuals vs Fitted 看异方差——点如果呈喇叭形张开,说明方差随拟合值变化;Normal Q-Q 看残差正态性——点偏离对角线两端翘起,说明尾部厚;Scale-Location 是标准化残差的开方,比第一张更容易看出趋势;Residuals vs Leverage 找强影响点——Cook's distance 超过 1 的行要单独看。

vif()输出里,如果 x、y、z 三个的 VIF 都在 5 以上,说明尺寸变量之间高度共线,处理办法是只留一个(通常留 volume 或者只留 carat)。dwtest()的 p 值在这个数据集上通常不显著,因为 diamonds 的行序跟价格没有系统性关联,不构成时间自相关——但这个检验在真实业务数据里必做,一旦数据是按时间导出的,残差自相关会让标准误被严重低估。

诊断结论直接决定下一步:异方差显著就给 price 加 log 变换重跑fit2,共线性显著就砍变量,强影响点过多就回去看 3.2 的异常值处理是不是剔得太狠。

5. 避坑排查:这份钻石回归脚本最容易翻车的 5 个地方

下面这 5 条都是我在跑这份分析时真真切切卡住过的,按"现象 → 原因 → 解决"记下来,照着对能省不少时间。

5.1 现象:shapiro.test 一跑就报错 "sample size must be between 3 and 5000"

原因:shapiro.test()内部实现基于 Shapiro-Wilk 统计量,样本量硬上限 5000,diamonds 全量 53940 行,必然超限。

解决:按组抽样,每组控制在 5000 以内再检验,同时固定set.seed()保证可复现:

set.seed(2024) diamonds_clean %>% group_by(cut) %>% slice_sample(n = 5000, replace = FALSE) %>% group_modify(~ broom::tidy(shapiro.test(.x$price)))

如果抽样后仍然拒绝正态,不要硬转参数检验,直接用 4.4 节的kruskal.test()走非参数路线。

5.2 现象:cut 的回归系数输出成.L、.Q、.C,方向完全看不懂

原因:cut被定义成ordered = TRUE的有序因子,R 对有序因子默认使用contr.poly多项式对比,系数表示的是线性趋势、二次趋势、三次趋势,不是"相对某个基准组的差异"。

解决:两种改法,按需选一种。要趋势解释就保留多项式对比,但必须在报告里说明.L代表"切工等级每升一档的线性效应";要基准组差异解释就在00_setup.R里加options(contrasts = c("contr.treatment", "contr.poly")),或者对单个变量用C(cut, contr.treatment)显式指定。改完之后记得重跑一遍summary(),基准组变了,所有系数的含义都会跟着变。

5.3 现象:异常值剔完之后,模型对高价钻石的预测系统性偏低

原因:3.2 节用 IQR 规则按 price 剔异常,价格分布右偏严重,上界通常落在 5000 美元上下,19000 美元的大钻全被当成离群点删了。模型训练集里最大价格只剩 5000,自然学不会预测 15000。

解决:分两种情况处理。如果分析目标是"解释价格影响因素",保留全部样本,异常值只在诊断阶段用plot(fit3_step)的第 4 张图标记出来,不参与删除;如果确实是录入错误(比如 y = 58.9 毫米那种物理上不可能的尺寸),按行删,删完要记录删了多少行、删的比例是多少。

# 只按物理尺寸合理性剔,不按价格剔 diamonds_clean <- diamonds %>% filter(y > 0, y < 20, x > 0, z > 0)

这样剔掉的是明显录错的尺寸,高价大钻保留在训练集里,模型才有机会学到价格上限。

5.4 现象:volume 算出 0 和 NaN,取 log 之后整列变成 -Inf

原因:diamonds 里有 x 或 y 或 z 为 0 的记录,三个尺寸相乘得 0,log(0)是 -Inf;如果后面还有除法(比如price / volume),会出现0/0 = NaN。这些值一旦进模型,lm()会整行丢弃并抛 warning,但很多人不看 warning,最后发现样本量对不上。

解决:构造衍生特征前先处理 0 值,要么过滤(如上一条),要么加极小值兜底:

diamonds_clean <- diamonds_clean %>% mutate( volume = pmax(x * y * z, 1e-6), # 兜底,避免 log(0) price_per_carat = price / pmax(carat, 1e-6) )

pmax()比ifelse()快,而且不用写两遍条件。加完兜底之后,再跑一遍summary(diamonds_clean$volume)确认最小值不再是 0。

5.5 现象:step() 跑了很久没出结果,或者结果每次不一样

原因:候选模型变量太多(全量交互项轻松上百个),逐步回归每一步都要重算 AIC,耗时随变量数是乘法级增长;另一个原因是没设set.seed(),如果候选模型里存在 AIC 相同的平局,step()的选择顺序不保证稳定。

解决:先降维再逐步。把明显共线的变量(x/y/z 只留一个)和业务上无关的变量(比如 depth 和 table 对价格解释力本来就弱)先手工剔除,候选模型控制在 30 个变量以内再跑step()。同时固定种子并把trace = 1打开,每一步删了什么、AIC 变化多少都能看到,出问题能定位到具体哪一步。

6. 二次开发:把一次性分析脚本改成可复用的建模流程

这份脚本跑通一次之后,最有价值的动作不是交报告,是把它改成能换数据复用的流程。我一般从三个方向改。

第一,把建模逻辑封成函数,入参是数据框和公式,出参是拟合对象加指标表。这样换一份带同样字段的钻石数据,改一行调用就行,不用复制粘贴整段代码:

build_price_model <- function(df, formula = price ~ carat + cut + color + clarity) { fit <- lm(formula, data = df) tibble( r2 = summary(fit)$r.squared, adj_r2 = summary(fit)$adj.r.squared, aic = AIC(fit), n = nobs(fit) ) } # 对比三种变量组合,不用写三遍 lm map_dfr( list(price ~ carat, price ~ carat + cut + color + clarity, price ~ (carat + cut + color + clarity)^2), ~ build_price_model(diamonds_clean, .x), .id = "model" )

第二,补一层留出验证。现在所有 R² 都是样本内拟合值,加个 5 折交叉验证看样本外误差,才能判断交互项是不是过拟合了:

改造点当前状态建议改法验证方式
模型评估只看样本内 R²加 5 折 CVcaret::train()看 RMSE 波动
因变量原始 pricelog(price)对比残差图喇叭口是否收敛
共线性靠 VIF 事后发现建模型前先算相关矩阵相关系数 > 0.9 的变量二选一
分类型变量手动设 levels封装成统一转换函数新增变量时不用重写

第三,把report.Rmd里的数字全部改成引用变量,不要手写。报告里写"R² = 0.8798"这种硬编码,下次数据一更新就全错。用 inline R 代码`r round(summary(fit2)$r.squared, 4)`让数字跟着模型走,knit 一次自动刷新。

改完这三处,这个项目就从"一份课程作业"变成了"一个能接新数据的分析模板"。我踩过最贵的一次坑是在一个类似的价格预测项目里,样本内 R² 做到 0.92 就交付了,上线后样本外误差翻了三倍——因为交互项把训练集的噪声也拟合进去了。从那以后我每次交付前都强制走一遍交叉验证,样本内指标再漂亮也要看一眼 CV 的 RMSE 标准差。希望这份拆解能帮到你,少走一遍我走过的弯路。

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

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

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

立即咨询