8 분 소요

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

머신러닝

1. 데이터 탐색 (EDA)

import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np 
import matplotlib.pyplot as plt
import seaborn as sns
hotel = pd.read_csv("https://raw.githubusercontent.com/ADPclass/ADP_book_ver01/main/data/hotel_bookings.csv")

1.1 데이터 개요

hotel

hotel_desc = hotel.describe()
hotel_desc

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

hotel.info()
 #   Column                          Non-Null Count  Dtype  
---  ------                          --------------  -----  
 0   is_canceled                     20000 non-null  int64  
 1   deposit_type                    20000 non-null  object 
 2   lead_time                       19995 non-null  float64
 3   stays_in_weekend_nights         20000 non-null  int64  
 4   stays_in_week_nights            20000 non-null  int64  
 5   is_repeated_guest               19642 non-null  float64
 6   previous_cancellations          20000 non-null  int64  
 7   previous_bookings_not_canceled  20000 non-null  int64  
 8   booking_changes                 20000 non-null  int64  
 9   days_in_waiting_list            20000 non-null  int64  
 10  adr                             18937 non-null  float64

📍 데이터 탐색 결과

  1. target: is_cancled
  2. missing value: lead_time, is_repeated_guest, adr에 결측치 존재
  3. outlier: 편차가 큰 lead_time, adr, days_in_waiting_list에 대해 이상치 탐색 필요
  4. scaling: 각 컬럼의 max 값이 상이하기 때문에 차이가 클 경우 스케일링 고려
  5. feature type: deposit_type외에 is_canceled, is_repeated_guest는 0과 1로 이루어진 categorical feature

1.2 시각화

# 수치형
continuous_columns = ['lead_time', 'stays_in_weekend_nights',
       'stays_in_week_nights', 'previous_cancellations',
       'previous_bookings_not_canceled', 'booking_changes',
       'days_in_waiting_list', 'adr']
# 범주형
categorical_columns = ['is_canceled', 'deposit_type', 'is_repeated_guest']

👀 수치형-히스토그램

fig, axes = plt.subplots(nrows=2, ncols=4, figsize=(15, 4))

for i, column in enumerate(continuous_columns):

    row = i // 4
    col = i % 4
    hotel[column].plot(kind='hist', bins=20, ax=axes[row, col], color='skyblue', edgecolor='black')
    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=2, ncols=4, figsize=(15, 4))

