머신러닝 학습 정리 [전처리]
- 머신러닝 [전처리]
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을 그 모든 속성을 복제해서 수를 늘리는 것이 아니라 A1과 A2, ...을 비교하여 데이터 포인트(소수 클래스의 데이터가 갖는 여러 속성들의 범위)를 가상으로 만들어 새로운 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) # 날짜 순서 랜덤화
# 불균형 타깃(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
})
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()