심화 프로젝트

Chapter.5 심화 프로젝트(4)

devjjun 2026. 5. 4. 20:31

TIL(Today I Learned)


1️⃣심화 프로젝트 

통계 검정

Shapiro-Wilk W 통계량 막대 + Q-Q plot 

더보기
cols = ['TPS', 'Power', 'RPM', 'Consumption L/H',
            'HC', 'CO_CO2_ratio', 'CO_log',
            'O2_RPM_Effect', 'lambda_dev', 'O2']

# Shapiro-Wilk (n>5000 → 샘플링)
for col in cols:
    sample = df_clean[col].sample(5000, random_state=42)
    stat, p = stats.shapiro(sample)
    skew    = stats.skew(df_clean[col])
    print(f"{col}: W={stat:.4f}, p={p:.2e}, skew={skew:.2f}")

# QQ-plot 시각화
plt.figure(figsize=(15,10))

for i, col in enumerate(cols):
    plt.subplot(3, 4, i+1)

    sample = df_clean[col].sample(5000, random_state=42)
    stats.probplot(sample, dist="norm", plot=plt)

    plt.title(col)

plt.tight_layout()
plt.show()

# 출력값
TPS: W=0.6057, p=4.27e-75, skew=2.35
Power: W=0.6366, p=2.11e-73, skew=2.12
RPM: W=0.9171, p=4.21e-46, skew=0.85
Consumption L/H: W=0.7895, p=1.46e-62, skew=2.16
HC: W=0.3471, p=5.03e-86, skew=6.16
CO_CO2_ratio: W=0.6021, p=2.73e-75, skew=3.02
CO_log: W=0.8577, p=2.50e-55, skew=1.23
O2_RPM_Effect: W=0.7404, p=1.30e-66, skew=1.99
lambda_dev: W=0.7744, p=6.94e-64, skew=2.05
O2: W=0.9370, p=9.41e-42, skew=0.53

Q-Q Plot 해석 (정규성 확인)

  • 비정상적 분포
    • TPS, Power, Consumption L/H, CO_CO2_ratio
      • 부드러운 곡선X / 계단식으로 끊어져 나타남
      • 오른쪽 끝(상위 값)에서 선을 이탈해 급격히 치솟는 모습 -> 심한 이상치 존재
    • HC
      • 대부분의 데이터가 아래쪽에 몰려 있다가 마지막에 급격히 상승
  • 비교적 양호하지막 왜곡된 분포
    • RPM, O2
      • 다른 변수들에 비해 점들이 빨간 선을 따라가지만, 양 끝(꼬리 부분)에서는 선을 벗어남
      • 완전한 정규분포는 아니지만, 분석 모델에서 그나마 다루기 수월한 변수들
    • CO_log
      • 로그 변환을 했음에도 불구하고 양 끝단에서 두껍게 나타나는 분포를 보임

요약

모든 데이터가 정규분포와는 거리가 멀기 때문에, 비모수 분석이 훨씬 정확한 결과를 준다.


Kruskal-Wallis H 통계량 검정

더보기
# Kruskal-Wallis
results = []

for var in cols:
    groups = [df_clean[df_clean['Fault']==f][var].values for f in [0,1,2,3]]
    H, p = stats.kruskal(*groups)
    results.append([var, H, p])  # p 그대로 사용

res_df = pd.DataFrame(results, columns=['var', 'H', 'p'])

# log 변환 (0 방지)
res_df['log_p'] = -np.log10(res_df['p'].replace(0, 1e-300))

res_df = res_df.sort_values('H', ascending=True)

# 시각화
fig, ax = plt.subplots(figsize=(15, 7))

sc = ax.scatter(
    res_df['H'],
    res_df['var'],
    c=res_df['log_p'],
    s=200,
    cmap='viridis',
    edgecolors='black',
    linewidths=0.5
)

# p값 텍스트 표시
for _, row in res_df.iterrows():
    p_text = f"p={row['p']:.1e}" if row['p'] < 0.05 else f"p={row['p']:.3f}"
    ax.text(row['H'] + 100, row['var'], p_text, va='center', fontsize=8)

plt.colorbar(sc, ax=ax, label='-log10(p)')
ax.set_xlabel('Kruskal H-통계량 (차이의 크기)', fontsize=11)
ax.set_ylabel('엔진 센서 변수', fontsize=11)
ax.set_title('Kruskal-Wallis 검정', fontsize=12)
ax.set_xlim(0, res_df['H'].max() * 1.2)
ax.grid(axis='x', linestyle='--', alpha=0.5)

plt.tight_layout()
plt.show()

