ADP 실기 연습문제 (3)
본 게시물은 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
📍 데이터 탐색 결과
- target: is_cancled
- missing value: lead_time, is_repeated_guest, adr에 결측치 존재
- outlier: 편차가 큰 lead_time, adr, days_in_waiting_list에 대해 이상치 탐색 필요
- scaling: 각 컬럼의 max 값이 상이하기 때문에 차이가 클 경우 스케일링 고려
- 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)
📍 시각화 결과
- 수치형 변수-히스토그램: previous_cancellations, previous_bookings_not_canceld, booking_changes, days_in_waiting_list의 데이터 분포는 한쪽에 몰려있음을 확인
- 수치형 변수-박스플롯: 그래프 상 Outlier들이 존재하나 연속적으로 나타나기에 제거를 위해서는 현업의 의견이 필요, 데이터 분석가의 입장에서는 IQR을 활용 할 수 있음
- 범주형 변수-막대그래프: is_cancled가 0이 많은 불균형 데이터, deposit_type과 is_repeated_guest 또한 불균형임
- 그룹별 데이터: previous_cancellations, previous_bookings_not_canceled에 따라 취소 비율이 확연히 달라짐을 알 수 있음
- 상관관계 분석: 설명변수간 높은 상관관계를 가지는 것이 없으므로 선형 모델 사용시 모든 변수를 사용하여도 무방할 것으로 판단됨
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()
댓글남기기