1. 기초 EDA / 전처리 및 통계적 분석
2일차에 진행했던 가설 및 검정들을 정리하였습니다.
- 형태결함(Pastry, Bumps) :
- 면적 변수 : 크기는 비슷했나 산포도가 작아 특정 면적에 치중되는 것을 알 수 있다. 유의성 검정으로는 판별할 수 없으나 산포도 측면에서 주요변수로 판정 ( 왜 산포가 집중 되는지는 추가적인 정보 필요)
- 이송 거리 변수 : 이송 거리가 많을 수록 평균적으로 형태결함이 발생한다는 것을 알 수 있음.
- 철판 두께 : 유의성 검정 결과 유의하다고 나왔지만 바이올린 플롯을 보면 데이터의 분포나 형태로 보았을 때, 두께는 근소하게 영향을 미침을 알 수 있음
- 방향도 : 결함의 방향성은 Pastry 양의 방향일 때, 영향을 미칠 수 있으나, Bumps는 영향을 안미침
- 표면결함(스크래치, Stains, Dirtiness):
- 면적 변수:
- Dirtiness를 제외하고 대부분 유의성을 띔
- K_Scratch를 제외하고 평균적으로 비슷하거나 좁은 면적에서 결함 발생
- K_Scratch를 제외하고 산포도가 낮음
- ➡️ 대부분 정상에 비해 비교적 좁은 면적에 결함이 발생하며, 산포도가 낮다. 그렇기에
- 이송 거리 :
- Dirtiness를 제외하고 산포도가 낮음
- 대체적으로 정상에 비해 평균과 비슷
- ➡️ 산포도가 낮은 것으로 미루어보아 특정 공정 단계에서 결함이 많이 발생
- 철판 두께 :
- Dirtiness를 제외하고 산포도가 낮음
- Dirtiness는 평균 두께가 높고 나머지는 다 비슷함, Dirtiness는 두께가 클수록 결함 발생
- ➡️ 대체적으로 산포도가 낮은 것으로 미루어보아 특정 두께에 결함이 치중되 있다는 것을 알 수 있음
- 방향도 :
- Z_Scratch와 Dirtiness는 양의 방향성을 가지고 K_Scratch와 Stains에서는 음의 방향성을 가짐
- 방향도에 따른 주요 변수 분류
- 양의 방향성 가짐 : Pastry, Z_scratch, Dirtiness
- 음의 방향성 가짐 : Bumps, K_scratch, Stains
- ➡️ 방향성 문제는 공정상의 문제라기보다 결정방향의 특징이라 orientation Index를 조작하여 한 결함(양의 방향성)을 줄이면, 다른 결함(음의 방향성)이 증가할 가능성이 매우 높다. 일명 Treadeoff라는 문제가 발생한다. 그래서 이 문제를 해결하기 위해서는 두 결함의 비용을 최소화하는 최적점을 찾는 것이 필요
- 면적 변수:
2. 이산형 변수에 대한 카이제곱 검정
0과 1로 나누어지는 이산형 변수인 강종타입에 대해 카이제곱검정을 진행했습니다.
import pandas as pd
import scipy.stats as stats
fault_columns = ['Pastry','Z_Scratch', 'K_Scratch', 'Stains', 'Dirtiness', 'Bumps', 'Other_Faults']
#TypeOfSteel_A300과의 카이제곱 검정 반복 수행
results = {}
for fault in fault_columns:
# 3-1. 각 결함 유형과 TypeOfSteel_A300 간의 분할표 생성
contingency_table = pd.crosstab(df[fault], df['TypeOfSteel_A300'])
# 3-2. 카이제곱 검정 실행 (correction=False는 Yates 보정을 사용하지 않음을 의미)
# chi2_contingency는 (통계량, p-value)를 반환
chi2, p_value, dof, expected = stats.chi2_contingency(contingency_table, correction=False)
# 3-3. 결과를 딕셔너리에 저장
results[fault] = {
'Chi2_Statistic': chi2,
'P_Value': p_value
}
# 4. 결과 출력 및 정리
print("--- TypeOfSteel_A300 강철 타입별 결함 유형 연관성 검정 결과 ---")
print("비교 변수: TypeOfSteel_A300")
print("-" * 50)
for fault, res in results.items():
print(f"결함: {fault}")
print(f" 카이제곱 통계량: {res['Chi2_Statistic']:.4f}")
print(f" p-value: {res['P_Value']:.5f}")
# p-value를 이용한 유의성 판단 (유의수준 0.05 기준)
if res['P_Value'] < 0.05:
print(" **결론: 강종타입과 통계적으로 유의한 연관성이 있음 (종속)** ✅")
else:
print(" **결론: 강종타입과 통계적으로 유의한 연관성이 없음 (독립)** ❌")
print("-" * 50)
--- TypeOfSteel_A300 강철 타입별 결함 유형 연관성 검정 결과 ---
비교 변수: TypeOfSteel_A300
--------------------------------------------------
결함: Pastry
카이제곱 통계량: 5.8531
p-value: 0.01555
**결론: 강종타입과 통계적으로 유의한 연관성이 있음 (종속)** ✅
--------------------------------------------------
결함: Z_Scratch
카이제곱 통계량: 223.4955
p-value: 0.00000
**결론: 강종타입과 통계적으로 유의한 연관성이 있음 (종속)** ✅
--------------------------------------------------
결함: K_Scratch
카이제곱 통계량: 321.9566
p-value: 0.00000
**결론: 강종타입과 통계적으로 유의한 연관성이 있음 (종속)** ✅
--------------------------------------------------
결함: Stains
카이제곱 통계량: 46.5526
p-value: 0.00000
**결론: 강종타입과 통계적으로 유의한 연관성이 있음 (종속)** ✅
--------------------------------------------------
결함: Dirtiness
카이제곱 통계량: 13.2285
p-value: 0.00028
**결론: 강종타입과 통계적으로 유의한 연관성이 있음 (종속)** ✅
--------------------------------------------------
결함: Bumps
카이제곱 통계량: 181.9420
p-value: 0.00000
**결론: 강종타입과 통계적으로 유의한 연관성이 있음 (종속)** ✅
--------------------------------------------------
결함: Other_Faults
카이제곱 통계량: 0.1192
p-value: 0.72989
**결론: 강종타입과 통계적으로 유의한 연관성이 없음 (독립)** ❌
--------------------------------------------------
3. 다중공선성 확인
선형회귀를 만들기 전에 다중공선성을 확인했습니다.
import pandas as pd
from statsmodels.stats.outliers_influence import variance_inflation_factor
import statsmodels.api as sm
# 1. 제거할 종속변수
fault_columns_to_remove = ['Pastry', 'Z_Scratch', 'K_Scratch', 'Stains', 'Dirtiness', 'Bumps', 'Other_Faults','X_Minimum','X_Maximum','Y_Maximum','Y_Minimum','Log_X_Index','Log_Y_Index','Sum_of_Luminosity','TypeOfSteel_A300','TypeOfSteel_A400','LogOfAreas']
# 2. 독립변수만 선택
X = df.drop(columns=fault_columns_to_remove)
# 3. VIF 계산을 위해 상수항(Intercept) 추가
X_with_const = sm.add_constant(X)
def calculate_vif(X):
vif_data = pd.DataFrame()
vif_data["Variable"] = X.columns
vif_data["VIF"] = [variance_inflation_factor(X.values, i)
for i in range(X.shape[1])]
return vif_data.sort_values('VIF', ascending=False)
# VIF 계산 실행
vif_result_cleaned = calculate_vif(X_with_const)
print(vif_result_cleaned)
Variable VIF
0 const 729.075273
2 X_Perimeter 41.196053
3 Y_Perimeter 25.473857
1 Pixels_Areas 18.417797
14 Orientation_Index 14.214397
15 Luminosity_Index 9.950001
12 Edges_Y_Index 7.424633
17 Minimum_of_Luminosity 6.881343
10 Outside_X_Index 5.896991
4 Maximum_of_Luminosity 5.506218
11 Edges_X_Index 4.986966
13 Outside_Global_Index 4.624415
16 SigmoidOfAreas 3.344112
8 Empty_Index 2.071037
9 Square_Index 1.794472
7 Edges_Index 1.306507
18 X_Center 1.294079
5 Length_of_Conveyer 1.269765
6 Steel_Plate_Thickness 1.254149
19 Y_Center 1.082909
다중공선성이 높아서
'X_Perimeter','Y_Perimeter','Pixels_Areas' 변수에 대해 PCA를 진행했습니다. 이유는 전부 위치 방향 벡터이기 때문입니다.
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
PCA_df = df[['X_Perimeter','Y_Perimeter','Pixels_Areas']]
scaler = StandardScaler()
X_scaled_PCA = scaler.fit_transform(PCA_df)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled_PCA)
print("주성분 변환 결과:\n", X_pca)
print("기여율:\n", pca.explained_variance_ratio_)