Kruskal-Wallis Analysis (No Correction) 그래프 해석

  • 주요 지표 해석 기준
    • X축(Kruskal H-statistic)
      • 집단 간 차이의 크기를 의미한다. 값이 클수록 해당 센서가 결함 유형(Fault 0~3)을 구분하는 데 결정적인 역할을 한다.
    • Y축
      • 분석에 사용된 엔진 센서 항목들
    • 색상 및 우측 바
      • 통계적 유의성을 나타낸다. 노란색에 가까울수록 p-값이 매우 작아 우연히 발생했을 확률이 거의 없는, 즉 매우 확실한 차이를 의미한다.
      • 점 크기 = 영향력 (H 기반) 큰 원 → 모델에 영향 큼 /작은 원 → 영향 거의 없음
    • 진한 파랑 = p값 매우 작음 = 확실한 차이
  • 변수별 상세 분석
    • HC (탄화수소), CO_log, CO_CO2_ratio, lambda_dev, RPM
      • H-statistic이 매우 높고(약 4,000 이상), 값이 거의 0()에 수렴한다.0.0e+00
      • p
      • 해석: 이 센서들은 엔진 결함 발생 시 수치 변화가 가장 뚜렷하다. 특히 HC와 CO 관련 지표들의 H값이 압도적으로 높은데, 이는 엔진 연소 상태와 배출가스 데이터가 결함 예측의 핵심 변수임을 알 수 있다.
    • O2_RPM_Effect, TPS, Power
      • H-statistic이 500~1,000 사이에 위치하며, 값은 매우 낮지만 최상위 지표들보다는 영향력이 적다.
      • p
      • 해석: 결함 구분에 유의미한 도움을 주지만, 배출가스 데이터만큼 강력한 구분 기준은 아니다.
    • Consumption L/H, O2
      • H-statistic이 가장 낮고 보라색에 가깝다.
      • 해석: 물론 로 통계적 유의성은 확보했으나, 다른 센서들에 비해 결함 유형별 데이터의 중첩이 많아 변별력이 상대적으로 떨어진다.
      • p<0.05

Dunn's test 히트맵

더보기
cols = ['TPS', 'Power', 'RPM', 'Consumption L/H',
        'HC', 'CO_CO2_ratio', 'CO_log',
        'O2_RPM_Effect', 'lambda_dev', 'O2']

pairs = ['0 vs 1', '0 vs 2', '0 vs 3', '1 vs 2', '1 vs 3', '2 vs 3']

# Dunn's test 전체 변수 실행 후 2 vs 3 쌍 추출
p_results = {}
for var in cols:
    sample = pd.concat([
        df_clean[df_clean['Fault']==f][['Fault', var]]
        .sample(3000, random_state=42)
        for f in [0,1,2,3]
    ])
    dunn = sp.posthoc_dunn(sample, val_col=var,
                           group_col='Fault',
                           p_adjust='bonferroni')
    p_results[var] = {
        '0 vs 1': dunn.loc[0, 1],
        '0 vs 2': dunn.loc[0, 2],
        '0 vs 3': dunn.loc[0, 3],
        '1 vs 2': dunn.loc[1, 2],
        '1 vs 3': dunn.loc[1, 3],
        '2 vs 3': dunn.loc[2, 3],
    }

p_df = pd.DataFrame(p_results).T

# 시각화
fig, ax = plt.subplots(figsize=(10, 7))

cmap = plt.cm.get_cmap('Blues_r')
im = ax.imshow(p_df.values, cmap=cmap, aspect='auto', vmin=0, vmax=1)

for i, var in enumerate(p_df.index):
    for j, pair in enumerate(p_df.columns):
        val = p_df.loc[var, pair]

        if val < 0.001:
            label = '***'
            color = 'white'
        elif val < 0.05:
            label = f'{val:.3f}\n*'
            color = 'white'
        else:
            label = 'ns'
            color = '#888888'

        ax.text(j, i, label, ha='center', va='center',
                fontsize=10, color=color, fontweight='bold')

ax.set_xticks(range(len(pairs)))
ax.set_xticklabels(pairs, fontsize=11)
ax.set_yticks(range(len(p_df.index)))
ax.set_yticklabels(p_df.index, fontsize=11)


plt.colorbar(im, ax=ax, label='p-value (lower = more significant)')
ax.set_title("Dunn's Test 결과 (본페로니 보정)\n*** p<0.001  * p<0.05  ns = 연관성 없음",
             fontsize=12)

plt.tight_layout()
plt.grid(False)
plt.show()

Dunn's Test Result (Bonferroni correction) 그래프 해석

  • 주요 지표 해석 기준
    • x축
      • 비교 대상인 결함 유형의 쌍이다.
    • y축
      • 분석에 사용된 엔진 센서 항목들
    • 색상
      • 색이 짙은 파란색일수록 값이 낮아 통계적 유의성이 높음을 의미한다.
      • p
    • 기호 해석
      • **:  (매우 강력한 유의성)
      • p<0.001
      • :  (통계적으로 유의미함)
      • p<0.05
      • ns (not significant): 통계적으로 유의미한 차이가 없음 (두 결함 유형 간 센서 값 분포가 비슷함)
  • 변수별 상세분석
    • 변별력이 높은 센서 (짙은 파란색이 많은 행)
      • HC (탄화수소)
        • 모든 결함 쌍( 포함)에 대해 유의미한 차이를 보인다. 이는 엔진 결함 유형을 구분하는 데 가장 핵심적인 지표임을 뜻한다.
        • 2 vs 3
      • O2_RPM_Effect, RPM, CO_log
        • 대부분의 결함 조합을 효과적으로 구분해내고 있다.
    • 특정 결함 쌍에 취약한 센서 (흰색/ns가 많은 열)
      • 0 vs 1
        • TPS, Power, Consumption L/H, HC를 제외한 나머지 지표들은 ns가 많다. 즉, 0번과 1번 결함을 구분할 때는 배출가스 농도보다는 물리적인 출력이나 스로틀(TPS) 데이터가 더 유용할 수 있다.
      • 2 vs 3
        • 가장 구분이 어려운 쌍으로 보인다. 많은 지표가 ns를 보이지만, HC, O2_RPM_Effect, RPM 등은 여전히 유의미한 차이(*)를 유지하며 두 결함을 구분하는 기준이 되어준다.

