머신러닝

머신러닝 학습 정리 [전처리]

jjaio8986 2025. 4. 20. 00:43
  • 머신러닝 [전처리]

1. [도메인] 별 [전처리]의 특징

 # 핵심 : 각 [도메인]의 키 포인트를 확인. 전처리를 할 때 데이터에서 "살려야 할 부분", "변형해야 할 부분", "버려야할 부분", "분석 목적을 위해 새롭게 조합해야할 부분"을 파악하는 것이 중요.

 

 1.1. [제조업]

  - 사용 목적 : 기계 센서의 정상 및 오작동을 확인하는 것이 주요!   

  # 사용 목적이 [이상탐지]라면, 제품의 [생산]과 관련한 부분의 데이터 혹은 컬럼을 살리고 나머지 데이터를 전처리 (제거 혹은 결합하여 다른 컬럼으로 전환)

 

 1.2. [금융]

  - 사용 목적 : [이상 탐지](사기거래, 특정 종목의 급변) 혹은 "예측"(유실된 데이터 혹은 향후 데이터의 예측)이 주요 목표

 

 1.3. [마케팅]

  - 사용 목적 : "예측", "분류", "클러스터링" ||

ex. 필요 정보의 누락 혹은 미수집

      특정 광고 중 상품의 노출/클릭 수가 월등히 높아 평균 왜곡   

           # 특정 커뮤니티의 단체활동('팬심', '분노', '유희' 등 다양한 원인)으로 인한 노출 및 클릭수의 이상

      [고객 이탈 예측] 시 이탈 고객 비율이 매우 적은 경우

 

 

 

2. [결측치 처리]

 2.1. 처리 방법 : 결측치를 [삭제]하거나 [대체]한다. (평균, 중앙값, 최빈값, 예측모델[회귀/분류 모델])

 - 삭제코드 : "df_drop = 데이터프레임 원본.dropna()"

 - <대치코드 : 평균값, 중앙값, 최빈값>    # 각 열 별로 평균값 및 중앙값으로 Nan이 대치된다.

<평균값>

df_mean = df.copy()     # 원본 데이터를 먼저 복사하여 변형하여 사용할 데이터에 사용!

df_mean = df_mean.fillna(df_mena.mean(numeric_only=True))

 

<중앙값>

df_median = df.copy()

df_median = df_median.fillna(df_median.median(numeric_only=True))

 

<최빈값>    # 각 열별로 최빈값이 1개가 아닐 수 있음. 그래서 iloc[0]으로 첫 행만 취한다.

df_mode = df.copy()

mode_values = df_mode.mode().iloc[0]     # 첫 번째 행(가장 상위 mode)만 취함

df_mode = df_mode.fillna(mode_values)

 

3. [이상치 탐지 및 제거]

 3.1. 탐지 방법

  A. 통계적 기법(3σ Rule)    # Z-score

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

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

 

  B. 박스플롯(Boxplot) 기준    # IQR

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

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

 

  C. 머신러닝 기반

- 이상치 탐지 알고리즘(Isolatiion Forest, DBSCAN )

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

 

 3.2. 처리 방법

  A. 이상치 단순 제거

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

  C. 별도로 구분하여 모델에서 제외하거나, 다른 모델(이상치 예측 모델)로 활용

 

<구현 코드 : IQR, Z-score>

# pandas 라이브러리 호출

import pandas as pd

 

# IQR - 박스 플롯을 이용한 간단한 이상치 제거 

Q1 = df['컬럼1'].quantile(0.25)

Q3 = df['컬럼1'].quantile(0.75)

 

