카테고리 없음

실무에 쓰는 머신러닝 기초 - 전처리 방법

iron-min 2025. 12. 18. 23:47

1. 전처리의 사례

제조업의 경우

  • 센서가 간헐적으로 측정에 실패해 결측값이 발생
  • 센서 오작동으로 인해 극단적으로 큰 값(이상치)이 기록
  • 정상 제품과 불량 제품의 데이터 분포가 매우 다름(불균형 데이터)

 

 

2.결측치 처리

1) 결측치 처리 기법 : 결측치가 있는 행(row) 또는 열(column)을 제거

→ 간단하지만 데이터 손실이 발생

→ 결측치가 전체 데이터에서 매우 소수일 때 적합

 

2) 대체

① 평균 또는 중앙값으로 대체

→ 수치형 데이터에서 많이 사용, 데이터 분포 왜곡이 비교적 적음

# 평균값으로 대치
df_mean = df.copy()
df_mean = df_mean.fillna(df_mean.mean(numeric_only=True))
df_mean
# 4) 중앙값으로 대치
df_median = df.copy()
df_median = df_median.fillna(df_median.median(numeric_only=True))
df_median

 

최빈값으로 대체

→ 범주형 데이터에서 사용

# 5) 최빈값으로 대치
#   - DataFrame의 mode()는 각 열별로 최빈값을 반환합니다.
#   - mode() 결과가 여러 개(동률)일 경우 첫 번째 행의 값을 취합니다.
df_mode = df.copy()
print(df_mode.mode()) # 확인용
mode_values = df_mode.mode().iloc[0]  # 첫 번째 행(가장 상위 mode)만 취함
df_mode = df_mode.fillna(mode_values)
df_mode

③ 예측 모델로 대체

→ 회귀/분류 모델을 이용해 결측값을 예측

 

제조업 예시

  • 센서 데이터에서 특정 시간대(야간)에 종종 측정 누락이 발생한다면,

→ 중앙값으로 간단히 대체하거나

→ 인접 시간대 센서 값의 추세(ex: 이동평균)를 바탕으로 결측치 채움

  • 중요한 센서라면, 예측 모델을 통해 결측치를 추정 (제조업 뿐만 아니더라도…)

 

 

3. 이상치 탐지 및 제거

  • 정상 범주에서 크게 벗어나는 값
  • 장비 오작동, 환경적 특이 상황 등 원인이 다양함

 

탐지기법

① 통계적 기법 (3σ Rule)

: 데이터가 정규분포를 따른다고 가정하고, 평균에서 ±3σ(표준편차) 범위를 벗어나는 값을 이상치로 간주

: 직관적이고 간단하나, 정규성 가정이 틀릴 수 있음

 

② 박스플롯(Boxplot) 기준

: 사분위수(IQR = Q3 - Q1)를 이용해 ‘Q1 - 1.5×IQR’, ‘Q3 + 1.5×IQR’를 벗어나는 데이터를 이상치로 간주

: 분포 특성에 영향을 적게 받는 장점

머신러닝 기반

: 이상치 탐지 알고리즘 (Isolation Forest, DBSCAN 등)

: 복합적 패턴을 고려할 수 있음

 

 

처리기법

① 이상치 단순제거

# 이상치 제거 (간단하게 박스플롯 기준 적용 예시)
Q1 = df['sensor_value'].quantile(0.25)
Q3 = df['sensor_value'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
df = df[(df['sensor_value'] >= lower_bound) & (df['sensor_value'] <= upper_bound)]
df

② 이상치 값을 조정(클리핑, Winsorizing)

③ 별도로 구분하여 모델에서 제외하거나, 다른 모델로 활용

 

 

제조업 예시)

  • 특정 센서값이 정상 범위(ex: 20~80)에서 갑자기 200 이상을 기록 → 장비 이상 가능성 높음
  • 매 생산 라인마다 센서값 범위가 조금씩 다를 수 있으므로, 라인별 통계 기준(평균, 표준편차)으로 이상치 판단

 

4. 정규화 표준화