for i, column in enumerate(continuous_columns):

    row = i // 4
    col = i % 4
    sns.boxplot(x=hotel[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=3, figsize=(15, 5))

for i, column in enumerate(categorical_columns):

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

plt.tight_layout()
plt.show()

👀 그룹별 데이터

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

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

columns = is_canceled.columns.to_list()

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

plt.tight_layout()
plt.show()

👀 상관관계 시각화

df_cor = hotel.drop(columns=["is_canceled"]).corr(method='pearson')
sns.heatmap(df_cor,
             xticklabels = df_cor.columns,
             yticklabels = df_cor.columns,
             cmap='RdBu_r',
             annot=True, 
             linewidth=3)

📍 시각화 결과

  1. 수치형 변수-히스토그램: previous_cancellations, previous_bookings_not_canceld, booking_changes, days_in_waiting_list의 데이터 분포는 한쪽에 몰려있음을 확인
  2. 수치형 변수-박스플롯: 그래프 상 Outlier들이 존재하나 연속적으로 나타나기에 제거를 위해서는 현업의 의견이 필요, 데이터 분석가의 입장에서는 IQR을 활용 할 수 있음
  3. 범주형 변수-막대그래프: is_cancled가 0이 많은 불균형 데이터, deposit_type과 is_repeated_guest 또한 불균형임
  4. 그룹별 데이터: previous_cancellations, previous_bookings_not_canceled에 따라 취소 비율이 확연히 달라짐을 알 수 있음
  5. 상관관계 분석: 설명변수간 높은 상관관계를 가지는 것이 없으므로 선형 모델 사용시 모든 변수를 사용하여도 무방할 것으로 판단됨

2 결측치 탐색 및 처리

2.1 결측치 탐색

df_isna = pd.DataFrame(hotel.isna().sum(), columns=['null_cnt'])
df_isna

  • 결측치가 존재하는 컬럼 중 is_repeated_guest는 categorical type이며 lead_time과 adr은 numerical type

2.2 결측치 비율 확인

for null_col in df_isna[df_isna.null_cnt > 0].index.to_list():
    print("*"*15)
    print("결측치 컬럼 :", null_col)
    print("결측치 비율 : {}%".format(df_isna.loc[null_col].null_cnt *100/ len(hotel)))
    print("describe")
    print(hotel[null_col].describe())
***************
결측치 컬럼 : lead_time
결측치 비율 : 0.025%
describe
count    19995.000000
mean        85.978345
std         96.427240
min          0.000000
25%         11.000000
50%         51.000000
75%        132.000000
max        629.000000
Name: lead_time, dtype: float64
***************
결측치 컬럼 : is_repeated_guest
결측치 비율 : 1.79%
describe
count    19642.000000
mean         0.038133
std          0.191521
min          0.000000
25%          0.000000
50%          0.000000
75%          0.000000
max          1.000000
Name: is_repeated_guest, dtype: float64
***************
결측치 컬럼 : adr
결측치 비율 : 5.315%
describe
count    18937.000000
mean       101.410239
std         49.245097
min         -6.380000
25%         68.800000
50%         94.500000
75%        126.000000
max        451.500000
Name: adr, dtype: float64

2.3 결측치 처리

hotel.dropna(subset=['lead_time'], axis=0, inplace=True)
hotel['is_repeated_guest'].fillna(0, inplace = True)
  • lead_time은 매우 작은 비중을 차지하기에 결측치 제거
  • is_repeated_guest은 categorical type이며 앞서 대부분의 데이터가 0임을 확인하였으므로 0으로 대체 (빈도수)
  • adr은 시각화를 통해 결측치 처리 방법 모색

👀 adr 상관관계 시각화-스캐터플롯

columns = hotel.columns.to_list()
columns.remove('adr')
target = 'adr'

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

for i, column in enumerate(columns):
    row = i // 2
    col = i % 2
    axes[row, col].scatter(hotel[column], hotel[target], alpha=0.5, color='b')
    axes[row, col].set_title('Scatter Plot of {} vs. {}'.format(column, target))
    axes[row, col].set_xlabel(column)
    axes[row, col].set_ylabel(target)

plt.tight_layout()

plt.show()

📍 adr 결측치 처리 방안

  • 다른 설명변수들과 특별한 관계가 파악되지 않음
  • 관계가 있다면 구간별 평균등을 사용할 수 있음
  • Tree model을 사용하여 결측치 대체 방법 수행

2.4 모델을 사용한 결측치 처리

df = hotel.copy()

df['deposit_type'] = df['deposit_type'].replace('No Deposit', 0)
df['deposit_type'] = df['deposit_type'].replace('Refundable', 1)
df['deposit_type'] = df['deposit_type'].replace('Non Refund', 2)
  • categorical 변수를 활용하기 위해 인코딩 수행
from sklearn.ensemble import RandomForestRegressor

missing_col = 'adr'
features = df.columns.to_list()
features.remove('adr')

train_data = df.dropna()
test_data = df[df.isnull().any(axis=1)]

rf_model = RandomForestRegressor(n_estimators=50)
rf_model.fit(train_data[features], train_data[missing_col])

predicted_values = rf_model.predict(test_data[features])
test_data[missing_col] = predicted_values
df_fill = pd.concat([train_data, test_data])

df_fill.isna().sum()
is_canceled                       0
deposit_type                      0
lead_time                         0
stays_in_weekend_nights           0
stays_in_week_nights              0
is_repeated_guest                 0
previous_cancellations            0
previous_bookings_not_canceled    0
booking_changes                   0
days_in_waiting_list              0
adr                               0

3. 데이터 질을 향상시키는 방법 제시

  • 현업의 의견을 통해 이상치를 제거해주는 방법을 사용
  • 현업의 의견을 구할 수 없을 경우 IQR 방식을 사용
  • 데이터 불균형을 해결할 수 있도록 데이터를 수집

4. 데이터 불균형 해결

4.1 데이터 불균형 파악

👀 시각화

sns.countplot(x='is_canceled', data =df_fill)
plt.title('count plot of is_canceled', fontsize =14)
plt.show()
ratio0 = round(len(df_fill[df_fill['is_canceled']==0])/len(df_fill)*100, 2)
ratio1 = round(len(df_fill[df_fill['is_canceled']==1])/len(df_fill)*100, 2)
print('0 비율: {}%'.format(ratio0))
print('1 비율: {}%'.format(ratio1))

0 비율: 88.0%
1 비율: 12.0%
  • 데이터의 비율이 약 9:1 이므로 불균형이라고 판단

4.2 Oversampling

📍 Random Oversampling

from imblearn.over_sampling import RandomOverSampler, SMOTE
import time

X = df_fill[df_fill.columns.difference(['is_canceled'])]
y = df_fill['is_canceled']
start = time.time()
# Random Oversampling
ros = RandomOverSampler(random_state =42)
X_ro, y_ro = ros.fit_resample(X, y)
print("time :", time.time() - start)
time : 0.00850820541381836

📍 SMOTE

start = time.time()
sm = SMOTE(random_state =42)
X_sm, y_sm = sm.fit_resample(X, y)
print("time :", time.time() - start)
time : 0.02542901039123535

📍 Oversampling 수행 결과

  • SMOTE가 더 오랜시간 걸림

5. Modeling

5.1 원본 및 오버샘플링 수행 데이터 셋으로 모델링 수행

📍 Origin Dataset

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report

start = time.time()
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size =0.2, stratify =y, random_state =100)
clf = RandomForestClassifier(n_estimators =100, min_samples_split =10)
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')
print("time :", time.time() - start)
train 정확도 : 0.934421105276319 

              precision    recall  f1-score   support

           0       0.92      1.00      0.96      3519
           1       0.96      0.40      0.56       480

    accuracy                           0.93      3999
   macro avg       0.94      0.70      0.76      3999
