10 분 소요

본 게시물은 ADP 실기의 연습문제 풀이에 대한 것이다.

머신러닝

1. 데이터 전처리

1.1 데이터의 특징을 파악 (EDA)

☀️ 데이터 개요

import pandas as pd
import numpy as np 
df= pd.read_csv("https://raw.githubusercontent.com/ADPclass/ADP_book_ver01/main/data/27_problem1.csv")
df.head()

df_desc = df.describe()
df_desc

pd.DataFrame(df_desc.loc['std',:]).sort_values(by=['std'], ascending=False)

df.info()
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   Time    1193 non-null   float64
 1   V1      1193 non-null   float64
 2   V2      1193 non-null   float64
 3   V3      1193 non-null   float64
 4   V4      1193 non-null   float64
 5   V5      1193 non-null   float64
 6   V6      1193 non-null   float64
 7   V7      1193 non-null   float64
 8   V8      1193 non-null   float64
 9   V9      1193 non-null   float64
 10  V10     1193 non-null   float64
 11  V11     1193 non-null   float64
 12  V12     1193 non-null   float64
 13  V13     1193 non-null   float64
 14  V14     1193 non-null   float64
 15  V15     1193 non-null   float64
 16  V16     1193 non-null   float64
 17  V17     1193 non-null   float64
 18  Amount  1193 non-null   float64
 19  Class   1193 non-null   int64 

📍 데이터 탐색 결과

  1. target: Class
  2. missing value: 결측치 없음
  3. outlier: amount를 제외한 표준편차 또한 일정함으로 이상치는 없어 보임, amount는 속성 상 차이가 많이 날 수 있으므로 시각화를 통해 확인
  4. scaling: amount의 범주가 다른 변수들 보다 크기에 스케일링 고려해야함
  5. feature type: 모두 float type이며 target인 class는 categorical type

☀️ 데이터 시각화

👀 시계열-꺾은선 그래프

import matplotlib.pyplot as plt

plt.plot(df.index, df['Time'])
plt.xlabel('Time')
plt.ylabel('Value')
plt.title('Line Chart')
plt.xticks(rotation=45)
plt.show()

  • Time은 Index에 따라 증가하는 단순 시간 지표이므로 변수에서 삭제
df.drop(columns=['Time'], inplace=True)

👀 수치형-히스토그램

import seaborn as sns

fig, axes = plt.subplots(nrows=3, ncols=6, figsize=(16, 5))

columns = df.columns.to_list()
columns.remove('Class')

for i, column in enumerate(columns):
    row = i // 6
    col = i % 6
    axes[row, col].hist(df[column], bins=20, color='skyblue', alpha=0.7)
    axes[row, col].set_title(f'Histogram of {column}')
    axes[row, col].set_xlabel(column)
    axes[row, col].set_ylabel('Frequency')

plt.tight_layout()

plt.show()

👀 수치형-박스플롯

fig, axes = plt.subplots(nrows=3, ncols=6, figsize=(16, 5))

for i, column in enumerate(columns):

    row = i // 6
    col = i % 6
    sns.boxplot(x=df[column], ax=axes[row, col], color='skyblue')
    axes[row, col].set_title(f'Box plot of {column}')
    axes[row, col].set_xlabel(column)
    axes[row, col].set_ylabel('Frequency')

plt.tight_layout()
plt.show()

👀 범주형-막대그래프

fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(3, 3))
target = ['Class']
for i, column in enumerate(target):

    df[column].value_counts().sort_index().plot(kind='bar', ax=axes, color='skyblue')
    axes.set_title(f'Bar Plot of {column}')
    axes.set_xlabel(column)
    axes.set_ylabel('Frequency')

plt.tight_layout()
plt.show()

👀 그룹별 데이터

  • classification 문제이기 때문에 그룹별로 데이터를 시각화하여 파악
group_class = df.groupby('Class').mean()
group_class

fig, axes = plt.subplots(3, 6, figsize=(16, 10))