왜 필요한가?

1. 모델에 따라 특정 변수의 스케일이 크게 영향을 미칠 수 있음.

2. 센서 A는 값 범위가 0~1000, 센서 B는 값 범위가 0~1 이라면, A가 모델에 더 큰 영향을 줌.

 

정규화(MinMaxScaler) : 모든 값을 0과 1 사이로 매핑

: 값의 스케일이 달라도 공통 범위로 맞출 수 있음.

: 딥러닝(신경망), 이미지 처리 등에서 입력값을 0~1로 제한해야 하거나, 각 특성이 동일한 범위 내 있어야 하는 경우 자주 사용.

: 거리 기반 알고리즘(유클리디안 거리 사용)이나, 각 특성의 범위를 동일하게 맞춤으로써 계산 안정성을 높이고 싶을 때.

: 최소값·최대값이 극단값(Outlier)에 민감. 만약 극단치가 있으면 대부분의 데이터가 [0, 1] 구간 내부 한쪽에 치우침.

: 새로운 데이터가 기존 최대값보다 커지거나, 최소값보다 작아지는 경우, 스케일링 범위를 벗어날 수 있어 재학습하거나 다른 처리가 필요.

from sklearn.preprocessing import MinMaxScaler

# 스케일링을 적용할 컬럼만 선정
cols_to_scale = ['impressions', 'clicks', 'conversions', 'cost', 'revenue']

# MinMaxScaler 객체 생성(기본 스케일: [0,1])
minmax_scaler = MinMaxScaler()

# fit_transform을 통해 스케일링된 결과를 데이터프레임으로 변환
df_minmax_scaled = pd.DataFrame(minmax_scaler.fit_transform(df[cols_to_scale]), 
                                columns=cols_to_scale)                            
print(df_minmax_scaled.max())
print(df_minmax_scaled.min())
                     
df_minmax_scaled

 

 

표준화(평균을 0, 표준편차를 1로 만듬)

: 분포가 정규분포에 가깝게 변형됨

: 평균이 0, 표준편차가 1로 맞춰지므로, 정규분포 가정을 사용하는 알고리즘(선형회귀, 로지스틱회귀, SVM 등)에 자주 쓰임.

: 변환된 값들이 이론적으로 -∞ ~ +∞ 범위를 가질 수 있습니다.

: 데이터가 특정 구간([0, 1] 등)에 고정되지는 않습니다.

: 데이터 분포가 심하게 치우쳐 있으면, 평균과 표준편차만으로는 충분한 스케일링이 되지 않을 수 있습니다(로그 변환, RobustScaler 등 추가 고려).

```python
from sklearn.preprocessing import StandardScaler

# StandardScaler 객체 생성
standard_scaler = StandardScaler()

# fit_transform을 통해 스케일링된 결과를 데이터프레임으로 변환
df_standard_scaled = pd.DataFrame(standard_scaler.fit_transform(df[cols_to_scale]), 
                                  columns=cols_to_scale)

print(df_standard_scaled.mean())
print(df_standard_scaled.std())
df_standard_scaled
```

 

5. 불균형 데이터 처리

불균형 데이터란 : 정상 99%, 불량 1%처럼 한 클래스가 극도로 적은 경우

→ 모델이 극도로 적은 클래스를 거의 예측하지 못할 가능성이 큼(편향 발생)

 

해결방법

Oversampling

 

📚 Random Oversampling

: 소수 클래스의 데이터를 단순 복제하여 개수를 늘림

📚 SMOTE(Synthetic Minority Over-sampling Technique)

: 소수 클래스를 "무작정 복사"만 하는 게 아니라, “비슷한” 데이터들을 서로 섞어서(Interpolation) 새로운 데이터 생성

: 즉, 소수 클래스(ex: 스팸) 안에서 가까운 데이터 둘(혹은 몇 개)을 고르고, 그 사이에 새 데이터 포인트를 만들어내어, 소수 클래스의 다양한 예시를 가상으로 늘리는 기법

: 예시로 생각해보기

