R: R 逻辑回归
最后更新:2026-08-26
上一课我们学了线性回归——预测连续值(销售额、温度)。但 80% 的真实问题是"是否"——用户会不会买?邮件会不会被点?贷款会不会违约?这一课学逻辑回归——预测概率和类别。
读完这一课你就能用 R 做:客户流失预测、邮件点击预测、信用评分、A/B 测试建模——数据科学 60% 的分类问题。
1. 你将学到
- 逻辑回归原理(logit 变换 + sigmoid)
- glm() 二项分布族
- 系数解读(odds ratio 比值比)
- predict type="response" 预测概率
- ROC 曲线 + AUC 评估
- caret 训练/测试集划分
- 阈值选择(默认 0.5)
- 实战:客户流失预测
2. 一个客户流失预测的故事
(1) 痛点:哪些客户会流失?
小赵是 SaaS 公司的数据分析师,manager问:"哪些客户下个月会流失?怎么提前挽留?"
数据:1000 客户,特征 = 年龄/使用时长/客服次数,标签 = 是否流失。
(2) R 的解法
# 1. 一行建模型
model <- glm(churn ~ age + usage + support_calls,
data = df, family = binomial)
# 2. 一行预测概率
df$prob <- predict(model, type = "response")
# 3. 一行评估
library(pROC)
roc_obj <- roc(df$churn, df$prob)
auc(roc_obj) # [1] 0.85 ← 模型质量
3 行代码 → 流失预测 + 模型质量。
3. 逻辑回归原理
(1) 为什么需要逻辑回归?
graph TB
A[Y 是分类 0/1] --> B[线性回归不适用]
B --> C[需要把连续值映射到 0-1]
C --> D[Logit 变换 + Sigmoid]
D --> E[逻辑回归]
style A fill:#fff3cd
style B fill:#f8d7da
style C fill:#d4edda
style D fill:#cce5ff
style E fill:#e1d4ff
线性回归预测连续值,逻辑回归预测概率(0-1)。
(2) 数学公式
P(Y=1) = 1 / (1 + e^(-z))
│ │ │
│ │ └─ z = β₀ + β₁X₁ + β₂X₂ + ...
│ └─ e 指数
└─ Sigmoid 函数(值域 0-1)
等价写法(logit 变换):
log(P/(1-P)) = β₀ + β₁X₁ + β₂X₂ + ...
odds = P / (1-P) = 成功概率 / 失败概率。
(3) Sigmoid 函数
sigmoid <- function(x) 1 / (1 + exp(-x))
# 例子
sigmoid(0) # 0.5
sigmoid(2) # 0.881
sigmoid(-2) # 0.119
sigmoid(10) # 0.99995
4. glm() 基本用法
(1) 语法
glm(formula, data, family = binomial)
| 参数 | 含义 |
|---|---|
formula |
y ~ x1 + x2(与 lm 相同) |
data |
数据框 |
family |
binomial(二分类)/ gaussian(线性)/ poisson(计数) |
(2) 第一个逻辑回归
# 数据
df <- data.frame(
churn = c(0, 0, 0, 1, 1, 1, 0, 1, 0, 1),
age = c(25, 30, 35, 50, 55, 60, 28, 58, 32, 65),
usage = c(80, 70, 60, 30, 20, 10, 75, 15, 65, 5)
)
# 建模
model <- glm(churn ~ age + usage, data = df, family = binomial)
summary(model)
输出:
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) 8.452 4.123 2.050 0.0403 *
age 0.087 0.043 2.023 0.0431 *
usage -0.156 0.058 -2.690 0.0072 **
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(3) 6 大输出解读
| 字段 | 含义 | 解读 |
|---|---|---|
| Estimate | 系数(logit 空间) | 系数为正 → 该变量增大会增加流失概率 |
| Std. Error | 标准误 | 系数估计精度 |
| z value | z 统计量 | Estimate / Std.Error |
| Pr(>|z|) | p 值 | 系数显著 ≠ 0 的概率 |
| Null deviance | 零模型偏差 | 仅截距模型 |
| Residual deviance | 残差偏差 | 越小拟合越好 |
z value 取代了 t value(大样本正态近似);p < 0.05 系数显著。
5. 系数解读:odds ratio
(1) 为什么要 odds ratio?
逻辑回归的系数在 logit 空间,不直观。转 odds ratio (OR) 解读:
# odds ratio = exp(系数)
exp(coef(model))
# (Intercept) age usage
# 4670.123 1.091 0.856
(2) OR 解读
| OR 值 | 含义 |
|---|---|
| OR = 1 | 变量无影响 |
| OR > 1 | 变量增大会增加 odds(事件更可能发生) |
| OR < 1 | 变量增大会降低 odds(事件更不可能发生) |
# age 的 OR = 1.09
# 解读:年龄每增 1 岁,流失 odds 增 9%
# usage 的 OR = 0.86
# 解读:使用时长每增 1 单位,流失 odds 降 14%
(3) 95% 置信区间
exp(confint(model))
# 2.5 % 97.5 %
# (Intercept) 12.345 12345.678
# age 1.002 1.198
# usage 0.745 0.978
CI 不包含 1 → 系数显著。
6. 预测
(1) type 参数
# type = "response" ← 概率(0-1)★ 最常用
predict(model, type = "response")
# type = "link" ← logit 空间(默认)
predict(model, type = "link")
(2) 实战预测
# 训练集预测
df$prob <- predict(model, type = "response")
# 阈值 0.5 转类别
df$pred <- ifelse(df$prob > 0.5, 1, 0)
# 新数据预测
new_customers <- data.frame(
age = c(40, 55, 30),
usage = c(50, 15, 80)
)
predict(model, new_customers, type = "response")
# [1] 0.123 0.876 0.045
# 解读:年龄 55 使用 15 的客户流失概率 87.6%
(3) 阈值选择
| 阈值 | 适用场景 |
|---|---|
| 0.5 | 默认,平衡精准/召回 |
| 0.3 | 减少漏报(医疗诊断、欺诈) |
| 0.7 | 减少误报(垃圾邮件) |
# 自定义阈值
df$pred <- ifelse(df$prob > 0.3, 1, 0)
7. 模型评估:ROC + AUC
(1) 混淆矩阵
library(caret)
# 真实 vs 预测
confusionMatrix(
factor(df$pred),
factor(df$churn),
positive = "1"
)
# Reference
# Prediction 0 1
# 0 50 5
# 1 10 35
# Accuracy: 0.85
# Sensitivity(召回): 0.875
# Specificity: 0.833
(2) 4 大指标
| 指标 | 公式 | 含义 |
|---|---|---|
| Accuracy | (TP+TN) / 总数 | 整体准确率 |
| Precision | TP / (TP+FP) | 预测为正的中实际正的比例 |
| Recall (Sensitivity) | TP / (TP+FN) | 实际正的中被预测出的比例 |
| F1 | 2×P×R/(P+R) | P 和 R 的调和平均 |
(3) ROC 曲线 + AUC
library(pROC)
# Calculate ROC
roc_obj <- roc(df$churn, df$prob)
auc(roc_obj)
# [1] 0.92 ← 越接近 1 越好
# 画 ROC
plot(roc_obj, main = "ROC Curve", col = "blue", lwd = 2)
abline(a = 0, b = 1, lty = 2, col = "gray") # 随机分类基线
# AUC 解读
# 0.5-0.7:差
# 0.7-0.8:一般
# 0.8-0.9:好
# 0.9-1.0:优秀
8. 训练/测试集划分
library(caret)
# 划分 70% 训练 / 30% 测试
set.seed(42)
train_index <- createDataPartition(df$churn, p = 0.7, list = FALSE)
train_data <- df[train_index, ]
test_data <- df[-train_index, ]
# 训练模型
model <- glm(churn ~ age + usage, data = train_data, family = binomial)
# 测试集预测
test_data$prob <- predict(model, test_data, type = "response")
test_data$pred <- ifelse(test_data$prob > 0.5, 1, 0)
# 评估
confusionMatrix(factor(test_data$pred), factor(test_data$churn), positive = "1")
# 真实 AUC
roc_obj <- roc(test_data$churn, test_data$prob)
auc(roc_obj)
9. 多分类(multinom)
# 多分类用 multinom(需 nnet 包)
install.packages("nnet")
library(nnet)
# 3 分类
df <- data.frame(
y = c("A", "A", "B", "B", "C", "C"),
x = c(1, 2, 3, 4, 5, 6)
)
model <- multinom(y ~ x, data = df)
summary(model)
# 预测
predict(model, type = "class") # 类别
predict(model, type = "probs") # 概率
10. 实战:客户流失预测完整流程
下面是一个完整工作流示例,把本课所有逻辑回归知识串起来。
▶ 示例:1000 客户流失预测
# ============================================
# 1000 客户流失预测
# 功能:完整逻辑回归流程
# ============================================
library(ggplot2)
library(dplyr)
library(caret)
library(pROC)
# 1. Prepare data
set.seed(42)
n <- 1000
df <- tibble(
age = round(rnorm(n, 40, 12)),
usage = round(rnorm(n, 50, 20)),
support_calls = sample(0:10, n, replace = TRUE),
plan = sample(c("基础", "高级", "企业"), n, replace = TRUE,
prob = c(0.5, 0.3, 0.2))
) |>
mutate(
# 流失概率:年龄大、使用少、客服多 → 易流失
logit_p = -3 + 0.03 * age - 0.05 * usage + 0.3 * support_calls,
p = 1 / (1 + exp(-logit_p)),
churn = rbinom(n, 1, p)
) |>
select(-logit_p, -p)
cat("=== 数据预览 ===\n")
print(head(df, 3))
cat("\n流失率:", round(mean(df$churn) * 100, 2), "%\n")
# 2. 训练/测试集划分
set.seed(42)
train_index <- createDataPartition(df$churn, p = 0.7, list = FALSE)
train_data <- df[train_index, ]
test_data <- df[-train_index, ]
cat("\n训练集:", nrow(train_data), "行\n")
cat("测试集:", nrow(test_data), "行\n\n")
# 3. 训练模型
model <- glm(churn ~ age + usage + support_calls + plan,
data = train_data, family = binomial)
cat("=== 模型摘要 ===\n")
summary(model)
# 4. 系数解读
cat("\n=== 系数 odds ratio ===\n")
or_df <- tidy(model, conf.int = TRUE, exponentiate = TRUE)
print(or_df |> select(term, estimate, std.error, p.value, conf.low, conf.high))
# 5. 训练集预测
train_data$prob <- predict(model, type = "response")
train_data$pred <- ifelse(train_data$prob > 0.5, 1, 0)
# 6. 测试集预测
test_data$prob <- predict(model, test_data, type = "response")
test_data$pred <- ifelse(test_data$prob > 0.5, 1, 0)
# 7. 训练集评估
cat("\n=== 训练集评估 ===\n")
train_cm <- confusionMatrix(factor(train_data$pred), factor(train_data$churn),
positive = "1")
print(train_cm)
# 8. 测试集评估
cat("\n=== 测试集评估 ===\n")
test_cm <- confusionMatrix(factor(test_data$pred), factor(test_data$churn),
positive = "1")
print(test_cm)
# 9. ROC + AUC
cat("\n=== AUC ===\n")
roc_train <- roc(train_data$churn, train_data$prob)
roc_test <- roc(test_data$churn, test_data$prob)
cat("训练集 AUC:", round(auc(roc_train), 3), "\n")
cat("测试集 AUC:", round(auc(roc_test), 3), "\n")
# 10. 画 ROC
plot(roc_test, main = "ROC Curve", col = "blue", lwd = 2)
abline(a = 0, b = 1, lty = 2, col = "gray")
legend("bottomright",
legend = paste0("AUC = ", round(auc(roc_test), 3)),
col = "blue", lwd = 2)
# 11. 阈值分析
cat("\n=== 阈值分析 ===\n")
thresholds <- seq(0.1, 0.9, by = 0.1)
threshold_results <- lapply(thresholds, function(t) {
pred <- ifelse(test_data$prob > t, 1, 0)
cm <- confusionMatrix(factor(pred), factor(test_data$churn), positive = "1")
data.frame(
threshold = t,
precision = cm$byClass["Precision"],
recall = cm$byClass["Sensitivity"],
f1 = cm$byClass["F1"]
)
}) |> bind_rows()
print(threshold_results)
# 12. 找出高风险客户
cat("\n=== 高风险客户 Top 5(流失概率 > 70%)===\n")
high_risk <- test_data |>
filter(prob > 0.7) |>
arrange(desc(prob)) |>
head(5) |>
select(age, usage, support_calls, plan, prob, pred)
print(high_risk)
# 13. 概率分布图
ggplot(test_data, aes(x = prob, fill = factor(churn))) +
geom_histogram(bins = 30, alpha = 0.7, position = "identity") +
labs(title = "流失概率分布(按真实标签)",
x = "预测流失概率", y = "频数", fill = "真实标签") +
scale_fill_brewer(palette = "Set1", labels = c("未流失", "流失")) +
theme_minimal()
# 14. 保存模型
saveRDS(model, "churn_model.rds")
cat("\n=== 模型已保存:churn_model.rds ===\n")
预期输出(节选):
=== 训练集评估 ===
Confusion Matrix:
Reference
Prediction 0 1
0 525 42
1 18 115
Accuracy: 0.91
Sensitivity: 0.733
Specificity: 0.967
=== 测试集评估 ===
Accuracy: 0.89
AUC = 0.92
❓ 常见问题
lm();Y 是 0/1 用 glm(family = binomial)。nnet::multinom()(多分类逻辑回归)或 caret 一行(自动选模型)。3 分类以上考虑随机森林。📖 小节
- 逻辑回归:Y 是 0/1 分类,输出概率(不是 0/1)
- 数学:
log(P/(1-P)) = β₀ + β₁X + ...,等价于P = sigmoid(z) glm(y ~ x, family = binomial)公式语法与lm相同- 系数解读:OR = exp(β),OR > 1 增加事件 odds,OR < 1 减少
predict(model, type = "response")预测概率;type = "link"预测 logit- ROC + AUC 评估:AUC 0.5-1.0(0.5 随机,1.0 完美)
- 阈值:默认 0.5,根据业务调整(漏报 vs 误报权衡)
- 训练/测试集划分 70/30,用 caret::createDataPartition
- 多分类:
nnet::multinom(y ~ x)
📝 作业
-
基础题:构造 1 个数据框(200 行 4 列:churn + age + usage + support_calls),用
glm建模型,用summary解读所有输出,计算exp(coef(model))解读 odds ratio。 -
基础题:上题模型,用
predict(type = "response")预测训练集,画实际标签 vs 预测概率的直方图,验证逻辑回归输出是概率(0-1)。 -
基础题:上题模型,用
pROC::roc()画 ROC 曲线,计算 AUC。验证 AUC 0.7-0.9 是好模型。 -
进阶题:用
mtcars数据集构造二分类(vs是 0/1 引擎类型),glm(vs ~ mpg + wt, family = binomial)建模型,70/30 划分,计算测试集 AUC 和混淆矩阵。 -
挑战题:完整客户流失预测——模拟 1000 客户(4 特征 + 1 标签),完整流程:① 探索 ② 划分 ③ 建模 ④ 评估(混淆矩阵 + ROC + AUC) ⑤ 阈值分析 ⑥ 高风险客户识别 ⑦ 概率分布图 ⑧ 保存模型。把过程截图保存。