columns = group_class.columns.to_list()

for i, column in enumerate(columns):
    row = i // 6
    col = i % 6
    sns.barplot(x=group_class.index, y=column, data=group_class, ax=axes[row, col])
    axes[row, col].set_title(f'Average {column}')
    axes[row, col].set_xlabel("class")
    axes[row, col].set_ylabel(f'Average {column}')

plt.tight_layout()
plt.show()

📍시각화 결과

  • 히스토그램 및 박스플롯을 확인한 결과 이상치가 있을 수 있음
  • 그러나 이상치가 비정상 거래에 영향을 주는 것일 수 있으므로 제거할 수 없음
  • 현업의 의견 없이 쉽게 제거할 수 없을 것으로 판단
  • 정상/ 비정상 거래에 따라 값의 평균 분포가 다름
  • 각 class에 따라 양수와 음수로 구분되는 특징이 있음

1.2 상관관계를 시각화하고 전처리가 필요함을 설명

👀 상관관계-scatter matrix

from pandas.plotting import scatter_matrix

scatter_matrix(df, alpha=0.5, figsize=(15,10), diagonal='hist')
plt.show()

👀 상관관계-pairplot

sns.pairplot(df, diag_kind='auto', hue='Class')
plt.show()

👀 상관관계-히트맵

plt.figure(figsize=(15, 10))
df_corr = df.corr(method='pearson')
sns.heatmap(df_corr,
             xticklabels = df_corr.columns,
             yticklabels = df_corr.columns,
             cmap='RdBu_r',
             annot=True, 
             linewidth=3,
             fmt=".2f")

df_corr[df_corr>0.9]

📍 상관관계 시각화 해석

  • 대부분 class와 높은 상관관계를 가지고 있음
  • 설명변수인 V16과 V17은 서로 강한 상관관계를 가지고 있기에 다중공선성을 제거하기 위해 전처리가 필요함
  • 선형 모델을 사용할 경우 다중공선성은 모델 성능의 저하를 가져옴
  • 트리 모델일 경우 크게 문제가 되지 않으나 컴퓨팅 파워를 저하시킬 수 있음

☀️ 차원 축소를 통한 대체

  • 상관관계가 높은 V16과 V17을 PCA를 사용해 하나의 변수로 대체함
from sklearn.decomposition import PCA

pca = PCA(n_components=1)

pca.fit(df[['V16', 'V17']])
new_V = pca.transform(df[['V16', 'V17']])
df.drop(columns=['V16', 'V17'], inplace=True)
df['V16'] = new_V

df_corr = df.corr(method='pearson')
df_corr[df_corr>0.9]

  • 차원 축소를 통해 설명변수간 높은 상관계수가 있는 것을 방지함

☀️ VIF

  • VIF 값을 확인해 10이 넘는 변수들은 제거
from statsmodels.stats.outliers_influence import variance_inflation_factor


def calculate_vif(data):
    vif_data = pd.DataFrame()
    vif_data["Variable"] = data.columns
    vif_data["VIF"] = [variance_inflation_factor(data.values, i) for i in range(data.shape[1])]
    return vif_data

vif_result = calculate_vif(df)

print(vif_result)
   Variable       VIF
0        V1  3.264282
1        V2  4.463299
2        V3  6.598955
3        V4  3.113218
4        V5  5.472737
5        V6  1.576476
6        V7  9.686757
7        V8  1.601987
8        V9  2.407308
9       V10  6.922507
10      V11  3.791752
11      V12  7.588225
12      V13  1.027083
13      V14  7.195011
14      V15  1.046278
15   Amount  3.160966
16    Class  4.141382
17      V16  4.942999

📍 상관관계를 통한 전처리 결과

  • 설명변수간 상관관계가 0.9 이하
  • 모든 VIF가 10 이하

2. 차원축소

2.1 차원축소 방법을 선택하고 이유를 설명