→ 오렌지(소수 클래스)와 사과(다수 클래스)가 있는 과일 바구니를 떠올려볼게요.

→ 사과는 90개, 오렌지는 10개뿐이면, 사과가 훨씬 많죠.

→ "오렌지를 조금 더 만들어서(복사)" 갯수를 맞출 수도 있지만, 그러면 똑같은 오렌지가 여러 개 생길 뿐, 다양성이 없어요.

→ SMOTE는 어떻게 하느냐면,

“모양이나 맛이 비슷한 두 오렌지를 고른 다음, 그 중간 정도 되는 새로운 오렌지를 상상해서 만들어낸다” 같은 느낌입니다.

이렇게 하면 기존 오렌지랑 똑같지도 않고, 완전히 엉뚱하지도 않은 새 오렌지를 얻을 수 있죠.

 

Undersampling

: 다수 클래스 데이터를 줄이는 방식

: 데이터 손실 위험이 있지만, 전체 데이터 균형을 맞출 수 있음

 

혼합 기법

: SMOTE와 언더샘플링을 적절히 섞어서 사용

import numpy as np
import pandas as pd

# 난수 고정 (재현성)
np.random.seed(42)

# 불균형 데이터 크기 설정
# 예: 총 100개 중 defect=1(불량)인 샘플 10개, defect=0(정상)인 샘플 90개
size_1 = 10
size_0 = 90

# 정상 클래스 (defect=0) 데이터 생성
feature1_0 = np.random.normal(loc=10, scale=2, size=size_0)
feature2_0 = np.random.normal(loc=5, scale=1, size=size_0)

# 불량 클래스 (defect=1) 데이터 생성
feature1_1 = np.random.normal(loc=20, scale=5, size=size_1)
feature2_1 = np.random.normal(loc=10, scale=2, size=size_1)

# 배열 병합
feature1 = np.concatenate([feature1_0, feature1_1])
feature2 = np.concatenate([feature2_0, feature2_1])
defect = np.array([0]*size_0 + [1]*size_1)

# 데이터프레임 생성
df = pd.DataFrame({
    'feature1': feature1,
    'feature2': feature2,
    'defect': defect
})

df

 

6. 범주형 데이터 변환

원-핫 인코딩

  • 범주형 변수를 각각의 범주별로 새로운 열로 표현, 해당 범주에 해당하면 1, 아니면 0
  • ex) 색상(‘Red’, ‘Blue’, ‘Green’) → ‘Red=1,Blue=0,Green=0’ / ‘Red=0,Blue=1,Green=0’ / …
  • 장점: 범주 간 서열 관계가 없을 때 사용하기 좋음
  • 단점: 범주가 매우 많으면 차원이 커짐
import pandas as pd
import numpy as np

# 예시 데이터프레임 생성
data_size = 10
np.random.seed(42)

labels = ['apple', 'banana', 'cherry']
random_labels = np.random.choice(labels, data_size)

df = pd.DataFrame({
    'id': range(1, data_size + 1),
    'label': random_labels,
    'value': np.random.randint(1, 100, data_size),
    'another_feature': np.random.choice(['A', 'B'], data_size)  # 또 다른 범주형 변수
})

df

 

레이블 인코딩(Label Encoding)

  • 범주를 숫자로 직접 맵핑(‘M’=0, ‘L’=1, ‘XL’=2 등)
  • 단순하지만, 모델이 숫자의 크기를 서열 정보로 잘못 해석할 수 있음
import pandas as pd
import numpy as np

# 예시 데이터프레임 생성
data_size = 10
np.random.seed(42)

labels = ['apple', 'banana', 'cherry']
random_labels = np.random.choice(labels, data_size)

df = pd.DataFrame({
    'id': range(1, data_size + 1),
    'label': random_labels,
    'value': np.random.randint(1, 100, data_size),
    'another_feature': np.random.choice(['A', 'B'], data_size)  # 또 다른 범주형 변수
})

df

 

7. 피처 엔지니어링

모델 성능 향상을 위해 기존 데이터를 변형, 조합하여 새로운 특성(피처)을 만드는 작업

 

