import pandas as pd
fpath = "./data/creditcard.csv"
df = pd.read_csv(fpath)
# 약 28만개 행 X 31개 컬럼으로 구성, 결측치는 없음
df.info()
## 출력 결과
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 284807 entries, 0 to 284806
# Data columns (total 31 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 Time 284807 non-null float64
# 1 V1 284807 non-null float64
# 2 V2 284807 non-null float64
# 3 V3 284807 non-null float64
# (중략)
# 26 V26 284807 non-null float64
# 27 V27 284807 non-null float64
# 28 V28 284807 non-null float64
# 29 Amount 284807 non-null float64
# 30 Class 284807 non-null int64
# dtypes: float64(30), int64(1)
# memory usage: 67.4 MB
# V1 ~ V28 컬럼은 개인정보 보호 차 수치형 변수로 비식별화되어 있음
df.head()
# 사기(1; Fraud)에 해당하는 데이터가 0.2% 정도 밖에 되지 않음
# 극심한 불균형 상태에 해당되어 향후 분석작업 유의 필요
df["Class"].value_counts(normalize=True).round(3)
## 출력 결과
# Class
# 0 0.998
# 1 0.002
# Name: proportion, dtype: float64
# 시각화
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.ticker import FuncFormatter
import warnings
warnings.filterwarnings("ignore")
plt.rcParams["font.family"] = "Liberation Sans"
count_info = df["Class"].value_counts()
class_mapper = {"0": "Normal", "1": "Fraud"}
fig, ax = plt.subplots(figsize=(4,4))
sns.barplot(count_info, ax=ax, palette={"0": "forestgreen", "1": "darkred"})
for p in ax.patches:
ax.annotate(f"{int(p.get_height()):,d}",
xy=((p.get_x()+p.get_width()/2), p.get_height()+10_000),
ha="center",
color="forestgreen" if p.get_height() > 150_000 else "darkred",
weight="bold",
size=12)
ax.set_title("Class Distribution")
ax.set_xticklabels([class_mapper.get(label.get_text()) for label in ax.get_xticklabels()])
ax.set_xlabel(None)
ax.set_ylabel(None)
ax.set_ylim(0, 320_000)
ax.yaxis.set_major_formatter(FuncFormatter(lambda val, pos: f"{int(val):,d}"))
plt.show()
X = df.drop(["Time", "Class"], axis=1) # Time 변수도 불필요하여 제거
y = df["Class"]
print(X.shape, y.shape)
## 출력 결과
# (284807, 29) (284807,)
# V1~V28 변수간에는 최소-최대값 간 편차가 심한 편으로 확인
# Amount 변수의 중위수-최대값의 차이가 상당히 커 이상치가 존재하는 것으로 보임
X.describe().round(2)
# 일부 이상치 확인되었으나, 향후 트리 기반 모델링 수행 예정으로 감안 가능한 수준으로 판단
fig, ax = plt.subplots(figsize=(20, 6))
sns.boxplot(X.loc[:,"V1":"V28"], ax=ax)
plt.show()
# 분포가 상당히 왜곡되어 있음을 알 수 있음
fig, ax = plt.subplots(figsize=(3, 6))
sns.boxplot(X["Amount"], ax=ax)
ax.set_title("Boxplot of Amount")
ax.set_xlabel(None)
ax.set_ylabel(None)
plt.show()
import numpy as np
X["Amount_Log"] = np.log1p(X["Amount"])
# 분포가 개선되었음을 확인
fig, ax = plt.subplots(figsize=(3, 6))
sns.boxplot(X["Amount_Log"], ax=ax)
ax.set_title("Boxplot of Amount (Log-Trans)")
ax.set_xlabel(None)
ax.set_ylabel(None)
plt.show()
X.drop("Amount", axis=1, inplace=True)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.3, random_state=42, stratify=y)
print("TRAIN\n", y_train.value_counts(normalize=True).round(3))
print("\nTEST\n", y_test.value_counts(normalize=True).round(3))
## 출력 결과
# TRAIN
# Class
# 0 0.998
# 1 0.002
# Name: proportion, dtype: float64
#
# TEST
# Class
# 0 0.998
# 1 0.002
# Name: proportion, dtype: float64
# 모델 평가용 함수
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, confusion_matrix
from time import time
# 지표 반환
def get_eval(y_test, pred):
acc = accuracy_score(y_test, pred)
prec = precision_score(y_test, pred)
rec = recall_score(y_test, pred)
f1 = f1_score(y_test, pred)
auc = roc_auc_score(y_test, pred)
return acc, prec, rec, f1, auc
# 지표 출력
def print_eval(y_test, pred):
acc, prec, rec, f1, auc = get_eval(y_test, pred)
conf = confusion_matrix(y_test, pred)
print("\n=== Confusion Mtx. ===")
print(conf)
print("\n=== Metrics ===")
print(f"Accuracy: {acc:.3f}")
print(f"Precision: {prec:.3f}")
print(f"Recall: {rec:.3f}")
print(f"F1-score: {f1:.3f}")
print(f"AUC: {auc:.3f}")
# 모델 적합 + 지표 출력 + 지표 반환 자동화
def auto_eval(estimator, X_train, y_train, X_test, y_test):
s_time = time()
estimator.fit(X_train, y_train)
e_time = time()
print(f"=== Fit Time ===\n{e_time-s_time:.2f}sec")
pred = estimator.predict(X_test)
print_eval(y_test, pred)
nm = estimator.__class__.__name__
acc, prec, rec, f1, auc = get_eval(y_test, pred)
res = pd.DataFrame([[acc, prec, rec, f1, auc]], columns=["Acc.", "Prec.", "Rec.", "F1", "AUC"], index=[nm])
return res
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(X_train, y_train)
pred = dt.predict(X_test)
print_eval(y_test, pred)
## 출력 결과
# === Fit Time ===
# 15.36sec
#
# === Confusion Mtx. ===
# [[85281 14]
# [ 39 109]]
#
# === Metrics ===
# Accuracy: 0.999
# Precision: 0.886
# Recall: 0.736
# F1-score: 0.804
# AUC: 0.868
from sklearn.ensemble import RandomForestClassifier
# n_estimators 조정을 통한 학습 시간 단축 (default=100)
rf = RandomForestClassifier(random_state=42, n_estimators=10, max_depth=10, n_jobs=-1)
rf_res = auto_eval(rf, X_train, y_train, X_test, y_test)
## 출력 결과
# === Fit Time ===
# 16.99sec
#
# === Confusion Mtx. ===
# [[85289 6]
# [ 34 114]]
#
# === Metrics ===
# Accuracy: 1.000
# Precision: 0.950
# Recall: 0.770
# F1-score: 0.851
# AUC: 0.885
from lightgbm import LGBMClassifier
lgbm = LGBMClassifier(random_state=42, verbose=0)
lgbm_res = auto_eval(lgbm, X_train, y_train, X_test, y_test)
## 출력 결과
# === Confusion Mtx. ===
# [[85119 176]
# [ 66 82]]
# === Metrics ===
# Accuracy: 0.997
# Precision: 0.318
# Recall: 0.554
# F1-score: 0.404
# AUC: 0.776
# 세 모델 중에서는 랜덤 포레스트의 성능이 전반적으로 우수함
all_res = pd.concat([dt_res, rf_res, lgbm_res,], axis=0)
all_res.round(3)
# SMOTE와 같은 오버샘플링 기법은 데이터 중복, 겹침 문제를 유발할 수 있어 실무 적용 시에는 신중해야 함
from imblearn.over_sampling import SMOTE
smt = SMOTE(random_state=42)
X_train_smt, y_train_smt = smt.fit_resample(X_train, y_train)
dt_res_smt = auto_eval(dt, X_train_smt, y_train_smt, X_test, y_test)
rf_res_smt = auto_eval(rf, X_train_smt, y_train_smt, X_test, y_test)
lgbm_res_smt = auto_eval(lgbm, X_train_smt, y_train_smt, X_test, y_test)
## 출력 결과: 생략
all_res_smt = pd.concat([dt_res_smt, rf_res_smt, lgbm_res_smt,], axis=0)
all_res_smt.round(3)
# 가장 나은 성능을 보인 RF 기준 변수 중요도 추출
rf.fit(X_train, y_train)
rf_fi = pd.DataFrame({"feature": X_train.columns, "importance": rf.feature_importances_})
rf_fi.sort_values(by="importance", ascending=False).head(10)
rf_fi.sort_values(by="importance", ascending=False, inplace=True)
fig, ax = plt.subplots(figsize=(20,4))
sns.barplot(x=rf_fi["feature"],
y=rf_fi["importance"],
hue=rf_fi["feature"],
ax=ax,
palette="RdBu")
ax.set_title("Feat. Imp. (Random Forest Model)")
ax.set_ylabel(None)
ax.set_xlabel(None)
plt.show()
*이 글은 제로베이스 데이터 취업 스쿨의 강의 자료 일부를 발췌하여 작성되었습니다.