ADP 실기 연습문제 (6)
본 게시물은 ADP 실기의 23회차 문제 풀이에 대한 것이다.
머신러닝
1. 온, 습, 조도, CO2 농도에 따른 객실의 사용 유무 판별
1.1 데이터 EDA 수행 후 의미있는 탐색
☀️ 데이터 개요
import pandas as pd
import numpy as np
df =pd.read_csv('https://raw.githubusercontent.com/Datamanim/datarepo/main/adp/23/problem1.csv')
df.head()
df_desc = df.describe()
df_desc
pd.DataFrame(df_desc.loc['std',:]).sort_values(by=['std'], ascending=False)
df.info()
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 date 17910 non-null object
1 Temperature 17910 non-null float64
2 Humidity 17910 non-null float64
3 Light 17910 non-null float64
4 CO2 17889 non-null float64
5 HumidityRatio 17910 non-null float64
6 Occupancy 17910 non-null int64
dtypes: float64(5), int64(1), object(1)
📍 데이터 탐색 결과
- target: Occupancy
- missing value: CO2
- outlier: CO2, Light 두 변수의 편차가 큰 것으로 보아 outlier가 존재할 수 있음
- scaling: 서로간의 데이터 범주가 다르기 때문에 스케일링 고려
- feature type: 모두 float type이며 target인 Occupancy categorical type
- feature eng: date 변수를 새로운 변수로 활용하여 요일을 추가할 수 있을 것 같음
☀️ 날짜 요일 변환
from datetime import datetime as dt
df['day'] = pd.to_datetime(df['date'], format='%Y-%m-%d')
df['weekday'] = df.apply(lambda x: dt.weekday(x['day']), axis=1)
df['is_weekend'] = df['weekday'].apply(lambda x: 1 if x >= 5 else 0)
df.drop(columns=['day', 'date'], inplace=True)
☀️ 데이터 시각화
categorical_columns = ['Occupancy', 'weekday', 'is_weekend']
numerical_columns = ['Temperature', 'Humidity', 'Light', 'CO2', 'HumidityRatio']
👀 수치형-히스토그램
import seaborn as sns
import matplotlib.pyplot as plt
fig, axes = plt.subplots(nrows=1, ncols=5, figsize=(15, 3))
for i, column in enumerate(numerical_columns):
ax = axes[i]
df[column].plot(kind='hist', bins=20, ax=ax, color='skyblue', edgecolor='black')
ax.set_title(f'Histogram of {column}')
ax.set_xlabel(column)
ax.set_ylabel('Frequency')
plt.tight_layout()
plt.show()
👀 수치형-박스플롯
fig, axes = plt.subplots(nrows=1, ncols=5, figsize=(16, 2))
for i, column in enumerate(numerical_columns):
ax = axes[i]
sns.boxplot(x=df[column], ax=ax, color='skyblue')
ax.set_title(f'Box plot of {column}')
ax.set_xlabel(column)
ax.set_ylabel('Frequency')
plt.tight_layout()
plt.show()
👀 범주형-막대그래프
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 3))
for i, column in enumerate(categorical_columns):
df[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 문제이기 때문에 그룹별로 데이터를 시각화하여 파악
group_class = df.groupby('Occupancy').mean()
group_class
fig, axes = plt.subplots(1, 5, figsize=(15, 3))
columns = group_class.columns.difference(['is_weekend', 'weekday']).to_list()
for i, column in enumerate(columns):
ax = axes[i]
sns.barplot(x=group_class.index, y=column, data=group_class, ax=ax)
ax.set_title(f'Average {column}')
ax.set_xlabel("Occupancy")
ax.set_ylabel(f'Average {column}')
plt.tight_layout()
plt.show()
group_counts = df.groupby(['is_weekend', 'Occupancy']).size().unstack(fill_value=0)
# 파이 그래프 그리기
fig, axes = plt.subplots(1, len(group_counts), figsize=(15, 4))
for i, (is_weekend, counts) in enumerate(group_counts.iterrows()):
ax = axes[i]
counts.plot(kind='pie', autopct='%1.1f%%', ax=ax)
ax.set_title(f'is_weekend = {is_weekend}')
ax.set_ylabel('')
ax.legend(title='Occupancy', labels=counts.index)
plt.tight_layout()
plt.show()
group_counts = df.groupby(['Occupancy', 'weekday']).size().unstack(fill_value=0)
# 파이 그래프 그리기
fig, axes = plt.subplots(1, len(group_counts), figsize=(15, 4))
for i, (occupancy, counts) in enumerate(group_counts.iterrows()):
ax = axes[i]
counts.plot(kind='pie', autopct='%1.1f%%', ax=ax)
ax.set_title(f'Occupancy = {occupancy}')
ax.set_ylabel('')
ax.legend(title='Weekday', labels=counts.index)
plt.tight_layout()
plt.show()
📍시각화 결과
- 히스토그램 및 박스플롯을 확인한 결과 Light에 이상치가 있을 수 있음
- 나머지 이상치들은 연속해서 분포되어 있기에 현업의 의견 없이 쉽게 제거할 수 없을 것으로 판단
- 객실 사용 유무에 따라 light와 co2는 확실한 평균의 차이가 있음
- 주말에 사용중인 경우는 없고 평일에 비슷한 비율로 사용하는 것으로 보임
1.2 결측치를 대체하는 방식 선택하고 근거제시, 대체 수행
👀 결측치 탐색
df_isna = pd.DataFrame(df.isna().sum(), columns=['null_cnt'])
df_isna
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(df)))
print("describe")
print(df[null_col].describe())
***************
결측치 컬럼 : CO2
결측치 비율 : 0.11725293132328309%
describe
count 17889.000000
mean 647.700865
std 285.997340
min 412.750000
25% 453.000000
50% 532.666667
75% 722.000000
max 2076.500000
📍 결측치 대체 방식 선택
- CO2의 결측치가 0.11%이기 때문에 제거하여도 무방해 보이나 대체하기로 함
- 결측치는 CO2 연속형 변수
- 따라서 평균 대체, 모델을 사용한 대체 등이 가능
- 나머지 데이터들의 데이터가 모두 있기에 KNN 모델을 사용해 결측치를 대체하도록 함
before_fill_df = df[df.isna().any(axis=1)].copy()
from sklearn.impute import KNNImputer
KNN_data = df.copy()
imputer = KNNImputer()
df_filled = imputer.fit_transform(KNN_data)
df_filled = pd.DataFrame(df_filled, columns=KNN_data.columns)
df[KNN_data.columns] = df_filled
df.isnull().sum()
Temperature 0
Humidity 0
Light 0
CO2 0
HumidityRatio 0
Occupancy 0
weekday 0
is_weekend 0
df.loc[before_fill_df.index]
1.3 추가적으로 데이터의 질 및 품질관리를 향상시킬만한 내용 작성
- Light에 결측치가 있는 것으로 보임
- 극단적으로 큰값과 음수가 있음
- IQR 방식으로 제거해 주는 것을 수행하면 데이터의 질이 향상 될 것
def detect_outliers(df=None, column=None, weight=1.5):
Q1 = df[column].quantile(0.25)
Q3 = df[column].quantile(0.75)
IQR = Q3 - Q1
IQR_weight = IQR*weight
outlier_idx = df[(df[column] < Q1 - IQR_weight) | (df[column] > Q3 + IQR_weight)].index
return outlier_idx
outlier_idx = detect_outliers(df=df, column='Light')
df.iloc[outlier_idx]
df.iloc[outlier_idx].Occupancy.value_counts()
1.0 2099
0.0 1684
📍 이상치 판단 기준
- 이를 모두 제거하면 Occupancy가 1인 데이터를 거의 모두 지우게 됨
- 음수와 가장 큰 값을 제거하는 방식으로 이상치 제거
# 기준 0
df = df[df.Light >= 0 ].reset_index(drop=True)
# 기준 99.9%
percentile = 99.9
threshold = np.percentile(df['Light'], percentile)
df = df[df['Light'] <= threshold]
2. 데이터 불균형 처리
2.1 데이터에 불균형이 있는지 확인, 불균형 판단 근거 작성
👀 시각화
sns.countplot(x='Occupancy', data =df)
plt.title('count plot of Occupancy', fontsize =14)
plt.show()
ratio0 = round(len(df[df['Occupancy']==0])/len(df)*100, 2)
ratio1 = round(len(df[df['Occupancy']==1])/len(df)*100, 2)
print('0 비율: {}%'.format(ratio0))
print('1 비율: {}%'.format(ratio1))
0 비율: 88.43%
1 비율: 11.57%
- 데이터의 비율이 약 9:1 이므로 불균형이라고 판단
4.2 Oversampling
📍 Random Oversampling
from imblearn.over_sampling import RandomOverSampler, SMOTE
import time
X = df[df.columns.difference(['Occupancy'])]
y = df['Occupancy']
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.006474018096923828
📍 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.01717376708984375
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.997124824684432
precision recall f1-score support
0.0 1.00 0.99 1.00 3153
1.0 0.94 0.99 0.97 412
accuracy 0.99 3565
macro avg 0.97 0.99 0.98 3565
weighted avg 0.99 0.99 0.99 3565
test 정확도 : 0.9918653576437587
time : 0.6616039276123047
📍 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.9992466296590008
precision recall f1-score support
0.0 1.00 0.99 1.00 3153
1.0 0.99 1.00 1.00 3153
accuracy 1.00 6306
macro avg 1.00 1.00 1.00 6306
weighted avg 1.00 1.00 1.00 6306
test 정확도 : 0.9955597843323819
time : 1.362199068069458
📍 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.9984932593180016
precision recall f1-score support
0.0 1.00 0.99 0.99 3153
1.0 0.99 1.00 0.99 3153
accuracy 0.99 6306
macro avg 0.99 0.99 0.99 6306
weighted avg 0.99 0.99 0.99 6306
test 정확도 : 0.9946083095464637
time : 1.8415770530700684
통계분석
1. 가설검정 (1)
공장에서는 진공관 수명이 1만 시간이라고 주장하여 품질관리팀에서 12개 샘플을 뽑았음 유의수준 5%에서 부호 검정하시오
import pandas as pd
df =pd.read_csv('https://raw.githubusercontent.com/Datamanim/datarepo/main/adp/23/problem2.csv')
df.head()
✏️ 문제 정의
- 모평균 1만시간
- 샘플사이즈 12
- 유의수준 5%
- 부호 검정
1.1 가설수립
- H0: 진공관 수명의 중위수는 1만 시간
- H1: 진공관 수명의 중위수는 1만 시간이 아님
1.2 유효한 데이터 개수
- 부호검정에서 중위수와 동일한 값은 불필요한 데이터
sample_data=df['life span']
df_fillter = df[df['life span'] != np.median(sample_data)]
print("유효 데이터:", sum(effective_data))
유효 데이터: 8
1.3 검정통계량 및 연구가설 채택 여부를 작성
import numpy as np
from scipy import stats
data = df['life span'].tolist()
# 중앙값
median_value = np.median(data)
# 부호검정
# 중앙값보다 큰 데이터의 개수
above = np.sum(data > median_value)
# 중앙값보다 작은 데이터의 개수
below = np.sum(data < median_value)
# 이항 검정
p_value = stats.binom_test(min(above, below), n=above + below, p=0.5)
print('p-value: ', p_value)
p-value: 0.7265625
혹은
import statsmodels.stats.descriptivestats as smsd
# 두 집단의 데이터
group1 = df['life span']
# 부호검정 수행
statistic, p_value = smsd.sign_test(group1 - 10000)
print("검정 통계량:", statistic)
print("p-value:", p_value)
alpha = 0.05 # 유의수준 설정
if p_value < alpha:
print("귀무가설을 기각합니다. 두 집단의 중앙값이 다릅니다.")
else:
print("귀무가설을 기각하지 않습니다. 두 집단의 중앙값이 같습니다.")
검정 통계량: -1.0
p-value: 0.7265625
귀무가설을 기각하지 않습니다. 두 집단의 중앙값이 같습니다.
📍 검정 결과
- P-value가 0.05보다 높기에 귀무가설 채택
- 중위수는 1만 시간이다
2. 가설검정 (2)
학과별 학점 분포 인원수 표가 있다. 학과와 성적이 관계있는지를 검정하라
df = pd.DataFrame({'사회과학':[15,60,24], '자연과학':[25,69,5],'공학':[10,77,13], 'score':['1.5-2.5','2.5-3.5','3.5-4.5']})
df = df.set_index(['score'])
df
✏️ 문제 정의
- 학과-범주형, 성적-범주형의 상관관계
- 교차분석의 독립성검정 수행
2.1 가설수립
- H0: 학과와 성적은 독립
- H1: 학과와 성적은 독립이 아님
2.2 기대값 및 검정통계량, 가설선택
from scipy.stats import chi2_contingency
chi, p, df, expect = chi2_contingency(df)
print('Statistic:', chi)
print('p-value:',p)
print('df:',df)
print('expect: \n', expect)
Statistic: 22.136920195949322
p-value: 0.00018822647762421383
df: 4
expect:
[[16.61073826 16.61073826 16.77852349]
[68.43624161 68.43624161 69.12751678]
[13.95302013 13.95302013 14.09395973]]
📍 검정 결과
- p-value < 0.05이므로 귀무가설 기각
- 독립이 아니며 상관관계가 있음
3. 시계열 분석
import pandas as pd
df =pd.read_csv('https://raw.githubusercontent.com/Datamanim/datarepo/main/adp/23/problem3_covid2.csv')
df.head()
3.1 데이터는 일자별 각 나라의 일일 확진자수를 나타낸다. 각 나라의 일자별 누적확진자 수를 나타내는 데이터 프레임을 생성
df_new = df.groupby(by=['location','date']).sum()
df_new
3.2 1에서 구한 데이터를 각 나라별로 acf값을 구하고(lag는 50개까지 구하고 첫번째 값을 제외하라) 국가를 기준으로 유클리디안 거리를 기준으로 클러스터링을 진행 후 계층적 군집 분석을 위해 덴드로그램 작성
import statsmodels.api as sm
df_idx = df_new.reset_index()
df_acf = pd.DataFrame()
for loc in df['location'].unique():
np.random.seed(0)
x = df_idx[df_idx.location == loc][['date', 'new_cases']].set_index('date')
acf_result = sm.tsa.stattools.acf(x, adjusted=False,
nlags=50, qstat=False,
fft=True, alpha=None,
bartlett_confint=True, missing='none')
df_t = pd.DataFrame({'acf':acf_result[1:]})
df_t = df_t.T
df_t['location'] = loc
df_t = df_t.set_index(['location'])
df_acf = pd.concat([df_acf, df_t])
from scipy.cluster.hierarchy import dendrogram, linkage, fcluster
single = linkage(df_acf, metric='euclidean', method='ward')
plt.figure(figsize=(10,3))
dendrogram(single, orientation='top', labels=df_acf.index.tolist(), distance_sort='descending'
, color_threshold=4.0, show_leaf_counts=True)
plt.axhline(y=20, color='r', linewidth=1)
plt.show()
댓글남기기