![](https://velog.velcdn.com/images/xswer19/post/373bc9d2-fee1-42e1-afc2-68b49a7ec3c7/image.png)
- 데이터 분석 관련 첫번 째 프로젝트를 진행했다.
- 처음에는 솔직히...Kaggle데이터 줘서 아쉬움이 컸지만(다른 수강생 역시 아쉬움을 표현했다.), 막상 해보니 정말 치열하고 이 데이터로 이런 인사이트를 발견할 수 있구나? 싶었다
- 나는 넌 내게 빠조의 조장으로서 역할을 수행했다.
- 우리조는 연령을 중심으로 특성을 분석하고 AI모델을 구축했다.
![](https://velog.velcdn.com/images/xswer19/post/96d1ecfb-5cd3-4203-9f3d-7fcb58ae064e/image.png)
- 먼저 데이터는 28개의 칼럼과 10만개의 데이터세트를 통해 분석을 진행했다.
- 해당 데이터의 가장 큰 특징은 한 명의 고객에 대한 8달의 데이터가 쌓여있다는 것이다.
![](https://velog.velcdn.com/images/xswer19/post/b4e3f16d-e417-4c06-8237-a0275ebc0ffb/image.png)
- 칼럼의 가독성을 높이기 위해 먼저 영어 칼럼을 한국어로 바꿨다.
데이터 전처리
- 데이터 전처리 작업에 많은 시간을 투자했다.
- 금액과 관련된 데이터가 많기에 모델의 성능을 향상을 중점으로 전처리 작업을 시작했다.
- 데이터 전처리의 경우 각 칼럼을 팀원과 나누고 각자 한 작업을 내가 모아서 데이터세트에 적용 후 csv파일로 공유했다.
![](https://velog.velcdn.com/images/xswer19/post/5b25f672-6355-48b7-bf97-b2cac59b601b/image.png)
- 먼저 '월'이 영어로 되어 있어 숫자로 데이터를 전처리했다.
![](https://velog.velcdn.com/images/xswer19/post/238b0bcf-bdb7-4d78-bc92-3d4c8462d026/image.png)
- 이후 나이 데이터에서 문자형을 삭제하고, int형으로 바꾸며 57세 이상의 경우 value_counts확인 결과 1~3개 즉, 8개의 이하 데이터로서(한 명의 고객이 8개 데이터가 있기에 최소 8개가 있어야한다.) 이상치로 판단하고 -1로 변환 후 고객 ID별(8개의 데이터에 대한 Key값) 최빈값으로 변환했다.
![](https://velog.velcdn.com/images/xswer19/post/37d6de60-d2db-4ce0-9321-b9d027dd6d67/image.png)
- 다음은 연체 지불횟수 데이터의 전처리이다.
- 먼저 문자열을 삭제하고, 빈값은 -1로 채운 후 int형으로 데이터를 바꿨다.
- 또한 전체 데이터의 1~3%에 해당하는 25이상의 연체 지불횟수는 -1로 변환 후 고객 ID별 최빈값으로 변환했다.
![](https://velog.velcdn.com/images/xswer19/post/ca8cb094-09c2-405a-a008-c0e7f0b791fc/image.png)
- 카드 한도 변경의 경우 특수 문자를 제거하고, 숫자가 아닌 경우 Nan값으로 대체하고 빈값은 -1로 다시 바꿨다. 이후 float타입으로 변환 후 고객 ID별 최빈값으로 변환했다.
- 소수 데이터로서 월마다 데이터의 값이 다르기 때문에 최빈값이 아닌 평균값으로 변환했다.
![](https://velog.velcdn.com/images/xswer19/post/00fd7fb9-1ac3-49e9-bad7-0fa49895ec09/image.png)
- 월별 투자금액의 경우 문자형을 삭제하고, 전체 데이터의 1~3%에 해당하는 데이터를 이상치로 판단 후 고객 ID별 중앙값으로 대체했다.
- 다른 데이터 또한 비슷한 방법으로 전체 데이터의 1~3%에 해당하며 이상치로 판단 후 고객 ID별 중앙값, 최빈값, 평균값으로 전처리를 작업했다.
![](https://velog.velcdn.com/images/xswer19/post/e4baf137-134e-4917-a5d6-074b43c490a6/image.png)
- 대출 종류의 경우 string형태로 총 10개의 고유값이 있는 칼럼인데, 예를 들어 자동차 할부, 자동차 할부, 주택 담보 대출 3개의 대출 종류가 있으면 각 칼럼을 만들어 카운터를 세는 원핫인코딩 방식으로 전처리 작업을 했다.
![](https://velog.velcdn.com/images/xswer19/post/d18d124e-dfdc-4eba-b729-4e5477cdd066/image.png)
- 위 이미지를 참고하면 이해하기에 쉬울 것 같다.
![](https://velog.velcdn.com/images/xswer19/post/bdc353c2-7f6e-40f1-9c3b-48da015a8f54/image.png)
- 카드 발급 날짜 이후의 경우 시계열 데이터 성격으로 중간에 NaN값이 있는 경우 이전 혹은 이후 데이터에 -1 or +1하는 방식으로 데이터를 전처리했다.
모델 평가 및 EDA
st = StandardScaler()
knn_model = joblib.load("./model/knn_best_model.h5")
df_model = pd.read_csv("./Dataset/train_preprocessing.csv")
df_model.drop(columns="Unnamed: 0", inplace=True)
x = df_model.drop(columns=["신용점수"])
y = df_model['신용점수']
x_train, x_valid, y_train, y_valid = train_test_split(x, y, test_size=0.2, random_state=13)
x_valid_drop = x_valid.drop(columns="고객ID")
x_valid_drop.shape
le = LabelEncoder()
y_valid_le = le.fit_transform(y_valid)
x_valid_drop_scale = st.fit_transform(x_valid_drop)
y_pred = knn_model.predict(x_valid_drop_scale)
print(accuracy_score(y_valid, y_pred))
print(confusion_matrix(y_valid, y_pred))
print(classification_report(y_valid, y_pred))
- autoML를 통한 성능 확인 결과 knn모델이 가장 높은 성능을 보였기에 optuna를 통해 구축한 model성능을 평가했다.
![](https://velog.velcdn.com/images/xswer19/post/4310b3b1-68ad-4a87-89c4-9d27d696545e/image.png)
- 결과에 EDA를 진행한 결과 Good과 Poor에 대한 오분류가 적지 않다.
- 고객의 입장에서 Good의 신용등급인데 Poor이라고 오분류 받으면 은행에 대한 신뢰를 잃고
- 은행의 입장에서 Poor인 고객을 Good으로 오분류한다면 리스크에 대한 부담이 커지게 된다.
- 그렇기 때문에 우리팀은 Good과 Poor에 대한 오분류를 낮추는 것을 목표로 EDA진행했다.
- 중점적으로 데이터의 복잡도를 낮추기 위한 파생변수 생성 및 파생변수의 신뢰성 및 통계적 유의성 검증을 작업했다.
![](https://velog.velcdn.com/images/xswer19/post/5931e43c-51d6-4049-8098-058514fa6aef/image.png)
- 먼저 연령대에 따른 칼럼의 추세를 분석했다.
- 여기서 50대에서만 다른 추세를 보인다는 것을 발견했다.
- 이 인사이트를 발굴하기 위해...힘들었다 정말...
![](https://velog.velcdn.com/images/xswer19/post/89830dd1-774a-4633-b8ed-8cf7e4769bd0/image.png)
- 이제 나이에 대한 구체적인 heatmap을 그려본 결과 46세를 기준으로 Label값인 신용등급의 분포가 달라지는 것을 확인할 수 있다.
- 그래서 우리팀은 46세를 기준으로 다른 모델을 구축 후 학습 및 테스트한다면 어떨까?라는 생각을 했다.
- 단순히 모델만 따로 구축하는 것이 아니라 파생변수 생성을 통해서 변수를 줄이고 모델의 복잡도를 낮추고자 노력했다.
![](https://velog.velcdn.com/images/xswer19/post/f8371251-6e3f-47ea-b19d-aa7cf5f15f27/image.png)
- 먼저 연이자금액 및 총 연체기간이라는 파생변수를 만들었다.
- 기존의 모델에서 feature_importance를 확인한 결과 영향력이 높은 6개의 변수에 대해서 여러 조합을 생각한 결과 두 파생변수를 만들었다.
![](https://velog.velcdn.com/images/xswer19/post/6ec6a416-50e3-4c90-9470-c5ec218474e2/image.png)
- 그리고 개인의 상대적인 지표인 대출 상환 의지에 대한 데이터를 만들고자 노력했다.
- 소비금액과 부채 대 소득 비율이라는 두 칼럼을 만들었다.
- 부채 대 소득 비율은 내가 숨만 쉬고 월급을 부채 상환에 사용할 때 몇달이 걸릴지에 대한 지표이다.
- 소비금액은 크면서 부채 대 소득 비율은 높다면, 상대적으로 부채 상환에 대한 의지가 적으며, 반대의 경우 높다고 볼 수 있다.
![](https://velog.velcdn.com/images/xswer19/post/db12f6db-00e5-4d4c-916d-61be192369cb/image.png)
- 그리고 만든 파생변수를 각 Label범주별로 통계적의 ANOVA분석 결과 유의미한 차이, 즉 P-Value가 0.05보다 낮다는 결과를 확인했다.
![](https://velog.velcdn.com/images/xswer19/post/f1d56972-604d-4c17-b772-8c9528cbabd3/image.png)
- 이후 만든 파생변수와 다른 칼럼과의 상관성을 분석함으로써 논리적으로 해당 칼럼의 신뢰성을 확인했다.
- 신용등급이 높은 연령일수록 이자금액이 낮으며, 이자율이 낮을수록 신용등급이 낮다는 것을 확인할 수 있다.
![](https://velog.velcdn.com/images/xswer19/post/4aae052a-726f-47b7-b2af-2fdc46ee7a49/image.png)
- 다음은 총 연체 기간이다. 신용등급이 좋을수록 총 연체 기간이 낮으며, 연체 지불횟수가 늘어날수록 연체기간이 늘어나는 것을 확인하며 해당 파생변수의 신뢰성을 검증했다.
- 파생변수를 만드는 데 사용하지 않은 변수와 상관성을 시각화 결과 논리적인 결과가 나온다면 신뢰성이 있다고 판단했다.
![](https://velog.velcdn.com/images/xswer19/post/09df7ced-4775-4fb9-823e-43b5418e83c2/image.png)
- 소비금액과 다른 칼럼과 상관성을 분석한 결과 연수입이 커질수록 소비금액 커지며, 신용등급이 낮을수록 소비금액이 낮다는 것을 확인할 수 있다.
![](https://velog.velcdn.com/images/xswer19/post/eab43e74-4dba-4346-bc50-0046366ba359/image.png)
- 마지막으로 부채 대 소득 비율 역시 상관성을 보이는데
- 0~5는 0은 High_spent이며 5로 갈수록 Low_spent이다.
- 소비가 클수록 부채 대 소득이 낮을수록 높다는 것을 확인할 수 있다.
cat_model = joblib.load("./model_list/cat_best_model_for.h5")
rf_model = joblib.load("./model_list/rf_best_model_46re_under.h5")
lg_model = joblib.load("./model_list/lgbm_best_model_under46.h5")
df_model = pd.read_csv("./Dataset/df_for.csv")
x = df_model.drop(columns=["신용등급", '연령대'])
y = df_model['신용등급']
x_train, x_valid, y_train, y_valid = train_test_split(x, y, test_size=0.2, random_state=13)
x_valid_drop = x_valid.drop(columns="고객ID")
le = LabelEncoder()
y_valid_le = le.fit_transform(y_valid)
st = StandardScaler()
x_valid_drop_scale = st.fit_transform(x_valid_drop)
result_score = []
model_list = ["lg", " rf", "cat"]
models = [lg_model]
for model in models:
predictions = model.predict(x_valid_drop)
result = accuracy_score(y_valid_le, predictions)
result_score.append(result)
models = [rf_model]
for model in models:
predictions = model.predict(x_valid_drop)
result = accuracy_score(y_valid, predictions)
result_score.append(result)
predictions = cat_model.predict(x_valid)
result = accuracy_score(y_valid, predictions)
result_score.append(result)
for model_name, model_score in zip(model_list, result_score):
print(model_name, ":",model_score)
weights = [0.2, 0.4, 0.4]
model1_proba = lg_model.predict_proba(x_valid_drop) * weights[0]
model2_proba = rf_model.predict_proba(x_valid_drop) * weights[1]
model3_proba = cat_model.predict_proba(x_valid) * weights[2]
sum_probas = np.array(model1_proba) + np.array(model2_proba) + np.array(model3_proba)
avg_probas = sum_probas / 3
# 평균 확률이 가장 큰 라벨을 최종 예측으로 선택합니다.
final_predictions = np.argmax(avg_probas, axis=1)
print(accuracy_score(y_valid_le, final_predictions))
print(confusion_matrix(y_valid_le, final_predictions))
print(classification_report(y_valid_le, final_predictions))
- 다음으로는 SoftVoting방식으로 내가 구축한 cat_boost와 다른 팀워의 모델을 사용했다.
![](https://velog.velcdn.com/images/xswer19/post/290d6762-d9fb-43b6-9ede-0a9043abf72d/image.png)
- 해당 그림을 통해 과정을 확인하면 될 것 같다.
- 중간에 군집분석 또한 사용했다.
- 정확히 말하면 군집분석이 아니라 군집 칼럼을 추가했다. CatBoost의 경우 범주형 자료에 가중치를 줄 수 있기 때문에 모델의 성능 개선을 위해 군집 칼럼을 추가했다.
![](https://velog.velcdn.com/images/xswer19/post/7235ca57-b3b5-4494-951e-bf3db636b741/image.png)
- 중요도가 높은 칼럼을 바탕으로 군집을 형성 후 군집칼럼을 추가했다.
Result
![](https://velog.velcdn.com/images/xswer19/post/dd8c289e-cd48-46bf-b424-db9f6e02d169/image.png)
- Softvoting 후 결과 확인을 해보니 성능은 각 연령대별로 85%성능을 보이며 Confusion_matrix확인 결과 Good고 Poor에 대한 오분류가 3개로 확연히 낮다는 것을 확인할 수 있다.
![](https://velog.velcdn.com/images/xswer19/post/239ee50c-499e-4448-b2a8-e3b985f11831/image.png)
- 위 3가지의 기대효과를 기대할 수 있다.
끝.