데이터 전처리의 필요성과 핵심방법
불균형 데이터 문제 해결 능력
범주형 데이터 인코딩 기법
피처 엔지니어링 기법
결측치, 이상치, 불균형 데이터를 제대로 처리하고, 의미 있는 파생 변수를 생성하여 모델의 성능을 높일 수 있다
1️⃣ 결측치와 이상치를 올바르게 처리하는 것은 모델의 기초
2️⃣ 불균형 데이터 문제를 해결하지 않으면 소수 클래스(불량)을 놓칠 수 있음
3️⃣ 범주형 변수는 원-핫 인코딩, 레이블 인코딩 등으로 적절히 변환
4️⃣ 피처 엔지니어링을 통해 데이터에서 추가 정보(패턴)을 추출하거나, 중요하지 않은 변수는 제거해 모델 효율과 성능을 높임
원시(raw) 데이터에서 불필요하거나 손실(노이즈)이 있는 부분을 처리하고, 분석 목적에 맞는 형태로 만드는 과정
머신러닝을 하기 전에 데이터 전처리부터 해야한다.
필요성
모델 정확도 및 신뢰도 향상
이상치나 결측치가 많은 상태로 학습하면 예측 성능이 크게 떨어짐
효율적인 데이터 분석과 모델 훈련을 위해 필수적인 단계
사례 - 마케팅, 금융, 제조업
발생원인 : 센서고장, 측정오류, 환경적 문제, 누락(수기입력)
삭제
: 결측치가 있는 행(row) 또는 열(column)을 제거
→ 간단하지만 데이터 손실이 발생
→ 결측치가 전체 데이터에서 매우 소수일 때 적합
대체
- 평균 또는 중앙값으로 대체
→ 수치형 데이터에서 많이 사용, 데이터 분포 왜곡이 비교적 적음
예시
- 제조업: 센서데이터 특정시간 누락
삭제 - dropna()
대치 - fillna(대치값)
import numpy as np
import pandas as pd
# 1) 가상 데이터 생성
# - 일부 값들을 np.nan으로 지정해 결측값을 만듭니다.
data = {
'A': [1, 2, np.nan, 4, 5, np.nan, 7],
'B': [5, 4, 2, np.nan, np.nan, 3, 1],
'C': [2, np.nan, np.nan, 6, 7, 8, 9]
}
df = pd.DataFrame(data)
df
# 2) 결측치 제거 (결측이 하나라도 있으면 해당 행을 제거)
df_drop = df.dropna()
df_drop
# 3) 평균값으로 대치
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
기존데이터

결측치 제거

결측치 평균값으로 대치

df_mean.mean(numeric_only=True)는 각 열(column)별 평균값을 계산.numeric_only=True는 숫자형 열에 대해서만 평균을 계산하겠다는 옵션중앙값으로 대치

최빈값으로 대치
df.mode()

fillna(mode().iloc[0])

이상치
정상 범주에서 크게 벗어나는 값으로 장비 오작동이나 환경적 특이 상황 등 원인이 다양함
탐지기법
통계적 기법
데이터가 정규분포를 따른다고 가정, 평균에서 ±3σ(표준편차, 약 99%) 범위를 벗어나는 값을 이상치로 간주
직관적이고 간단하나, 정규성 가정(정규분포라 가정했을 때만 성립. 아니면 안됨)이 틀릴 수 있다.
박스플롯(Box plot)기준

