범주형 변수와 로지스틱 회귀
선형 회귀는 종속변수가 연속형일 때 씁니다. 그런데 "생존/사망", "합격/불합격", "구매함/안 함"처럼 결과가 두 가지(이진)라면 어떻게 해야 할까요. 로지스틱 회귀가 그 답입니다.
왜 선형 회귀를 쓸 수 없는가
이진 결과(0 또는 1)에 선형 회귀를 적용하면 예측값이 0 미만 또는 1 초과로 나올 수 있습니다. 확률이 음수거나 1을 넘을 수는 없습니다. 로지스틱 회귀는 출력을 0~1 사이 확률로 변환하는 로지스틱 함수(시그모이드)를 사용합니다.
# 로지스틱 함수
# P(y=1) = 1 / (1 + exp(-(β₀ + β₁x₁ + ...)))
# 결과는 항상 0~1 범위
# 로그 오즈(log-odds) 변환
# log(P / (1-P)) = β₀ + β₁x₁ + ...
# 이것이 선형 형태를 가지므로 회귀 계수로 추정 가능
Titanic 데이터로 생존 예측하기
Titanic 데이터는 R의 내장 데이터입니다. 탑승자의 성별, 등급, 나이별 생존 여부가 담겨 있습니다.
# 분석에 편리하도록 데이터프레임 형태로 변환
data(Titanic)
titanic_df <- as.data.frame(Titanic)
head(titanic_df)
# 개별 탑승자 수준으로 확장 (집계된 데이터이므로)
titanic_expanded <- titanic_df[rep(1:nrow(titanic_df), titanic_df$Freq), 1:4]
rownames(titanic_expanded) <- NULL
# 생존 여부를 이진 변수로 변환
titanic_expanded$survived_bin <- ifelse(titanic_expanded$Survived == "Yes", 1, 0)
str(titanic_expanded)
head(titanic_expanded)
glm()으로 로지스틱 회귀 적합하기
# glm(종속변수 ~ 독립변수, family = binomial, data = 데이터)
logit_model <- glm(survived_bin ~ Class + Sex + Age,
family = binomial(link = "logit"),
data = titanic_expanded)
summary(logit_model)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) 2.04022 0.17309 11.789 < 2e-16 ***
Class2nd -1.01756 0.19572 -5.199 2.01e-07 ***
Class3rd -1.77820 0.17225 -10.324 < 2e-16 ***
ClassCrew -0.85763 0.15755 -5.443 5.24e-08 ***
SexMale -2.42054 0.13741 -17.616 < 2e-16 ***
AgeChild 1.06181 0.24683 4.302 1.70e-05 ***
---
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 2769.5 on 2200 degrees of freedom
Residual deviance: 2210.8 on 2195 degrees of freedom
AIC: 2222.8
계수 해석: 로그 오즈와 오즈비
로지스틱 회귀 계수는 로그 오즈 단위입니다. 직관적으로 해석하려면 지수 변환(exp)으로 오즈비(Odds Ratio)를 구합니다.
# 오즈비 계산
exp(coef(logit_model))
(Intercept) Class2nd Class3rd ClassCrew SexMale AgeChild
7.6928 0.3615 0.1690 0.4242 0.0888 2.8924
# 오즈비 해석
# SexMale = 0.089 → 남성은 여성 대비 생존 오즈가 91.1% 낮다
# AgeChild = 2.89 → 어린이는 성인 대비 생존 오즈가 2.89배 높다
# Class3rd = 0.169 → 3등석은 1등석 대비 생존 오즈가 83.1% 낮다
# 95% 신뢰구간 포함
exp(confint(logit_model))
2.5 % 97.5 %
(Intercept) 5.460046 10.990296
Class2nd 0.245936 0.531119
Class3rd 0.119989 0.237380
ClassCrew 0.311684 0.576668
SexMale 0.068200 0.115194
AgeChild 1.795459 4.785199
예측 확률 계산하기
# type = "response" → 0~1 확률 반환
# type = "link" → 로그 오즈 반환 (기본값)
pred_prob <- predict(logit_model, type = "response")
head(pred_prob)
# 새로운 탑승객에 대한 생존 확률 예측
new_passengers <- data.frame(
Class = c("1st", "3rd", "1st", "3rd"),
Sex = c("Female", "Male", "Male", "Female"),
Age = c("Adult", "Adult", "Child", "Child")
)
predict(logit_model, newdata = new_passengers, type = "response")
1 2 3 4
0.9281 0.1114 0.4925 0.8022
# 1등석 성인 여성: 93% 생존
# 3등석 성인 남성: 11% 생존
# 1등석 남자 어린이: 49% 생존
# 3등석 여자 어린이: 80% 생존
혼동행렬 (Confusion Matrix)
예측 확률을 0.5를 기준으로 0/1로 변환한 후, 실제값과 비교합니다.
# 예측 클래스 (임계값 0.5)
pred_class <- ifelse(pred_prob >= 0.5, 1, 0)
# 혼동행렬
confusion <- table(실제 = titanic_expanded$survived_bin,
예측 = pred_class)
confusion
예측
실제 0 1
0 1360 124
1 321 396
# 성능 지표 계산
TN <- confusion[1, 1] # 진음성(True Negative)
FP <- confusion[1, 2] # 위양성(False Positive)
FN <- confusion[2, 1] # 위음성(False Negative)
TP <- confusion[2, 2] # 진양성(True Positive)
# 정확도 (Accuracy): 전체 중 올바르게 예측한 비율
accuracy <- (TP + TN) / sum(confusion)
cat("정확도:", round(accuracy, 4)) # 0.7982
# 민감도 (Sensitivity / Recall): 실제 양성 중 양성으로 예측한 비율
sensitivity <- TP / (TP + FN)
cat("민감도:", round(sensitivity, 4)) # 0.5523
# 특이도 (Specificity): 실제 음성 중 음성으로 예측한 비율
specificity <- TN / (TN + FP)
cat("특이도:", round(specificity, 4)) # 0.9164
# 양성 예측도 (Precision): 양성으로 예측한 것 중 실제 양성 비율
precision <- TP / (TP + FP)
cat("정밀도:", round(precision, 4)) # 0.7619
caret으로 혼동행렬 한번에
install.packages("caret")
library(caret)
confusionMatrix(factor(pred_class), factor(titanic_expanded$survived_bin),
positive = "1")
Confusion Matrix and Statistics
Reference
Prediction 0 1
0 1360 321
1 124 396
Accuracy : 0.7982
95% CI : (0.7807, 0.8149)
No Information Rate : 0.6773
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.5074
Sensitivity : 0.5523
Specificity : 0.9164
Pos Pred Value : 0.7619
Neg Pred Value : 0.8090
ROC 곡선과 AUC
임계값을 0부터 1까지 변화시키며 민감도와 특이도의 균형을 시각화합니다.
install.packages("pROC")
library(pROC)
roc_obj <- roc(titanic_expanded$survived_bin, pred_prob)
plot(roc_obj, col = "steelblue", lwd = 2,
main = paste("ROC Curve (AUC =", round(auc(roc_obj), 3), ")"))
abline(a = 0, b = 1, lty = 2, col = "gray")
# AUC 해석
# AUC = 0.5 : 무작위 예측과 동일
# AUC > 0.7 : 적절한 판별력
# AUC > 0.9 : 우수한 판별력
cat("AUC:", round(auc(roc_obj), 3))
로지스틱 회귀는 해석이 쉽고 결과가 확률이라는 장점 덕분에 의학, 사회과학 분야 연구에서 여전히 가장 많이 쓰이는 분류 기법입니다.