☀️ 주성분 분석

  • 데이터를 정확히 알지 못할 때, 주관적인 견해가 필요한 요인분석을 사용하기 어려움
  • 주성분 분석은 데이터의 선형관계만을 사용하기에 가명 처리된 데이터에 적합한 기법
X = df[df.columns.difference(['Class', 'Amount'])].copy()

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

scaler = StandardScaler()
scaled_amount = scaler.fit_transform(df[['Amount']])
X['Amount'] = scaled_amount

pca = PCA(n_components=10)  # 주성분 10개 선택
pca.fit(np.array(X))

# 주성분과 주성분의 설명된 분산 비율 확인
print("\n주성분의 설명된 분산 비율 (Explained Variance Ratio):")
print(pca.explained_variance_ratio_)

print("주성분 2개 까지의 분산 설명력: ", pca.explained_variance_ratio_[:2].sum()*100)
print("주성분 3개 까지의 분산 설명력: ", pca.explained_variance_ratio_[:3].sum()*100)

# 고유값(eigenvalues)을 x축에 대한 그래프로 플롯트합니다.
plt.figure(figsize=(8, 6))
plt.plot(range(1, len(pca.explained_variance_) + 1), pca.explained_variance_, marker='o', linestyle='--')
plt.xlabel('num of eigen value')
plt.ylabel('value')
plt.title('scree plot')
plt.grid()
plt.show()
주성분의 설명된 분산 비율 (Explained Variance Ratio):
[0.66178089 0.07876812 0.05260773 0.04012797 0.0368832  0.02135704
 0.01614583 0.01582915 0.01262278 0.01155196]
주성분 2개 까지의 분산 설명력:  74.05490151252522
주성분 3개 까지의 분산 설명력:  79.31567489552914

📍 주성분 분석 해석

  • scree plot을 통해 확인한 적절한 차원 수는 2로 나타남
  • 그러나 2차원으로 줄일시 분산은 전체 데이터의 80%를 설명하지 못함
  • 따라서 주성분 분석을 사용한 데이터는 적절하지 않음

3. 모델링

3.1 분류 모델 생성 및 성능 측정

☀️ Random Forest

from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_curve, roc_auc_score, classification_report
from sklearn.model_selection import train_test_split

y = df['Class']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y, random_state=20)
clf = RandomForestClassifier()
clf.fit(X_train, y_train)
print('train 정확도 :', clf.score(X_train, y_train), '\n')
pred=clf.predict(X_test)
print(classification_report(y_test, pred))
print('test 정확도 :', clf.score(X_test, y_test), '\n')
train 정확도 : 1.0 

              precision    recall  f1-score   support

           0       0.96      0.99      0.98       298
           1       0.96      0.82      0.88        60

    accuracy                           0.96       358
   macro avg       0.96      0.90      0.93       358
weighted avg       0.96      0.96      0.96       358

test 정확도 : 0.9636871508379888 
fpr, tpr, thresholds = roc_curve(y_test, clf.predict_proba(X_test)[:,1])
roc_auc = roc_auc_score(y_test, clf.predict_proba(X_test)[:,1])

