KKBox μμ μ€νΈλ¦¬λ° μλΉμ€μ κ³ κ° μ΄ν(Churn) μμΈ‘ νλ‘μ νΈμ λλ€.
KKBoxλ μμμ μ΅λμ μμ μ€νΈλ¦¬λ° μλΉμ€μ λλ€. μ΄ νλ‘μ νΈλ μ¬μ©μμ ꡬλ νλ λ°μ΄ν°λ₯Ό λΆμνμ¬ λ€μ λ¬μ ꡬλ μ ν΄μ§ν κ°λ₯μ±μ΄ μλ μ¬μ©μλ₯Ό μμΈ‘νλ κ²μ λͺ©νλ‘ ν©λλ€.
- μ΄ν μμΈ‘: μ΄ν κ°λ₯μ±μ΄ λμ κ³ κ°μ μ¬μ μ νμ
- νκ² λ§μΌν : μμΈ‘λ μ΄ν κ³ κ°μκ² λ§μΆ€ν νλ‘λͺ¨μ μ 곡
- μμ΅ λ³΄νΈ: κ³ κ° μ΄νλ‘ μΈν λ§€μΆ μμ€ λ°©μ§
Kaggle - KKBox Churn Prediction Challenge
# μ μ₯μ ν΄λ‘
git clone <repository-url>
cd KKBox
# Python λ²μ νμΈ (3.13 μ΄μ νμ)
python --version
# μμ‘΄μ± μ€μΉ (uv μ¬μ©)
uv syncKaggleμμ λ°μ΄ν°λ₯Ό λ€μ΄λ‘λνμ¬ data/csv/ ν΄λμ λ°°μΉν©λλ€:
data/csv/
βββ raw_train_v1.csv
βββ raw_train_v2.csv
βββ raw_transactions_v1.csv
βββ raw_transactions_v2.csv
βββ raw_user_logs_v1.csv
βββ raw_user_logs_v2.csv
βββ raw_members_v3.csv
# μ μ²λ¦¬ νμ΄νλΌμΈ μ€ν
uv run python src/main.py
# νΌμ² μμ§λμ΄λ§ μ€ν
uv run python src/feature-engineer.pyKKBox/
βββ README.md # νλ‘μ νΈ μ€λͺ
μ (νμ¬ νμΌ)
βββ pyproject.toml # νλ‘μ νΈ μ€μ λ° μμ‘΄μ±
βββ src/
β βββ main.py # λ°μ΄ν° μ μ²λ¦¬ νμ΄νλΌμΈ
β βββ feature-engineer.py # νΌμ² μμ§λμ΄λ§ νμ΄νλΌμΈ
β βββ code-guide.md # μ½λ μ€νμΌ κ°μ΄λ
βββ data/
β βββ csv/ # μλ³Έ CSV νμΌ (gitignore)
β βββ parquet/ # μ²λ¦¬λ Parquet νμΌ (gitignore)
β βββ analysis/ # λΆμ κ²°κ³Ό (ννΈλ§΅, μ°¨νΈ λ±)
β βββ data.duckdb # DuckDB λ°μ΄ν°λ² μ΄μ€ (gitignore)
βββ .venv/ # Python κ°μνκ²½ (gitignore)
[CSV νμΌλ€]
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. CSV β DuckDB λ‘λ β
β β’ λμ©λ CSVλ₯Ό μ²ν¬ λ¨μλ‘ μ½μ΄ DuckDB ν
μ΄λΈλ‘ μ μ₯ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. ν
μ΄λΈλͺ
μ κ·ν β
β β’ train β train_v1 (λ²μ λͺ
μ) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. v1/v2 λ³ν© β
β β’ train_v1 + train_v2 β train_merge β
β β’ transactions, user_logs, members λμΌνκ² μ²λ¦¬ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 4. user_id λ§€ν μμ± β
β β’ msno(λ¬Έμμ΄ ν΄μ) β user_id(μ μ) λ³ν β
β β’ λ©λͺ¨λ¦¬ ν¨μ¨ λ° μ‘°μΈ μ±λ₯ ν₯μ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 5. λ°μ΄ν° νν°λ§ β
β β’ 5-1. κ³΅ν΅ user_id κ΅μ§ν© νν°λ§ β
β β’ 5-2. κΈ°μ€ ν
μ΄λΈ κΈ°λ° νν°λ§ β
β β’ 5-3. Churn μ μ΄ κΈ°λ° νν°λ§ (v1=0 β v2=0/1) β
β β’ 5-4. μ€λ³΅ νΈλμμ
μ μ μ μΈ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 6~8. λ°μ΄ν° λ³ν β
β β’ gender: male/female β 0/1 μ μ λ³ν β
β β’ λ μ§: YYYYMMDD μ μ β DATE νμ
λ³ν β
β ⒠컬λΌλͺ
: msno β user_id ν΅μΌ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 9. Parquet λ΄λ³΄λ΄κΈ° β
β β’ μμΆλ Parquet ν¬λ§·μΌλ‘ μ μ₯ (zstd) β
β β’ ML λͺ¨λΈ νμ΅μ λ°λ‘ μ¬μ© κ°λ₯ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| ν μ΄λΈλͺ | μ€λͺ | μ£Όμ μ»¬λΌ |
|---|---|---|
train_merge |
νμ΅ λ μ΄λΈ (μ΄ν μ¬λΆ) | user_id, is_churn |
transactions_merge |
κ²°μ νΈλμμ κΈ°λ‘ | user_id, payment_method_id, plan_list_price, transaction_date |
user_logs_merge |
μΌλ³ μ¬μ© λ‘κ·Έ | user_id, date, num_25, num_50, num_75, num_100, total_secs |
members_merge |
νμ μ 보 | user_id, city, bd (λμ΄), gender, registered_via |
| ν¨μλͺ | μ€λͺ | μΆλ ₯ |
|---|---|---|
analyze_churn_transition() |
v1βv2 μ΄ν μν μ μ΄ λΆμ | 3x3 ννΈλ§΅ |
analyze_duplicate_transactions() |
μ€λ³΅ νΈλμμ μ μ λΆμ | νμ΄ μ°¨νΈ |
analyze_feature_correlation() |
νΌμ² κ° μκ΄κ΄κ³ λΆμ | μκ΄κ΄κ³ ννΈλ§΅ |
- ꡬλ μλΉμ€μμ κ³ κ°μ΄ ꡬλ μ ν΄μ§νλ κ²
is_churn = 1: μ΄νν¨ (ꡬλ ν΄μ§)is_churn = 0: μ μ§ν¨ (ꡬλ μ§μ)
- v1: 2017λ 2μ κΈ°μ€ λ°μ΄ν°
- v2: 2017λ 3μ κΈ°μ€ λ°μ΄ν°
- 2μ(v1)μ μ μ§νλ κ³ κ°μ΄ 3μ(v2)μ μ΄ννλ ν¨ν΄μ μμΈ‘
num_25: 곑μ 25% λ―Έλ§ μ¬μ ν μ€ν΅ν νμnum_50: 곑μ 25~50% μ¬μ ν μ€ν΅ν νμnum_75: 곑μ 50~75% μ¬μ ν μ€ν΅ν νμnum_985: 곑μ 75~98.5% μ¬μ ν μ€ν΅ν νμnum_100: 곑μ λκΉμ§ μ¬μν νμnum_unq: μ¬μν κ³ μ 곑 μtotal_secs: μ΄ μ¬μ μκ° (μ΄)
- νμΌ κΈ°λ° λΆμμ© λ°μ΄ν°λ² μ΄μ€
- SQLiteμ²λΌ κ°λ³μ§λ§ λμ©λ λΆμμ μ΅μ ν
- λ³λ μλ² μ€μΉ μμ΄ νμΌ νλλ‘ λμ
| κ΅¬λΆ | κΈ°μ | λ²μ |
|---|---|---|
| Language | Python | 3.13+ |
| Package Manager | uv | - |
| Database | DuckDB | 1.4+ |
| Data Processing | Pandas | 2.3+ |
| Visualization | Matplotlib, Seaborn | - |
| Progress Bar | tqdm | - |
νλ‘μ νΈμ μ½λ μ€νμΌμ src/code-guide.mdλ₯Ό μ°Έκ³ νμΈμ.
μ£Όμ κ·μΉ:
- νμ ννΈ νμ
- Google μ€νμΌ Docstring μ¬μ©
- λ‘κΉ
: μμ
μμ/μλ£ μ
=== μμ λͺ ===νμ - μ«μ ν¬λ§·:
{value:,}(μ² λ¨μ μ½€λ§)
src/main.pyμ __main__ λΈλ‘μμ μ£Όμμ ν΄μ νμ¬ νΉμ λ¨κ³λ§ μ€νν μ μμ΅λλ€:
if __name__ == "__main__":
DB_PATH = "data/data.duckdb"
# 1λ¨κ³λ§ μ€ννλ €λ©΄:
load_csv_to_duckdb(...)
# λλ¨Έμ§λ μ£Όμ μ²λ¦¬
# rename_tables_add_v1_suffix(...)
# create_merge_tables(...)- μ μ ν μΉμ λ²νΈ λΆμ¬ (μ: 5-8, 6 λ±)
- ꡬλΆμ μΆκ°:
# ============================================================================ # μΉμ λ²νΈ. ν¨μ μ€λͺ # ============================================================================
- νμ ννΈμ Docstring μμ±
- μμ μ /ν λ‘κ·Έ μΆλ ₯
- μ΄λ―Έμ§:
data/analysis/ - CSV:
data/analysis/
- λ¬Έμ : μΌλΆ νμμ
total_secsκ° INT64 μ΅λκ° (9.22e+15) - μμΈ: μλ³Έ λ°μ΄ν° μ€λ₯ λλ μ€λ²νλ‘μ°
- ν΄κ²°:
nullify_out_of_range()ν¨μλ‘ 0~86400 λ²μ λ° κ°μ NULL μ²λ¦¬
- μ£Όμ:
bdλ μλ (birth year)μ΄ μλλΌ λμ΄(age) bd = 0: λμ΄ μ 보 μμ (κ²°μΈ‘)bd = 27: 27μΈ
gender = -1: μ±λ³ μ 보 μμgender = 0: μ¬μ±gender = 1: λ¨μ±
μ 체 νμ΄νλΌμΈ μ€ν μ μ½ 30λΆ~1μκ° μμ (νλμ¨μ΄μ λ°λΌ λ€λ¦)
μ²λ¦¬λ data.duckdb νμΌμ μ½ 35GBμ
λλ€. SSD μ¬μ©μ κΆμ₯ν©λλ€.
src/main.pyμμ ν΄λΉ ν¨μμ μ£Όμλ§ ν΄μ νκ³ μ€ννμΈμ.
chunksizeνλΌλ―Έν°λ₯Ό μ€μ΄μΈμ (κΈ°λ³Έ 1,000,000 β 500,000)sample_sizeμ΅μ μΌλ‘ μνλ§νμ¬ λΆμ
μ΄ νλ‘μ νΈλ κ΅μ‘ λ° μ°κ΅¬ λͺ©μ μΌλ‘ μ¬μ©λ©λλ€. λ°μ΄ν°λ Kaggle Competition Rulesλ₯Ό λ°λ¦ λλ€.
- 2026 QI AI Winter ν