사분위수(IQR = Q3 - Q1)를 이용해 ‘Q1 - 1.5×IQR’, ‘Q3 + 1.5×IQR’를 벗어나는 데이터를 이상치로 간주
분포 특성에 영향을 적게 받는 장점
머신러닝 기반
이상치 탐지 알고리즘 (Isolation Forest, DBSCAN 등)
복합적 패턴을 고려가능
처리기법
단순제거
이상치 값 조정(클리핑, Winsorizing)
별도구분 -> 모델제외 or 다른모델(이상치 예측 모델)로 활용
이상치를 무조건 버리지 않고, 오히려 따로 떼어내서 다른방식으로 활용한다는 말.
이상치는 평범한 데이터와는 다른 패턴을 가지니까, 다른 패턴을 예측하는 모델을 따로 만들 수 있다!
- 📌 예시 1. 이상치 분류기 만들기
이상치만 따로 모아서,
어떤 조건일 때 이상치가 발생하는지를 학습하는 이상치 예측 모델을 만드는 거야.
👉 예: 제조업에서 센서 데이터 중 이상치를 고장 징후로 보고, 이상 패턴을 학습시켜 고장 예측 모델을 만드는 경우.
- 📌 예시 2. 이상치 전용 모델 만들기
전체 데이터 → 정상 데이터 모델과 이상치 모델로 분리해서 각각 모델을 학습
예측 시, 입력 데이터를 먼저 판단해서 “이건 이상치 같다” → 이상치 모델 사용
“이건 정상 같다” → 정상 모델 사용
👉 예: 금융 사기 탐지 모델
평범한 거래 패턴 → 일반 모델
수상한 패턴 → 사기 탐지용 모델 따로 활용
import pandas as pd
import numpy as np
# 예시 데이터프레임 생성
np.random.seed(42) # 재현성을 위해 시드 설정
normal_values = np.random.normal(loc=50, scale=5, size=30) # 평균 50, 표준편차 5인 정규분포에서 30개 값 생성
outliers = [150, 180, 200, 300] # 눈으로 봐도 이상치로 판단될 수 있는 큰 값들
# normal_values와 outliers를 합쳐서 하나의 리스트로 구성
all_values = np.concatenate([normal_values, outliers])
# 예시로 0~39 범위의 임의 날짜/시간 데이터를 간단히 만들기
dates = pd.date_range('2021-01-01', periods=len(all_values), freq='D')
df = pd.DataFrame({
'date': dates,
'sensor_value': all_values
})
df
# 이상치 제거 (간단하게 박스플롯 기준 적용 예시)
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
모델(거리기반 알고리즘, 딥러닝)에 따라 특정 변수의 스케일이 큰 영향을 미칠 수 있다.
ex) 센서 A는 값 범위가 0~1000, 센서 B는 값 범위가 0~1이라면, A가 모델에 더 큰 영향을 줌
모든 값을 0과 1사이로 매핑하는 것
특징
평균을 0, 표준편차를 1로 만듦
특징
분포가 정규분포에 가깝게 변형됨
평균이 0, 표준편차가 1로 맞춰지므로, 정규분포 가정을 사용하는 알고리즘(선형회귀, 로지스틱회귀, SVM 등)에 자주 쓰임.
변환된 값들이 이론적으로 -∞ ~ +∞ 범위를 가질 수 있다.
데이터가 특정 구간([0, 1] 등)에 고정되지x.
데이터 분포가 심하게 치우쳐 있으면, 평균과 표준편차만으로는 충분한 스케일링이 되지 않을 수 있습니다(로그 변환, RobustScaler 등 추가 고려).
import pandas as pd
import numpy as np
# 난수를 재현하기 위해 시드 설정
np.random.seed(42)
# 예시 마케팅 지표 데이터 생성
data_size = 10
df = pd.DataFrame({
'impressions': np.random.randint(1000, 10000, size=data_size), # 광고 노출 횟수
'clicks': np.random.randint(0, 300, size=data_size), # 광고 클릭 횟수
'conversions': np.random.randint(0, 50, size=data_size), # 광고를 통해 구매한 횟수
'cost': np.random.randint(100, 5000, size=data_size), # 광고비 지출액
'revenue': np.random.randint(100, 10000, size=data_size) # 광고를 통해 발생한 매출
})
df
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
기존 데이터