1. 복잡한 데이터 구조 안에 존재하는 패턴을 효과적으로 추출해 모델이 쉽게 학습하게 함

2. 

  • 제조업에서는 센서 데이터 간 시계열적·물리적 관계를 반영하는 경우가 많음
  • 금융에서의 데이터는 고객 신용도, 거래내역, 시장 지표 등 복잡하고 다양한 변수를 포함하는 경우가 많음
  • 마케팅에서는 고객 행동 데이터(클릭, 구매 기록, 웹사이트 체류 시간 등)와 고객 특성 데이터(나이, 지역, 관심 분야 등)를 통합해 피처를 만들어야 효과적인 캠페인 타깃팅, 고객 세분화, 개인화 추천이 가능한 경우가 많음

1. 날짜 파생 변수

: ex) 측정 시간이 ‘2025-02-24 10:35:00’이라면, ‘월(2)’, ‘요일(월=1)’, ‘시(10)’, ‘주말여부(0/1)’ 등으로 분해

 

2. 수치형 변수 조합

: ex) ‘온도’와 ‘습도’가 있을 때, 새로운 피처 ‘온도×습도(TEMP×HUMID)’를 추가

: 두 변수의 상호작용이 불량 발생에 영향을 줄 수 있음

 

3. 로그 변환, 제곱근 변환 등

: 분포가 매우 치우친 변수(오른쪽 꼬리가 긴 경우)에 로그 변환을 적용하여 정규성에 가까워지도록 조정

 

import pandas as pd
import numpy as np

np.random.seed(42)  # 재현성을 위한 시드 고정

# 10개 데이터 샘플 생성
data_size = 10

# 날짜/시간 컬럼(예시)
dates = pd.date_range(start="2023-01-01", periods=data_size, freq='D')

# 온도(°C) : 15 ~ 35 사이 정수
temperature = np.random.randint(15, 36, size=data_size)

# 습도(%) : 30 ~ 90 사이 정수
humidity = np.random.randint(30, 91, size=data_size)

df = pd.DataFrame({
    'date': dates,
    'temperature': temperature,
    'humidity': humidity
})

df

 

 

추가) 변수 선택

  • 상관관계
    • 두 변수 간 상관도가 높은 상황인 경우 다중공선성 의심. 중복 정보가 클 수 있으므로, 하나만 남기거나 둘 다 제거 고려
  • 다중공선성
더보기
  • 회귀분석(집값 예측, 매출 예측 등)을 할 때, 여러 설명 변수(독립 변수)를 사용
  • 그런데 이 변수들이 서로 너무 비슷한 정보를 담고 있어 (즉, 서로 강하게 상관이 있어) 모델이 헷갈리는 문제가 생김
  • 이런 다중공선성(multicollinearity) 문제는 회귀계수(모델 파라미터)의 의미 해석과 모델 안정성을 해침
    • ex) 집 크기(㎡)와 방 개수가 거의 정비례한다면, 둘 다 넣었을 때 겹치는 정보가 많아집니다.
    • 예시로 생각해보기
      • "방 개수"와 "평수(㎡)"라는 두 변수를 예로 들어볼게요.
        • 방이 5개면 평수도 대체로 넓고, 1개면 대체로 좁죠(둘은 서로 높은 상관 관계).
      • 둘 다 회귀분석에 넣으면 모델 입장에서 "비슷한 정보가 두 번 들어온 셈"이라,어떤 변수가 집값에 얼마나 영향을 주는지(독립적 기여도)를 구분하기 어려워집니다.
      • 이런 경우, VIF가 높게 나타납니다.
  • VIF
    • 회귀분석에서 다중공선성 문제를 파악할 때 사용
    • VIF는 어떤 변수 하나가, 다른 변수들과 얼마나 겹치는지(상관이 큰지) 수치로 보여주는 지표
    • VIF가 일정 기준(예: 10 이상)을 넘으면 해당 변수를 제거하거나 비슷한 변수들을 합치는(변환) 등의 방법으로 문제를 해결
  • 모델 기반 중요도(Feature Importance)
    • 트리 기반 모델(랜덤 포레스트, XGBoost 등)을 훈련 후 중요도가 낮은 변수를 제거

 

 

 