plt.figure()
plt.plot(fpr, tpr, color='darkorange', lw=2, label='Classifer (AUC = %0.2f)' % roc_auc)
plt.plot([0,1],[0,1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.title('ROC')
plt.legend(loc='lower right')
plt.show()

print("score:", roc_auc)

score: 0.9812080536912751

📍 모델링 결과 해석

  • AUC가 0.96으로 매우 높은 값(=정확도)을 나타냄
  • 그러나 class가 1인 사기에 대해 recall이 상대적으로 낮음
  • 데이터 불균형에 따라 정확도가 높은 것으로 나올 수 있으므로 오버샘플링을 통해 확인 필요

3.2 오버샘플링을 적용한 분류 모델 생성 및 성능 측정

☀️ Random Over Sampling

from imblearn.over_sampling import RandomOverSampler 

ros = RandomOverSampler()
X_ro, y_ro = ros.fit_resample(X_train,y_train)
print('기존의 타깃 분포')
print(y_train.value_counts()/len(y_train))
print('-'*10)
print('upsampling의 타깃 분포')
print(y_ro.value_counts()/len(y_ro))
기존의 타깃 분포
0    0.832335
1    0.167665
Name: Class, dtype: float64
----------
upsampling의 타깃 분포
1    0.5
0    0.5
Name: Class, dtype: float64
clf = RandomForestClassifier()
clf.fit(X_ro, y_ro)
print('train 정확도 :', clf.score(X_ro, y_ro), '\n')
pred=clf.predict(X_test)
print(classification_report(y_test, pred))
print('test 정확도 :', clf.score(X_test, y_test), '\n')
train 정확도 : 1.0 

              precision    recall  f1-score   support

           0       0.97      0.99      0.98       298
           1       0.96      0.83      0.89        60

    accuracy                           0.97       358
   macro avg       0.96      0.91      0.94       358
weighted avg       0.97      0.97      0.97       358

test 정확도 : 0.9664804469273743 
fpr, tpr, thresholds = roc_curve(y_test, clf.predict_proba(X_test)[:,1])
roc_auc = roc_auc_score(y_test, clf.predict_proba(X_test)[:,1])

plt.figure()
plt.plot(fpr, tpr, color='darkorange', lw=2, label='Classifer (AUC = %0.2f)' % roc_auc)
plt.plot([0,1],[0,1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.title('ROC')
plt.legend(loc='lower right')
plt.show()

print("score:", roc_auc)

score: 0.9849832214765101

☀️ SMOTE

from imblearn.over_sampling import SMOTE
import warnings 
warnings.filterwarnings('ignore')

smote = SMOTE(random_state=0)
X_sm, y_sm = smote.fit_resample(X_train,y_train)

print('기존의 타깃 분포')
print(y_train.value_counts()/len(y_train))
print('-'*10)
print('upsampling의 타깃 분포')
print(y_sm.value_counts()/len(y_sm))
기존의 타깃 분포
0    0.832335
1    0.167665
Name: Class, dtype: float64
----------
upsampling의 타깃 분포
1    0.5
0    0.5
Name: Class, dtype: float64
clf = RandomForestClassifier()
clf.fit(X_sm, y_sm)
print('train 정확도 :', clf.score(X_sm, y_sm), '\n')
pred=clf.predict(X_test)
print(classification_report(y_test, pred))
print('test 정확도 :', clf.score(X_test, y_test), '\n')
train 정확도 : 1.0 

              precision    recall  f1-score   support

           0       0.97      0.99      0.98       298
           1       0.93      0.85      0.89        60

    accuracy                           0.96       358
   macro avg       0.95      0.92      0.93       358
weighted avg       0.96      0.96      0.96       358

test 정확도 : 0.9636871508379888 
fpr, tpr, thresholds = roc_curve(y_test, clf.predict_proba(X_test)[:,1])
roc_auc = roc_auc_score(y_test, clf.predict_proba(X_test)[:,1])

plt.figure()
plt.plot(fpr, tpr, color='darkorange', lw=2, label='Classifer (AUC = %0.2f)' % roc_auc)
plt.plot([0,1],[0,1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.title('ROC')
plt.legend(loc='lower right')
plt.show()

print("score:", roc_auc)

score: 0.9823825503355704

📍 오버샘플링 모델링 결과 해석

  • Random Oversampling: 전체적인 성능이 향상됨
  • SMOTE: 1(=사기)인 데이터에 대해 recall 이 증가
  • 사기를 찾는 것이 중요할 경우 SMOTE를 사용하는 것을 고려할 수 있음

4. 이상치 탐지

4.1 이상치 탐지 모델링 수행

☀️ DBSCAN

df_new = pd.concat([X, y], axis=1)
df_outlier = df_new[df_new['Class']==1]
df_new = df_new.drop(df_outlier.index)
df_com = pd.concat([df_new, df_outlier])
X_sp = df_com[df_com.columns.difference(['Class'])]
y_sp = df_com['Class']

from sklearn.neighbors import NearestNeighbors
neigh = NearestNeighbors(n_neighbors=2)
nbrs = neigh.fit(X_sp)
distances, indices = nbrs.kneighbors(X_sp)

distances
array([[0.00000000e+00, 7.79016257e-01],
       [0.00000000e+00, 3.65444216e+00],
       [8.42936970e-08, 1.59509238e+00],
       ...,
       [0.00000000e+00, 9.80715259e+00],
       [0.00000000e+00, 2.36952563e+00],
       [0.00000000e+00, 4.12129265e+00]])
distances = np.sort(distances, axis=0)
distances = distances[:,1]
plt.figure(figsize=(10,5))
plt.plot(distances)
plt.title('K-distance Graph')
plt.xlabel('Data Points sorted by distane')
plt.ylabel('Epsilon')
plt.show()

- 최적 epslion의 값은 K-distance 그래프에서 최대 곡률 지점
- minPoint는 도메인 지식에 따라 다름 (=실험적으로 수행)
from sklearn.cluster import DBSCAN

db = DBSCAN(eps=6, min_samples=35).fit(X_sp)
labels = db.labels_
data = pd.DataFrame()
data['Class'] = y_sp.copy()
data['labels'] = labels
data.labels.value_counts()
 0    998
-1    195
Name: labels, dtype: int64
  • 1은 비정상 데이터로 판별

4.2 이상치 탐지 모델 성능 평가

from sklearn.metrics import confusion_matrix
from sklearn import metrics
# 이상치를 찾으려고 하는 Positive로 두기 위해 1로 대체
data.loc[data.labels==-1, "labels"] = 1
confusion_matrix(data['Class'], data['labels'])
array([[967,  26],
       [ 31, 169]])
accuracy = metrics.accuracy_score(data.Class,data.labels)
print("정확도:", accuracy)
precision = metrics.precision_score(data.Class,data.labels)
print("정밀도:", precision)
recall = metrics.recall_score(data.Class,data.labels)
print("재현율:", recall)
f1 = metrics.f1_score(data.Class,data.labels)
print("f1 점수:", f1)
정확도: 0.9522212908633697
정밀도: 0.8666666666666667
재현율: 0.845
f1 점수: 0.8556962025316455

📍 이상치 탐지 모델 해석

  • 모든 지표가 Random Forest 모델에 비해 떨어짐

통계분석

1. 평균(대표값)

2년 전 제품 생산량이 100,000개, 1년 전 제품 생산량이 150,000개, 그 후 팩토리 기술의 상승으로 제품 생산량이 250,000개 되었을 때, 연평균 상승률의 대푯값을 구하시오(반올림하여 소수점 아래 둘째자리까지 표기)

n1 = 100000
n2 = 150000
n3 = 250000

☀️ 기하평균

  • 1년차 상승률(r1): n2/n1
  • 2년차 상승률(r2): n3/n2
  • 평균 상승률: (r1 * r2) **(1/2)
r1 = (n2)/(n1)
r2 = (n3)/(n2)
np.round(((r1 * r2)**(1/2) -1)*100, 2)
58.11

2. 가설검정 (1)

12건의 광고시간을 측정한 데이터에서, 평균은 15.5초, 분산은 3.2초였다. 이때 광고시간의 90% 신뢰구간을 구하시오.

✏️ 문제 정의

  • 단일 집단 신뢰구간 추정
  • 모분산을 모르는 경우
from scipy.stats import t

sample_size = 12
degrees_of_freedom = sample_size-1

confidence = 0.90

two_side_alpha = (1 - confidence)/2
two_side = 1 - two_side_alpha

two_side_value = t.ppf(two_side, df=degrees_of_freedom)

print(f"t 분포 95% 양측검정 백분위수: {two_side_value}")
t 분포 95% 양측검정 백분위수: 1.7958848187036691
mu_x = 15.5
var_x = 3.2
bound = two_side_value * ((var_x **(1/2))/(sample_size**(1/2)))
upper = mu_x + bound
under = mu_x - bound

print(under, upper)
14.572609067393861 16.427390932606137

3. 가설검정(2)

강의 상류와 하류의 생물 다양성 점수에 차이가 있는지 유의수준 0.1하에 검정하시오(단, 같은 강에서 상류와 하류는 서로 독립적이지 않으며, 종속적인 관계에 있음. 정규성을 만족한다고 가정).

import pandas as pd
import numpy as np
df = pd.read_csv("https://raw.githubusercontent.com/ADPclass/ADP_book_ver01/main/data/27_problem7.csv")
df

✏️ 문제 정의

  • 대응 표본 집단
  • 정규성 만족
  • 대응표본 t-test
  • 종속적이다 -> 독립표본이 아님

☀️ 가설 수립

  • H0: 다양성 점수에 차이는 없다.
  • H1: 다양성 점수에 차이는 있다.
import numpy as np
from scipy import stats

# 대응표본 데이터 설정
before_treatment = df['up'].tolist()
after_treatment = df['down'].tolist()

result = stats.ttest_rel(a=before_treatment, b=after_treatment, alternative='two-sided')

# 결과 출력
t_statistic = result.statistic
p_value = result.pvalue

print("대응표본 t-검정 통계량 (t-statistic):", t_statistic)
print("p-값 (p-value):", p_value)

alpha = 0.05  
if p_value < alpha:
    print("귀무가설을 기각합니다. 차이가 있습니다.")
else:
    print("귀무가설을 기각하지 않습니다. 차이가 업습니다.")
대응표본 t-검정 통계량 (t-statistic): 3.3526056764717995
p-값 (p-value): 0.028499777234053288
귀무가설을 기각합니다. 차이가 있습니다.

4. 가설검정(3)

지하철 호선과 월별 승객 수 간 상관관계 확인

import pandas as pd 
df = pd.read_csv("https://raw.githubusercontent.com/ADPclass/ADP_book_ver01/main/data/27_problem9.csv")
df

✏️ 문제 정의

  • line: 명목형/ month: 수치형 or 명목형/ total: 수치형
  • 요인은 월, 승객수
    • 따라서 month는 명목형
  • 이원배치 분산분석 수행
  • 각 호선당 월이 2번씩 반복되기 때문에 반복이 있는 경우

☀️ 가설 수립

  1. 상호작용
    • H0: 요인간 상호작용 없음
    • H1: 요인간 상호작용 있음
  2. 주효과
    • H0: 호선에 따라 승객수의 차이는 존재
    • H1: 호선에 따라 승객수의 차이는 존재하지 않음
    • H0: 월에 따라 승객수의 차이는 존재
    • H1: 월에 따라 승객수의 차이는 존재하지 않음
from statsmodels.formula.api import ols
from statsmodels.stats.anova import anova_lm
formula ='total ~C(line)*C(month)'
model = ols(formula, df).fit()
aov_table = anova_lm(model, typ=3)
aov_table

📍 ANOVA 해석

  • 상호 작용에 대한 가설은 p-value가 0.1보다 작기에 귀무가설 기각
    • 상호작용이 있음
  • 주효과 검정에 대한 가설 또한 p-value가 0.1보다 작으므로 귀무가설 기각
    • 각 요인은 승객수에 영향을 미침
from statsmodels.graphics.factorplots import interaction_plot
import matplotlib.pyplot as plt
## Series로 변경
total = df["total"]
line = df["line"]
month = df["month"]
fig, ax = plt.subplots(figsize=(6, 6))
fig = interaction_plot(month,line, total,ms=10, ax=ax)

👀 교호작용 시각화

  • interaction 그래프에서 Month와 Line에 따라 그래프가 교차하지 않아야 교호작용이 없는 것
  • 시각화를 통해 교차(=교호작용이 있음)를 확인함

댓글남기기