데이터 분석/ADP 자격증 공부

(파이썬 한권으로 끝내기) 모의고사 제2회 소스코드, 샘플 데이터

나르시스트 2026. 4. 6. 21:36

데이터: 당뇨병 유무와 신체검사 데이터

diabetes_for_test.csv
0.02MB
metalicity.csv
0.00MB
lot_quality.csv
0.00MB

 

<머신러닝>

1. 데이터 탐색

(1) 시각화 포함 탐색적 자료분석을 시행하시오(EDA)

import pandas as pd
import numpy as np

import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')

df = pd.read_csv('data/diabetes_for_test.csv')
print(df)

diabetes = df.groupby('Outcome').mean()
print(diabetes)

fig, axes = plt.subplots(2, 4, figsize=(20, 14))

for i in range(4):
    sns.barplot(x=diabetes.index, y=diabetes.iloc[:, i], ax=axes[0][i])
    axes[0][i].set_title(diabetes.columns[i])

for i in range(4):
    sns.barplot(x=diabetes.index, y=diabetes.iloc[:, i+4], ax=axes[1][i])
    axes[1][i].set_title(diabetes.columns[i+4])

plt.suptitle('EDA')
plt.show()

→ 당뇨병이 있는 사람은 없는 사람보다 Pregnancies, Glucose, Insulin, BMI의 평균 수치가 높다

 

*변수들의 상관관계를 시각화 해보자

→ Outcome을 제외한 변수들 간의 상관성을 보았을 때, 0.9 이상의 상관관계를 가지는 변수는 없음
→ 따라서 모든 변수를 사용해서 모델링해도 될 것으로 판단

 

(2) 이상치를 식별하고 처리하시오.

print(df.describe())
X = df.drop(columns=['Outcome'])
df_v1 = pd.melt(X, var_name='col', value_name='value')  # boxplot을 한 번에 그려주기 위해 데이터 재구조화
print(df_v1)

plt.figure(figsize=(15, 7))
sns.boxplot(x='col', y='value', data=df_v1)
plt.ylim([-50, 1100])
plt.xticks(range(8), X.columns)
plt.show()

 

*Age 중앙값 및 boxplot 확인

print('Age의 중앙값: ', df.Age.median())
outlier_index = df[df['Age'] > 400].index
df.loc[outlier_index, 'Age'] = df.Age.median()
sns.boxplot(df['Age'])
plt.show()

추가적으로 데이터를 살펴봤을 때, Glucose와 BloodPressure 컬럼에는 0이 존재하지 않아야 함 – 이상치로 판단

 

*이상치를 제외한 중앙값으로 이상치를 대체

outlier_index = df[df['Glucose'] == 0].index
df.loc[outlier_index, 'Glucose'] = df.Glucose.median()
outlier_index = df[df['BloodPressure'] == 0].index
df.loc[outlier_index, 'BloodPressure'] = df.BloodPressure.median()
print(df.describe())

(3) 앞선 두 단계에서 발견한 향후 분석 시에 고려해야 할 사항을 작성하시오.

min과 max 차이가 많이 나는 컬럼이 존재하므로, 선형 모델 사용 시 scale을 적용할 필요가 있어 보임

 

2. 클래스 불균형 처리

(1) 업 샘플링 과정을 설명하고 결과를 작성하시오.

  • Random: 데이터를 단순 복사하는 방식이므로, 기존 데이터와 동일한 복제 데이터를 생성
    → 사용방법이 간단하지만, 소수 클래스에 과적합이 발생할 가능성이 있다는 단점이 있음
  • SMOTE: 적은 데이터세트에 있는 개별 데이터들의 k-최근접 이웃을 찾아, 해당 데이터와 k개 이웃들의 차이를 일정한 값으로 만들어 기존 데이터와 약간의 차이를 지닌 새로운 데이터를 생성
    → 처리 속도가 느리지만, 과적합 문제를 예방할 수 있다는 장점이 있음
from imblearn.over_sampling import RandomOverSampler

print(df['Outcome'].value_counts())

X = df.drop(['Outcome'], axis=1)
y = df[['Outcome']]

ros = RandomOverSampler()
X_upsampling, y_upsampling = ros.fit_resample(X, y)

print('기존 타깃 분포')
print(df['Outcome'].value_counts()/len(df))
print('-'*10)
print('upsampling의 타깃 분포')
print(y_upsampling['Outcome'].value_counts()/len(y_upsampling))

(2) 언더 샘플링 과정을 설명하고 결과를 작성하시오.

  • RandomUnderSampler: 랜덤하게 다수 클래스의 데이터를 선택하여 삭제
  • Tomek link: 서로 다른 클래스에 가장 가까운 데이터들이 토맥 링크로 묶여서 토맥 링크 중 다수 클래스의 데이터를 제거하는 방식
