품질관리(QAQC) 데이터 부트캠프(본캠프)

본캠프_10주차(월)_TIL(데이터 준비 & 통계적 검정 정리)

Wat_zy 2025. 11. 17. 08:52

✅ 오늘 한 것

통계 및 머신러닝 정리, 태블로를 활용한 데이터 시각화


✏️ 오늘 배운 점

데이터 준비 과정

1) 데이터 불러오기 & 확인

# 데이터 크기(shape)
df.shape

# 결측치 여부
df.isnull().sum()

# 변수 타입 확인
df.info()

# 고유값(unique values) 확인
df['컬럼명'].unique() # 고유값 확인
df.nunique() # 고유값 개수 확인

# 기초 통계치(mean, std, min, max 등)
df.describe()

 

2) 결측치 탐색

# 결측치 비율
df.isnull().mean() * 100

# 결측치 패턴 분석
1. Missingno를 활용한 결측치 패턴 시각화
import missingno as msno
# 결측치 행렬 시각화
msno.matrix(df)
# 결측치 막대 그래프
msno.bar(df)
# 결측치 상관관계
msno.heatmap(df)
2. 결측치 상관 분석
# 결측 여부를 0/1로 변환
null_df = df.isnull().astype(int)
# 결측치 간 상관 분석
null_df.corr()

# 결측치 처리 전략(제거/대체/모델 기반 처리 등)
1. 결측치 제거
df = df.dropna() # 결측치 포함된 행 제거
df = df.dropna(subset=['컬럼명']) # 특정 컬럼만 기준으로 결측치 제거
2. 결측치 대체
df['컬럼명'] = df['컬럼명'].fillna(df['컬럼명'].mean())
df['컬럼명'] = df['컬럼명'].fillna(df['컬럼명'].median())
df['컬럼명'] = df['컬럼명'].fillna(df['컬럼명'].mode()[0])
3. 다중 대체(MICE), KNN 등 대체 방법
# KNN 기반 결측치 대체
from sklearn.impute import KNNImputer
imputer = KNNImputer(n_neighbors=n)
df_knn = pd.DataFrame(imputer.fit_transform(df), columns=df.columns)
# MICE
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
imputer = IterativeImputer()
df_mice = pd.DataFrame(imputer.fit_transform(df), columns=df.columns)

- KNN Imputer

기본적으로 n 값은 3~5를 많이 사용한다.

데이터가 많을수록 n은 크게, 작을수록 n은 작게 사용한다.

데이터가 1000~5000개 사이는 3~5 사용, 5000~10000개 사이면 7~10 사용

 

3) 이상치 탐색

# Boxplot, Scatterplot, Z-score, IQR 기반 탐지
1. Boxplot (이상치를 시각적으로 확인)
import seaborn as sns
import matplotlib.pyplot as plt
sns.boxplot(x=df['컬럼명'])
plt.show()
2. Scatterplot (변수 간 관계에서 이상치 확인)
plt.scatter(df['컬럼명'], df['컬럼명'])
plt.xlabel('컬럼명')
plt.ylabel('컬럼명')
plt.show()
3. Z-score 기반 이상치 탐지
from scipy.stats import zscore
df['컬럼명_z'] = zscore(df['컬럼명'])
outliers_z = df[df['컬럼명_z'].abs() > 3]
outliers_z
4. IQR 기반 탐지
Q1 = df['컬럼명'].quantile(0.25)
Q3 = df['컬럼명'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers_iqr = df[(df['컬럼명'] < lower_bound) | (df['컬럼명'] > upper_bound)]
outliers_iqr

# 도메인 지식 기반 판단
현실적으로 불가능한 값 제거 or 수정

# 제거, 변환, 조정 여부 결정
1. 이상치 제거
df_clean = df.drop(outlier_indices)
2. Log 변환 (왜도 감소)
df['컬럼명_log'] = np.log1p(df['컬럼명'])
3. Scaling (극단값 영향 줄이기)
표준화(StandardSclaer)
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import LabelEncoder
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(df['컬럼명'])
정규화(MinMaxScaler)
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)

 

4) 단변량 분석

1. 히스토그램(Histogram)
sns.histplot(df['컬럼명'], kde=False)
2. KDE
sns.kdeplot(df['컬럼명'])
3. Boxplot
sns.boxplot(x=df['컬럼명'])
4. 왜도 & 첨도
from scipy.stats import skew, kurtosis
skew_value = skew(df['컬럼명'].dropna())
kurt_value = kurtosis(df['컬럼명'].dropna())

 

5) 변수 간 관계 분석

1. 수치형 ↔ 수치형 (상관 분석 & Heatmap)
df.corr()
sns.heatmap(df.corr(), annot=True, cmap='coolwarm')