실습

 

1) 준비단계

import numpy as np
import pandas as pd

# 스케일링, 폴리노미얼, 라벨인코딩, SMOTE
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.preprocessing import PolynomialFeatures, LabelEncoder
from imblearn.over_sampling import SMOTE

# VIF 계산용
from statsmodels.stats.outliers_influence import variance_inflation_factor

 

2) 예제 데이터 생성단계

# ----------------------------------------------------------
# 1) 임의 데이터프레임 생성
#    - 수치형 변수 2개, 범주형 변수 1개, 날짜변수 1개, 타깃 변수(불균형) 1개
# ----------------------------------------------------------

np.random.seed(42)
N = 200

# 수치형 변수를 생성 (평균 50, 표준편차 10)
num1 = np.random.normal(loc=50, scale=10, size=N)
num2 = np.random.normal(loc=100, scale=20, size=N)

# 범주형 변수 (A, B, C 중 무작위)
cat_col = np.random.choice(['A', 'B', 'C'], size=N)

# 날짜변수(최근 200일 내 임의 날짜) 생성
date_rng = pd.date_range('2025-01-01', periods=N, freq='D')
print(date_rng)
#np.random.shuffle(date_rng)  # 날짜 순서 랜덤화

# 불균형 타깃(1이 10%, 0이 90%라고 가정)
#   - 실제로 1이 대략 20개, 0이 180개
target = np.random.choice([0,1], p=[0.9, 0.1], size=N)

# 데이터프레임 구성
df = pd.DataFrame({
    'num1': num1,
    'num2': num2,
    'cat_col': cat_col,
    'date_col': date_rng,
    'target': target
})
# ----------------------------------------------------------
# 2) 결측치 추가 & 이상치(Outlier) 주입
#    - 일부 값을 NaN으로 바꾸고, 극단값 몇 개 삽입
# ----------------------------------------------------------

# (2-1) 결측치 추가
missing_indices_num1 = np.random.choice(df.index, size=5, replace=False)  # 5개 결측
missing_indices_num2 = np.random.choice(df.index, size=5, replace=False)  # 5개 결측
missing_indices_cat = np.random.choice(df.index, size=3, replace=False)   # 3개 결측
missing_indices_date= np.random.choice(df.index, size=2, replace=False)   # 2개 결측

df.loc[missing_indices_num1, 'num1'] = np.nan
df.loc[missing_indices_num2, 'num2'] = np.nan
df.loc[missing_indices_cat, 'cat_col'] = np.nan
df.loc[missing_indices_date,'date_col'] = pd.NaT

# (2-2) 이상치(Outlier) 주입: num1, num2에 극단값
outlier_indices_num1 = np.random.choice(df.index, size=3, replace=False)
outlier_indices_num2 = np.random.choice(df.index, size=3, replace=False)
df.loc[outlier_indices_num1, 'num1'] = df['num1'].mean() + 8*df['num1'].std()
df.loc[outlier_indices_num2, 'num2'] = df['num2'].mean() + 10*df['num2'].std()

print("=== [원본 데이터 일부] ===")
print(df.head(10))
print()

 

 

3) 결측치 처리

# ----------------------------------------------------------
# 3) 결측치 처리
#    (3-1) 일부 행 제거, (3-2) 평균·중앙값 등으로 대체
# ----------------------------------------------------------

df_dropna = df.dropna()  # 모든 컬럼 중 결측값이 있으면 제거

df_fillna = df.copy()
# 수치형은 열별 평균으로 대체 (mean)
df_fillna['num1'] = df_fillna['num1'].fillna(df_fillna['num1'].mean())
df_fillna['num2'] = df_fillna['num2'].fillna(df_fillna['num2'].mean())
# 범주형은 최빈값으로 대체 (mode)
most_freq_cat = df_fillna['cat_col'].mode().iloc[0]
df_fillna['cat_col'] = df_fillna['cat_col'].fillna(most_freq_cat)
# 날짜열은 제거하지 않고 그대로 둠(또는 임의 날짜로 대체 가능)
# -> 시연을 위해 NaT(결측)도 남겨둠

