채무 불이행 여부 예측 해커톤 - 최종 코드
이번에는 채무 불이행 여부 EDA에 이어서 최종 코드를 살펴보도록 하겠습니다. 지난 EDA를 통해서 데이터 왜도 보정, 파생변수 등을 하였는 데, 이를 바탕으로 제가 작성한 최종적인 코드에 대해서 설명하겠습니다. 전반적인 과정은 아래를 먼저 참고해주시면 될 것 같습니다.
* 최종 코드 주요 내용
1. Data Load
2. EDA
3. Add to New Columns
4. 왜도 보정
5. Label Encoding
6. Correlation Value Check
7. Colunms Drop
8. Data Scaling & Hold out
9. LGBM + CAT BOOST Ensemble
데이콘 링크
: https://dacon.io/competitions/official/236450/leaderboard
채무 불이행 여부 예측 해커톤: 불이행의 징후를 찾아라! - DACON
분석시각화 대회 코드 공유 게시물은 내용 확인 후 좋아요(투표) 가능합니다.
dacon.io
EDA 과정
: 2025.03.27 - [Personal Projects/Dacon] - [Dacon] 채무 불이행 여부 예측 해커톤 (2) - EDA
[Dacon] 채무 불이행 여부 예측 해커톤 (2) - EDA
채무 불이행 여부 예측 해커톤 - EDA 이번 글은 채무 불이행 여부 예측 해커톤에서 수행했던 탐색적 데이터 분석(EDA)에 대한 글입니다. 대회를 수행하면서 EDA는 데이터는 어떤 데이터이며, 컬럼
muns-da2.tistory.com
Code Review
* Computing Resource : RunPod A40 (Colab 기본 환경에서도 충분히 가능합니다.)
1. Data Load
# 학습/평가 데이터 로드
train_df = pd.read_csv('./train.csv').drop(columns=['UID'])
test_df = pd.read_csv('./test.csv').drop(columns=['UID'])
train_df.head(5)
print(train_df.columns.tolist())
데이터는 판다스를 이용하여 기본적으로 불러오는 방식을 사용하고, columns.tolist를 통해서 컬럼 명을 리스트로 뽑아 확인해봅니다.
2. EDA
# 채무 불이행 여부의 비율 확인
default_counts = train_df['채무 불이행 여부'].value_counts()
default_percentages = default_counts / default_counts.sum() * 100 # 퍼센트 계산
train_df.describe()
train_df.info()
train_df.isnull().sum()
test_df.isnull().sum()
# 바 그래프 그리기
plt.figure(figsize=(9, 7))
sns.barplot(x=default_percentages.index, y=default_percentages.values, palette='viridis')
# 그래프 제목과 레이블 추가
plt.title('채무 불이행 여부 비율 (%)')
plt.xlabel('채무 불이행 여부')
plt.ylabel('비율 (%)')
plt.xticks(ticks=[0, 1], labels=['0', '1'], rotation=0)
# y축 눈금을 퍼센트로 설정
plt.yticks(np.arange(0, 101, 10)) # 0부터 100까지 10 단위로 설정
plt.gca().set_ylim(0, 100) # y축 범위 설정
# 퍼센트 값 텍스트 추가
for i, v in enumerate(default_percentages.values):
plt.text(i, v + 1, f"{v:.1f}%", ha='center', va='bottom')
# 그래프 표시
plt.show()
# 컬럼별 시각화
for col in train_df.columns:
plt.figure(figsize=(8, 4))
if train_df[col].dtype == 'object': # 범주형 데이터
train_df[col].value_counts().plot(kind='bar', color='skyblue', edgecolor='black')
plt.ylabel('Count')
else: # 수치형 데이터
train_df[col].hist(bins=30, color='cornflowerblue', edgecolor='black')
plt.ylabel('Frequency')
plt.title(col)
plt.xlabel(col)
plt.grid(axis='y', linestyle='--', alpha=0.7)
plt.show()
# '채무 불이행 여부' 컬럼을 제외한 데이터프레임 생성
filtered_columns = train_df.drop(columns=['채무 불이행 여부'])
# '신용 점수' 컬럼과 나머지 컬럼 간의 산점도 그리기
for column in filtered_columns.columns:
if column != '신용 점수': # '신용 점수' 자신과 비교하지 않도록 제외
plt.figure(figsize=(6, 4))
sns.scatterplot(x=train_df['신용 점수'], y=train_df[column], alpha=0.5)
plt.title(f'신용 점수 vs {column}')
plt.xlabel('신용 점수')
plt.ylabel(column)
plt.grid()
plt.show()
print()
EDA에서 언급한 부분이기도 하지만, 데이터 정보 및 결측치를 확인하고 컬럼 요소를 그래프를 확인한 과정입니다. 여기서 그래프를 그리는 것은 단일 데이터를 보기 위해 Bar 그래프, 컬럼 간의 관계를 확인하기 위해 관계형 그래프인 Scatter를 사용하였습니다.
3. Add to New Columns
train_df['월 소득'] = train_df['연간 소득'] / 12
train_df['잔여 월 소득'] = train_df['월 소득'] - train_df['월 상환 부채액']
test_df['월 소득'] = test_df['연간 소득'] / 12
test_df['잔여 월 소득'] = test_df['월 소득'] - test_df['월 상환 부채액']
'연간 소득' 컬럼을 사용하여 '월 소득' 컬럼, '월 소득'과 '월 상환 부채액'을 통해 '잔여 월 소득' 컬름을 파생 변수로 추가하였습니다.
4. 왜도 보정
# 변환 전 데이터 복사
df_original = train_df.copy()
# 새로운 변환 적용
df_transformed = train_df.copy()
df_transformed["월 소득"] = np.sqrt(df_transformed["월 소득"])
df_transformed["잔여 월 소득"] = np.sqrt(df_transformed["잔여 월 소득"])
df_transformed["월 상환 부채액"] = np.sqrt(df_transformed["월 상환 부채액"])
df_transformed["신용 점수"], _ = boxcox(df_transformed["신용 점수"] + 1)
# 컬럼별 히스토그램 비교
fig, axes = plt.subplots(nrows=2, ncols=4, figsize=(16, 8))
columns = ["월 소득", "잔여 월 소득", "월 상환 부채액", "신용 점수"]
for i, col in enumerate(columns):
# 변환 전
sns.histplot(df_original[col], bins=30, kde=True, ax=axes[0, i], color="blue")
axes[0, i].set_title(f"변환 전: {col}")
# 변환 후
sns.histplot(df_transformed[col], bins=30, kde=True, ax=axes[1, i], color="red")
axes[1, i].set_title(f"변환 후: {col}")
plt.tight_layout()
plt.show()
import numpy as np
import pandas as pd
from scipy.stats import boxcox
# Box-Cox 변환을 위한 lambda 값 저장 (train 데이터에서 구한 값을 사용해야 함)
_, boxcox_lambda = boxcox(train_df["신용 점수"] + 1)
# 변환 함수 정의
def transform_data(df):
df["월 소득"] = np.sqrt(df["월 소득"])
df["잔여 월 소득"] = np.sqrt(df["잔여 월 소득"])
df["월 상환 부채액"] = np.sqrt(df["월 상환 부채액"])
df["신용 점수"] = boxcox(df["신용 점수"] + 1, lmbda=boxcox_lambda) # train에서 구한 λ 적용
return df
# train_df & test_df 변환 적용
train_df = transform_data(train_df)
test_df = transform_data(test_df)
사용하는 컬럼 중 왜도가 있는 컬럼을 선별하여 왜도 방향에 따른 보정이 들어갑니다.
5. Label Encoding
import pandas as pd
from sklearn.preprocessing import LabelEncoder
# LabelEncoder 초기화
label_encoder = LabelEncoder()
# object 타입의 컬럼 선택
object_cols = train_df.select_dtypes(include=['object']).columns
# 각 object 컬럼에 대해 Label Encoding 적용
for col in object_cols:
train_df[col] = label_encoder.fit_transform(train_df[col])
test_df[col] = label_encoder.transform(test_df[col]) # test 데이터에 대해 동일한 인코더 사용
Object 타입의 컬럼 요소 변경을 위해 수치형으로 바꾸어주는 역할을 합니다. 더미 변수화를 사용할 수도 있지만 그러면 컬럼이 너무 많아지기 때문에 Label Encoder를 사용하였습니다.
6. Correlation Value Check
# 상관관계 행렬 계산
correlation_matrix = train_df.corr()
# 타겟 변수와의 상관관계 선택
target_correlation = correlation_matrix['채무 불이행 여부']
# 정렬
sorted_target_correlation = target_correlation.sort_values(ascending=False)
# 시각화
plt.figure(figsize=(8, 6))
sns.heatmap(sorted_target_correlation.values.reshape(-1, 1), annot=True, fmt=".2f", cmap='coolwarm',
yticklabels=sorted_target_correlation.index, cbar=True)
plt.title('Sorted Correlation Heatmap with Target Variable: 채무 불이행 여부')
plt.xlabel('Correlation Coefficient')
plt.ylabel('Features')
plt.show()
7. Colunms Drop
# 훈련 데이터를 X와 y로 나누기
X = train_df.drop(columns=['채무 불이행 여부', '현재 미상환 신용액','마지막 연체 이후 경과 개월 수', '체납 세금 압류 횟수', '개인 파산 횟수', '대출 목적', '최대 신용한도', '신용 거래 연수', '개설된 신용계좌 수', '연간 소득'])
y = train_df['채무 불이행 여부']
# 테스트 데이터에서 '현재 미상환 신용액' 제외
test_df = test_df.drop(columns=['현재 미상환 신용액','마지막 연체 이후 경과 개월 수', '체납 세금 압류 횟수', '개인 파산 횟수','대출 목적', '최대 신용한도', '신용 거래 연수', '개설된 신용계좌 수', '연간 소득'], errors='ignore')
# X의 컬럼 확인
print("X의 컬럼:")
print(X.columns.tolist())
한 차례 학습 후 Feature Importance를 통해서 학습에 별로 반영이 되지 않는 컬럼을 확인하고 이후에 재 학습을 시도할 때, 컬럼 Drop을 반영합니다.
8. Data Scaling & Hold Out
import pandas as pd
import numpy as np
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
from lightgbm import LGBMClassifier
from catboost import CatBoostClassifier
from sklearn.ensemble import ExtraTreesClassifier
# 데이터 스케일링
def scale_data(X_train, X_val, test_df):
minmax_scaler = MinMaxScaler()
X_train_scaled = minmax_scaler.fit_transform(X_train)
X_val_scaled = minmax_scaler.transform(X_val)
test_scaled = minmax_scaler.transform(test_df)
return X_train_scaled, X_val_scaled, test_scaled
# 데이터 분할
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.1, random_state=42)
# 스케일링 적용
X_train_scaled, X_val_scaled, test_scaled = scale_data(X_train, X_val, test_df)
print(X_train.shape)
print(X_val.shape)
9. LGBM + CAT BOOST Ensemble
# 모델 학습 및 예측 함수
def train_and_predict_lgbm(X_train, y_train, X_val):
model = LGBMClassifier(
n_estimators=2000,
learning_rate=0.001,
random_state=42,
eval_metric='auc'
)
model.fit(X_train, y_train, eval_set=[(X_val, y_val)])
return model
def train_and_predict_catboost(X_train, y_train, X_val):
model = CatBoostClassifier(
iterations=22000,
depth=5,
learning_rate=0.0005,
random_seed=42,
verbose=500,
eval_metric='AUC'
)
model.fit(X_train, y_train, eval_set=(X_val, y_val))
return model
# 각 모델 학습 및 예측
# model_extra_trees = train_and_predict_extra_trees(X_train_scaled, y_train, X_val_scaled)
model_lgbm = train_and_predict_lgbm(X_train_scaled, y_train, X_val_scaled)
model_catboost = train_and_predict_catboost(X_train_scaled, y_train, X_val_scaled)
# 앙상블: 각 모델의 예측 결과를 평균하여 최종 예측
y_val_pred_lgbm = model_lgbm.predict_proba(X_val_scaled)[:, 1]
y_val_pred_catboost = model_catboost.predict_proba(X_val_scaled)[:, 1]
# y_val_pred_extra_trees = model_extra_trees.predict_proba(X_val_scaled)[:, 1]
# 최종 예측
y_val_pred_ensemble = (y_val_pred_lgbm + y_val_pred_catboost) / 2
# ROC-AUC 평가
val_auc_ensemble = roc_auc_score(y_val, y_val_pred_ensemble)
print(f'앙상블 검증 데이터 ROC-AUC: {val_auc_ensemble:.4f}')
# [LightGBM] [Warning] Unknown parameter: eval_metric
# 앙상블 검증 데이터 ROC-AUC: 0.6808
# 채무 불이행 '확률'을 예측합니다.
preds_lgbm = model_lgbm.predict_proba(test_scaled)[:, 1]
preds_catboost = model_catboost.predict_proba(test_scaled)[:, 1]
# preds_extra_trees = model_extra_trees.predict_proba(test_scaled)[:, 1]
# 최종 테스트 예측 앙상블
preds_ensemble = (preds_lgbm + preds_catboost) / 2
■ 마무리
여기까지 채무 불이행 여부에 대한 최종 코드를 살펴보았습니다. 이렇게 긴 여정이 끝나고 3월의 대회도 잘 마무리할 수 있었습니다. 다음으로는 4월 대회인 아이콘 이미지 데이터와 고객 세그먼트 분류 대회에 도전해보도록 하겠습니다. 그럼 다들 화이팅하시고 여기서 마무리하도록 하겠습니다.
■ 진행 중 대회
대회 1: https://dacon.io/competitions/official/236460/overview/description
신용카드 고객 세그먼트 분류 AI 경진대회 - DACON
분석시각화 대회 코드 공유 게시물은 내용 확인 후 좋아요(투표) 가능합니다.
dacon.io
대회 2 : https://dacon.io/competitions/official/236459/overview/description
이미지 분류 해커톤: 데이터 속 아이콘의 종류를 맞혀라! - DACON
분석시각화 대회 코드 공유 게시물은 내용 확인 후 좋아요(투표) 가능합니다.
dacon.io
'Personal Projects > Dacon' 카테고리의 다른 글
[Dacon] 악성 URL 분류 AI 경진대회 (2) - EDA (0) | 2025.03.31 |
---|---|
[Dacon] 악성 URL 분류 AI 경진대회 (1) (0) | 2025.03.31 |
[Dacon] 채무 불이행 여부 예측 해커톤 (2) - EDA (0) | 2025.03.31 |
[Dacon] 채무 불이행 여부 예측 해커톤 (1) - 후기 (0) | 2025.03.31 |
[Dacon] 건설공사 사고 예방 및 대응책 생성 경진 대회 (2) - Code (0) | 2025.03.26 |