# 정상의 범위!    데이터에서 상위 75%와 25%에 위치한 값의 차이로 그 사이의 범위를 확인!

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)]

 

 3.3. [도메인]별 이상치 판단 기준

  A. [제조업]

   - 이상 탐지 : 특정 센서값이 정상범위(ex.20~80)에서 갑자기 200 이상을 기록    # 장비 이상 가능성!

   - 즉, 매 생산 라인마다 센서값 범위가 조금씩 다를 수 있으므로, 라인별 통계 기준(평균, 표준편차)으로 이상치 판단

 

  B. [금융]

   - 이상 탐지 : 특정 [종목]의 하루 거래량평소 대비 수십 배로 급등하거나, [주가]가 순간적으로 비정상적인 폭락 혹은 폭등을 보일 때 이상치로 판단

                       [고객 신용도] 데이터에서도 소득이나 부채가 일반 범위에서 크게 벗어난 값이 나오면 의심 거래 혹은 데이터 오류로 판단.

 

  C. [마케팅]

   - 이상 탐지 : 어떤 광고 [캠페인]에서 클릭률(CTR)다른 캠페인에 비해 극단적으로 높아, 평균 분석을 왜곡하는 경우

                      [고객 구매 이력]에서 특정 기간평소와 전혀 다른 과도한 소비(장바구니 금액 폭증)가 포착되면 마케팅 분석에서 이상치

 

4. [정규화]와 [표준화]

4.1. [정규화]와 [표준화] 필요성    # 둘 중 하나만 사용할 수도 다 사용할 수도 있다.

A. 모델(특히 거리 기반 알고리즘, 딥러닝 등)에 따라 특정 변수의 스케일이 크게 영향을 미칠 수 있음.

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

    # 즉, 각 컬럼별담겨있는 값의 의미가 다르고 그 기록되는 기준 또한 다르다! 이걸 컴퓨터가 연산할 수 있게 하나의 기준으로 통합한다. (흔히 수치로 정규화, 표준화)

 

4.2. [정규화] 기법

 A. "MinMaxScaler"    # 자주 사용하는 기법! 컬럼 별 최솟값 최대값을 사용한다.

  A.a. 정의 : 모든 값을 0과 1사이로 매핑한다.

                   # 즉, 해당 컬럼의 최솟값을 0 최대값을 1로 지정해 그 사이의 값을 0~1사이의 숫자로 매핑한다!

 

  A.b. 특징

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

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

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

   - 최소값, 최대값이 극단값에 민감! 만일 극단치가 있으면 대부분의 데이터가 [0,1]구간 내부 한쪽에 치우치는 (단점1)

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

 

4.3. [표준화]기법

 A. "StandardScaler"

  A.a. 정의 : 평균을 0, 표준편차를 1로 만듦

  A.b. 특징

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

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

    - 변환된 값들이 이론적으로 -infinity ~ +infinity 범위를 가질 수 있음

    - 데이터가 특정 구간([0, 1] )에 고정되지는 않음.

    - 데이터 분포가 심하게 치우쳐 있으면, 평균과 표준편차만으로는 충분한 스케일링이 되지 않을 수 있다.

    # 즉, 데이터 분포가 "긴 꼬리 분포"를 따르면 [로그 변환]기법을 사용하여 정규분포화가 필요할 수 있다.

    # 혹은 [RobustScaler] 등의 기법들을 모델마다 사용해볼 수 있다. 즉, 데이터의 분포 그리고 모델링 기법에 따라 표준화 기법을 추가로 고려해야 한다.

 

<코드 구현 : MinMaxScaler>

# 스케일러 불러오기

from sklearn.preprocessing import MinMaxScaler

 

# 스케일링을 적용할 컬럼만 선정

cols_to_scale = ['컬럼1', '컬럼2', '컬럼3', '컬럼4', '컬럼5']

 

# 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 )    # minmax_scaler() 기법을 적용한 데이터 프레임을 만들고 그 컬럼의 이름을 앞서 리스트로 담아둔 컬럼명을 사용한다.

 

print(df_minmax_scaled.max())

print(df_minmax_scaled.min())

 

5. [불균형 데이터] 처리    

# 이산형타입 데이터 혹은 표본의 숫자가 불균형한 경우에 흔히 사용 한다.

# 분석을 위한 전처리가 끝나고, 머신러닝을 위한 데이터 전처리 단계

 

5.1. 정의 : 정상 99%, 불량 1%와 같이 한 클래스가 극도로 적은 경우

