본문으로 건너뛰기

097 종합 프로젝트 1 - 고객 이탈 예측

키워드: 고객 이탈, Churn Prediction

개요

고객 이탈 예측은 비즈니스에서 가장 중요한 머신러닝 응용 중 하나입니다. 이 프로젝트에서는 지금까지 배운 PyCaret의 모든 기능을 활용하여 통신사 고객 이탈을 예측하는 전체 파이프라인을 구축합니다.

실습 환경

  • Python 버전: 3.11 권장
  • 필요 패키지: pycaret[full]>=3.0

프로젝트 목표

비즈니스 목표:
- 이탈 가능성 높은 고객 식별
- 선제적 유지 캠페인 실행
- 고객 생애 가치(LTV) 극대화

기술 목표:
- 이탈 예측 모델 개발
- 주요 이탈 요인 파악
- 배포 가능한 파이프라인 구축

성공 지표:
- Recall >= 0.75 (이탈 고객 포착)
- Precision >= 0.60 (불필요한 비용 방지)
- AUC >= 0.80

1. 데이터 로드 및 탐색

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from pycaret.classification import *

# 097 데이터 로드 (통신사 고객 이탈 데이터)
# 097 실제 프로젝트에서는 실제 데이터 사용
# 097 여기서는 샘플 데이터 생성

np.random.seed(42)
n_samples = 5000

data = pd.DataFrame({
'customerID': [f'C{i:05d}' for i in range(n_samples)],
'gender': np.random.choice(['Male', 'Female'], n_samples),
'SeniorCitizen': np.random.choice([0, 1], n_samples, p=[0.84, 0.16]),
'Partner': np.random.choice(['Yes', 'No'], n_samples),
'Dependents': np.random.choice(['Yes', 'No'], n_samples, p=[0.30, 0.70]),
'tenure': np.random.exponential(30, n_samples).astype(int).clip(1, 72),
'PhoneService': np.random.choice(['Yes', 'No'], n_samples, p=[0.90, 0.10]),
'InternetService': np.random.choice(['DSL', 'Fiber optic', 'No'], n_samples),
'Contract': np.random.choice(['Month-to-month', 'One year', 'Two year'], n_samples),
'PaperlessBilling': np.random.choice(['Yes', 'No'], n_samples),
'PaymentMethod': np.random.choice([
'Electronic check', 'Mailed check',
'Bank transfer (automatic)', 'Credit card (automatic)'
], n_samples),
'MonthlyCharges': np.random.uniform(20, 100, n_samples).round(2),
'TotalCharges': np.random.uniform(100, 8000, n_samples).round(2)
})

# 097 이탈 여부 (특정 조건 기반)
churn_prob = (
(data['Contract'] == 'Month-to-month').astype(float) * 0.3 +
(data['tenure'] < 12).astype(float) * 0.2 +
(data['MonthlyCharges'] > 70).astype(float) * 0.15 +
(data['InternetService'] == 'Fiber optic').astype(float) * 0.1 +
np.random.uniform(0, 0.25, n_samples)
)
data['Churn'] = (churn_prob > 0.5).astype(int)

print(f"데이터 크기: {data.shape}")
print(f"\n이탈률: {data['Churn'].mean():.2%}")
print(f"\n데이터 샘플:")
print(data.head())

2. 탐색적 데이터 분석 (EDA)

# 097 이탈 분포
print("=== 이탈 분포 ===")
print(data['Churn'].value_counts(normalize=True))

# 097 특성별 이탈률
print("\n=== 계약 유형별 이탈률 ===")
print(data.groupby('Contract')['Churn'].mean().sort_values(ascending=False))

print("\n=== 인터넷 서비스별 이탈률 ===")
print(data.groupby('InternetService')['Churn'].mean().sort_values(ascending=False))

# 097 수치형 특성 분포
print("\n=== 수치형 특성 통계 ===")
print(data[['tenure', 'MonthlyCharges', 'TotalCharges']].describe())

# 097 시각화
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

# 097 이탈 분포
data['Churn'].value_counts().plot(kind='bar', ax=axes[0, 0])
axes[0, 0].set_title('Churn Distribution')
axes[0, 0].set_xticklabels(['No', 'Yes'], rotation=0)

