数据挖掘与R语言
2026年11月11日
Logs, Categories and a Test Set — 项目五收口课
重要
第19~20讲我们介绍了线性回归模型,如果不是线性关系:①尺度(取对数,转换成线性关系);
前面都是数值型变量,如果是分类变量:②类别("有促销"和无促销);
如何将数据拆分成训练集和验证集:③验收(别用同一批数据既建模又打分)。
本讲内容不考试,目标是"会写、会读、会比",不推公式。
多模型对比:模型一多就要排成一张表对账——本讲用 modelsummary() 把多个模型并排,系数、标准误、R² 一次看全。
lm(y ~ log(x))、lm(log(y) ~ x)、lm(log(y) ~ log(x))
exp() 还原factor(levels = , labels = ) 把类别列转成因子放进 lm(),解释每个虚拟变量系数(照样带"在……不变时")modelsummary() 排一张表,并说出哪几列的 R² 可以比大小
initial_split() 切训练/测试集,在测试集上做样本外预测注记
本讲数据:四份 set.seed() 模拟数据(广告、工资、价格、促销)+ 内置 mtcars,造数代码在各节第一页当场可见。dplyr、ggplot2 已随 library(tidyverse) 一起来,不必单装;机房断网时 modelsummary 的表会退化成裸表格,不影响读系数。
先把第20讲的三个数对完账,再开始今天的内容
Call:
lm(formula = mpg ~ wt + hp, data = mtcars)
Residuals:
Min 1Q Median 3Q Max
-3.941 -1.600 -0.182 1.050 5.854
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 37.22727 1.59879 23.28 < 2e-16 ***
wt -3.87783 0.63273 -6.13 1.1e-06 ***
hp -0.03177 0.00903 -3.52 0.0015 **
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 2.59 on 29 degrees of freedom
Multiple R-squared: 0.827, Adjusted R-squared: 0.815
F-statistic: 69.2 on 2 and 29 DF, p-value: 9.11e-12
1. 第20讲 m1 <- lm(mpg ~ wt + hp) 里 wt 的系数是 -3.88。下面哪种读法完整且不错?
2. 第20讲那张对账表里,mpg ~ wt 的 R² = 0.753,加上 hp 之后 R² = 0.827。关于 R² 说法正确的是:
hp 有用3. 由 summary() 的结果写回归方程时,三个数字都抄 ______ 列;判断某个变量有没有用,看 Pr(>|t|) 列,通常以 ______ 为门槛。
4. 根据模型对新数据做预测,要用:______(m1, newdata = data.frame(wt = 3, hp = 150))。
散点不是一条直线时,lm() 依然只会给你一条直线
set.seed(42) # 本页只看形状,种子固定即可
n <- 200 # 每张图 200 个点
d1 <- data.frame(x = runif(n, 1, 50)) %>% # ① 线性
mutate(y = 5 + 2 * x + rnorm(n, sd = 8))
p1 <- ggplot(d1, aes(x, y)) +
geom_point(size = 1.5, alpha = 0.5, color = c_navy) + # 散点
geom_smooth(method = "lm", se = FALSE, color = c_green, # 直线拟合
linewidth = 1.2) +
labs(title = "✓ 线性关系", subtitle = "直接用线性回归", x = "x", y = "y")
d2 <- data.frame(x = runif(n, 1, 100)) %>% # ② 对数增长(收益递减)
mutate(y = 10 + 30 * log(x) + rnorm(n, sd = 8))
p2 <- ggplot(d2, aes(x, y)) +
geom_point(size = 1.5, alpha = 0.5, color = c_navy) +
geom_smooth(method = "lm", se = FALSE, color = c_orange, # 硬拟合的直线
linetype = "dashed", linewidth = 1.2) +
geom_smooth(method = "loess", se = FALSE, color = c_blue, # 真实形态
linewidth = 1.2) +
labs(title = "对数增长(收益递减)", subtitle = "橙线很差,蓝线才对",
x = "x", y = "y")
d3 <- data.frame(x = runif(n, 0, 4)) %>% # ③ 指数增长
mutate(y = 2 * exp(x) + rnorm(n, sd = 3))
p3 <- ggplot(d3, aes(x, y)) +
geom_point(size = 1.5, alpha = 0.5, color = c_navy) +
geom_smooth(method = "lm", se = FALSE, color = c_orange,
linetype = "dashed", linewidth = 1.2) +
geom_smooth(method = "loess", se = FALSE, color = c_blue,
linewidth = 1.2) +
labs(title = "指数增长", subtitle = "直线严重低估右侧", x = "x", y = "y")
p1 + p2 + p3 # patchwork:第18讲的并排写法判断依据不是"老师让取",而是变量本身的形态:
警告
取对数有个硬前提:变量必须全为正数。log(0) 是 -Inf、log(-2) 是 NaN,整行数据当场作废。业务表里 0 太常见(当月利润为 0、新增客户为 0),所以取对数前先查最小值:真有 0 就写 log1p(x)(即 \(\ln(x+1)\)),别用均值把 0 填掉,那是伪造数据。
| 名字 | 方程 |
lm() 写法 |
\(\hat{\beta}_1\) 怎么读 | 本讲例子 |
|---|---|---|---|---|
| level-log | \(y = \beta_0 + \beta_1 \ln x\) | lm(y ~ log(x)) |
\(x\) 增 1% → \(y\) 变 \(\hat{\beta}_1/100\) 个单位 | 广告费 → 销量 |
| log-level | \(\ln y = \beta_0 + \beta_1 x\) | lm(log(y) ~ x) |
\(x\) 增 1 单位 → \(y\) 变约 \(\hat{\beta}_1 \times 100\%\) | 经验 → 工资 |
| log-log | \(\ln y = \beta_0 + \beta_1 \ln x\) | lm(log(y) ~ log(x)) |
\(x\) 增 1% → \(y\) 变 \(\hat{\beta}_1\%\)(弹性) | 价格 → 需求量 |
广告费 → 销量:\(x\) 每增加 1%,\(y\) 增加几个单位?
set.seed(7) # 写死种子:全班数字一样
n <- 80 # 80 个月的投放记录
ad_log <- data.frame(广告费 = runif(n, 1, 200)) %>% # 万元,均匀分布
mutate(销量 = 10 + 25 * log(广告费) + rnorm(n, sd = 8)) # 万件 + 噪声
p_raw <- ggplot(ad_log, aes(x = 广告费, y = 销量)) + # 左图:原始尺度
geom_point(size = 2, alpha = 0.7, color = c_navy) +
geom_smooth(method = "lm", se = FALSE, color = c_orange, # 硬拟合的直线
linetype = "dashed", linewidth = 1) +
geom_smooth(method = "loess", se = FALSE, color = c_blue, # 数据真实形态
linewidth = 1) +
labs(title = "原始散点:明显是弯的", subtitle = "橙线差,蓝线才对",
x = "广告费(万元)", y = "销量(万件)")
p_log <- ggplot(ad_log, aes(x = log(广告费), y = 销量)) + # 右图:x 取对数
geom_point(size = 2, alpha = 0.7, color = c_navy) +
geom_smooth(method = "lm", se = FALSE, color = c_green,
linewidth = 1.2) +
labs(title = "对 x 取对数后:直了", subtitle = "ln(广告费) 与销量线性",
x = "ln(广告费)", y = "销量(万件)")
p_raw + p_log # patchwork:同一数据两种尺度提示
level-log 口诀:\(x\) 每增加 1%,\(y\) 增加约 \(\hat{\beta}_1 / 100\) 个单位。
本例:广告费每增加 1%,销量平均增加 \(\hat{\beta}_1/100 =\) 0.256 万件;广告费翻一番,销量增加 \(\hat{\beta}_1 \times \ln 2 \approx\) 17.75 万件。R 的 log() 默认是自然对数 ln,不是 log10。
警告
最常见的错读:"广告费每增加 1,销量增加 25.61"。\(x\) 一旦取了对数,"每增 1 单位"就不存在了,只能问"增 1%"。
| 直线 | level-log | |
|---|---|---|
| + p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001 | ||
| (Intercept) | 87.136*** | 8.087+ |
| (2.558) | (4.585) | |
| 广告费 | 0.329*** | |
| (0.022) | ||
| log(广告费) | 25.605*** | |
| (1.020) | ||
| Num.Obs. | 80 | 80 |
| R2 | 0.748 | 0.890 |
| R2 Adj. | 0.744 | 0.888 |
| AIC | 611.5 | 545.2 |
| BIC | 618.7 | 552.4 |
| Log.Lik. | -302.774 | -269.607 |
| F | 230.947 | 629.924 |
| RMSE | 10.65 | 7.04 |
这张表怎么读(\(y\) 两边都是"销量"、没变,所以可以正相比大小):R² 0.748 → 0.890,残差标准差 10.72 → 7.08 万件(生成时的噪声是 8)。再拿二次项验弯曲:lm(销量 ~ 广告费 + I(广告费^2)) 的二次项 p = 3.7e-10,确实弯——取对数不是多此一举。
经验 → 工资:\(x\) 增加 1,\(y\) 变化百分之几?
set.seed(21) # 沿用上学期第12讲的种子
n <- 100 # 100 位员工的横截面
exp_data <- data.frame(经验年数 = runif(n, 0, 30)) %>% # 0—30 年
mutate(工资 = exp(7.5 + 0.06 * 经验年数 + rnorm(n, sd = 0.25)))
q1 <- ggplot(exp_data, aes(x = 经验年数, y = 工资)) + # 左:原始尺度
geom_point(size = 2, alpha = 0.7, color = c_navy) +
geom_smooth(method = "lm", se = FALSE, color = c_orange,
linetype = "dashed", linewidth = 1) +
geom_smooth(method = "loess", se = FALSE, color = c_blue, linewidth=1) +
labs(title = "原始:工资 ~ 经验(上凸)", subtitle = "直线两头都拟合不好",
x = "工作经验(年)", y = "工资(元/月)")
q2 <- ggplot(exp_data, aes(x = 经验年数, y = log(工资))) + # 右:y 取对数
geom_point(size = 2, alpha = 0.7, color = c_navy) +
geom_smooth(method = "lm", se = FALSE, color = c_green, linewidth=1.2) +
labs(title = "对 y 取对数后:直了", subtitle = "ln(工资) ~ 经验 线性",
x = "工作经验(年)", y = "ln(工资)")
q1 + q2 # 同一份数据,只换纵轴尺度提示
log-level 口诀:\(x\) 每增加 1 个单位,\(y\) 变化约 \(\hat{\beta}_1 \times 100\%\)。
本例:经验每多 1 年,工资平均高约 6.04%(精确式 \((e^{\hat{\beta}_1}-1)\times100\%\) = 6.2%,\(\hat{\beta}_1\) 小的时候两者几乎一样,大于 0.2 时必须用精确式)。
这就是经济学里最有名的 Mincer 工资方程。生成数据用的真值是 0.06,估出 0.0604——模拟数据的好处:能拿估计值和真值对表。
警告
两个高频错误:① 说成"经验每增 1 年,工资增加 0.0604 元"——左边已经是 \(\ln(\text{工资})\),不再是元;② 把 0.0604 乘 100 说成"涨 6 个百分点",单位应是 %(工资 5000 涨 6% 是涨 300 元)。
| 直线 | log-level | |
|---|---|---|
| + p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001 | ||
| (Intercept) | 569.354 | 7.478*** |
| (364.806) | (0.052) | |
| 经验年数 | 309.618*** | 0.060*** |
| (19.167) | (0.003) | |
| Num.Obs. | 100 | 100 |
| R2 | 0.727 | 0.833 |
| R2 Adj. | 0.724 | 0.832 |
| AIC | 1779.0 | 1705.5 |
| BIC | 1786.8 | 1713.3 |
| Log.Lik. | -886.511 | -0.733 |
| F | 260.944 | 490.352 |
| RMSE | 1713.29 | 0.24 |
表里 R² = 0.727 和 0.833 不能比大小——一个是"解释了工资的几分之几",另一个是"解释了 ln(工资) 的几分之几",分母不是同一个东西。Std. Error 同理(一边是元、一边是对数单位)。
那凭什么说变换有用? 三条不依赖 R² 的证据:① 4.1 右图那条线直了;② 残差图从"两头正、中间负"的弧形变成随机撒在 0 线上下;③ 直线版残差标准差 1721.9 元(工资高的人更散,喇叭口),log-level 版 0.245(对数尺度上稳定)。板书记一句:比模型,先看 \(y\)。
exp() 还原curve_data <- data.frame(经验年数 = seq(0, 30, length.out = 200)) %>%
mutate(预测工资 = exp(predict(ex1, newdata = pick(经验年数)))) # 逐点还原
ggplot(exp_data, aes(x = 经验年数, y = 工资)) + # 散点:原始尺度
geom_point(size = 2, alpha = 0.6, color = c_navy) +
geom_line(data = curve_data, aes(x = 经验年数, y = 预测工资),
color = c_green, linewidth = 1.3) + # 还原后的拟合曲线
labs(title = "log-level 模型放回原始尺度:一条指数曲线",
subtitle = "绿线 = exp(截距 + 斜率 × 经验),正好顺着点走",
x = "工作经验(年)", y = "工资(元/月)")价格 → 需求量:斜率直接就是弹性
set.seed(99) # 沿用上学期第12讲的种子
n <- 80 # 80 个价格—销量观测
price_data <- data.frame(价格 = runif(n, 5, 100)) %>% # 元
mutate(需求量 = exp(8 - 1.3 * log(价格) + rnorm(n, sd = 0.2)))
w1 <- ggplot(price_data, aes(x = 价格, y = 需求量)) + # 左:原始尺度
geom_point(size = 2, alpha = 0.7, color = c_navy) +
geom_smooth(method = "lm", se = FALSE, color = c_orange,
linetype = "dashed", linewidth = 1) +
labs(title = "原始:双曲线下坠", subtitle = "直线在低价区严重偏高",
x = "价格(元)", y = "需求量(件)")
w2 <- ggplot(price_data, aes(x = log(价格), y = log(需求量))) + # 右:双对数
geom_point(size = 2, alpha = 0.7, color = c_navy) +
geom_smooth(method = "lm", se = FALSE, color = c_green, linewidth=1.2) +
labs(title = "两边都取对数:一条直线", subtitle = "斜率 = 弹性",
x = "ln(价格)", y = "ln(需求量)")
w1 + w2 # 左:幂函数;右:线性化提示
log-log 口诀(最省事):\(x\) 每增加 1%,\(y\) 变化 \(\hat{\beta}_1\%\)。
本例斜率 -1.29(真值 −1.3)→ 价格每涨 1%,需求量平均降约 72.4%。\(|\hat{\beta}_1| > 1\) 富有弹性(涨价很伤销量),\(|\hat{\beta}_1| < 1\) 缺乏弹性(如必需品)。
一句话就能给经营结论:价格翻倍,需求量变成 \(2^{\hat{\beta}_1} - 1 =\) -59.0%——这就是定价会上要的那个数字。
| ① 原始 | ② level-log | ③ log-level | ④ log-log | |
|---|---|---|---|---|
| + p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001 | ||||
| (Intercept) | 119.099*** | 310.736*** | 4.788*** | 7.910*** |
| (11.447) | (18.678) | (0.094) | (0.111) | |
| 价格 | -1.500*** | -0.032*** | ||
| (0.190) | (0.002) | |||
| log(价格) | -71.928*** | -1.286*** | ||
| (4.842) | (0.029) | |||
| Num.Obs. | 80 | 80 | 80 | 80 |
| R2 | 0.445 | 0.739 | 0.848 | 0.962 |
| R2 Adj. | 0.438 | 0.736 | 0.846 | 0.962 |
| AIC | 843.5 | 783.1 | 561.0 | 450.0 |
| BIC | 850.6 | 790.3 | 568.2 | 457.1 |
| Log.Lik. | -418.745 | -388.574 | -34.366 | 21.164 |
| F | 62.500 | 220.713 | 435.819 | 1981.308 |
| RMSE | 45.39 | 31.13 | 0.37 | 0.19 |
这张表要分两组看(规矩:\(y\) 相同才能比):
三种形态只有一个区别——log() 包在谁身上(回到 2.2 那张表):
| 形态 | 长什么样 | 一句话记忆 | 本讲例子 |
|---|---|---|---|
| level-log | 慢慢涨,越涨越平 | 收益递减 | 广告费 → 销量 |
| log-level | 越涨越快 | 利滚利(复利) | 经验 → 工资 |
| log-log | 往下掉的双曲线 | 弹性,涨价伤量 | 价格 → 需求量 |
把"有促销 / 无促销"这样的文字,变成能乘系数的数
回归方程只会做乘法和加法,"有促销" 这种文字没法乘系数。办法是配一个 0/1 开关(虚拟变量,dummy variable):
\[D_{\text{有促销}} = \begin{cases} 1 & \text{有促销} \\ 0 & \text{无促销(参照组)} \end{cases} \qquad \widehat{\text{销量}} = \beta_0 + \beta_1 \times \text{广告费} + \beta_2 D_{\text{有促销}}\]
提示
不用手工造开关。 只要那一列是因子(factor),R 会自动生成开关;levels 里的第一个类别自动成为参照组(基准组),它不出现在系数表里——被藏进截距。
set.seed(5) # 沿用上学期第12讲的种子
n <- 100 # 100 个月
promo_data <- data.frame(
广告费 = runif(n, 10, 80), # 万元
促销 = sample(c("无促销", "有促销"), n, replace = TRUE) # 文字类别
) %>%
mutate(促销虚拟 = if_else(促销 == "有促销", 1, 0), # 手工造开关(只为演示)
销量 = 5 + 1.2 * 广告费 + 15 * 促销虚拟 + # 真值:促销 +15 万件
rnorm(n, sd = 6))
ggplot(promo_data, aes(x = 广告费, y = 销量, color = 促销)) +
geom_point(size = 2, alpha = 0.7) + # 两组点,同一横轴
geom_smooth(method = "lm", se = FALSE, linewidth = 1.2) + # 各自一条线
scale_color_manual(values = c("无促销" = c_blue, "有促销" = c_orange)) +
labs(title = "两条几乎平行的回归线",
subtitle = "斜率差不多,截距差 ≈ 15:那就是虚拟变量系数",
x = "广告费(万元)", y = "销量(万件)") +
theme(legend.position = "top")提示
促销有促销 = 13.89 的读法(三个要素一个都不能少):
在广告费相同的前提下,有促销的月份比无促销的月份销量平均高 13.89 万件(p = 1.4e-18)。
这是促销的净效应——已经把"促销月往往多花钱投放"这层影响剥掉了。行名是列名 + 类别名拼出来的,第一次见会懵,指一遍给学生;截距 5.74 是无促销(参照组)那条线的起点。真值 15,估出 13.89。
| 只有广告费 | 广告费 + 促销 | |
|---|---|---|
| + p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001 | ||
| (Intercept) | 11.654*** | 5.742*** |
| (2.263) | (1.617) | |
| 广告费 | 1.201*** | 1.197*** |
| (0.044) | (0.030) | |
| 促销有促销 | 13.895*** | |
| (1.273) | ||
| Num.Obs. | 100 | 100 |
| R2 | 0.882 | 0.947 |
| R2 Adj. | 0.880 | 0.946 |
| AIC | 735.5 | 657.4 |
| BIC | 743.3 | 667.8 |
| Log.Lik. | -364.764 | -324.688 |
| F | 729.127 | 863.911 |
| RMSE | 9.29 | 6.22 |
\(y\) 两边都是销量 → 可以比:R² 0.882 → 0.947。注意 广告费 那一行几乎没动(1.201 → 1.197),因为本数据里促销与广告费独立(相关 0.013)。6.9 会遇到"一动就是几倍"的情形,那才是控制变量的重点。
例:营销渠道有三类(线下门店 / 社交媒体 / 搜索引擎),只需要两个开关:
| 渠道 | \(D_{\text{社交媒体}}\) | \(D_{\text{搜索引擎}}\) | 预测值 |
|---|---|---|---|
| 线下门店(参照组) | 0 | 0 | \(\hat{\beta}_0\) |
| 社交媒体 | 1 | 0 | \(\hat{\beta}_0 + \hat{\beta}_1\) |
| 搜索引擎 | 0 | 1 | \(\hat{\beta}_0 + \hat{\beta}_2\) |
Call:
lm(formula = mpg ~ cyl_f, data = mc)
Residuals:
Min 1Q Median 3Q Max
-5.264 -1.836 0.029 1.389 7.236
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 26.664 0.972 27.44 < 2e-16 ***
cyl_f六缸 -6.921 1.558 -4.44 0.00012 ***
cyl_f八缸 -11.564 1.299 -8.90 8.6e-10 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 3.22 on 29 degrees of freedom
Multiple R-squared: 0.732, Adjusted R-squared: 0.714
F-statistic: 39.7 on 2 and 29 DF, p-value: 4.98e-09
| 系数 | 数值 | 怎么读(必须带参照组) |
|---|---|---|
| 截距 | 26.66 | 四缸车的平均里程 |
cyl_f六缸 |
-6.92,p = 0.00012 | 六缸比四缸平均少跑这些英里/加仑 |
cyl_f八缸 |
-11.56,p = 8.6e-10 | 八缸比四缸平均少跑这些英里/加仑 |
警告
标签要写成"四缸/六缸/八缸"这种中文数字:若写 labels = c("4缸","6缸","8缸"),R 会把以数字开头的名字改成 cyl_fX4缸,系数表看起来像乱码。
cyl_sum <- mc %>% # 每组的均值与 95% 置信区间
group_by(cyl_f) %>%
summarise(均值 = mean(mpg), se = sd(mpg)/sqrt(n()), .groups = "drop") %>%
mutate(下限 = 均值 - 1.96 * se, 上限 = 均值 + 1.96 * se)
g1 <- ggplot(mc, aes(x = cyl_f, y = mpg, fill = cyl_f)) +
geom_boxplot(alpha = 0.6, width = 0.5) + # 左:三组分布
geom_jitter(aes(color = cyl_f), width = 0.1, size = 2, alpha = 0.7) +
scale_fill_manual(values = c("四缸" = c_green, "六缸" = c_blue,
"八缸" = c_orange)) +
scale_color_manual(values = c("四缸" = c_green, "六缸" = c_blue,
"八缸" = c_orange)) +
labs(title = "各气缸数的油耗分布", x = "气缸数", y = "mpg") +
theme(legend.position = "none")
g2 <- ggplot(cyl_sum, aes(x = cyl_f, y = 均值, color = cyl_f)) +
geom_point(size = 4) + # 右:组均值 ± 1.96 se
geom_errorbar(aes(ymin = 下限, ymax = 上限), width=0.15, linewidth=1) +
geom_hline(yintercept = cyl_sum$均值[1], linetype = "dashed",
color = "grey60") + # 虚线 = 参照组基准(截距)
labs(title = "组均值与 95% 置信区间",
subtitle = "系数 = 各组均值与虚线(四缸)的高度差",
x = "气缸数", y = "mpg") +
scale_color_manual(values = c("四缸" = c_green, "六缸" = c_blue,
"八缸" = c_orange)) +
theme(legend.position = "none")
g1 + g2 # patchwork 并排rsample 三行:划分 → 训练 → 测试
library(rsample) # 本讲唯一的新包:现场加载
set.seed(42) # 种子写进代码,别人才能复现
sp <- initial_split(promo_data, prop = 0.8) # 随机 8:2 划分
tr <- training(sp) # 训练集:模型用它拟合
te <- testing(sp) # 测试集:模型从没见过
rs_m <- lm(销量 ~ 广告费 + 促销, data = tr) # 只在训练集上建模
# 均方根误差:预测值与真实值的平均偏差
rmse <- function(m, d) sqrt(mean((d$销量 - predict(m, newdata = d))^2))
# 训练/测试各打一分:样本外才是真成绩
c(训练集 = round(rmse(rs_m, tr), 2), 测试集 = round(rmse(rs_m, te), 2))训练集 测试集
5.94 7.24
pred_te <- predict(rs_m, newdata = te) # 样本外预测
bad_i <- which.max(abs(te$销量 - pred_te)) # 误差最大的那一行
set.seed(7) # 换一组种子重跑:只为验证"结论会漂"
sp7 <- initial_split(promo_data, prop = 0.8)
rs7 <- lm(销量 ~ 广告费 + 促销, data = training(sp7))
rm7 <- c(训练 = round(rmse(rs7, training(sp7)), 2), # 正文引用,不单独印
测试 = round(rmse(rs7, testing(sp7)), 2))警告
一次划分不稳。 换 set.seed() 再跑,两个 RMSE 会一起变,甚至测试集反而更低(同一段代码把种子换成 7:训练 6.56 / 测试 4.71)。所以报结论要带上"换种子会变"这句自觉,实验报告里必须把 set.seed() 写进代码。
三题当堂做,参考答案只在教师版显示
只写形态名(level-log / log-level / log-log / 不变换),一句话说明理由:
动手(3 分钟):把 levels 顺序改成 c("有促销", "无促销") 重新拟合,回答三问:
fitted() 变不变?am:手动挡对油耗的影响有多大内置数据 mtcars 里 am 是变速箱:0 = 自动挡、1 = 手动挡,共 32 辆车(第20讲用它建过 mpg ~ wt)。做三件事:
factor(am, levels = c(0, 1), labels = c("自动挡", "手动挡")) 造一个因子列,拟合 lm(mpg ~ am_f):参照组是谁?系数怎么读?lm(mpg ~ wt + am_f),手动挡那一行的系数和 p 值变成什么?这说明什么?三样工具各收一句,回家写实验报告 9 时照抄这个顺序
| 场景 | R 写法 | 怎么读 | 报告交什么 |
|---|---|---|---|
| ① 左密右疏 | lm(y ~ log(x)) |
x 加 1%,y 加 β₁/100 | 方程 + 单位 |
| ① 利滚利 | lm(log(y) ~ x) |
x 加 1,y 加 β₁×100% |
exp() 还原 |
| ① 要弹性 | lm(log(y) ~ log(x)) |
x 加 1%,y 加 β₁% | 直接叫弹性 |
| ② 促销/气缸 | factor(levels = ) |
与参照组比,k−1 个开关 | 读法带参照组 |
| ③ 怕背答案 | initial_split(prop = 0.8) |
测试集 RMSE 才算成绩 | 报误差 + 换种子 |
提示
三句口诀:① 比模型,先看 \(y\)。② 类别进方程,先说参照组。③ 预测要不要 exp(),只看模型左边是什么。
| 高频错误 | 正确说法 |
|---|---|
| "广告费每增加 1,销量增加 25.61" | level-log 只能问"增 1%",\(y\) 变 \(\hat{\beta}_1/100\) 个单位(本例 0.256 万件) |
predict() 的对数结果直接当工资 |
log-level 要 exp() 还原:8.082 → 3236 元/月 |
| 跨 \(y\) 比 R²(0.727 vs 0.833) | 同 \(y\) 才可比;跨 \(y\) 看散点形态 + 残差图 |
对可能为 0 的变量直接 log()
|
log(0) = -Inf 整行作废;先查最小值或用 log1p()
|
lm(mpg ~ cyl) 把类别当数值 |
先 factor(levels = , labels = ),读系数带参照组 |
| 自己造满 \(k\) 个 0/1 列一起塞 | 只能 \(k-1\) 个,第 \(k\) 列会被算成 NA
|
| "换了参照组,结论反了" | 只改说法:R²、fitted() 不变,系数名称/符号变 |
| 说"促销有效"却不控制广告费 | 类别要与连续变量一起进模型,读成"在……不变时" |
请花 1 分钟,在纸条或在线问卷上简要回答(不计分,用来决定第22讲总复习的侧重点):
levels 里排在第一个的类别,去哪儿了?(一句话)modelsummary 那张四列表里,为什么前两列的 R² 能比大小、后两列不能?注记
顺手自查:今天这几件事你都能做到了吗——判断该不该取对数 ✓ 写出三种 lm() 并各说一句读法 ✓ 给 log(y) 模型做完预测并还原 ✓ 说清参照组与 \(k-1\) ✓ 判断两张表能不能比 R² ✓ 用 initial_split() 算测试集 RMSE
实验报告 9
第22讲:学期总复习与期末备考
lm() 的写法、读法、比法、验法第21讲:虚拟变量、对数变换与随机抽样 —— 项目五收口
让线性回归解决非线性问题;让分类变量进入回归模型。
朱 奇 | 锦城大学 · 财会学院