print("=== [결측치 제거 후 shape] ===")
print(df_dropna.shape)
print("=== [결측치 대체 후 shape] ===")
print(df_fillna.shape)
print()

 

4) 이상치 제거

# ----------------------------------------------------------
# 4) 이상치 제거
#    - (4-1) 표준편차 기준 (mu ± 3*std)
#    - (4-2) IQR 기준
# ----------------------------------------------------------

df_outlier_std = df_dropna.copy()
mean_num1, std_num1 = df_outlier_std['num1'].mean(), df_outlier_std['num1'].std()
mean_num2, std_num2 = df_outlier_std['num2'].mean(), df_outlier_std['num2'].std()

# 임계값 설정: ±3σ
lower_num1, upper_num1 = mean_num1 - 3*std_num1, mean_num1 + 3*std_num1
lower_num2, upper_num2 = mean_num2 - 3*std_num2, mean_num2 + 3*std_num2

df_outlier_std = df_outlier_std[
    (df_outlier_std['num1'] >= lower_num1) & (df_outlier_std['num1'] <= upper_num1) &
    (df_outlier_std['num2'] >= lower_num2) & (df_outlier_std['num2'] <= upper_num2)
]

# (4-2) IQR 기반
df_outlier_iqr = df_dropna.copy()
Q1_num1 = df_outlier_iqr['num1'].quantile(0.25)
Q3_num1 = df_outlier_iqr['num1'].quantile(0.75)
IQR_num1 = Q3_num1 - Q1_num1

Q1_num2 = df_outlier_iqr['num2'].quantile(0.25)
Q3_num2 = df_outlier_iqr['num2'].quantile(0.75)
IQR_num2 = Q3_num2 - Q1_num2

low_num1 = Q1_num1 - 1.5*IQR_num1
up_num1  = Q3_num1 + 1.5*IQR_num1
low_num2 = Q1_num2 - 1.5*IQR_num2
up_num2  = Q3_num2 + 1.5*IQR_num2

df_outlier_iqr = df_outlier_iqr[
    (df_outlier_iqr['num1'] >= low_num1) & (df_outlier_iqr['num1'] <= up_num1) &
    (df_outlier_iqr['num2'] >= low_num2) & (df_outlier_iqr['num2'] <= up_num2)
]

print(f"=== [이상치 제거 전 shape] : {df_dropna.shape}")
print(f"=== [표준편차 기준 제거 후 shape] : {df_outlier_std.shape}")
print(f"=== [IQR 기준 제거 후 shape] : {df_outlier_iqr.shape}")
print()

 

 

5) 스케일링

# ----------------------------------------------------------
# 5) 스케일링: 표준화(StandardScaler), 정규화(MinMaxScaler)
#    - 예시로 df_outlier_iqr 를 사용
# ----------------------------------------------------------

df_scaled = df_outlier_iqr.copy()

scaler_std = StandardScaler()
scaler_minmax = MinMaxScaler()

df_scaled['num1_std'] = scaler_std.fit_transform(df_scaled[['num1']])
df_scaled['num2_std'] = scaler_std.fit_transform(df_scaled[['num2']])
df_scaled['num1_minmax'] = scaler_minmax.fit_transform(df_scaled[['num1']])
df_scaled['num2_minmax'] = scaler_minmax.fit_transform(df_scaled[['num2']])

print("=== [스케일링 결과 컬럼 확인] ===")
print(df_scaled[['num1','num1_std','num1_minmax','num2','num2_std','num2_minmax']].head())
print()

 

6) 범주형 데이터 변환

# ----------------------------------------------------------
# 6) 범주형 데이터 변환 (원-핫, 라벨 인코딩)
#    - 라벨 인코딩: cat_col
#    - 원-핫 인코딩: cat_col (또는 라벨 인코딩 후 다른 DF에 적용 가능)
# ----------------------------------------------------------