MinMaxScaler()를 만들어서 scaler라는 이름으로 할당.fit_transform(df[['칼럼이름']]) 호출fit: 열을 살펴 최솟값과 최댓값을 찾습니다.transform: 찾은 최소·최대값으로 각 데이터를 0~1 범위로 바꿉니다.scaler.fit(data) # 데이터를 보고 "학습" (예: 최솟값, 최댓값을 기억)
scaler.transform(data) # 학습된 기준으로 실제 데이터를 "변환"
# 두 코드 한번에 쓰면
scaler.fit_transform(data)
df_minmax_scaled = pd.DataFrame(minmax_scaler.fit_transform(df[cols_to_scale]),
columns=cols_to_scale)
df[cols_to_scale] → 스케일링할 컬럼만 뽑고
fit_transform() → 최솟값/최댓값을 계산하고, 그걸로 변환까지 함
pd.DataFrame(..., columns=cols_to_scale) → 다시 원래 컬럼 이름으로 DataFrame 만들기

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
: StandardScaler()를 만들어서 standard_scaler라는 이름으로 할당합니다.
: fit_transform(df[['칼럼이름']]) 호출
1️⃣ fit: 열의 평균과 표준편차를 구합니다.
2️⃣ transform: 각 값을 (x - 평균) / 표준편차로 변환해, 평균 0, 표준편차 1인 분포를 만듭니다.
pd.DataFrame(standard_scaler.fit_transform(df[cols_to_scale]),
columns=cols_to_scale)
주어진 데이터를 보고 스케일링 기준 저장
정규화 - min/max
표준화 - 평균/표준편차
어떤 기준을 저장할지는 어떤 스케일러를 쓰냐에 따라 달라짐
scaler = MinMaxScaler() -> 정규화
scaler = StandardScaler() -> 표준화
minmax_scaler = MinMaxScaler()
standard_scaler = StandardScaler()
이렇게 객체 생성후 fit진행 -> 자동으로 값 저장
transform은 fit을 기준으로 값 변환
불균형 데이터: 정상 99%, 불량 1%처럼 한 클래스가 극도로 적은 경우
모델이 극도로 적은 클래스를 거의 예측하지 못할 가능성이 큼(편향 발생)
해결기법
📚 SMOTE(Synthetic Minority Over-sampling Technique)
: 소수 클래스를 "무작정 복사"만 하는 게 아니라, “비슷한” 데이터들을 서로 섞어서(Interpolation) 새로운 데이터 생성
즉, 소수 클래스(ex: 스팸) 안에서 가까운 데이터 둘(혹은 몇 개)을 고르고, 그 사이에 새 데이터 포인트를 만들어내어, 소수 클래스의 다양한 예시를 가상으로 늘리는 기법
ex)오렌지(소수 클래스)와 사과(다수 클래스)가 있는 과일 바구니
→ 사과는 90개, 오렌지는 10개뿐
→ "오렌지를 조금 더 만들어서(복사)" 갯수를 맞출 수도 있지만, 그러면 똑같은 오렌지가 여러 개 생길 뿐, 다양성 x.
→ 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

np.random.normal(평균, 표준편차, 개수)
from imblearn.over_sampling import SMOTE
# 불균형 데이터 처리 (SMOTE)
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(특징 행렬)로 사용합니다.smote = SMOTE(random_state=42)random_state=42는 재현성(코드 실행 시 동일 결과)을 위해 난수 시드를 고정하는 역할.
몇 안되는 지피티가 외우라는 것.. 행0열1
X_res, y_res = smote.fit_resample(X, y)fit_resample을 통해 SMOTE 알고리즘이 X, y를 바탕으로 소수 클래스 데이터를 자동 생성.X_res, y_res에는 클래스 불균형이 개선된(1:1에 가깝거나 원하는 비율이 된) 상태가 됨.fit(X, y)
→ y를 보고 소수 클래스가 뭔지 파악하고, X를 분석해서 유사 샘플을 어떻게 만들지 학습
y(타깃값)를 보고 클래스 비율 확인, 소수 클래스의 데이터를 X에서 찾아서 그 데이터들을 기준으로 "유사한 가짜 데이터"를 어떻게 만들지 판단
resample(X, y)
→ 분석 결과를 바탕으로, 소수 클래스 샘플을 새로 생성해서 X, y에 추가
근데 이걸 한 줄로 동시에 하게 만든 게 → fit_resample() 함수!


범주형 변수를 각각의 범주별로 새로운 열로 표현, 해당 범주에 해당하면 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
# 범주형 변수 변환 (원-핫 인코딩 예시)
df = pd.get_dummies(df, columns=['label'])
pd.get_dummies(df, columns=['칼럼이름'])
→열의 범주들(A, B, C 등)을 각각 별도 열로 만들어, 해당하는 행에는 1, 그렇지 않은 행에는 0을 넣어줍니다.
apple/namama/cherry열이 생김
참인 값 1 아니면 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
np.random.choice(labels, data_size)
선택할 값들의 목록(labels), 몇 개 뽑을지(정수, data_size)

# 범주형 변수 변환 (레이블 인코딩 예시)
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
df["label"] = encoder.fit_transform(df["label"])
df
df["label"] = encoder.fit_transform(df["label"])df["label"] 열(column)을 fit_transform 메서드에 전달하여,df["label"]에 덮어쓰기["red", "blue", "blue", "green"] 같은 문자열 범주가 존재하면,
모델 성능 향상을 위해 기존 데이터를 변형, 조합하여 새로운 특성(피처)을 만드는 작업
복잡한 데이터 구조 안에 존재하는 패턴을 효과적으로 추출해 모델이 쉽게 학습하게 함
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

# 피처 엔지니어링 (온도와 습도 간 상호작용)
df['temp_humid_interaction'] = df['temperature'] * df['humidity']
df

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