weighted avg       0.93      0.93      0.91      3999

test 정확도 : 0.9259814953738434 

time : 1.3518381118774414

📍 Random Oversampling Dataset

start = time.time()
X_ro_train, X_ro_test, y_ro_train, y_ro_test = train_test_split(X_ro, y_ro, test_size =0.2, stratify =y_ro, \
                                                                random_state =100)
clf_ro = RandomForestClassifier(n_estimators =100, min_samples_split=10, random_state =100)
clf_ro.fit(X_ro_train, y_ro_train)
print('train 정확도 :', clf_ro.score(X_ro_train, y_ro_train), '\n')
pred_ro=clf_ro.predict(X_ro_test)
print(classification_report(y_ro_test, pred_ro))
print('test 정확도 :', clf_ro.score(X_ro_test, y_ro_test), '\n')
print("time :", time.time() - start)
train 정확도 : 0.9868925831202046 

              precision    recall  f1-score   support

           0       0.98      0.93      0.96      3519
           1       0.94      0.98      0.96      3519

    accuracy                           0.96      7038
   macro avg       0.96      0.96      0.96      7038
weighted avg       0.96      0.96      0.96      7038

test 정확도 : 0.9568059107701051 

time : 2.283308982849121

📍 SMOTE Dataset

start = time.time()
X_sm_train, X_sm_test, y_sm_train, y_sm_test = train_test_split(X_sm, y_sm, test_size =0.2, stratify =y_sm, \
                                                                random_state =100)
clf_sm = RandomForestClassifier(n_estimators =100, min_samples_split=10, random_state =234)
clf_sm.fit(X_sm_train, y_sm_train)
print('train 정확도 :', clf_sm.score(X_sm_train, y_sm_train), '\n')
pred_sm=clf_sm.predict(X_sm_test)
print(classification_report(y_sm_test, pred_sm))
print('test 정확도 :', clf_sm.score(X_sm_test, y_sm_test), '\n')
print("time :", time.time() - start)
train 정확도 : 0.9648337595907929 

              precision    recall  f1-score   support

           0       0.88      0.90      0.89      3519
           1       0.90      0.88      0.89      3519

    accuracy                           0.89      7038
   macro avg       0.89      0.89      0.89      7038
weighted avg       0.89      0.89      0.89      7038

test 정확도 : 0.8908780903665814 

time : 2.5965330600738525

5.2 모델링 결과 해석

  • 원본 데이터보다 oversampling한 데이터에서 train, test 모두 우수한 성능을 보임을 확인

통계분석

1. 가설검정 (1)

공장에서는 시제품의 농도(%)가 60이라고 주장하며 품질관리팀에서 10개의 샘플을 뽑았다. 유의수준 5%에서 다음을 검정하시오.

x =[52 ,50 ,62 ,75 ,26 ,45 ,62 ,35 ,57 ,14]

✏️ 문제정의

  • 모평균 추정
  • 데이터의 수가 충분하지 않으므로 정규성 검정 불가
  • 윌 콕슨의 부호 순위 검정

1.1 가설 수립

  • H0: 모평균의 값은 60임
  • H1: 모평균의 값은 60이 아님