from imblearn.under_sampling import RandomUnderSampler  #TomekLinks

rus = RandomUnderSampler()
X_undersampling, y_undersampling = rus.fit_resample(X, y)

print('기존 타깃 분포')
print(df['Outcome'].value_counts()/len(df))
print('-'*10)
print('undersampling의 타깃 분포')
print(y_undersampling['Outcome'].value_counts()/len(y_undersampling))

(3) 둘 중 하나를 선택하고 선택한 이유를 서술하시오.

→ 데이터가 총 768개로 당뇨병 환자를 대표하기에는 너무 적은 데이터이기 때문에 Oversampling이 적합함

 

3. 모델링

(1) 최소 3개 이상의 알고리즘을 제시하고 정확도 측면의 모델 1개와 속도 측면의 모델 1개를 선정하시오.

→ 속도 측면에서 Logistic Regression, 정확도 측면에서 SVM, 기타로 XGBoost를 제시

(2) 모델을 비교하고 결과를 설명하시오.

from sklearn.linear_model import LogisticRegression
from xgboost import XGBClassifier
import sklearn.svm as svm

import time
from sklearn.model_selection import KFold
from sklearn.metrics import accuracy_score
from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=0)

log = LogisticRegression()
xgb = XGBClassifier(random_state=0)
svm_clf = svm.SVC(kernel='linear')

kfold = KFold()

def model_result(model):
    pred_li = []
    for train_index, test_index in kfold.split(X):
        X_train, X_test = X.iloc[train_index, :], X.iloc[test_index, :]
        y_train, y_test = y.iloc[train_index, :], y.iloc[test_index, :]
        
        X_train_resample, y_train_resample = smote.fit_resample(X_train, y_train)
        
        start = time.time()
        model.fit(X_train_resample, y_train_resample)
        end = time.time()
        
        pred = model.predict(X_test)
        pred_li.append(accuracy_score(pred, y_test['Outcome']))
        
    ## 마지막 데이터 학습 속도
    print(f'{end - start:.5f} sec')
    print(np.mean(pred_li))

model_result(log)
model_result(xgb)
model_result(svm_clf)

(3) 속도 개선을 위한 차원축소 방법을 설명하고 수행하시오. 그리고 예측 성능과 속도를 비교하고 결과를 작성하시오.

→ PCA 차원축소 수행
→ 데이터 스케일에 따라 각 주성분이 설명 가능한 분산량이 달라질 수 있기 때문에 데이터 스케일링을 꼭 수행

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

smote = SMOTE(random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, test_size=0.3, random_state=2022)

scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
pca = PCA(n_components=8)
X_train_pca = pca.fit(X_train_s)

print(pca.explained_variance_ratio_)
print(pca.explained_variance_ratio_[:5].sum())

 

→ 8개 독립변수 대신, 주성분 5개를 사용하면 전체 데이터에 81%를 설명할 수 있음

def model_result(model):
    pred_li = []
    for train_index, test_index in kfold.split(X):
        X_train, X_test = X.iloc[train_index, :], X.iloc[test_index, :]
        y_train, y_test = y.iloc[train_index, :], y.iloc[test_index, :]

        X_train_resample, y_train_resample = smote.fit_resample(X_train, y_train)

        scaler = StandardScaler()
        X_train_res_s = scaler.fit_transform(X_train_resample)
        X_test_s = scaler.transform(X_test)

        pca = PCA(n_components=5)
        X_train_pca = pca.fit_transform(X_train_res_s)  # transform을 왜하지?
        X_test_pca = pca.transform(X_test_s)

        start = time.time()
        model.fit(X_train_pca, y_train_resample)
        end = time.time()

        pred = model.predict(X_test_pca)
        pred_li.append(accuracy_score(pred, y_test['Outcome']))

    ## 마지막 데이터 학습 속도
    print(f'{end - start:.5f} sec')
    print(np.mean(pred_li))

model_result(log)
model_result(xgb)
model_result(svm_clf)

→ 예측 성능은 다소 떨어졌지만, 속도는 엄청 빨라짐

 

<통계분석>

1. 회사 제품의 금속 재질 함유량의 분산이 1.3을 넘으면 불량이라고 판단한다. 회사에서는 품질경영팀으로부터 제조사별로 금속 함유량이 차이가 난다고 제보를 받았다. 해당 금속 함유량 데이터에 대한 검정을 수행하시오(유의확률: 0.05)

(1) 귀무가설과 대립가설을 작성하시오.

  • 귀무가설(H0): 제품들의 금속 재질 함유량 분산은 1.3이다
  • 대립가설(H1): 제품들의 금속 재질 함유량 분산은 1.3이 아니다.

