ADP 실기 연습문제 (4)
본 게시물은 ADP 실기의 연습문제 풀이에 대한 것이다.
머신러닝
1. 데이터 전처리 및 군집 생성
1.1 결측치를 확인하고 제거
import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
data = pd.read_csv("https://raw.githubusercontent.com/ADPclass/ADP_book_ver01/main/data/26_problem1.csv")
data
data_desc = data.describe()
data_desc
pd.DataFrame(data_desc.loc['std',:]).sort_values(by=['std'], ascending=False)
data.info()
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 ID 2240 non-null int64
1 Year_Birth 2240 non-null int64
2 Marital_Status 2240 non-null object
3 Income 2216 non-null float64
4 Kidhome 2240 non-null int64
5 Teenhome 2240 non-null int64
6 MntWines 2240 non-null int64
7 MntFruits 2240 non-null int64
8 MntMeatProducts 2240 non-null int64
9 MntFishProducts 2240 non-null int64
10 MntSweetProducts 2240 non-null int64
11 NumDealsPurchases 2240 non-null int64
12 NumWebPurchases 2240 non-null int64
13 NumCatalogPurchases 2240 non-null int64
14 NumStorePurchases 2240 non-null int64
15 NumWebVisitsMonth 2240 non-null int64
dtypes: float64(1), int64(14), object(1)
📍 데이터 탐색 결과
- missing value: Income에 결측치 존재
- outlier: 편차가 큰 Income에 대해 이상치 탐색 필요
- scaling: 각 컬럼의 max 값이 상이하기 때문에 차이가 클 경우 스케일링 고려
- feature type: Kidhome, Teenhome, Marital_Status은 categorical feature 나머지는 numerical feature
👀 결측치 탐색
df_isna = pd.DataFrame(data.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(data)))
print("describe")
print(data[null_col].describe())
***************
결측치 컬럼 : Income
결측치 비율 : 1.0714285714285714%
describe
count 2216.000000
mean 52247.251354
std 25173.076661
min 1730.000000
25% 35303.000000
50% 51381.500000
75% 68522.000000
max 666666.000000
☀️ 결측치 처리
- 약 1%의 데이터가 결측치로 확인됨
- 이상치가 있을 수 있으므로 모델링을 통한 제거 보다는 mean, median 등을 통한 대체
income_mean = data['Income'].median()
data['Income'].fillna(income_mean, inplace=True)
data.isna().sum()
ID 0
Year_Birth 0
Marital_Status 0
Income 0
Kidhome 0
Teenhome 0
MntWines 0
MntFruits 0
MntMeatProducts 0
MntFishProducts 0
MntSweetProducts 0
NumDealsPurchases 0
NumWebPurchases 0
NumCatalogPurchases 0
NumStorePurchases 0
NumWebVisitsMonth 0
1.2 이상치 제거
👀 데이터 시각화
# 수치형
numerical_columns = ['Year_Birth', 'Income',
'MntWines', 'MntFruits', 'MntMeatProducts', 'MntFishProducts',
'MntSweetProducts', 'NumDealsPurchases', 'NumWebPurchases',
'NumCatalogPurchases', 'NumStorePurchases', 'NumWebVisitsMonth']
# 범주형
categorical_columns = ['Kidhome', 'Teenhome','Marital_Status']
👀 수치형-히스토그램
fig, axes = plt.subplots(nrows=3, ncols=4, figsize=(15, 10))
for i, column in enumerate(numerical_columns):
row = i // 4
col = i % 4
data[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=3, ncols=4, figsize=(15, 8))
for i, column in enumerate(numerical_columns):
row = i // 4
col = i % 4
sns.boxplot(x=data[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, 4))
for i, column in enumerate(categorical_columns):
data[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()
📍 이상치 탐색을 위한 시각화 결과
- Year_Birth는 1900 이전은 이상치로 판단
- Income은 평균보다 많이 떨어진 지점이 있으므로 IQR을 통해 제거
- 나머지 컬럼들은 이상치가 연속적이므로 제거를 위해서는 현업의 의견이 필요
data = data[data.Year_Birth > 1900].reset_index(drop=True)
☀️ 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=data, column='Income')
data.iloc[outlier_idx]
- 666666을 제외하고는 비슷한 값을 가지므로 고소득층이라 생각해도 무방
data = data[data.Income < 666666]
1.3 Kmeans, DBSCAN의 방식으로 군집화 수행
☀️ 군집화를 위한 범주형 변수처리
- categorical columns 중 Marital_Status는 범주형 변수 처리가 필요
- 순서가 없는 컬럼이기에 one-hot encoding 수행
df_encoded = pd.get_dummies(data, columns=['Marital_Status'], prefix=['Category'])
df_encoded
☀️ Kmeans
- 최적의 군집을 찾기 위한 elbow 기법
from sklearn.cluster import KMeans
def elbow(X):
sse = []
for i in range(1, 11):
km = KMeans(n_clusters=i, random_state=1)
km.fit(X)
sse.append(km.inertia_)
# SSE를 그래프로 시각화하여 최적 클러스터 수 결정
plt.plot(range(1, 11), sse, marker='o')
plt.xlabel('The Number of Clusters')
plt.ylabel('SSE')
plt.title('Elbow Method for Optimal k')
plt.show()
X = np.array(df_encoded[['Year_Birth', 'Income', 'Kidhome', 'Teenhome', 'MntWines',
'MntFruits', 'MntMeatProducts', 'MntFishProducts', 'MntSweetProducts',
'NumDealsPurchases', 'NumWebPurchases', 'NumCatalogPurchases',
'NumStorePurchases', 'NumWebVisitsMonth', 'Category_Absurd',
'Category_Alone', 'Category_Divorced', 'Category_Married',
'Category_Single', 'Category_Together', 'Category_Widow',
'Category_YOLO']])
elbow(X)
km = KMeans(n_clusters =3, random_state=1)
km.fit(X)
data['label_kmeans'] = km.labels_
☀️ DBSCAN
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaled_data = scaler.fit_transform(X)
dbscan = DBSCAN(eps=5, min_samples=2) # eps와 min_samples는 조정 가능
dbscan.fit(scaled_data)
cluster_labels = dbscan.labels_
data['label_dbscan'] = cluster_labels
data.label_dbscan.value_counts()
0 2136
2 77
5 4
-1 3
3 3
6 3
1 2
4 2
7 2
8 2
9 2
👀 군집화 시각화-빈도수
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(15, 4))
for i, column in enumerate(['label_kmeans', 'label_dbscan']):
data[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()
👀 군집 시각화
sns.pairplot(data[['label_kmeans', 'Income', 'Year_Birth', 'MntMeatProducts']],
diag_kind='kde',
hue="label_kmeans",
corner =True,
palette='bright')
plt.show()
👀 군집 시각화-PCA
from sklearn.decomposition import PCA
X = df_encoded.drop(columns=['ID']).reset_index(drop=True)
# PCA 모델 생성 및 적합
pca = PCA(n_components=2) # 주성분 2개 선택
pca.fit(X)
# 주성분과 주성분의 설명된 분산 비율 확인
print("주성분 (Principal Components):")
print(pca.components_)
print("\n주성분의 설명된 분산 비율 (Explained Variance Ratio):")
print(pca.explained_variance_ratio_)
# 데이터를 주성분으로 변환
X_pca = pca.transform(X)
# 주성분으로 변환된 데이터 시각화
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=data['label_kmeans'])
plt.xlabel('PC 1 (Principal Component 1)')
plt.ylabel('PC 2 (Principal Component 2)')
plt.title('PCA Plot')
plt.show()
주성분의 설명된 분산 비율 (Explained Variance Ratio):
[9.99799806e-01 1.33989352e-04]
📍 군집화 결과
- dbscan은 군집화가 골고루 되지 않음을 확인
- Kmeans를 통한 군집화는 분포가 균일
- PCA를 통해 확인해 보아도 구분이 잘 되어있음을 확인
2. 군집분석
2.1 군집의 특성 분석
👀 그룹별 데이터 확인
data_group = df_encoded.drop(columns=['ID']).groupby('label').mean()
data_group
fig, axes = plt.subplots(4, 6, figsize=(16, 12))
columns = data_group.columns.to_list()
for i, column in enumerate(columns):
row = i // 6
col = i % 6
sns.barplot(x=data_group.index, y=column, data=data_group, ax=axes[row, col])
axes[row, col].set_title(f'Average {column}')
axes[row, col].set_xlabel("data_group")
axes[row, col].set_ylabel(f'Average {column}')
plt.tight_layout()
plt.show()
📍 그룹별 데이터 특성
- 고객 정보 데이터
- Income: 그룹 0 > 2> 1 순으로 소득의 차이가 있음
- Kidhome, Teenhome: 그룹 1에는 아이가 2그룹에는 청소년이 있는 고객이 많이 분포됨
- Marital_Status: 그룹 0에만 Absurd가 있으며 Alone에는 그룹 0이 없음, 그룹 2에만 YOLO가 존재
- 소비 제품 데이터
- 그룹 0은 대부분의 소비 제품에서 높은 비율을 차지
- 구매 채널 데이터
- 그룹 0은 다양한 구매채널을 활용하나 웹페이지 방문 횟수는 적음
- 그룹 1, 2는 웹사이트를 방문하는 비중은 높으나 직접 구매로 이루어지지는 않음
📍 그룹별 데이터 해석
- 그룹 0은 높은 소득을 기반으로 많은 소비를 함
- 그룹 1은 상대적으로 젊은 고객이 주를 이루며 웹 사이트 방문을 많이하나 소득이 낮아 구매 비율이 낮음
- 그룹 2는 그룹 1과 2의 중간 정도의 특성을 나타내는 고객으로 보임
2.2 군집별 상품 추천
👀 그룹별 구매 비율
# 각 열의 비율 계산
df_mnt = data_group[["MntWines","MntFruits","MntMeatProducts","MntFishProducts","MntSweetProducts"]]
df_ratios = df_mnt.divide(df_mnt.sum(axis=1), axis=0)
df_ratios
📍 그룹별 데이터 해석
- 그룹 0: 와인 추천
- 그룹 1: 고기 추천
- 그룹 2: 와인 추천
2.3 특정 고객 대상 상품 추천 (ID=10870)
data[data.ID == 10870]
data_group.iloc[2,:]
Year_Birth 1966.114957
Income 52364.688504
Kidhome 0.412855
Teenhome 0.815822
MntWines 290.609394
MntFruits 19.034611
MntMeatProducts 101.075402
MntFishProducts 25.189122
MntSweetProducts 18.262052
NumDealsPurchases 3.100124
NumWebPurchases 4.744129
NumCatalogPurchases 2.254635
NumStorePurchases 6.065513
NumWebVisitsMonth 5.692213
Category_Absurd 0.000000
Category_Alone 0.001236
Category_Divorced 0.117429
Category_Married 0.372064
Category_Single 0.203956
Category_Together 0.259580
Category_Widow 0.043263
Category_YOLO 0.002472
📍 데이터에 기반한 상품 추천
- 해당 고객은 그룹 2에 속하므로 와인을 추천
- 웹에서 구매하는 비중이 그룹 2의 평균보다 높으므로 웹 홍보 전략 수행
통계분석
1. 가설 검정 (1)
한 공장에서 생산된 제품에서 최근 추정 불량률은 90%였다. 오차의 한계가 5% 이하가 되도록 하는 최소 표본 사이즈를 구하시오
✏️ 문제 정의
- 단일 집단의 모비율 신뢰구간 표본 크기를 구하는 문제
- 추정 불량률 90%, 오차한계 5%
- 가정: 신뢰구간 95%
p = 0.9
e = 0.05
confidence = 0.95
from scipy.stats import norm
# 정규 분포의 평균과 표준 편차
mu = 0
sigma = 1
two_side_alpha = (1 - confidence)/2
two_side = 1 - two_side_alpha
one_side_alpha = 1 - confidence
one_side = 1 - one_side_alpha
two_side_value = norm.ppf(two_side, loc=mu, scale=sigma)
one_side_value = norm.ppf(one_side, loc=mu, scale=sigma)
print(f"Z 분포 95% 양측검정 백분위수: {two_side_value}")
Z 분포 95% 양측검정 백분위수: 1.959963984540054
n = two_side_value**2 * (p*(1-p)) / e**2
print("최소 표본 수 :", int(np.ceil(n)))
최소 표본 수 : 139
2. 가설 검정 (2)
아래 그래프는 A, B, C 자치구별 H 의원에 대한 찬성, 반대 투표 결과이다. 자치구별 지지율이 같은지에 대해서 검정하시오.
df = pd.DataFrame({'A':[176, 124], 'B':[193, 107], 'C':[159, 141],'gubun':['agree','disagree']})
df = df.set_index('gubun').T
df
✏️ 문제 정의
- 범주형 변수에 대한 빈도수를 사용한 분석 ☞ 교차분석
- 지지율은 비율이기에 적합성 검정
📍 가설 수립
- H0: 지지율이 다르다고 할 수 없음
- H1: 지지율이 다르다고 할 수 있음
df['support'] = df.agree / (df.agree + df.disagree)
df['support']
A 0.586667
B 0.643333
C 0.530000
from scipy.stats import chisquare
ratio = df.support.sum()/3
chi = chisquare(df['support'], f_exp=[ratio, ratio, ratio])
chi
Power_divergenceResult(statistic=0.010946969696969688, pvalue=0.9945414673770099)
📍 적합도 검정 해석
- p-value가 0.05보다 크므로 귀무가설 채택
- 자치구별 지지율이 다르다고 할 수 없음
3. 가설 검정(3)
A학교 남녀 학생들의 평균 혈압 차이가 있는지 여부에 대한 검정하시오
- (단, 남학생과 여학생의 혈압 데이터는 정규분포를 따르며 등분산임을 가정)
import pandas as pd
data = pd.read_csv("https://raw.githubusercontent.com/ADPclass/ADP_book_ver01/main/data/26_problem6.csv")
data
✏️ 문제 정의
- 평균 혈압 차이 ☞ 두개 집단 차이 가설검정
- 정규성과 등분산성을 가정하고 있음 ☞ 분산이 같은 독립표본 t-test
3.1 남녀 학생들의 평균 혈압 차이가 있는지 가설 수립
📍 가설 수립
- H0: 남녀 학생 평균 혈압 차이는 없음
- H1: 남녀 학생 평균 혈압 차이는 있음
3.2 검정통계량을 구하고 판단
from scipy import stats
# 독립표본 데이터 설정
group1 = np.array(data[data.gender=='male'].pressure)
group2 = np.array(data[data.gender=='female'].pressure)
# 독립표본 t-검정 수행
result = stats.ttest_ind(a=group1, b=group2, equal_var=False)
# 결과 출력
print(result)
Ttest_indResult(statistic=1.3495215111176884, pvalue=0.19643211975943328)
📍 t-test 해석
- p-value가 0.05보다 크기에에 귀무가설 채택
- 남녀 학생 평균 혈압 차이는 없음
3.3 평균 혈압차의 신뢰구간을 구하고 결과가 3.2의 결과를 지지하는지 설명
✏️ 문제 정의
- 모평균 차이 신뢰구간 - 모분산을 모르는 경우
- 소표본에 대한 신뢰구간
- 신뢰구간은 95% 설정
mu_male = data[data.gender == 'male'].pressure.mean()
mu_female = data[data.gender == 'female'].pressure.mean()
n_male = len(data[data.gender == 'male'])
n_female = len(data[data.gender == 'female'])
dof = n_male + n_female -2
std_male = data[data.gender == 'male'].pressure.std()
std_female = data[data.gender == 'female'].pressure.std()
# 결합 분산 구하기
def get_sp(n1, n2, s1, s2):
under = n1+n2-2
over1 = (n1-1) * s1**2
over2 = (n2-1) * s2**2
return np.sqrt((over1 + over2)/under)
sp = get_sp(n_male, n_female, std_male, std_female)
# 분포 값 구하기
from scipy.stats import t
confidence = 0.95
two_side_alpha = (1 - confidence)/2
two_side = 1 - two_side_alpha
two_side_value = t.ppf(two_side, df=dof)
print(f"t 분포 95% 양측검정 백분위수: {two_side_value}")
t 분포 95% 양측검정 백분위수: 2.0686576104190406
mu_diff = mu_male - mu_female
sqrt = np.sqrt((1/n_male) + (1/n_female))
under = mu_diff - two_side_value * sp * sqrt
over = mu_diff + two_side_value * sp * sqrt
print("표본 평균 차이: ", mu_diff)
print("신뢰구간: ", under, over)
표본 평균 차이: 5.296527777777783
신뢰구간: -2.635362714413926 13.228418269969492
📍 신뢰구간 해석
- 보유한 데이터의 평균의 차이가 신뢰구간 안에 들어감
- 3.2의 혈압 차이가 없음에 부합
4. 시계열 분석
다음은 1월부터 9월까지의 은의 가격이다.
import pandas as pd
import numpy as np
data = pd.read_csv("https://raw.githubusercontent.com/ADPclass/ADP_book_ver01/main/data/26_problem4.csv")
data
4.1 은의 가격 및 이동평균값 3이 설정된 시계열 그래프
ma_data = data.transpose( )
ma_data.columns = ["month_price"]
ma_data['ma_3'] = ma_data['month_price'].rolling(window=3).mean()
ma_data.fillna(0, inplace=True)
ma_data
plt.plot(ma_data.index, ma_data['month_price'][:],c='blue',marker='o')
plt.plot(ma_data.index[2:], ma_data['ma_3'][2:],c='red')
4.2 1월 대비 9월의 은의 가격은 몇 % 올랐는가?(소수점 두번째 자리에서 반올림)
- 대비 증감률: (비교-기준)/기준 * 100
- A 대비 B ☞ A가 기준
m9 = ma_data["month_price"][-1]
m1 = ma_data["month_price"][0]
print("증감률 {}%".format(round((m9-m1)/m1*100,1)))
증감률 158.5%
댓글남기기