1.2 검정 후 가설 채택

from scipy import stats

result = stats.wilcoxon(pd.Series(x)-60)
print(result)
WilcoxonResult(statistic=9.5, pvalue=0.064453125)

📍 검정결과 해석

  • p-value가 유의수준 0.05보다 크므로 귀무가설 채택
  • 모평균의 값은 60

2. 가설검정 (2)

사회과학, 자연과학, 공학 세 개 학과의 평점조사표를 보고 학과와 성적이 관계있는지 검정

a = [16, 30, 12]
b = [12, 20, 3]
c = [18, 13, 14]

data = {
    '사회과학': a,
    '자연과학': b,
    '공학': c
}

index = ['3.5~4.5', '2.5~3.5', '1.5~2.5']

table = pd.DataFrame(data, index=index)
print(table)
         사회과학  자연과학  공학
3.5~4.5    16    12  18
2.5~3.5    30    20  13
1.5~2.5    12     3  14

✏️ 문제정의

  • 범주형 변수가 두 개 이상인 경우 상관이 있는지 검정
  • 교차분석 중 독립성 검정 수행

2.1 가설수립

  • H0: 성적과 학과는 독립이다
  • H1: 성적과 학과는 독립이 아니다

2.2 학과와 성적이 독립일 때, 기대값

from scipy import stats
statistics, p_value, dof, expected_freq = stats.chi2_contingency(observed=table)
pd.DataFrame(expected_freq, index=index, columns=table.columns)

2.3 검정통계량을 구하고 가설 채택

print("검정통계량:",statistics)
print("p-value:", p_value)
검정통계량: 10.199441509990177
p-value: 0.03719883770303157
  • p-value가 0.05 보다 작으므로 대립가설 채택
  • 학과와 성적은 독립이 아님

3. 시계열 분석

import pandas as pd
covid = pd.read_csv("https://raw.githubusercontent.com/ADPclass/ADP_book_ver01/main/data/%EC%84%9C%EC%9A%B8%ED%8A%B9%EB%B3%84%EC%8B%9C%20%EC%BD%94%EB%A1%9C%EB%82%9819.csv")
covid

3.1 ACF 사용해서 distance를 계산하시오

covid1 = covid[covid.columns.difference(['날짜'])]

import statsmodels.api as sm

def acf(x, n_lags):
    return sm.tsa.stattools.acf(x, nlags =n_lags)
# Max ACF lags
n_lags = len(covid1)
lag_arr = np.repeat(n_lags, covid1.shape[1])
acf_list = list(map(acf, covid1.transpose().to_numpy(), lag_arr))
acf_df = pd.DataFrame(acf_list).transpose()
acf_df.columns = covid1.columns
acf_df

  • sm.tsa.stattools.acf를 사용해 ACF distance를 계산
  • 이때 lag는 데이터의 관측 개수
acf_df =acf_df.T
acf_df.head()

3.2 계층적 군집 분석을 위해 덴드로그램을 작성

  • ACF distance 계산값으로 계층적 군집분석을 수행
  • acf_df의 인덱스를 label로 지정, label은 덴드로그램의 노드 이름으로 사용됨
  • sch의 linkage( )에 acf_df를 입력하여 계층적 군집분석을 수행
    • method는 ‘average’를 선택하여 평균연결법을 구현
    • 임곗값(cut-off)는 linkage matrix 3번째 열의 최댓값의 30%로 설정
  • linkage matrix는 sch.linkage( )의 결과로 반환되는 행렬, 각 열은 다음과 같은 특징이 있음

    ① 첫 번째 열 : 한 클래스의 인덱스
    ② 두 번째 열 : 다른 클래스의 인덱스
    ③ 세 번째 열 : 클래스 사이의 거리
    ④ 네 번째 열 : 클래스를 만드는 데 사용된 데이터 포인트의 개수
import scipy.cluster.hierarchy as sch
from matplotlib import font_manager, rc
# 윈도우 폰트 위치
# C:\Windows\Fonts
font_path ="/Library/Fonts/Arial Unicode.ttf"
font = font_manager.FontProperties(fname =font_path).get_name()
rc('font', family =font)

plt.figure(figsize=(15, 5))
label = acf_df.index
dend1=sch.linkage(acf_df, method ='average')
cutoff =0.5 *max(dend1[:,2])
dend_res1=sch.dendrogram(dend1, color_threshold=cutoff, labels=label)
plt.show()

댓글남기기