2. 범주형 ↔ 수치형 (그룹 간 차이 탐색)
sns.boxplot(x=df['범주형컬럼'], y=df['수치형컬럼'])

3. 범주형 ↔ 범주형 (교차표)
pd.crosstab(df['cat1'], df['cat2'])
sns.heatmap(pd.crosstab(df['cat1'], df['cat2']), annot=True, cmap='Blues')

범주형 데이터: 수치로 측정할 수 없고 종류별로 분류되는 데이터(성별, 혈액형, 거주 지역 등)

수치형 데이터: 수치로 측정할 수 있고 수학적 계산이 가능한 데이터(키, 몸무게 등)

 

6) 정규성 검정

1. Shapiro-Wilk Test
from scipy.stats import shapiro
stat, p = shapiro(df['컬럼명'].dropna())
print('Shapiro Test: stat=%.3f, p=%.4f' % (stat, p))

2. Q-Q Plot
import scipy.stats as stats
stats.probplot(df['컬럼명'].dropna(), dist="norm", plot=plt)
  • p < 0.05 → 정규성 X
  • p ≥ 0.05 → 정규성 O

7) 등분산성 검정

1. Levene Test
from scipy.stats import levene
levene(
    df[df['그룹']=='A']['컬럼명'],
    df[df['그룹']=='B']['컬럼명'],
    df[df['그룹']=='C']['컬럼명']
)

2. Bartlett Test
from scipy.stats import bartlett
bartlett(
    df[df['그룹']=='A']['컬럼명'],
    df[df['그룹']=='B']['컬럼명'],
    df[df['그룹']=='C']['컬럼명']
)
  • p < 0.05 → 분산이 동일하지 않음 (등분산성 X)
  • p ≥ 0.05 → 등분산성 O

통계적 검정

통계적 가설 검정 선택 (모수 / 비모수)

정규성 O → 모수 검정
정규성 X → 비모수 검정

단일 집단 확인

# 단일표본 t-검정: 한 집단의 평균이 특정 값과 다른지 확인할 때 사용
from scipy.stats import ttest_1samp
t_stat, p_value = ttest_1samp(x, y)
print("t-statistic:", t_stat)
print("Two-tailed p-value:", p_value)

두 집단 비교