(2) 가설을 양측 검정하시오.

import pandas as pd
import numpy as np

df = pd.read_csv('data/metalicity.csv')
print(df)

import scipy.stats as stats
print(stats.shapiro(df['metalicity']))

## 정규분포이며, 해당 데이터의 평균을 갖고 분산이 1.3인 150행의 데이터를 만드록 등분산 검정을 진행
test = np.random.normal(45.32, 1.3**0.5, 150)  # 평균: 45.32, n: 150
print(test)

stats.levene(df['metalicity'], test)

→ 원래 카이제곱이 더 정확하지만, 라이브러리가 없으니 패스(?)

→ p-value 값이 0.05 보다 크므로, 정규성 가정 만족

→ p-value 값이 0.05 보다 작으므로, 귀무가설을 기각하여 분산은 1.3이 아니라고 할 수 있음

2. 제품 200개의 Lot별 불량 제품 수량 데이터에 대해 p 관리도*를 구하고 시각화하시오.

*p 관리도 구하는 방법
– n: lot 별 생산수량
– p: lot 별 불량수량 %
– CL(관리중심선): 전체 불량수량 / 전체 생산수량
– 관리 상하한선: p ± 3 √p(1-p) / n

 

(1) p 관리도에 따라 관리중심선(Center Line), 관리 상한선, 하한선을 구하시오.

import pandas as pd
import numpy as np

df = pd.read_csv('data/lot_quality.csv')
df = df.set_index('lot')
print(df)

df['p'] = df['불량수량'] / df['생산수량']
df['UCL'] = df['p'] + (3 * (df['p'] * (1 - df['p']) / df['생산수량'])**0.5)
df['LCL'] = df['p'] - (3 * (df['p'] * (1 - df['p']) / df['생산수량'])**0.5)

CL = df['불량수량'].sum() / df['생산수량'].sum()
print('관리중심선: ', CL)
print(df)

 

(2) 관리도를 시각화하시오.

import matplotlib.pyplot as plt

plt.figure(figsize=(14,10))
plt.plot(df['UCL'])
plt.plot(df['LCL'])
plt.plot(df['p'], marker='o')
plt.hlines(CL, 1, 200)
plt.legend(['UCL', 'p', 'CL', 'LCL'])
plt.show()

3. 제품 1, 2를 만드는 데 재료 a, b, c가 일부 사용되며, 제품 1과 2를 만들 때 12만원과 18만원을 벌 수 있다. 재료는 한정적으로 주어지는데, 이때 최대 수익을 낼 수 있을 때의 제품 1과 제품 2의 개수를 구하라.

재료 공급량 { a: 1300, b: 1000, c: 1200 }

# 제품2를 만들 수 있는 최대 수량은 32, 남은 원재료는 a: 20, b: 40, c: 240
x = 0
y = 32

max_profit = 32 * 18

material_a = 20
material_b = 40
material_c = 240

while((material_a > 0) or (material_b > 0) or (material_c > 0)):
    y -= 1
    material_a += 40
    material_b += 30
    material_c += 30

    while((material_a > 20) or (material_b > 20) or (material_c > 20)):
        x += 1
        material_a -= 20
        material_b -= 20
        material_c -= 20

        if(y*18 + x*12) > max_profit:
            max_profit = (y*18 + x*12)
            result_x = x
            result_y = y

    if y==0:
        break

print('최대 수익: ', max_profit)
print('제품1 수량: ', result_x)
print('제품2 수량: ', result_y)

4. 상품 a와 b가 있을 때 다음과 같은 구매 패턴이 있다고 한다.

[‘a’,’a’,’b’,’b’,’a”a”a”a’,’b’,’b’,’b’,’b’,’b’,’a”a’,’b’,’b’,’a”b’,’b’]

 

(1) 구매 패턴으로 볼 때 두 상품이 연관이 있는지 가설을 세우고 검정하시오.

  • 귀무가설(H0): 연속적인 관측값이 임의적이다. 즉, 연관성이 없다
  • 대립가설(H1): 연속적인 관측값이 임의적이 아니다. 즉, 연관성이 있다

(2) 가설을 채택하시오.

import pandas as pd
from statsmodels.sandbox.stats.runs import runstest_1samp

data = ['a','a','b','b','a','a','a','a','b','b','b','b','b','a','a','b','b','a','b','b']

df = pd.DataFrame(data, columns=['product'])
df.loc[df['product']=='a', 'product'] = 1
df.loc[df['product']=='b', 'product'] = 0
print(df['product'])

print(runstest_1samp(df['product']))

→ p-value가 0.05보다 크므로, 귀무가설을 기각할 수 없다. 즉, 연관성이 없다고 할 수 있다