5.2. 해결 기법

 A. [Oversampling]    # 데이터 증폭! 결국 실제 데이터를 표본으로 만드는 것이 아니기에 모델링 후 실제 테스트 시 과적합 혹은 과소적합의 문제가 발생할 수 있음.

  A.a. "Random Oversampling" : 소수 클래스의 데이터를 단순복제개수를 늘림

 

  A.b. "SMOTE(Synthetic Minority Over-sampling Technique)"

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

             # 소수 클래스(ex. 스펨메일) 안에서 가까운 데이터 둘(혹은 몇 개)을 고르고, 그 사이에 새 데이터 포인트를 만들어내어, 소수 클래스의 다양한 예시를 가상으로 늘리는 기법이다! , A1을 그 모든 속성을 복제해서 수를 늘리는 것이 아니라 A1A2, ...을 비교하여 데이터 포인트(소수 클래스의 데이터가 갖는 여러 속성들의 범위)를 가상으로 만들어 새로운 An1, An2, .... 같이 여러 개 만드는 것

 

B. [Undersampling] : 다수 클래스의 데이터 개수를 줄이는 방식! 데이터 손실 위험은 있으나 전체 데이터 균형을 맞출 수 있다.

 

C. 혼합 기법 : SMOTE와 언더샘플링을 적절히 섞어 사용

 

<코드 구현 : SMOTE>

# 결측치 처리, 이상치 제거, 범주형 인코딩 등의 사전 처리이후의 시점!

#df['defect'] : 데이터 프레임에서 이상치와 정상치를 0과 1로 인코딩하여 구분한 새로 만든 컬럼!

 

X = df.drop('defect', axis=1)     

y = df['defect']

smote = SMOTE(random_state=42)

X_res, y_res = smote.fit_resample(X, y)

 

<코드 설명>

# X= df.drop('defect', axis=1)

- df 데이터프레임에서 defect컬럼(레이블)을 제외한 나머지를 x(특징 행렬)로 사용!

- 이때, 이미 결측치, 이상치, 범주형 인코딩 등의 사전 전처리 마쳤다고 가정!

 

# y = df['defect']

- 'defect'컬럼을 타겟(레이블)으로 설정! -- 'defect'값이 1이면 결함! 0이면 무결함 제품

 

# smote = SMOTE(random_state=42)

- smote 객체 생성!

- smote는 소수 클래스(ex. 결함사례)가 너무 적을 때, 기존 소수 클래스 데이터 바탕 유사한 새로운 예시를 만들어 데이터 개수를 늘려주는 기법

- random_state=42는 재현성(코드 실행시 동일 결과)을 위해 난수 시드를 고정하는 역할

 

# X_res, y_res = smote.fit_resample(X, y)

- fit_resemple을 통해 smote알고리즘이 X, y를 바탕으로 소수 클래스 데이터를 자동 생성한다.

- 결과적으로, 오버샘플링된 X_res, y_res에는 클래스 불균형이 개선된(1:1에 가까운 혹은 원하는 비율이 된) 상태가 된다.

 

6. 범주형 데이터 변환

6.1. 원-핫 인코딩

# 변수가 3개 이상이지만 각 변수가 상관관계 즉, 서열이 없을 때 사용 가능! 데이터가 연속하지 않을 때 사용 가능!

# 범주형 변수를 각각의 범주별로 새로운 열로 표현, 해당 범주에 해당하면 1, 아니면 0

ex. 색상('RED', 'GREEN', 'BLUE') => 'RED'=0, 'GREEN'=1, 'BLUE'=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 = pd.get_dummies(df, columns=['label'])

#“pd.get_dummies(df, columns=['칼럼이름'])”

- 열의 범주들을 각각 별도의 열로 만들어, 해당하는 행에는 1, 그렇지 않은 행에는 0을 넣어준다.

 

6.2. 레이블 인코딩

- 범주를 숫자로 직접 맵핑('XL'=3, 'L'=2, 'M'=1, 'S'=0)

- 단순하지만, 모델이 숫자의 크기를 서열 정보로 잘못 해석할 수 있음.

<구현 코드>

df['label'].unique() # label의 고유값들을 array의 형태로 반환

 

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) # 또 다른 범주형 변수

})

 

<코드 해설>

from sklearn.preprocessing import LabelEncoder    # 문자열 혹은 범주형 데이터를 정수로 변환하기 위한 클래스