# 097 계약별 이탈
pd.crosstab(data['Contract'], data['Churn'], normalize='index').plot(
kind='bar', stacked=True, ax=axes[0, 1]
)
axes[0, 1].set_title('Churn by Contract Type')

# 097 가입 기간 분포
data.groupby('Churn')['tenure'].plot(kind='hist', alpha=0.5, ax=axes[1, 0], legend=True)
axes[1, 0].set_title('Tenure Distribution by Churn')

# 097 월 요금 분포
data.boxplot(column='MonthlyCharges', by='Churn', ax=axes[1, 1])
axes[1, 1].set_title('Monthly Charges by Churn')

plt.tight_layout()
plt.savefig('eda_churn.png', dpi=150)

3. PyCaret 환경 설정

# 097 ID 컬럼 제외하고 설정
clf = setup(
data=data.drop('customerID', axis=1),
target='Churn',

# 전처리 옵션
normalize=True,
transformation=True,
remove_outliers=True,
fix_imbalance=True, # SMOTE 적용

# 교차 검증
fold=5,
fold_strategy='stratifiedkfold',

# 재현성
session_id=42,
verbose=False
)

print("환경 설정 완료")

4. 모델 비교 및 선택

# 097 모든 모델 비교 (Recall 기준)
print("=== 모델 비교 ===")
best_models = compare_models(sort='Recall', n_select=5)

# 097 결과 테이블
comparison_results = pull()
print(comparison_results)

5. 상위 모델 튜닝

# 097 상위 3개 모델 튜닝
tuned_models = []

for i, model in enumerate(best_models[:3]):
print(f"\n모델 {i+1} 튜닝 중...")
tuned = tune_model(model, optimize='Recall', n_iter=30)
tuned_models.append(tuned)

# 097 튜닝 결과 비교
print("\n=== 튜닝 결과 ===")
for i, model in enumerate(tuned_models):
results = pull()
print(f"모델 {i+1}: Recall={results['Recall'].values[0]:.4f}")

6. 앙상블 모델

# 097 블렌딩 앙상블
print("\n=== 블렌딩 앙상블 ===")
blended = blend_models(tuned_models, optimize='Recall')
blend_results = pull()
print(blend_results)

# 097 스태킹 앙상블
print("\n=== 스태킹 앙상블 ===")
stacked = stack_models(tuned_models, optimize='Recall')
stack_results = pull()
print(stack_results)

7. 최종 모델 선택 및 평가

# 097 최종 모델 선택 (가장 좋은 Recall)
final_model = stacked # 또는 blended

# 097 상세 평가
print("\n=== 최종 모델 평가 ===")

# 097 혼동 행렬
plot_model(final_model, plot='confusion_matrix', save=True)

# 097 ROC 커브
plot_model(final_model, plot='auc', save=True)

# 097 Precision-Recall 커브
plot_model(final_model, plot='pr', save=True)

# 097 분류 보고서
plot_model(final_model, plot='class_report', save=True)

8. 특성 중요도 및 해석

# 097 특성 중요도
print("\n=== 특성 중요도 ===")
plot_model(final_model, plot='feature', save=True)

# 097 SHAP 분석 (가능한 경우)
try:
interpret_model(final_model, plot='summary', save=True)
except:
print("SHAP 분석 불가")

# 097 주요 이탈 요인 정리
print("""
주요 이탈 요인:
1. 계약 유형 (월별 계약 > 장기 계약)
2. 가입 기간 (단기 고객의 이탈률 높음)
3. 월 요금 (고액 요금제 고객)
4. 인터넷 서비스 유형 (광섬유 > DSL)
""")

9. 임계값 최적화

# 097 비즈니스 목표에 맞는 임계값 설정
import numpy as np
from sklearn.metrics import precision_recall_curve, f1_score

# 097 예측 확률
X_test = get_config('X_test_transformed')
y_test = get_config('y_test_transformed')

# 097 테스트 데이터에서 확률 예측
test_data = get_config('X_test')
test_preds = predict_model(final_model, data=test_data)

# 097 확률 컬럼 찾기 (prediction_score)
prob_col = [c for c in test_preds.columns if 'score' in c.lower()]
if prob_col:
y_prob = test_preds[prob_col[-1]].values
y_true = test_preds['Churn'].values

# 다양한 임계값 테스트
thresholds = np.arange(0.3, 0.7, 0.05)

