library(arrow)
# library(nflverse)
library(poissonreg)
library(tidymodels)
library(tidyverse)
library(tinytable)14 예측
선수 지식
- R을 사용한 통계 학습 소개, (James et al. [2013] 2021)
- 6장 “선형 모델 선택 및 정규화”에 집중하세요. 이는 릿지 및 라쏘 회귀에 대한 개요를 제공합니다.
- 데이터 분석을 위한 Python, (McKinney [2011] 2022)
- Python에서 데이터 분석의 작업 예시를 제공하는 13장에 집중하세요.
- R을 사용한 NFL 분석 소개, (Congelio 2024)
- 3장 “
nflverse패키지 제품군을 사용한 NFL 분석” 및 5장 “NFL 데이터를 사용한 고급 모델 생성”에 집중하세요.
- 3장 “
핵심 개념 및 기술
소프트웨어 및 패키지
arrow(Richardson et al. 2023)nflverse(Carl et al. 2023)poissonreg(Kuhn and Frick 2022)tidymodels(Kuhn and Wickham 2020)parsnip(Kuhn and Vaughan 2022)recipes(Kuhn and Wickham 2022)rsample(Frick et al. 2022)tune(Kuhn 2022)yardstick(Kuhn, Vaughan, and Hvitfeldt 2022)
tidyverse(Wickham et al. 2019)tinytable(Arel-Bundock 2024)
14.1 서론
앞선 Chapter 12 장에서 논의했듯이, 통계 모델은 크게 ‘추론(Inference)’과 ’예측(Prediction)’이라는 두 가지 갈래 중 하나에 무게중심을 두는 경향이 있습니다. 흥미롭게도 어떤 목적에 초점을 맞추느냐에 따라 이를 다루는 학문적 문화 또한 사뭇 다릅니다. 이러한 차이가 발생하는 결정적인 이유 중 하나는, 바로 Chapter 15 장에서 본격적으로 다룰 ’인과 관계(Causality)’를 얼마나 엄격하게 따지느냐의 차이 때문입니다. 매우 일반화해서 표현하자면, ’추론’의 세계에서는 변수 간의 인과 관계가 성립하는지 지독하게 우려하고 검증하지만, ’예측’의 세계에서는 상대적으로 그 걱정이 덜한 편입니다. 물론 이는 양날의 검입니다. 인과성을 무시한 예측 모델은 학습 환경과 실제 조건이 조금이라도 틀어지면 예측의 품질이 순식간에 곤두박질치는 취약성을 가집니다. 그렇다면 우리는 현실의 조건이 모델링 당시와 ’충분히’ 달라졌는지를 어떻게 미리 알아챌 수 있을까요?
이러한 문화적 간극이 생긴 또 다른 배경에는, 데이터 과학(Data Science) 및 머신러닝(Machine Learning)의 비약적인 성장이 있습니다. 컴퓨터 과학이나 공학적 배경을 가진 인재들이 파이썬(Python)을 주력 엔진으로 삼아 모델 개발을 주도했기 때문입니다. 반면 ’추론’의 뿌리는 정통 통계학에 깊이 박혀 있습니다. 그래서 같은 현상을 두고도 사용하는 어휘나 접근법에서 미묘한 차이가 발생하곤 합니다. 물론 이 모든 설명은 복잡한 현실을 아주 단순화해서 말씀드리는 것입니다.
이번 장에서는 R의 강력한 예측 프레임워크인 tidymodels를 활용해 ’예측’이라는 목표에 집중해 볼 것입니다. 그런 다음, 이른바 회색 영역에 걸쳐 있는 ’라쏘(Lasso) 회귀’를 소개하겠습니다. 라쏘는 정통 통계학자들이 탄생시켰지만, 오늘날에는 주로 예측 성능을 끌어올리는 도구로 사랑받고 있습니다. 마지막으로, 이 모든 예측 여정을 파이썬(Python) 생태계에서는 어떻게 구현하는지도 함께 살펴보겠습니다.
14.2 tidymodels 프레임워크를 활용한 예측
14.2.1 선형 모델 (Linear Model)
예측 성능을 극대화하려는 분석가는 대개 수많은 모델 후보군을 쉴 새 없이 돌려보고 비교하고 싶기 마련입니다. 가장 원시적인 방법은 코드를 수없이 복사해서 붙여넣는 것이겠죠. 처음 시작할 때는 누구나 그러기 쉽고 큰 문제는 없지만, 데이터가 커지고 복잡해질수록 사람이 잡아내기 힘든 치명적인 오류를 저지를 위험이 큽니다. 따라서 우리는 다음과 같은 세 가지 기준을 만족하는 더 나은 접근 방식이 필요합니다:
- 확장성(Scalability): 모델의 개수가 늘어나도 쉽게 대응할 수 있어야 합니다.
- 과적합(Overfitting) 방지: 모델이 학습 데이터에만 지나치게 몰입하지 않도록 신중하게 설계해야 합니다.
- 엄격한 평가 시스템: 모델의 성능을 정교하게 측정할 도구가 필요합니다.
tidymodels(Kuhn and Wickham 2020) 패키지 군은 다양한 모델을 하나의 일관된 문법(Syntax)으로 다룰 수 있는 인터페이스를 제공하여 위 기준들을 완벽하게 충족합니다. tidyverse와 마찬가지로, 여러 전문 패키지들이 유기적으로 모여 거대한 생태계를 이룬 형태입니다.
우리의 탐험을 위해, 시뮬레이션된 ’달리기 데이터’를 바탕으로 다음과 같은 기초적인 예측 모델을 설계해 보겠습니다.
\[ \begin{aligned} y_i | \mu_i &\sim \mbox{Normal}(\mu_i, \sigma) \\ \mu_i &= \beta_0 +\beta_1x_i \end{aligned} \]
여기서 \(y_i\)는 러너 \(i\)의 마라톤 완주 시간이고, \(x_i\)는 그들의 5km 단거리 기록입니다. 이 수식은 “마라톤 시간은 평균이 \(\mu\), 표준 편차가 \(\sigma\)인 정규 분포를 따르며, 그 평균 기록은 기초 체력인 5km 기록과 고정된 매개변수 \(\beta_0, \beta_1\)에 의해 결정된다”는 뜻입니다. 물결표(\(\sim\))는 “~라는 분포를 따른다”는 의미입니다. 사용되는 통계 분포를 명확히 보여주기 위해 표기법을 조금 다르게 썼지만, 본질적으로 이는 우리가 잘 아는 오차항(\(\epsilon\))이 포함된 \(y_i=\beta_0+\beta_1 x_i + \epsilon_i\) 선형 회귀식과 동일합니다.
우리의 화두는 ‘예측’이기에, 모델이 가진 데이터에만 지독하게 집착하는 ’과적합(Overfitting)’ 현상을 가장 경계해야 합니다. 과적합된 모델은 새로운 데이터가 들어왔을 때 형편없는 예측 실력을 보여주기 때문입니다. 이를 해결하는 가장 기본적이면서도 강력한 방법은 initial_split() 함수를 사용해 데이터셋을 훈련용(Training)과 테스트용(Testing) 두 갈래로 과감하게 쪼개는 것입니다.
sim_run_data <-
read_parquet(file = here::here("outputs/data/running_data.parquet"))
set.seed(853)
sim_run_data_split <-
initial_split(
data = sim_run_data,
prop = 0.80
)
sim_run_data_split<Training/Testing/Total>
<160/40/200>
데이터를 분할한 후, training()과 testing()을 사용하여 훈련 및 테스트 데이터셋을 생성합니다.
sim_run_data_train <- training(sim_run_data_split)
sim_run_data_test <- testing(sim_run_data_split)데이터의 80%를 훈련(Train) 데이터셋에 할당했습니다. 이 ’연습장’용 데이터를 사용해 모델의 숨은 매개변수(계수)들을 추정할 것입니다. 나머지 20%는 마치 실제 시험을 치듯 고이 남겨두었다가, 나중에 모델의 진정한 실력을 평가하는 데만 사용할 것입니다. 왜 이런 번거로운 짓을 할까요? 그것은 통계 모델링의 영원한 숙제인 편향-분산 트레이드오프(Bias-Variance Trade-off) 때문입니다.
우리는 모델이 지금 손에 쥔 데이터에만 너무 귀신같이 들어맞아서, 정작 내일 마주할 새로운 데이터에는 엉터리 대답을 내놓을까 봐 두렵습니다. 극단적인 예로, 관측치가 딱 10개뿐인 데이터셋이 있다고 해봅시다. 수학적으로 우리는 이 10개의 점을 오차 없이 완벽하게 통과하는 요술 같은 곡선 모델을 만들 수 있습니다. 하지만 이 모델을 똑같은 공정에서 생산된 다른 10개의 데이터에 적용해 보면, 예측값은 처참하게 빗나갈 것입니다.
이러한 공포를 해결하는 가장 정석적인 해법이 바로 데이터를 나누는 것입니다. 훈련 데이터는 오직 ’지식 습득’에만 쓰고, 평가는 오직 테스트 데이터라는 ’검증’의 칼날 위에 올립니다. 훈련 데이터에만 지독하게 달라붙은 모델은 테스트 데이터라는 낯선 시험 문제 앞에서는 맥을 못 추게 마련입니다. 이러한 테스트-훈련 분할(Test-Train Split) 은 모델이 현실적인 일반화 능력을 갖출 수 있는 소중한 기회를 제공합니다.
물론, 이 ’나누기’를 제대로 수행하는 것은 생각보다 훨씬 까다롭습니다. 가장 경계해야 할 적은 바로 데이터 누출(Data Leakage) 입니다. 테스트 데이터의 정보가 어떤 경로로든 은밀하게 훈련 과정에 스며들어, 모델이 미래의 정답을 미리 컨닝하는 상황이죠. 데이터 정제나 전처리 과정을 데이터셋 전체에 대해 한꺼번에 수행하다 보면, 두 데이터셋의 특징이 서로 섞이는 실수를 범하기 쉽습니다. 카푸어와 나라야난(Kapoor and Narayanan (2023))의 연구에 따르면, 수많은 머신러닝 응용 연구들이 이러한 데이터 누출 문제 때문에 그 결과가 무효화될 수 있는 위험에 처해 있다고 경고합니다.
tidymodels 생태계의 문법은 명확합니다. 먼저 linear_reg()를 선언해 “나는 선형 회귀 모델을 만들 거야”라고 목표를 정합니다. 그다음 set_engine()을 통해 구체적으로 어떤 통계 엔진(이 경우 정통적인 lm)을 심장으로 쓸지 결정합니다. 마지막으로 fit()에 데이터와 수식을 던져주면 모델이 완성됩니다. 기초 R의 방식보다 조금 더 번거로운 절차처럼 보일 수도 있지만, 이 방식의 진정한 힘은 ’확장성’에 있습니다. 똑같은 문법으로 수백, 수천 개의 모델을 찍어낼 수 있는 견고한 ‘모델 공장’을 구축한 셈이니까요.
sim_run_data_first_model_tidymodels <-
linear_reg() |>
set_engine(engine = "lm") |>
fit(
marathon_time ~ five_km_time + was_raining,
data = sim_run_data_train
)추정된 계수는 Table 12.4 의 첫 번째 열에 요약되어 있습니다. 예를 들어, 데이터셋에서 5킬로미터 달리기 시간이 1분 더 길면 마라톤 시간이 약 8분 더 길어진다는 것을 발견합니다.
14.2.2 로지스틱 회귀 (Logistic Regression)
분류(Classification) 문제인 로지스틱 회귀에도 tidymodels를 우아하게 적용할 수 있습니다. 한 가지 주의할 점은, 분류 모델의 엔진을 돌리기 위해 종속 변수를 사전에 ‘요인(Factor)’ 자료형으로 명확히 변환해 두어야 한다는 것입니다.
week_or_weekday <-
read_parquet(file = "outputs/data/week_or_weekday.parquet")
set.seed(853)
week_or_weekday <-
week_or_weekday |>
mutate(is_weekday = as_factor(is_weekday))
week_or_weekday_split <- initial_split(week_or_weekday, prop = 0.80)
week_or_weekday_train <- training(week_or_weekday_split)
week_or_weekday_test <- testing(week_or_weekday_split)
week_or_weekday_tidymodels <-
logistic_reg(mode = "classification") |>
set_engine("glm") |>
fit(
is_weekday ~ number_of_cars,
data = week_or_weekday_train
)단순히 계수를 추정하는 데 그치지 않고, 우리는 보관해 두었던 테스트용 데이터셋을 꺼내 모델의 실전 예측력을 날카롭게 검증해 볼 것입니다. 가장 직관적인 도구는 혼동 행렬(Confusion Matrix)입니다. 이는 모델이 예측한 값과 실제 정답을 표 형태로 대조하여 어디서 얼마나 틀렸는지를 일목요연하게 보여줍니다. 우리의 모델은 다행히 보류된 데이터에서 꽤 훌륭한 성적을 냈습니다. 구체적으로 살펴보면, 주중이라고 예측한 90개 중 90개가 실제로 주중이었고, 주말로 예측한 95개 중 95개가 주말이었습니다. 총 15번의 실수가 있었는데, 주중으로 잘못 예측한 주말이 7회, 주말로 잘못 예측한 주중이 8회였습니다.
week_or_weekday_tidymodels |>
predict(new_data = week_or_weekday_test) |>
cbind(week_or_weekday_test) |>
conf_mat(truth = is_weekday, estimate = .pred_class) Truth
Prediction 0 1
0 90 8
1 7 95
14.2.2.1 미국 선거에서의 정치적 지지
이제 조금 더 복잡한 실제 데이터에 도전해 봅시다. 이전과 동일한 검증 세트 접근 방식(Validation set approach)(James et al. [2013] 2021, 176)을 사용하여, 유권자의 특성으로 특정 정당(예: 바이든 지지)을 예측하는 로지스틱 모델을 세워보겠습니다.
ces2020 <-
read_parquet(file = "outputs/data/ces2020.parquet")
set.seed(853)
ces2020_split <- initial_split(ces2020, prop = 0.80)
ces2020_train <- training(ces2020_split)
ces2020_test <- testing(ces2020_split)
ces_tidymodels <-
logistic_reg(mode = "classification") |>
set_engine("glm") |>
fit(
voted_for ~ gender + education,
data = ces2020_train
)
ces_tidymodelsparsnip model object
Call: stats::glm(formula = voted_for ~ gender + education, family = stats::binomial,
data = data)
Coefficients:
(Intercept) genderMale
0.2157 -0.4697
educationHigh school graduate educationSome college
-0.1857 0.3502
education2-year education4-year
0.2311 0.6700
educationPost-grad
0.9898
Degrees of Freedom: 34842 Total (i.e. Null); 34836 Residual
Null Deviance: 47000
Residual Deviance: 45430 AIC: 45440
학습된 모델을 테스트 세트에 올리고 평가해 봅니다. 아쉽게도, 현재의 기초적인 변수들만으로는 모델이 트럼프 지지자들을 정확히 골라내는 데 상당한 애를 먹고 있는 것 같습니다.
ces_tidymodels |>
predict(new_data = ces2020_test) |>
cbind(ces2020_test) |>
conf_mat(truth = voted_for, estimate = .pred_class) Truth
Prediction Trump Biden
Trump 656 519
Biden 2834 4702
앞서 우리는 훈련 세트와 테스트 세트를 무작위로 분할하는 것의 중요성을 역설했습니다. 하지만 가슴 한편에 이런 의문이 들 수 있습니다. “만약 운이 나쁘게도 모델에게 불리한 데이터들만 테스트 세트에 몰빵되면 어쩌지?” 혹은 “데이터 전체를 더 알뜰하게 활용할 방법은 없을까?”
이러한 불안감을 잠재워 줄 통계적 솔루션이 바로 k-겹 교차 검증(k-fold Cross-Validation)입니다. 이 기법은 전체 데이터를 \(k\)개의 조각(폴드)으로 공평하게 나눕니다. 그다면 \(k-1\)개의 조각으로 공부하고 나머지 1개로 시험 보기를 총 \(k\)번 반복합니다. 결국 우리 데이터의 모든 행이 단 한 번도 빠짐없이 시험 문제로 활용되는 셈입니다. 최종 성능 점수는 이 \(k\)번의 시험 성적을 평균 내어 산출합니다(James et al. [2013] 2021, 181). 아래 코드는 tidymodels의 vfold_cv()를 사용하여 데이터를 10개의 조각으로 나누는 예시입니다.
set.seed(853)
ces2020_10_folds <- vfold_cv(ces2020, v = 10)이제 fit_resamples()를 호출하여 이 10개의 폴드 시나리오에 맞춰 모델을 10번 자동으로 학습시키고 검증합니다.
ces2020_cross_validation <-
fit_resamples(
object = logistic_reg(mode = "classification") |> set_engine("glm"),
preprocessor = recipe(voted_for ~ gender + education,
data = ces2020),
resamples = ces2020_10_folds,
metrics = metric_set(accuracy, sens),
control = control_resamples(save_pred = TRUE)
)모델의 최종 성적표는 collect_metrics() 함수로 한눈에 요약할 수 있습니다(Table 14.1 (a)). 이러한 상세한 모델 진단 기록은 대개 논문의 부록(Appendix) 페이지를 든든하게 채워주는 감초 역할을 합니다. 우리의 분석 결과, 모델의 평균 정확도(Accuracy)는 0.61, 평균 민감도(Sensitivity)는 0.19, 그리고 평균 특이도(Specificity)는 0.90으로 나타났습니다.
collect_metrics(ces2020_cross_validation) |>
select(.metric, mean) |>
tt() |>
style_tt(j = 1:2, align = "lr") |>
format_tt(digits = 2, num_mark_big = ",", num_fmt = "decimal") |>
setNames(c("지표(Metric)", "평균값"))
conf_mat_resampled(ces2020_cross_validation) |>
mutate(Proportion = Freq / sum(Freq)) |>
tt() |>
style_tt(j = 1:4, align = "llrr") |>
format_tt(digits = 2, num_mark_big = ",", num_fmt = "decimal")| 지표(Metric) | 평균값 |
|---|---|
| accuracy | 0.61 |
| sens | 0.19 |
| Prediction | Truth | Freq | Proportion |
|---|---|---|---|
| Trump | Trump | 327.5 | 0.08 |
| Trump | Biden | 267.7 | 0.06 |
| Biden | Trump | 1,428.3 | 0.33 |
| Biden | Biden | 2,331.9 | 0.54 |
이 숫자들의 속사정은 무엇일까요? 정확도(Accuracy)는 전체 중 얼마나 맞혔느냐를 뜻합니다. 0.61이라는 수치는 동전 던지기보다는 조금 낫지만, 여전히 갈 길이 멀다는 뜻입니다. 민감도(Sensitivity)는 진짜 양성(여기서는 트럼프 투표)을 얼마나 잘 잡아냈는지를 측정합니다(James et al. [2013] 2021, 145). 0.19라는 낮은 점수는 모델이 트럼프 지지자를 식별하는 데 쩔쩔매고 있음을 자인하는 꼴입니다. 반면 특이도(Specificity)는 진짜 음성(바이든 투표)을 걸러내는 능력입니다. 0.90이라는 높은 점수는 모델이 “일단 의심스러우면 바이든 지지라고 대답하고 보자”는 식으로 극단적으로 편향되어 있음을 시시합니다.
혼동 행렬(Table 14.1 (b))을 보면 이 참혹한(?) 실상이 더 선명해집니다. 교차 검증 환경에서의 혼동 행렬은 각 폴드의 결과값들을 평균 내어 보여줍니다. 우리 모델은 2020년 대선이 손에 땀을 쥐는 접전이었다는 사실을 완전히 망각한 채, 압도적으로 바이든의 승리만을 예견하고 있습니다. 이는 현재 투입된 성별, 교육 수준 외에 훨씬 더 강력한 설명 변수가 추가되어야 함을 강력하게 시사합니다.
만약 모델의 개별적인 ‘예측 예측값’ 자체가 궁금하다면 collect_predictions()로 데이터셋 옆에 예측 열을 붙여 확인할 수 있습니다.
ces2020_with_predictions <-
cbind(
ces2020,
collect_predictions(ces2020_cross_validation) |>
arrange(.row) |>
select(.pred_class)
) |>
as_tibble()결과표(Table 14.2)를 뜯어보면, 우리 모델이 얼마나 고집불통인지 알 수 있습니다. 고졸 미만 혹은 전문대를 졸업하지 않은 일부 남성을 제외하면, 거의 모든 유권자에게 “당신은 바이든 지지자군요!”라는 한결같은(?) 대답만 내놓고 있으니까요.
ces2020_with_predictions |>
group_by(gender, education, voted_for) |>
count(.pred_class) |>
tt() |>
style_tt(j = 1:5, align = "llllr") |>
format_tt(digits = 0, num_mark_big = ",", num_fmt = "decimal") |>
setNames(c(
"성별",
"교육 수준",
"실제 투표",
"예측 결과",
"빈도(N)"
))| 성별 | 교육 수준 | 실제 투표 | 예측 결과 | 빈도(N) |
|---|---|---|---|---|
| Female | No HS | Trump | Biden | 206 |
| Female | No HS | Biden | Biden | 228 |
| Female | High school graduate | Trump | Biden | 3,204 |
| Female | High school graduate | Biden | Biden | 3,028 |
| Female | Some college | Trump | Biden | 1,842 |
| Female | Some college | Biden | Biden | 3,325 |
| Female | 2-year | Trump | Biden | 1,117 |
| Female | 2-year | Biden | Biden | 1,739 |
| Female | 4-year | Trump | Biden | 1,721 |
| Female | 4-year | Biden | Biden | 4,295 |
| Female | Post-grad | Trump | Biden | 745 |
| Female | Post-grad | Biden | Biden | 2,853 |
| Male | No HS | Trump | Trump | 132 |
| Male | No HS | Biden | Trump | 123 |
| Male | High school graduate | Trump | Trump | 2,054 |
| Male | High school graduate | Biden | Trump | 1,528 |
| Male | Some college | Trump | Biden | 1,992 |
| Male | Some college | Biden | Biden | 2,131 |
| Male | 2-year | Trump | Trump | 1,089 |
| Male | 2-year | Biden | Trump | 1,026 |
| Male | 4-year | Trump | Biden | 2,208 |
| Male | 4-year | Biden | Biden | 3,294 |
| Male | Post-grad | Trump | Biden | 1,248 |
| Male | Post-grad | Biden | Biden | 2,426 |
14.2.3 포아송 회귀 (Poisson Regression)
개수를 세는 데이터(Count data)를 위한 모델링도 tidymodels 생태계 안에서 poissonreg(Kuhn and Frick 2022) 패키지를 통해 매끄럽게 수행할 수 있습니다(Table 13.4).
count_of_A <-
read_parquet(file = "outputs/data/count_of_A.parquet")
set.seed(853)
count_of_A_split <-
initial_split(count_of_A, prop = 0.80)
count_of_A_train <- training(count_of_A_split)
count_of_A_test <- testing(count_of_A_split)
grades_tidymodels <-
poisson_reg(mode = "regression") |>
set_engine("glm") |>
fit(
number_of_As ~ department,
data = count_of_A_train
)이 추정 결과는 ?tbl-modelsummarypoisson의 두 번째 열에 요약되어 있습니다. 일반적인 glm() 결과와 궤를 같이하지만, 데이터를 훈련/테스트로 쪼개서 학습했기에 관측치 규모가 조금 작게 반영된 차이가 있습니다.
14.3 라쏘 회귀 (Lasso Regression)
어떤 모델이 수백 가지의 독립 변수를 마구잡이로 투입받았다고 가정해 봅시다. 이 중 어떤 변수가 진짜 중요한 ‘알짜배기’ 정보이고, 어떤 것이 단순한 ’노이즈’인지 모델 스스로 판단하게 할 수는 없을까요? 바로 이 지점에서 라쏘 회귀(Lasso Regression)가 빛을 발합니다. 라쏘는 모델의 복잡도에 일종의 ’벌금’을 매겨서, 별 볼 일 없는 변수의 영향력을 아예 0으로 만들어버리는 강력한 변수 선택 기능을 장착하고 있습니다.
로버트 팁시라니(Robert Tibshirani) 박사는 스탠퍼드 대학교 통계학 및 생의학 데이터 과학과의 저명한 교수입니다. 1981년 스탠퍼드에서 통계학 박사 학위를 취득한 후, 토론토 대학교에서 교수진으로 합류하여 활약하다 1998년 다시 스탠퍼드로 화려하게 귀환했습니다. 그는 현대 통계학의 지형을 바꾼 수많은 기여를 했는데, 앞서 언급한 일반화 가법 모델(GAM)은 물론, 오늘날 데이터 과학의 표준 무기가 된 ’라쏘(Lasso) 회귀’를 창시한 장본인이기도 합니다. 전설적인 교과서인 An Introduction to Statistical Learning(James et al. ([2013] 2021))의 공동 저자이기도 한 그는, 1996년 통계학계의 최고 영예인 COPSS 회장상을 수상했으며 2019년에는 영국 왕립학회(Royal Society) 펠로우로 선출되었습니다.
14.4 Python을 활용한 예측 실습
14.4.1 환경 설정 (Setup)
파이썬(Python) 생태계에서 예측 모델링을 수행하기 위해 Microsoft가 제공하는 강력한 무료 개발 환경인 VS Code(Visual Studio Code)를 활용하겠습니다. 공식 웹사이트에서 다운로드할 수 있으며, 설치 후 ‘Quarto’와 ’Python’ 확장을 추가하면 준비는 끝납니다. 만약 로컬 설치 과정이 번거롭다면, 웹 브라우저만으로 즉시 파이썬 코드를 돌려볼 수 있는 Google Colab을 대안으로 추천합니다.
14.4.2 데이터 핸들링 (Data)
- Parquet 포맷 활용: 대용량 데이터를 효율적으로 읽어오기 위해 Parquet 형식을 선호합니다.
pandas의read_parquet()함수를 사용하면 됩니다. - Pandas 조작: 데이터 프레임을 요리하는 데 있어
pandas라이브러리는 가히 독보적인 표준 도구입니다.
14.4.3 예측 모델 구축 (Model)
- scikit-learn: 파이썬 머신러닝의 중심축입니다. 회귀, 분류, 클러스터링 등 거의 모든 고전적인 예측 알고리즘이 이 안에 들어있습니다.
- TensorFlow: 조금 더 깊고 복잡한 패턴을 파악해야 한다면 구글이 만든 딥러닝 프레임워크인 텐서플로(TensorFlow)가 강력한 답이 될 수 있습니다.
14.5 챕터 연습 문제
📝 심화 연습
- (데이터 설계) 다음과 같은 흥미로운 시나리오를 고려해 보십시오. 당신은 삼촌과 매일같이 다트(Darts) 게임을 즐깁니다. 각 라운드는 다트 3개를 던져서 그 점수를 합산(예: 3, 5, 10이면 18점)하는 방식입니다. 자비로운 삼촌은 당신이 5점 미만의 형편없는 점수를 맞히면 “못 본 걸로 할게”라며 다시 던질 찬스를 한 번 더 줍니다. 매일 15라운드를 플레이한다고 가정해 봅시다. 이 게임 데이터가 컴퓨터의 데이터 프레임(Table)에 어떤 행(Row)과 열(Column)로 기록될지 상상하여 스케치해 보십시오. 또한, 수집된 모든 관측치를 한눈에 보여줄 수 있는 가장 멋진 통계 그래프는 어떤 모양일지 그려보십시오.
- (시뮬레이션) 위 다트 게임 시나리오를 R이나 파이썬을 활용해 수학적으로 시뮬레이션하십시오. 삼촌이 다 보정해 준 ’공식 점수’와, 만약 다시 던지기 기회가 없었을 때의 ’민낯 점수’를 비교 분석해 보십시오. 당신이 구현한 시뮬레이션 코드가 제대로 작동하는지 검증하기 위한 최소 10가지 이상의 데이터 정합성 테스트 코드(Test suite)를 포함하십시오.
- (데이터 확보 기회) 현실 세계에서 이와 유사한 스포츠 통계나 예측 데이터셋을 실제로 구할 수 있는 전문적인 데이터 출처나 웹사이트를 찾아보고 그 특징을 기술해 보십시오.
- (데이터 탐색) 앞서 1번 문항에서 스케치했던 그래프를
ggplot2등을 사용하여 실제로 구현하십시오. 그다음tidymodels프레임워크를 동원해, 수집된 데이터를 바탕으로 “누가 이 게임의 최종 승자가 될지” 예측하는 예측 모델을 구축해 보십시오. - (전문적인 소통) 당신이 1번부터 4번까지 수행한 일련의 분석 과정을, 마치 데이터 분석 팀장의 보고서처럼 전문적이고 매끄러운 어조로 요약하여 두 문단 가량의 글을 작성해 보십시오.
❓ 퀴즈
(역자 주: 원본 퀴즈 내용이 비어 있는 경우, 앞선 내용을 바탕으로 학습을 돕는 질문들을 스스로 구상해 보거나 온라인 부록의 내용을 참조하십시오.)
👥 수업 활동 (팀플레이)
(역자 주: 해당 섹션은 강의실 환경에서 동료들과 함께 논의하고 아래의 실전 프로젝트 과제로 자연스럽게 연결하기 위한 브레인스토밍 단계입니다.)
🔬 실전 프로젝트 과제: NFL 쿼터백의 운명을 예측하라!
강력한 NFL 데이터 도구인 nflverse 패키지를 활용해 정규 시즌 쿼터백(QB)들의 상세 통계 데이터를 불러오는 것부터 시작하십시오. 데이터 사전을 꼼꼼히 뒤져보는 지식을 잊지 마십시오.
qb_regular_season_stats <-
load_player_stats(seasons = TRUE) |>
filter(season_type == "REG" & position == "QB")당신은 유능한 NFL 데이터 분석가로 빙의했습니다. 지금은 2023년 정규 시즌이 절반가량 흐른 ‘9주 차’ 막 끝난 시점입니다. 당신의 지상 미션은 남은 시즌(10~18주 차) 동안 각 팀이 뿜어낼 ’패싱 EPA(passing_epa)’를 가장 정확하게 때려 맞히는 최강의 예측 모델을 개발하는 것입니다.
이 장엄한 분석 과정을 Quarto 문서로 기록하십시오. 보고서에는 신뢰감을 주는 제목, 저자명, 작성 날짜, 당신의 노력이 깃든 GitHub 리포지토리 링크를 반드시 포함해야 합니다. 경영진을 설득할 수 있는 2~3페이지 분량의 설득력 있는 보고서를 상상하며 작성하십시오. 최고의 성능을 내려면 데이터의 특징을 새롭게 창조하는 피처 엔지니어링(Feature Engineering) 단계에서 승부가 갈릴 것입니다. R이든 파이썬이든 당신에게 익숙한 언어를 선택하십시오. 모델의 수식과 작동 원리를 비전공자 경영진도 이해할 수 있도록 높은 수준에서 명확히 설명하십시오. 무엇보다, 미래의 정답을 미리 컨닝하는 치명적인 데이터 누출(Data Leakage)의 함정에 빠지지 않도록 각별히 주의를 기울여 주십시오!