df_cat = df_scaled.copy()

# (6-1) 라벨 인코딩
label_encoder = LabelEncoder()
df_cat['cat_label'] = label_encoder.fit_transform(df_cat['cat_col'])

# (6-2) 원-핫 인코딩
df_cat = pd.get_dummies(df_cat, columns=['cat_col'])

print("=== [라벨 인코딩 + 원핫 인코딩 결과 컬럼] ===")
print(df_cat.head())
print()

 

7) 파생 변수 생성

# ----------------------------------------------------------
# 7) 파생변수 생성
#    - (7-1) 날짜 파생: 연, 월, 요일, 주말여부 등
#    - (7-2) 수치형 변수 조합
#
# ----------------------------------------------------------

df_feat = df_cat.copy()

# (7-1) 날짜 파생
df_feat['year'] = df_feat['date_col'].dt.year
df_feat['month'] = df_feat['date_col'].dt.month
df_feat['dayofweek'] = df_feat['date_col'].dt.dayofweek  # 월=0, 화=1, ...
df_feat['is_weekend'] = df_feat['dayofweek'].apply(lambda x: 1 if x>=5 else 0)

# (7-2) 수치형 변수 조합 예시: num1 + num2
df_feat['num_sum'] = df_feat['num1_std'] + df_feat['num2_std']


print("=== [파생 변수 생성 결과] ===")
print(df_feat[['date_col','year','month','dayofweek','is_weekend','num1','num2','num_sum']].head())
print()

 

8) 다중공선성 확인

# ----------------------------------------------------------
# 8) 다중공선성 확인 (상관관계, VIF)
#    - 예시로 수치형 변수(num1, num2, num_sum, num1_log 등)만 확인
# ----------------------------------------------------------

df_corr = df_feat[['target',	'num1_std',	'num2_std', 'cat_label', 'year',	'month',	'dayofweek',	'is_weekend', 'num_sum']].dropna()
print("=== [상관계수] ===")
print(df_corr.corr())

# VIF 계산 함수
def calc_vif(df_input):
    vif_data = []
    for i in range(df_input.shape[1]):
        vif = variance_inflation_factor(df_input.values, i)
        vif_data.append((df_input.columns[i], vif))
    return pd.DataFrame(vif_data, columns=['feature','VIF'])

vif_df = calc_vif(df_corr)
print("\n=== [VIF 결과] ===")
print(vif_df)
print()

 

9) 데이터 불균형 처리

# ----------------------------------------------------------
# 9) 불균형 데이터 처리: SMOTE
#    - 타깃이 [0,1]로 되어 있고, 1이 매우 적은 상태
#    - SMOTE 적용 위해선 '피처'와 '타깃' 분리 필요
# ----------------------------------------------------------

df_smote = df_feat.copy().dropna(subset=['target'])  # 일단 이상치,결측 제거된 DF 사용
X = df_smote[['num_sum', 'cat_label',	'year', 'month',	'dayofweek',	'is_weekend']]  # 간단히 수치형 2개만 피처로
y = df_smote['target']

print("=== [SMOTE 전 레이블 분포] ===")
print(y.value_counts())

sm = SMOTE(random_state=42)
X_res, y_res = sm.fit_resample(X, y)

print("=== [SMOTE 후 레이블 분포] ===")
print(pd.Series(y_res).value_counts())
print()

 

 

 

느낀점

지금까지 배운 머신러닝을 다시 처음부터 배울수있어서 좋은것 같습니다. 지식이 쌓이다보면 잊어버리는 것도 많아지게 되는데 다시 복습을 진행하면서 더 탄탄하게 프로젝트를 진행할 수 있을 것 같습니다.

전에 강의를 들을때는 어려운 코드같은경우 그냥 넘어가고 '이런게 있다' 식으로 지나갔었는데 지금 어느정도 머신러닝을 배우고 다시 보니 그때봤던 어려운 코드도 충분히 이해가능해진 것 같습니다.