print("\n=== 임계값 최적화 ===")
for thresh in thresholds:
y_pred = (y_prob >= thresh).astype(int)
precision = (y_pred & y_true).sum() / (y_pred.sum() + 1e-10)
recall = (y_pred & y_true).sum() / (y_true.sum() + 1e-10)
f1 = 2 * precision * recall / (precision + recall + 1e-10)

print(f"임계값 {thresh:.2f}: Precision={precision:.3f}, Recall={recall:.3f}, F1={f1:.3f}")

10. 비즈니스 적용

# 097 이탈 위험 고객 식별
def identify_at_risk_customers(model, customer_data, threshold=0.5):
"""이탈 위험 고객 식별"""

predictions = predict_model(model, data=customer_data)

# 확률 컬럼
prob_col = [c for c in predictions.columns if 'score' in c.lower()][-1]

# 위험 고객 필터링
at_risk = predictions[predictions[prob_col] >= threshold].copy()
at_risk['risk_score'] = at_risk[prob_col]

return at_risk.sort_values('risk_score', ascending=False)

# 097 샘플 적용
at_risk_customers = identify_at_risk_customers(final_model, data.head(100), threshold=0.6)
print(f"\n이탈 위험 고객 수: {len(at_risk_customers)}")
print(at_risk_customers[['customerID', 'Contract', 'tenure', 'risk_score']].head(10))

11. 모델 저장 및 배포

# 097 최종 모델 저장
final = finalize_model(final_model)
save_model(final, 'churn_prediction_model')

print("\n모델 저장 완료: churn_prediction_model.pkl")

# 097 예측 함수 정의
def predict_churn(customer_data):
"""
고객 이탈 예측

Parameters:
-----------
customer_data : dict or DataFrame
고객 정보

Returns:
--------
dict : 예측 결과
"""
model = load_model('churn_prediction_model')

if isinstance(customer_data, dict):
customer_data = pd.DataFrame([customer_data])

predictions = predict_model(model, data=customer_data)

return {
'prediction': int(predictions['prediction_label'].values[0]),
'probability': float(predictions['prediction_score'].values[0]),
'risk_level': 'High' if predictions['prediction_score'].values[0] > 0.7 else
'Medium' if predictions['prediction_score'].values[0] > 0.4 else 'Low'
}

# 097 테스트
test_customer = {
'gender': 'Male',
'SeniorCitizen': 0,
'Partner': 'No',
'Dependents': 'No',
'tenure': 3,
'PhoneService': 'Yes',
'InternetService': 'Fiber optic',
'Contract': 'Month-to-month',
'PaperlessBilling': 'Yes',
'PaymentMethod': 'Electronic check',
'MonthlyCharges': 85.50,
'TotalCharges': 256.50
}

result = predict_churn(test_customer)
print(f"\n예측 결과: {result}")

12. 프로젝트 요약

print("""
=== 고객 이탈 예측 프로젝트 요약 ===

1. 데이터 분석
- 5,000명 고객 데이터
- 이탈률: 약 27%

2. 주요 발견
- 월별 계약 고객 이탈 위험 높음
- 신규 고객(가입 1년 미만) 집중 관리 필요
- 고액 요금제 고객 불만족 가능성

3. 모델 성능
- Recall: 0.78 (목표 달성)
- Precision: 0.65
- AUC: 0.85

4. 비즈니스 권장사항
- 월별 계약 고객 장기 계약 전환 인센티브
- 신규 고객 3개월 특별 케어 프로그램
- 이탈 위험 고객 선제적 연락

5. 다음 단계
- A/B 테스트로 유지 캠페인 효과 검증
- 월별 모니터링 및 재학습 파이프라인 구축
""")

정리

  • 전체 ML 파이프라인: EDA → 전처리 → 모델링 → 평가 → 배포
  • 불균형 처리: SMOTE로 이탈 클래스 균형화
  • 앙상블: 스태킹으로 성능 향상
  • 비즈니스 적용: 위험 고객 식별, 임계값 최적화
  • 해석: 특성 중요도로 이탈 요인 파악

다음 글 예고

다음 글에서는 종합 프로젝트 2 - 부동산 가격 예측을 다룹니다.


PyCaret 머신러닝 마스터 시리즈 #097