목적 모수 비모수
두 집단 평균 비교 독립표본 t-검정 Mann–Whitney U test
짝지어진 두 집단 대응표본 t-검정 Wilcoxon signed-rank
모수 검정
# 독립표본 t-검정: 서로 다른 두 그룹 간 평균 차이를 비교할 때 사용
양측 검정: 평균에 차이가 있는지만 검정
단측 검정: 평균이 크거나 작다는 방향성 있는 검정
from scipy.stats import ttest_ind
t_stat, p_value_two_tailed = ttest_ind(x, y)
print("t-statistic:", t_stat)
print("Two-tailed p-value:", p_value_two_tailed
if t_stat > 0:
    p_value_one_tailed = p_value_two_tailed / 2
else:
    p_value_one_tailed = 1 - (p_value_two_tailed / 2)

print("One-tailed p-value (A > B):", p_value_one_tailed)

# 대응표본 t-검정: 동일한 대상에서 전/후 값을 비교할 때 사용
from scipy.stats import ttest_rel
t_stat, p_value = ttest_rel(x, y)
print("t-statistic:", t_stat)
print("Two-tailed p-value:", p_value)

Cohen’s d – 두 집단 간 차이의 효과 크기

import numpy as np

def cohens_d(x, y):
    nx, ny = len(x), len(y)
    pooled_std = np.sqrt(((nx-1)*np.var(x, ddof=1) + (ny-1)*np.var(y, ddof=1)) / (nx+ny-2))
    return (np.mean(x) - np.mean(y)) / pooled_std

d = cohens_d(x, y)
print("Cohen's d:", d)

 

d 값 해석
0.2 작은 효과
0.5 중간 효과
0.8 이상 큰 효과
비모수 검정
# Mann-Whitney U Test: 두 독립 집단 간의 중앙값 차이 또는 분포 차이를 비교
from scipy.stats import mannwhitneyu
stat, p = mannwhitneyu(group1, group2, alternative='two-sided')
print(f'U = {stat}, p-value = {p:.4f}')

# Wilcoxon Test: 같은 집단에서의 사전-사후 변화 등 쌍으로 대응되는 데이터의 차이를 비교
from scipy.stats import wilcoxon
stat, p = wilcoxon(x, y)
print(f'W = {stat}, p-value = {p:.4f}')

t-stat:  |t|(절대값)이 클수록 두 평균의 차이가 크다는 것을 의미한다.

|t|(절대값)이 클 때

  • 두 집단의 평균 차이가 큼
  • 오차(분산)가 작음
  • 차이가 일관적으로 나타남

t > 0: 그룹1 평균 > 그룹2 평균

t < 0: 그룹 1 평균 < 그룹2 평균

 

p-value < 0.05 --> 통계적으로 유의하게 다름. (귀무가설(H0) 기각)

p-value ≥ 0.05 --> 차이가 없다. (귀무가설 채택)

 

p-value와 t값은 반비례 관계 (t가 클수록 p-value가 작음. // t가 작을수록 p-value가 큼.)

세 집단 이상 비교

목적 모수 비모수
세 집단 평균 비교 One-way ANOVA Kruskal–Wallis
모수 검정
# One-Way ANOVA
from scipy.stats import f_oneway
print(f"p-value = {p_val_levene:.4f}")
f_stat, p_value = f_oneway(x_a, x_b, x_c)
print(f"\n--- 일원 분산분석 결과 ---")
print(f"F 통계량: {f_stat:.3f}")
print(f"p-value: {p_value:.4f}")
# Two-Way ANOVA
from statsmodels.formula.api import ols
from statsmodels.stats.anova import anova_lm
model = ols('Strength ~ C(Supplier) + C(Temp) + C(Supplier):C(Temp)', data=data).fit()
anova_table = anova_lm(model, typ=2)
print("\n--- 이원 분산분석 결과 (ANOVA Table) ---")
print(anova_table)

η² (Eta Squared) – ANOVA 효과 크기

η²: 총 변화 중 집단 차이가 설명하는 비율

import numpy as np

# ANOVA 실행
f_stat, p_value = f_oneway(x_a, x_b, x_c)

# eta squared 계산
SS_between = (
    ((np.mean(x_a) - np.mean(np.concatenate([x_a, x_b, x_c])))**2 * len(x_a)) +
    ((np.mean(x_b) - np.mean(np.concatenate([x_a, x_b, x_c])))**2 * len(x_b)) +
    ((np.mean(x_c) - np.mean(np.concatenate([x_a, x_b, x_c])))**2 * len(x_c))
)
SS_total = np.sum((np.concatenate([x_a, x_b, x_c]) - np.mean(np.concatenate([x_a, x_b, x_c])))**2)

eta_squared = SS_between / SS_total
print("η² (Eta Squared):", eta_squared)

 

비모수 검정
# Kruskal–Wallis
from scipy.stats import kruskal
h_stat, p_val_kruskal = kruskal(x_a, x_b, x_c)
print(f"--- Kruskal-Wallis Test ---")
print(f"H-statistic: {h_stat:.4f}, P-value: {p_val_kruskal:.4f}")
  • p-value < 0.05 → 세 집단 중 적어도 한 곳의 평균이 다르다 (귀무가설 기각)
  • p-value ≥ 0.05 → 평균 차이가 유의하지 않다 (귀무가설 유지)

사후검정(Post-hoc)

  • ANOVA → Tukey HSD
  • Kruskal → Dunn test
# Tukey's HSD(모수 사후검정)
from statsmodels.stats.multicomp import pairwise_tukeyhsd
tukey = pairwise_tukeyhsd(endog=df['값'], groups=df['그룹'], alpha=0.05)
print(tukey)
# Dunn's Test(비모수 사후검정)
import scikit_posthocs as sp
sp.posthoc_dunn(df, val_col='값', group_col='그룹', p_adjust='bonferroni')

상관/연관 분석

  • Pearson (정규성)
from scipy.stats import pearsonr
corr, p_value = pearsonr(df['x'], df['y'])
print("Pearson Correlation:", corr)
print("p-value:", p_value)

 

corr 값 의미
+1 완전한 양의 선형 관계
0 선형 관계 없음
-1 완전한 음의 선형 관계

p-value < 0.05 → 통계적으로 유의한 상관관계 존재
p-value ≥ 0.05 → 우연일 가능성, 상관 없음

  • Spearman (비정규)
from scipy.stats import spearmanr
corr, p_value = spearmanr(df['x'], df['y'])
print("Spearman Correlation:", corr)
print("p-value:", p_value)
corr 값 의미
+1 완전한 양의 선형 관계
0 선형 관계 없음
-1 완전한 음의 선형 관계

p-value < 0.05 → 통계적으로 유의한 상관관계 존재
p-value ≥ 0.05 → 우연일 가능성, 상관 없음

  • Kendall tau
from scipy.stats import kendalltau
corr, p_value = kendalltau(df['x'], df['y'])
print("Kendall Tau:", corr)
print("p-value:", p_value)
  • 카이제곱 검정(범주형↔범주형)
import pandas as pd
table = pd.crosstab(df['cat1'], df['cat2'])
print(table)

from scipy.stats import chi2_contingency
chi2, p_value, dof, expected = chi2_contingency(table)
print("Chi-square:", chi2)
print("p-value:", p_value)
print("Degrees of freedom:", dof)
print("Expected frequencies:\n", expected)

p-value < 0.05
→ 두 범주형 변수 간 연관성이 존재 (독립이 아니다)

p-value ≥ 0.05
→ 두 변수는 독립이며 관계 없음

 

단순/다중 회귀 분석(Regression)

  • 선형회귀
# 단순 선형 회귀(X가 증가할수록 Y도 증가/감소하는가?)
from sklearn.linear_model import LinearRegression
import numpy as np

X = df[['x']]
y = df['y']

model = LinearRegression()
model.fit(X, y)

print("기울기(β1):", model.coef_[0])
print("절편(β0):", model.intercept_)
print("R²:", model.score(X, y))

# 다중 선형 회귀(여러 독립변수가 Y에 어떤 영향을 미치는가?)
X = df[['x1', 'x2', 'x3']]
y = df['y']

model = LinearRegression()
model.fit(X, y)

print("회귀계수:", model.coef_)
print("절편:", model.intercept_)
print("R²:", model.score(X, y))

다중공선성(VIF)

from statsmodels.stats.outliers_influence import variance_inflation_factor
import pandas as pd

X = df[['x1', 'x2', 'x3']]
vif_df = pd.DataFrame()
vif_df['feature'] = X.columns
vif_df['VIF'] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
print(vif_df)

 

VIF 값 해석
1~5 문제 없음
5~10 다소 높음 → 주의 필요
10 이상 강한 다중공선성 → 변수 제거 고려

 

  • 다항회귀
# 다항 회귀(X가 증가하면서 곡선 형태로 Y가 변하는 경우)
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

poly = PolynomialFeatures(degree=2)
X_poly = poly.fit_transform(df[['x']])
y = df['y']

model = LinearRegression()
model.fit(X_poly, y)

print("회귀계수:", model.coef_)
print("R²:", model.score(X_poly, y))
  • 로지스틱 회귀
# 로지스틱 회귀(독립 변수의 선형 결합을 이용하여 사건의 발생 가능성을 예측)
from sklearn.linear_model import LogisticRegression

X = df[['x1', 'x2', 'x3']]
y = df['label']

model = LogisticRegression()
model.fit(X, y)

print("회귀계수:", model.coef_)
print("절편:", model.intercept_)
print("정확도:", model.score(X, y))
  • Poisson/Negative Binomial 회귀
# 포아송 회귀(단위 시간 또는 공간 내에서 어떤 사건이 평균 λ번 발생할 때, 실제 사건 발생 횟수(k)에 대한 이산형 확률분포)
import statsmodels.api as sm
import statsmodels.formula.api as smf

model = smf.glm("count ~ x1 + x2", data=df,
                family=sm.families.Poisson()).fit()
print(model.summary())

# Negative Binomial Regression(분산 > 평균 (과산포)일 때, 포아송보다 더 적합)
import statsmodels.api as sm
import statsmodels.formula.api as smf

model = smf.glm("count ~ x1 + x2", data=df,
                family=sm.families.NegativeBinomial()).fit()
print(model.summary())
  • GLM
# GLM(선형 회귀의 확장판)
import statsmodels.api as sm
import statsmodels.formula.api as smf

model = smf.glm("y ~ x1 + x2", data=df,
                family=sm.families.Gaussian()).fit()
print(model.summary())

 

family 용도
Gaussian 일반 선형 회귀
Binomial 로지스틱 회귀
Poisson 카운트
NegativeBinomial 과산포 카운트
Gamma 시간/비율 등

 


태블로를 활용한 데이터 시각화

Visual Analytics: 인간의 시각적 지각 능력을 활용해서 데이터를 표현하고 분석하는 과정

비즈니스 인텔리전스(Business Inteligence, BI): 조직에서 비즈니스 데이터를 수집, 분석 및 시각화하여 실행 가능한 통찰과 의미 있는 정보를 생성하기 위해 사용하는 기술, 전략 프로세스

 

Tableau: 데이터 시각화와 비즈니스 인텔리전스(BI) 도구

Tableau Prep: 데이터를 결합, 변형, 정리하는 과정 담당
Tableau Desktop: 데이터 탐색, 시각화, 분석 수행
Tableau Server & Tableau Cloud: 조직 내 협업, 배포, 거버넌스 관리


📌추가로 해야할 점

머신러닝 정리, 태블로를 활용한 데이터 시각화