先描述再推断:建模前要看清数据
很多人一上来就跑模型,结果被一堆系数和警告淹没。正确顺序是:先用描述统计把数据看清楚,再谈建模。R 的好处是,这些探索性操作几乎都是一行命令,很快就能对数据形状建立直觉。
# 用内置的 mtcars 数据:32 款车型的油耗与性能
data(mtcars)
summary(mtcars$mpg) # 油耗的最小/中位/均值/最大
table(mtcars$cyl) # 气缸数有几类、各多少辆
cor(mtcars[, c("mpg", "disp", "hp", "wt")]) # 相关系数矩阵
先问三个问题:目标变量(这里是油耗 mpg)大概什么范围?有没有明显的极端值?哪些自变量看起来和它相关?把这些先看明白,后面建模时你才知道哪个系数是"理所当然"、哪个是"出乎意料需要解释"。
建模不是跑一行 lm 就结束。从描述统计到拟合、再到残差诊断和可视化,是一条必须走完的闭环,缺哪一环,结论都站不稳。
- lm用 y ~ x 公式拟合线性回归,summary 看系数与显著性。
- R²/p值R² 衡量解释力,p 值判断系数是否显著,别只看 R²。
- 残差残差应随机散布、无规律,否则模型假设不成立。
- kmeans无监督聚类,按 k 把样本自动分成若干簇,再画图验证。
线性回归 lm:公式接口怎么写
R 最优雅的设计之一是公式接口:y ~ x1 + x2 直接念出来就是"用 x1 和 x2 解释 y"。把它交给 lm(),就得到一个线性回归模型对象,后续所有诊断都围着这个对象转。
data(mtcars)
# 用车重 wt 和马力 hp 预测油耗 mpg
fit <- lm(mpg ~ wt + hp, data = mtcars)
# 看拟合出的系数
coef(fit)
# (Intercept) wt hp
# 37.227 -3.878 -0.031
系数怎么读?截距 37.2 是两个自变量都为 0 时的基准油耗(现实中未必有意义);wt 的系数是负的 -3.88,表示车每重一千磅,油耗平均下降约 3.9 英里每加仑——直觉上完全合理,车越重越费油。公式里用 + 连接自变量,乘 * 则还会自动加入交互项。
读懂回归输出:系数、R² 与 p 值
光看系数数字不够,要 summary() 把整张表调出来,重点看三样:系数的 p 值、残差标准误、以及 R²。
summary(fit)
# 节选关键部分:
# Coefficients:
# Estimate Std. Error t value Pr(>|t|)
# (Intercept) 37.22755 1.59874 23.281 < 2e-16 ***
# wt -3.87783 0.63273 -6.129 1.12e-06 ***
# hp -0.03177 0.00903 -3.518 0.00110 **
#
# Multiple R-squared: 0.8268
| 指标 | 含义 | 怎么判断 |
|---|---|---|
| Estimate | 系数估计值 | 符号和量级是否符合业务直觉 |
| Pr(>|t|) | 系数的 p 值 | 小于 0.05 才算显著,有星号更直观 |
| R² | 模型解释了多少方差 | 越接近 1 越好,但不能脱离样本量空谈 |
这里 R² 约 0.83,说明车重和马力合起来解释了约 83% 的油耗差异,对一个只有两个变量的模型来说相当不错;两个系数的 p 值都远小于 0.01,说明它们对油耗的影响是统计上真实的,不是随机噪声。
残差诊断:模型假设成立吗
线性回归有几个前提假设:残差(真实值减预测值)应当均值为零、方差恒定、大致正态。lm 直接对模型对象画图就能看到诊断图。理想情况下,残差应像一团随机散开的云,没有明显的曲线或喇叭口。
par(mfrow = c(2, 2)) # 把四诊断图画在一页
plot(fit) # 残差vs拟合、Q-Q、尺度-位置、残差vs杠杆
- 残差 vs 拟合值:如果出现明显的曲线弯曲,说明线性假设不够,可能需要加二次项。
- Q-Q 图:点大致贴在对角线上,说明残差近似正态;严重偏离就该警惕。
- 尺度-位置图:点应水平随机散布;若越往右越散开(喇叭口),说明方差不齐。
- 个别高杠杆点(Q-Q 图角落的编号点)要单独看,它是不是个别特殊样本。
诊断不是走形式:残差有规律,意味着模型漏掉了重要结构,此时再漂亮的 R² 也不能放心拿去做预测。
kmeans 聚类:无监督地把样本分组
回归需要一个明确的目标变量;但有时候你只有一堆特征,想看看"这些样本自然能分成几类"——这就是聚类。R 里最常用的是 kmeans,你先告诉它想聚成几类(k),它就迭代地把距离最近的点归到一起。
data(mtcars)
# 只用数值特征,先标准化消除量纲影响
X <- scale(mtcars[, c("mpg", "disp", "hp", "wt")])
set.seed(1) # 固定随机种子,结果可复现
km <- kmeans(X, centers = 3) # 聚成 3 类
km$cluster # 每辆车属于哪一类
table(km$cluster) # 每类各有多少辆车
两个要点:第一,聚类前一定要标准化,否则"马力几百"会把"车重几吨"的距离比下去,量纲大的变量主导一切;第二,k 是你自己定的,可以先用"肘部法则"看不同 k 下类内平方和的下降速度,在下降变缓的拐点附近挑 k。聚类结果是一种探索,不是标准答案——要结合业务解释每一类代表什么。
用 ggplot2 把结果画出来
模型结论不画出来就不算完成。ggplot2 的图层语法很顺手:先铺数据、再映射颜色形状、然后一层层加几何对象。下面把回归拟合线和聚类分组同一张图里呈现。
library(ggplot2)
data(mtcars)
mtcars$cluster <- factor(km$cluster) # 聚类结果作为分组颜色
ggplot(mtcars, aes(x = wt, y = mpg, color = cluster)) +
geom_point(size = 2) + # 散点,按簇着色
geom_smooth(method = "lm", se = FALSE, color = "gray30") + # 加回归线
labs(x = "车重", y = "油耗(mpg)", title = "车重与油耗")
读图时一眼就能看出三件事:散点整体从左上往右下走(车重越大油耗越低)、三种颜色的簇各自扎堆、灰色回归线概括了整体趋势。图把"系数表上的几个数字"变成了肉眼可辨的结构,这正是建模最后一步可视化的价值。
实战:一次完整的回归加聚类分析
把整条流程串成一个脚本:先看数据、再拟合回归、做残差诊断、同时跑聚类、最后画在一张图上。这就是一份迷你数据分析报告的骨架。
library(ggplot2)
data(mtcars)
# 1) 描述
summary(mtcars$mpg)
# 2) 回归
fit <- lm(mpg ~ wt + hp, data = mtcars)
summary(fit)$r.squared
# 3) 残差诊断(理想:残差随机散布)
plot(fit, which = 1)
# 4) 聚类
set.seed(1)
X <- scale(mtcars[, c("mpg", "disp", "hp", "wt")])
mtcars$cluster <- factor(kmeans(X, 3)$cluster)
# 5) 可视化
ggplot(mtcars, aes(wt, mpg, color = cluster)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) +
theme_minimal()
这个脚本的思路可以直接搬到你自己的数据上:把 mtcars 换成读进来的 csv,把 mpg ~ wt + hp 换成你关心的因变量和自变量,聚类特征换成你想分组的维度。流程不变,结论随数据而变。
建模检查清单与常见误区
把 R 统计建模记成一条闭环:看清数据 → 拟合回归 → 看系数和 p 值 → 查残差 → 必要时聚类 → 画图验证。提交分析前,对照这份清单自查:
- 建模前有没有先做描述统计,而不是直接套模型?
- 重点系数的 p 值是否显著,符号和量级是否符合业务直觉?
- 残差诊断图是否随机?有没有曲线、喇叭口或极端点被忽略?
- 聚类前是否做了标准化,k 值是否有依据而不是随手填?
- 聚类的每一类,是否都能用业务语言讲清楚"这是一群什么样本"?
- 结论有没有画成图?光靠系数表很难向别人讲明白。
R 不替你思考,但它把"拟合—诊断—可视化"这条专业流水线做到了一行命令。你要做的,是对每一个数字多问一句:它真的成立吗。
下一篇我们回到 C++:在没有解释器兜底的世界里,怎么用智能指针和 STL 容器,把"谁来释放、数据怎么存"这些事一次性安排妥当。