Cohen's d 막대 + O2_RPM_Effect KDE

더보기
results = []

sub = df_clean[df_clean['Fault'].isin([2,3])].copy()

for var in cols:
    g2 = sub[sub['Fault']==2][var]
    g3 = sub[sub['Fault']==3][var]

    pooled_std = np.sqrt((g2.std()**2 + g3.std()**2) / 2)
    d = (g2.mean() - g3.mean()) / pooled_std

    results.append([var, d])

d_df = pd.DataFrame(results, columns=['var','cohens_d'])
d_df = d_df.sort_values('cohens_d')

plt.figure(figsize=(10,6))
plt.barh(d_df['var'], d_df['cohens_d'])

# 기준선
plt.axvline(0, linestyle='--')   # +, -
plt.axvline(0.2, linestyle=':')  # 낮음

plt.xlabel("효과 크기(Cohen's d)")
plt.title("변수별 효과크기 분석(결함 2vs3)")
plt.show()

변수 별 효과크기 그래프 해석

  • 주요 지표 해석 기준
    • X축 (Cohen's d)
      • 두 집단 평균 차이를 표준편차로 나눈 값이다. 0에서 멀어질수록(절대값이 클수록) 두 결함 유형 간의 차이가 실질적으로 크다는 것을 의미다.
    • y축(Features)
      • 분석 대상이 된 엔진 센서 변수들이다.
    • 수직 점선 (관습적 기준)
      • 0.2 (Small): 작은 효과 크기
      • 0.5 (Medium): 중간 효과 크기
      • 0.8 (Large): 큰 효과 크기
  • 센서별 상세 진단
    • 가장 큰 차이를 보이는 변수
      • O2 (산소 센서)
        • 양(+)의 방향으로 가장 큰 막대를 가졌다. 결함 3에 비해 결함 2에서 산소 농도가 상대적으로 높게 나타나는 경향이 있으며, 실질적인 변별력이 가장 높다.
      • HC (탄화수소)
        • 음(-)의 방향으로 가장 큰 막대를 보인다. 이는 결함 2보다 결함 3에서 HC 배출량이 더 높게 측정될 가능성이 크다는 것을 시사한다.
    • 중간 정도의 차이
      • RPM, O2_RPM_Effect
        • 0에서 어느 정도 떨어져 있어 결함 구분에 보조적인 도움을 준다.
    • 차이가 거의 없는 변수
      • Consumption L/H, TPS, lambda_dev
        • 막대가 거의 0에 가깝다. 통계적 유의성과 상관없이, 실질적인 수치 분포 면에서는 결함 2와 3 사이에서 큰 차이가 없음을 나타난다.

결론

  • 실질적 변별력 확인
    • 현재 모든 변수의 Cohen's d 절대값이 0.2(Small) 미만으로 나타난다. 이는 결함 2와 3이 데이터적으로 매우 유사한 성격을 가지고 있어, 단일 센서 값만으로는 완벽하게 분리하기 어렵다는 것을 뜻한다.
  • 모델링 전략
    • 효과 크기가 미미하므로, 단일 변수보다는 O2와 HC의 조합 또는 여러 센서 데이터를 동시에 사용하는 머신러닝 앙상블 모델을 활용해야 결함 2와 3을 더 정확하게 분류할 수 있다.

💡 느낀점

전처리와 스케일링 등의 과정을 거친 정제된 데이터를 통계검정을 통해 정말 유의미한지? 생성한 파생변수가 연관성을 갖는지 확인할 수 있었다. 가설과 완전히 동일한 결과가 나오지는 않았지만, 어느정도의 연관성을 띄었다. 추후에는 다양한 모델들을 비교하여 결함 2,3을 높은 정확도로 분류할 수 있는 모델을 찾고, 그 모델 내에서도 파라미터 튜닝 등의 시행착오를 겪으며 문제를 풀어나갈 예정이다.

'심화 프로젝트' 카테고리의 다른 글

Chapter.5 심화 프로젝트(6)  (0) 2026.05.08
Chapter.5 심화 프로젝트(5)  (0) 2026.05.07
Chapter.5 심화 프로젝트(3)  (0) 2026.05.01
Chapter.5 심화 프로젝트(2)  (0) 2026.04.30
Chapter.5 심화 프로젝트(1)  (0) 2026.04.29