최종 실습: 게임 플레이 데이터 분석과 간단한 예측 모델 보고서 완성
22강. 최종 실습: 게임 플레이 데이터 분석과 간단한 예측 모델 보고서 완성
1. 이번 강의에서 해결할 문제
분석 코드, 그래프와 모델 점수가 흩어져 있으면 어떤 데이터와 규칙으로 결론이 나왔는지 검토하기 어렵습니다. 입력부터 제안과 한계까지 한 번에 재현해야 합니다.
2. 학습 목표
3. 핵심 개념
최종 결과물은 높은 모델 점수가 아니라 재현 가능한 근거 묶음입니다. 데이터 사전, 품질 전후, 세 가지 이상 변수 분석, 두 집계표, 두 그래프, 개선 제안, 분리된 테스트 평가, 기준선, 한계·편향·추가 데이터·개인정보 체크를 연결합니다. 모델은 실제 서비스 배포 대상이 아닙니다.
4. 단계별 실습
1. 예제 파일 작성
실행 환경: 코드 편집기
실행 위치: C:\dev\game-data-lab
실행 전 확인: 기존 파일이 있으면 내용을 덮어쓰지 말고 Git diff로 이번 강의 변경 범위를 확인합니다. 실제 개인정보·API Key·토큰은 사용하지 않습니다.
대상 파일: src/final_report.py
from pathlib import Path
import json
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, precision_score, recall_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
SOURCE = Path("data/raw/game_sessions_model.csv")
TABLES, FIGURES = Path("reports/tables"), Path("reports/figures")
TABLES.mkdir(parents=True, exist_ok=True); FIGURES.mkdir(parents=True, exist_ok=True)
df = pd.read_csv(SOURCE, parse_dates=["match_date"])
quality = {
"rows": len(df),
"missing": df.isna().sum().astype(int).to_dict(),
"duplicate_sessions": int(df.duplicated("session_id").sum()),
"play_minutes_outside_1_180": int((~df["play_minutes"].between(1, 180)).sum()),
}
df = df.drop_duplicates("session_id").dropna(subset=["game_mode", "play_minutes", "score", "win", "character"])
df = df[df["play_minutes"].between(1, 180)].copy()
character = df.groupby("character").agg(matches=("win", "size"), win_rate=("win", "mean"), avg_score=("score", "mean")).reset_index()
mode = df.groupby("game_mode").agg(matches=("win", "size"), median_minutes=("play_minutes", "median"), avg_score=("score", "mean")).reset_index()
character.to_csv(TABLES / "character_summary.csv", index=False)
mode.to_csv(TABLES / "mode_summary.csv", index=False)
fig, ax = plt.subplots(); ax.bar(character["character"], character["win_rate"]); ax.set(ylim=(0, 1), title="Win rate by character", ylabel="win rate"); fig.tight_layout(); fig.savefig(FIGURES / "character_win_rate.png", dpi=150); plt.close(fig)
fig, ax = plt.subplots(); ax.scatter(df["play_minutes"], df["score"], alpha=.6); ax.set(title="Play time and score", xlabel="minutes", ylabel="score"); fig.tight_layout(); fig.savefig(FIGURES / "playtime_score.png", dpi=150); plt.close(fig)
X = df[["game_mode", "character"]]; y = df["win"].astype(int)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.25, random_state=42, stratify=y)
prep = ColumnTransformer([("cat", OneHotEncoder(handle_unknown="ignore"), ["game_mode", "character"])])
model = Pipeline([("prep", prep), ("model", LogisticRegression(max_iter=1000))]).fit(X_train, y_train)
baseline = DummyClassifier(strategy="most_frequent").fit(X_train, y_train)
def metrics(pred): return {"accuracy": round(accuracy_score(y_test, pred), 3), "precision": round(precision_score(y_test, pred, zero_division=0), 3), "recall": round(recall_score(y_test, pred, zero_division=0), 3)}
evaluation = {"baseline": metrics(baseline.predict(X_test)), "logistic_regression": metrics(model.predict(X_test)), "test_rows": len(X_test)}
Path("reports/final_metrics.json").write_text(json.dumps({"quality": quality, "evaluation": evaluation}, ensure_ascii=False, indent=2), encoding="utf-8")
report = f"""# 게임 플레이 데이터 분석·예측 보고서
## 데이터 사전
한 행은 가상 플레이어의 완료 매치 한 건이다.
| 열 | 의미 | 타입·범위 |
|---|---|---|
| session_id | 가상 매치 결과 식별자 | 중복 없는 문자열 |
| player_id | 가상 플레이어 식별자 | 개인을 식별하지 않는 문자열 |
| game_mode | 게임 모드 | casual 또는 ranked |
| play_minutes | 완료 매치 플레이 시간 | 1~180 사이 정수 |
| score | 완료 매치 점수 | 0 이상 정수 |
| win | 승패 레이블 | 패배 0, 승리 1 |
| character | 사용 캐릭터 | Mage, Knight, Rogue |
| match_date | 매치 날짜 | YYYY-MM-DD |
## 품질과 분석
원본 {quality['rows']}행에서 결측·session_id 중복·1~180분 범위를 검사했다. 캐릭터 표와 모드 표, 승률 막대와 플레이 시간-점수 산점도를 생성했다. 평균만으로 결론 내리지 않고 경기 수와 중앙값을 함께 본다.
## 개선 제안
캐릭터별 승률 차이가 모드와 표본 구성에서도 유지되는지 추가 가상/내부 테스트한 뒤 작은 밸런스 실험을 한다.
## 예측과 평가
매치 시작 시점의 승패를 레이블로 정의하고, 이때 알 수 있는 모드와 캐릭터만 특성으로 사용한 뒤 train/test를 먼저 분리했다. 최종 점수와 플레이 시간은 누수 방지를 위해 특성에서 제외했다. 테스트 평가는 {evaluation}이다. 정확도뿐 아니라 정밀도와 재현율을 기준선과 비교한다.
## 한계·편향·추가 데이터
합성 규칙이 만든 패턴이므로 실제 플레이를 대표하지 않는다. 숙련도, 패치 버전, 팀 구성과 연결 품질이 추가로 필요하며 모드·캐릭터별 오류를 점검해야 한다.
## 개인정보 체크리스트
- 실제 이름·이메일·IP·토큰을 수집하지 않는다.
- 목적에 필요한 최소 열만 사용하고 보관 기간과 삭제 절차를 정한다.
- 가명 ID의 재식별·결합 위험과 접근 권한을 검토한다.
- 사용자 동의와 적용 법규는 실제 수집 전에 담당자와 확인한다.
"""
Path("reports/final_report.md").write_text(report, encoding="utf-8")
print(json.dumps(evaluation, ensure_ascii=False, indent=2))
2. 실행과 결과 확인
실행 환경: Windows PowerShell
실행 위치: C:\dev\game-data-lab
실행 전 확인: Python 코드는 프로젝트의 .venv 인터프리터를 사용하고 입력 파일 경로가 존재하는지 확인합니다.
대상: src/final_report.py과 이 강의가 만드는 출력
Set-Location C:\dev\game-data-lab
.\.venv\Scripts\python.exe .\src\generate_model_data.py
.\.venv\Scripts\python.exe .\src\final_report.py
Get-ChildItem .\reports\tables,.\reports\figures
Get-Content .\reports\final_report.md
예상 결과: 집계 CSV 2개, PNG 그래프 2개, 품질·평가 JSON과 분석·제안·한계·개인정보 체크가 담긴 최종 보고서가 생성됩니다.
5. 분석 또는 모델이 동작하는 이유
하나의 스크립트가 고정 입력에서 품질 검사, 집계, 시각화, 분리, 전처리, 기준선·모델 평가와 보고서 생성을 반복합니다. Pipeline은 테스트 정보 누수를 줄이며 출력 파일은 주장과 근거를 함께 검토하게 합니다.
6. 자주 하는 실수와 해결법
7. 직접 실습
8. 이해 점검 질문 3개
9. 핵심 요약
최종 실습: 게임 플레이 데이터 분석과 간단한 예측 모델 보고서 완성 미니 퀴즈
선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.
학습을 마쳤나요?
직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.