오늘은 어제 중고차 가격 예측에 사용한 결정 트리를 랜덤 포레스트로 확장하고, 통신사 고객 이탈 데이터로 분류 실습을 진행했다. 여러 트리의 결과를 합치는 방식과 특성 중요도를 확인했다.
어제 전처리한 중고차 데이터와 같은 학습·테스트 분할을 사용했다. 랜덤 포레스트는 여러 결정 트리를 학습하고, 회귀에서는 각 트리의 예측값을 평균 내어 최종 값을 구한다.
rf = RandomForestRegressor(random_state=2026)
rf.fit(X_train, y_train)
y_pred4 = rf.predict(X_test)
rmse = root_mean_squared_error(y_pred4, y_test)
이번에는 표준화 전의 입력값으로 학습했다. 테스트 R²는 약 0.943, RMSE는 약 0.754였다. 같은 테스트 데이터에서 확인한 오차는 아래와 같다.
| 모델 | 테스트 RMSE |
|---|---|
| 기본 결정 트리 | 1.179 |
| 선형 회귀 | 1.107 |
| 랜덤 포레스트 | 0.754 |
특성 중요도에서는 신차 가격인 Present_Price가 약 0.902로 가장 높았고, 차량 나이인 Vehicle_Age가 약 0.069로 뒤를 이었다. 이번 모델의 분할 과정에서 어떤 특성이 많이 사용됐는지 살펴볼 수 있었다.
통신사 고객 데이터는 7,043개 행으로, 고객 정보와 계약·서비스 이용 내역을 이용해 이탈 여부를 예측하는 문제였다. 고객 ID는 제외하고 각 컬럼의 고유값과 개수를 먼저 확인했다.
TotalCharges는 금액 컬럼인데 문자열로 읽혔다. 공백을 결측값으로 바꾼 뒤 실수형으로 변환하고 중앙값으로 채웠다.
telecom_df["TotalCharges"] = (
telecom_df["TotalCharges"]
.replace(r"^\s*$", np.nan, regex=True)
.astype(float)
)
telecom_df["TotalCharges"] = telecom_df["TotalCharges"].fillna(
telecom_df["TotalCharges"].median()
)
내 풀이에서는 성별과 Yes·No 컬럼을 직접 0·1로 변환하고, 나머지 범주형 컬럼에 더미 변수를 적용했다. 층화 추출로 데이터를 나눈 뒤 랜덤 포레스트를 학습하니 학습 정확도는 약 0.998, 테스트 정확도는 약 0.799였다. 학습 점수와 테스트 점수 사이에 차이가 있었다.
혼동 행렬과 classification_report()도 추가해, 전체 정확도와 이탈 고객을 찾아내는 성능을 함께 확인했다.
이후 같이 푼 코드에서는 배우자·부양가족 여부를 더한 FamilySize와 서비스 컬럼 중 Yes인 항목을 센 TotalServices를 추가했다. 계약 유형, 가입 기간, 월 청구액과 이탈 여부도 그래프로 비교했다.
범주형 입력값은 get_dummies()로 변환하고, 학습 5,634개와 테스트 1,409개로 나눴다. 트리 100개와 random_state=2026을 지정해 학습했다.
rf = RandomForestClassifier(
n_estimators=100,
n_jobs=-1,
random_state=2026
)
rf.fit(X_train, y_train)
y_pred = rf.predict(X_test)
print(classification_report(y_test, y_pred))
특성 중요도는 누적 청구액, 가입 기간, 월 청구액 순으로 높았다.
테스트 데이터는 두 풀이 모두 유지 고객 1,035명, 이탈 고객 374명으로 구성됐다. 아래 정밀도·재현율·F1은 이탈 고객 클래스 기준이며, 분류 보고서에 표시된 소수 둘째 자리 값을 사용했다.
| 지표 | 내 풀이 | 같이 푼 풀이 |
|---|---|---|
| 전체 정확도 | 79.91% | 79.35% |
| 이탈 정밀도 | 0.65 | 0.63 |
| 이탈 재현율 | 0.53 | 0.52 |
| 이탈 F1 | 0.58 | 0.57 |
혼동 행렬을 실제 정답 기준으로 나누면 다음과 같다.
| 분류 결과 | 내 풀이 | 같이 푼 풀이 |
|---|---|---|
| 유지 고객을 유지로 예측 | 927 | 922 |
| 유지 고객을 이탈로 예측 | 108 | 113 |
| 이탈 고객을 유지로 예측 | 175 | 178 |
| 이탈 고객을 이탈로 예측 | 199 | 196 |
이번 실행에서는 내 풀이가 이탈 고객을 3명 더 찾아냈고, 유지 고객을 이탈로 잘못 분류한 경우도 5명 적었다. 다만 내 모델은 RandomForestClassifier()로 난수 시드를 지정하지 않았고, 같이 푼 모델은 random_state=2026을 사용했다. 파생변수와 모델 설정이 함께 달라졌으므로 이 차이를 파생변수 추가 효과로만 해석하기는 어렵다.
두 풀이 모두 실제 이탈 고객의 약 절반을 놓쳤다. 정확도가 약 80%여도 이탈 고객을 찾는 목적에서는 재현율을 함께 확인해야 한다. 다음 비교에서는 모델의 난수 시드와 설정을 맞춘 뒤 파생변수 유무에 따른 결과를 확인해 봐야겠다.