시각화를 해보니 잘 적용된것을 볼 수 있습니다.
# Outside_Global_Index 제거
import pandas as pd
from statsmodels.stats.outliers_influence import variance_inflation_factor
import statsmodels.api as sm
# 1. 제거할 종속변수
fault_columns_to_remove = ['Pastry', 'Z_Scratch', 'K_Scratch', 'Stains', 'Dirtiness', 'Bumps', 'Other_Faults','X_Minimum','X_Maximum','Y_Maximum','Y_Minimum','Log_X_Index','Log_Y_Index','Sum_of_Luminosity','TypeOfSteel_A300','TypeOfSteel_A400','LogOfAreas','X_Perimeter', 'Y_Perimeter', 'Pixels_Areas','Outside_Global_Index']
# 2. 독립변수만 선택
X = df.drop(columns=fault_columns_to_remove)
X_final = pd.concat([X.reset_index(drop=True),pd.DataFrame(X_pca, columns=['PC1', 'PC2'])], axis=1)
# 3. VIF 계산을 위해 상수항(Intercept) 추가
X_with_const = sm.add_constant(X_final)
def calculate_vif(X):
vif_data = pd.DataFrame()
vif_data["Variable"] = X.columns
vif_data["VIF"] = [variance_inflation_factor(X.values, i)
for i in range(X.shape[1])]
return vif_data.sort_values('VIF', ascending=False)
# VIF 계산 실행
vif_result_cleaned = calculate_vif(X_with_const)
print(vif_result_cleaned)
추가로 Outside_Global_Index 와 Orientation_index 또한 다중공선성이 높게 나왔고 데이터를 살펴보니 Outside_Global_Index 는 Orientation_index의 음양 방향만 나타내주었기에 제거하도록 했습니다.
그리고 결과를 보면
Variable VIF
0 const 697.835280
11 Luminosity_Index 9.853895
16 PC1 7.594801
10 Orientation_Index 6.847634
13 Minimum_of_Luminosity 6.735392
9 Edges_Y_Index 6.550003
1 Maximum_of_Luminosity 5.478486
8 Edges_X_Index 4.624516
7 Outside_X_Index 4.402057
12 SigmoidOfAreas 3.226400
5 Empty_Index 2.068983
17 PC2 1.985720
6 Square_Index 1.523274
4 Edges_Index 1.306253
14 X_Center 1.292959
2 Length_of_Conveyer 1.264637
3 Steel_Plate_Thickness 1.253889
15 Y_Center 1.080967
다중공선성이 깔끔하게 제거된 것을 볼 수 있습니다.
여기까지가 제가 한 역할이고 팀원분들이 한것을 확인하도록 하겠습니다.
1. 머신러닝 돌려서 적합한 모델 확인하기
XGBoost 결과
feature importance: 모델이 결과를 예측할 때 각 입력 변수가 얼마나 중요한 역할을 했는가?
1.Pastry 결함: 부풀어오른 요철, 비정상적인 덩어리
- 주요 변수
- Outside_Global_Index, Orientation_Index, Edge_Y_Index
- 철판 가장자리의 불균일함과 패턴의 방향성이 Pastry 결함 발생에 관련 있음
2.Z_Scratch: 지그재그형 스크래치, 불연속적인 스크래치
- 주요 변수
- TypeofSteel_A300, Typeofsteel_A400, Length_of_Conveyer
- 강종과 컨베이어 길이가 주요한 요인으로, 철판의 종류와 이송 조건의 차이가 Z 스크래치 결함에 영향을 미침
3.K_Scratch: 일자형 스크래치, 패턴을 가진 스크래치
- 주요 변수
- Steel_Plate_Thickness, Log_Y_Index, Outside_X_Index
- 두꺼운 철판일수록 K스크래치 발생 가능성이 높고, X 방향으로의 불균일성이 주요 원인
4.Stains: 변색, 오염으로 보이는 얼룩
- 주요 변수
- Pixels_Areas, LogOfAreas, SigmoidofAreas
- 얼룩은 결함 영역의 크기와 연관된 시각적 요인이 핵심
5.Dirtness: 먼지, 이물, 기름막 등
- 주요 변수
- Square_Index, Orientation_Index, Typeofsteel_A400
- 형상적 불균일함과 재질 차이(강종)에 따라 dirtness 결함이 발생할 수 있음
6.Bumps: 표면이 불룩하게 튀어나온 미세 돌기나융기로 인한 결함
- 주요 변수
- X_Perimeter, Pixels_Areas, Square_Index
- 돌출 결함은 물리적인 형상의 특성(길이나 면적)과 밀접한 연관이 있음
느낀점
이렇게 조원들과 역할을 분담하여 스스로 할일을 하니 프로젝트가 잘되어가고 있는 느낌입니다.
현업에서도 이렇게 잘맞는 팀원들과 협업하여 의미있는 성과를 내었으면 좋겠습니다.