encoder = LabelEncoder()    # LabelEncoder를 인스턴스화하여 encoder라는 이름의 객체를 생성

 

df["label"] = encoder.fit_transform(df["label"])

- df["label"] 열을 fit_transform 메서드에 전달

- fit : 데이터에 등장하는 범주를 학습

- transform : 학습한 매핑에 따라 데이터를 정수로 변환

- 변환된 결과(정수 라벨)를 다시 `df["label"]`에 덮어쓰기

- 예를 들어, `["red", "blue", "blue", "green"]` 같은 문자열 범주가 존재하면,

- "blue" 0

- "green" 1

- "red" 2와 같이 매핑

 

cf) "np.random.seed(0이상의 정수)"

- 시드를 설정해 주는 과정

- 0~1사이의 난수를 발생시킨다!

- 특정 시작 숫자값==시드를 정해주면 정해진 알고리즘에 따라 마치 난수처럼 보이는 수열을 생성!

- 보통 현재 시각 등을 이용해 자동으로 정하지만 직접 사람이 수동으로 정할 수 있다. 특정 시드값이 사용될 때 발생되는 난수를 알고리즘에 따라 직접 예측할 수 있다.

- “np.random.seed(42)”를 사용하면 차후 출력하는 난수들이 고정되어 출력된다!

ex. np.random.seed(42)

np.random.rand(5) # array([5개의 0~1사이의 정해진 난수 배출!])

 

7. 피처 엔지니어링 개요 

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

 

7.1. 피처 엔지니어링 필요성 : 복잡한 데이터 구조 안에 존재하는 패턴을 효과적으로 추출해 모델이 쉽게 학습하게 함

 

7.2. [도메인] 별 사용 예시

[제조업] : 센서 데이터 간 시계열적, 물리적 관계를 반영하는 경우가 많음

[금융] : 고객 신용도, 거래내역, 시장 지표 등 다양한 변수를 포함하는 경우가 많음.

[마케팅] : 고객 행동 데이터(클릭, 구매 기록, 웹사이트 체류 시간 등)와 고객 특성 데이터(나이, 지역, 관심 분야 등)통합한 피처를 만들어야 효과적 캠페인 타깃팅, 고객 세분화, 개인화 추천이 가능

 

7.3. <코드 구현>

 - 구현 전 확인 사항

 A. 변수 선택

  A.a. 상관관계 : 두 변수 간 상관도가 높은 상황인 경우 다중공선성 의심 가능, 따라서 중복 정보가 클수 있기에 하나만 남기거나 둘 다 제거를 고려하기

  A.b. VIF : [회귀]분석을 할 때 다중공선성 문제 파악을 목적으로 사용 "어떤 변수가 다른 변수들과 얼마나 결치는 지(상관이 큰 지) 수치로 보여주는 지표"

  A.c. 모델 기반 중요도(Feature Importance) : 트리 기반 모델(ex. 랜덤 포레스트, XGBoost 등)을 훈련한 뒤 중요도가 낮은 변수를 제거하기 위해 확인!

 

 B. 변수 간 상호작용

  B.a. 다항식 / 교차 항 생성 - 2차 다항식(Quadratic Features)

    ex. 제조 공정에서 온도, 압력, 속도 등이 곱해져야 비로소 의미가 생기는 경우

 

- 실제 코드 구현 : 피쳐 엔지니어링(교차 항 생성!)

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['temp_humid_interaction'] = df['temperature'] * df['humidity']

 


<총 구현 코드 정리>

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) # 날짜 순서 랜덤화

 

# 불균형 타깃(110%, 090%라고 가정)

# - 실제로 1이 대략 20, 0180

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

})

 

3. 결측치 추가, 이상치 주입

# 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()

 

>= 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()

 

4. 결측치 처리

# 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()

 

5. 이상치 제거

# 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()

 

6. 스케일링(정규 혹은 표준화)

# 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()

 

7. 범주형 데이터 변환

# 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()

 

8. 파생변수 생성 # 피처 엔지니어링

# 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()

 

9. 다중공선성 확인

# 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()

 

10 불균형 데이터 처리

# 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()