# 게임 유저 샘플 데이터셋

결측치·이상치 처리 예제용으로 **직접 만든 가상 데이터**다. 실제 게임사 데이터가 아니다.

생성 스크립트: `scripts/generate-game-users-dataset.py` (시드 고정, 몇 번 돌려도 같은 파일)

| 파일 | 행 수 | 설명 |
|---|---|---|
| `game-users.csv` | 4,030 | 결측·이상치·중복이 심어진 원본 |
| `game-users-clean.csv` | 4,000 | 정답지. 결측·오류가 없는 상태 |

## 불러오기

```python
import pandas as pd

URL = "https://1000page.pages.dev/data/game-users.csv"
df = pd.read_csv(URL)
```

## 컬럼

| 컬럼 | 타입 | 설명 |
|---|---|---|
| `user_id` | str | 유저 식별자. **30건 중복** |
| `signup_date` | date | 가입일. 미래 날짜 3건(서버 시간 오류) |
| `age` | int | 나이. 결측 5% |
| `gender` | cat | `M`/`F`/`N`. 결측 15%, **표기 혼재** |
| `country` | cat | `KR`/`JP`/`US`/`TW`/`DE`. 결측 2%, **표기 혼재** |
| `platform` | cat | `PC`/`Mobile`/`Console` |
| `total_playtime_hours` | float | 누적 플레이타임 |
| `avg_session_min` | float | 평균 세션 길이(분) |
| `level` | int | 캐릭터 레벨 |
| `total_purchase_krw` | int | 누적 결제액(원). 72%가 0 |
| `last_login_date` | date | 마지막 접속일. 결측 37% |
| `churn_flag` | int | 이탈 여부(1=이탈) |
| `review_score` | float | 리뷰 평점 1.0~5.0. 결측 54% |
| `review_text_len` | int | 리뷰 글자 수. 결측 54% |

## 심어둔 결측 패턴

기준일(스냅샷)은 **2026-08-01**이다.

### MCAR — `age`, `gender`, `country`

아무 이유 없이 무작위로 빈다. 다른 열로 설명되지 않는다.

### MAR — `last_login_date`

이탈 유저에서 결측률이 급증한다. **`churn_flag`라는 관측된 열로 설명 가능**하다.

| churn_flag | 결측률 |
|---|---|
| 0 (잔존) | 6.4% |
| 1 (이탈) | 62.7% |

### MNAR — `review_score`, `review_text_len`

**불만족한 유저일수록 리뷰를 쓰지 않는다.** 값 자체가 결측 여부를 결정하므로
관측된 데이터만으로는 복구할 수 없다. 관측된 평점 분포가 3.5점에 몰려 있고
1점은 14건뿐인데, 이건 실제로 불만족 유저가 적어서가 아니라 **그들이 사라졌기 때문**이다.

두 열은 **항상 함께 결측**된다(리뷰를 안 썼으니 길이도 없다).
`missingno.heatmap()`에서 상관계수 **1.0**이 나온다.

```python
import missingno as msno
msno.matrix(df)    # review 두 열의 흰 줄이 같은 위치
msno.heatmap(df)   # review_score ↔ review_text_len = 1.0
```

## 심어둔 이상치

**지우면 안 되는 것과 지워야 하는 것을 섞어 놨다.** IQR로 일괄 절단하면
고래 유저 같은 진짜 데이터까지 날아간다.

### 정당한 극단값 — 지우면 안 됨

| 항목 | 건수 | 내용 |
|---|---|---|
| 고래 유저 | 12 | 결제액 300만~1,200만원 |
| 매크로 봇 | 15 | 플레이타임 8,000~21,000시간인데 레벨은 20~60 |

봇은 `level / sqrt(playtime)` 비율로 잡으면 일반 유저와 분리된다.
단변량 이상치 탐지로는 안 잡히고 **두 열의 관계를 봐야** 보인다.

### 입력 오류 — 지워야 함

| 항목 | 건수 | 내용 |
|---|---|---|
| 나이 오류 | 18 | 1~5세, 120·150·199·999세, 음수 |
| 미래 가입일 | 3 | 스냅샷 이후 날짜 |
| 세션 0분 | 40 | 접속 직후 튕긴 로그 |
| 음수 플레이타임 | 6 | 집계 버그 |

### 표기 혼재 — 정규화 대상

```text
gender   M / m / 남 / male / Male / F / f / 여 / female / N
country  KR / kr / Korea / korea / 대한민국 / " KR " / US / us / USA / United States / ...
```

`" KR "` 처럼 **앞뒤 공백이 있는 값**도 섞어 놨다. `.str.strip()`을 빠뜨리면 안 잡힌다.

## 재생성

```bash
python scripts/generate-game-users-dataset.py
```
