검색
← 모듈 로드맵

특성 공학과 데이터 전처리: 스케일링 · 인코딩 · 결측값 · 파이프라인

강의 · 입문자 · KAITABASE 정리

표준화 · 원-핫 · 결측 대치 · 데이터 누수 · Pipeline을 실행 코드로 익히는 전처리 수업

먼저 알아야 할 것

평균 · 표준편차로지스틱 회귀학습 · 검증 · 테스트 나누기교차 검증

다루는 내용

숫자 특성의 표준화 · 최소-최대 · 로그 변환을 작은 숫자로 계산하고, 어떤 모델에 스케일링이 필요한지 정리합니다. 원-핫과 순서 인코딩, 처음 보는 범주 처리, 평균 · 중앙값 결측 대치와 그 함정을 다룹니다. scikit-learn 문서의 데이터 누수 규칙을 소개하고 ColumnTransformer · Pipeline · 교차 검증을 묶은 코드를 실제로 실행한 결과를 보여 줍니다.

해설

특성 공학과 데이터 전처리: 스케일링 · 인코딩 · 결측값 · 파이프라인

실무에서 모델 성능을 가장 크게 좌우하는 것은 알고리즘보다 데이터를 모델이 먹을 수 있는 숫자로 바꾸는 방식인 경우가 많습니다. 이 수업은 숫자 특성의 크기 맞추기(표준화 · 최소-최대 · 로그 변환), 범주 특성을 숫자로 바꾸기(원-핫 · 순서 인코딩), 빈칸 채우기(결측값 대치), 그리고 이 모든 단계를 데이터 누수 없이 묶는 scikit-learn Pipeline · ColumnTransformer를 직접 실행한 코드로 익힙니다.

항목내용
참고 자료scikit-learn 사용자 안내서 '7.3 Preprocessing data' · scikit-learn.org/stable/modules/preprocessing.html, '11. Common pitfalls and recommended practices'
트랙 · 수준머신러닝 · 기초
이 수업의 범위표준화 · 최소-최대 스케일링 · 로그 변환, 원-핫 · 순서 인코딩, 처음 보는 범주 처리, 결측값 대치, 특성 만들기, 데이터 누수, Pipeline · ColumnTransformer · 교차 검증
먼저 알면 좋은 것평균 · 표준편차, 로지스틱 회귀, 학습 · 검증 · 테스트 나누기

들어가기 전에

한눈에 보기

  • 왜 특성 크기를 맞춰야 하는지, 어떤 모델은 필요 없고 어떤 모델은 꼭 필요한지 압니다.
  • 표준화 · 최소-최대 · 로그 변환을 작은 숫자로 직접 계산합니다.
  • 범주 특성에 원-핫과 순서 인코딩 중 무엇을 쓸지 고르고, 처음 보는 범주를 처리합니다.
  • 결측값을 평균 · 중앙값으로 채우는 법과 그 함정을 압니다.
  • 데이터 누수가 무엇인지 알고, Pipeline으로 막는 코드를 실행해 봅니다.

1. 숫자 특성의 크기 맞추기

직관(해설용 비유): 나이(20~65)와 월소득(만 원 단위, 수십~수천)을 그대로 넣으면, 거리를 재거나 경사 하강법으로 학습하는 모델은 숫자가 큰 소득에만 끌려다닙니다. 서로 다른 단위의 자를 하나의 눈금으로 맞추는 작업이 스케일링입니다.

표준화(standardization)

z=x−μσz = \frac{x - \mu}{\sigma}
기호뜻
μ\mu학습 데이터에서 잰 특성의 평균
σ\sigma학습 데이터에서 잰 표준편차
zz평균 0, 표준편차 1로 옮긴 값

숫자로 해 보기(해설용): x=(10,20,30,40)x = (10, 20, 30, 40)이면 μ=25\mu = 25, σ=(225+25+25+225)/4=125≈11.18\sigma = \sqrt{(225 + 25 + 25 + 225)/4} = \sqrt{125} \approx 11.18입니다. z≈(−1.342,−0.447,0.447,1.342)z \approx (-1.342, -0.447, 0.447, 1.342). scikit-learn StandardScaler도 같은 값을 냅니다(표준편차를 nn으로 나눈 값을 씀 — 실행해 scale_ = 11.18을 확인).

최소-최대 스케일링(min-max scaling)

x′=x−xmin⁡xmax⁡−xmin⁡x' = \frac{x - x_{\min}}{x_{\max} - x_{\min}}

같은 xx는 (0,0.333,0.667,1)(0, 0.333, 0.667, 1)이 됩니다. 범위를 [0, 1]로 딱 맞추고 싶을 때(이미지 픽셀 등) 씁니다. 대신 이상치 하나가 xmax⁡x_{\max}를 끌어올리면 나머지가 모두 0 근처로 눌립니다.

로그 변환(log transform)

소득 · 조회수 · 가격처럼 오른쪽으로 길게 늘어진 분포는 로그를 취하면 고르게 펴집니다. 0이 있을 수 있으면 log⁡(1+x)\log(1 + x)를 씁니다. log⁡(1+x)\log(1 + x)는 x=0,9,99,999x = 0, 9, 99, 999를 0,2.30,4.61,6.910, 2.30, 4.61, 6.91로 바꿉니다 — 1000배 차이가 7 정도의 차이로 줄어듭니다.

어떤 모델에 필요한가:

모델스케일링 필요?이유
kNN, k-평균, SVM(RBF)꼭 필요거리 계산이 큰 단위 특성에 좌우됨
선형 · 로지스틱 회귀 + 정규화필요벌점이 가중치 크기에 걸려 단위에 따라 불공평
신경망필요경사 하강법이 길쭉한 손실 면에서 느려짐
결정 트리 · 랜덤 포레스트 · 부스팅거의 불필요한 특성의 임곗값 비교라 단조 변환에 영향 없음

scikit-learn SVM 문서도 SVM은 크기에 불변이 아니므로 데이터 스케일링을 강하게 권하고, 테스트 데이터에도 같은 스케일링을 적용해야 한다고 적고 있습니다.

2. 범주 특성을 숫자로

원-핫 인코딩(one-hot encoding)

직관: "지역 = 서울/부산/대구"를 1, 2, 3으로 바꾸면 모델이 "대구 = 서울 × 3" 같은 엉뚱한 크기 관계를 배웁니다. 범주마다 0/1 칸을 하나씩 만들어 해당 칸만 1로 둡니다.

숫자로 해 보기: 범주가 알파벳순으로 Busan, Daegu, Seoul이면 Busan → (1,0,0)(1, 0, 0), Seoul → (0,0,1)(0, 0, 1)입니다. OneHotEncoder(handle_unknown='ignore')로 학습하면, 학습 때 없던 Gwangju는 (0,0,0)(0, 0, 0)이 됩니다(실행 확인). 기본값 handle_unknown='error'이면 오류를 냅니다 — 서비스 중 새 범주가 들어올 수 있다면 꼭 정해 두어야 합니다.

함정: 범주가 수천 개(상품 ID, 우편번호)면 칸이 수천 개로 늘어납니다. 드문 범주를 '기타'로 묶거나(min_frequency, max_categories 옵션), 빈도 · 목표값 평균으로 바꾸는 방법, 신경망이라면 임베딩을 씁니다.

순서 인코딩(ordinal encoding)

옷 크기 S < M < L처럼 순서가 진짜 있는 범주는 0, 1, 2로 바꿔도 됩니다. OrdinalEncoder(categories=[['S','M','L']])처럼 순서를 직접 지정합니다. 지정하지 않으면 알파벳순(L, M, S)이 되어 순서가 뒤틀립니다. 트리 모델은 순서가 없는 범주도 순서 인코딩으로 넣어 쓰는 경우가 많습니다.

3. 결측값 채우기

직관: 설문의 빈칸을 그대로 두면 대부분의 모델은 계산을 못 합니다. 가장 흔한 방법은 학습 데이터의 평균이나 중앙값으로 채우는 것입니다.

숫자로 해 보기(해설용): 값 (1,빈칸,3,8)(1, \text{빈칸}, 3, 8)

  • 평균 대치: 빈칸 =(1+3+8)/3=4= (1 + 3 + 8)/3 = 4 → (1,4,3,8)(1, 4, 3, 8)
  • 중앙값 대치: 빈칸 =3= 3 → (1,3,3,8)(1, 3, 3, 8)

이상치(8)가 있으면 중앙값이 덜 흔들립니다. scikit-learn SimpleImputer의 기본 전략은 mean입니다.

함정과 요령

  • 빠진 것 자체가 정보일 때: "소득 미기입"이 특정 고객층에 몰려 있다면 빈칸 여부가 예측에 도움이 됩니다. add_indicator=True로 "빠졌음" 표시 칸을 함께 만듭니다.
  • 많이 빠진 특성: 대부분이 빈칸이면 채운 값이 오히려 잡음입니다. 특성을 빼는 것도 선택지입니다.
  • 일부 모델(예: scikit-learn의 히스토그램 기반 부스팅)은 결측값을 직접 다룹니다.

4. 특성 만들기: 도메인 지식을 숫자로

특성 공학(feature engineering)은 원래 칸을 조합해 모델이 쓰기 쉬운 새 칸을 만드는 일입니다.

원래 데이터만든 특성 예왜 도움이 되나
가입일, 기준일가입 기간(일)날짜 문자열은 그대로 못 씀
주문 시각요일, 시간대, 주말 여부주기 패턴을 드러냄
키, 몸무게체질량지수두 칸의 관계를 한 칸에
총구매액, 구매 횟수1회 평균 구매액비율이 더 안정적
위도, 경도매장까지 거리선형 모델이 못 만드는 비선형 관계

선형 모델에서는 특성 공학의 효과가 특히 큽니다. 트리 · 신경망은 조합을 어느 정도 스스로 찾지만, 좋은 특성은 데이터를 덜 쓰고도 같은 성능을 내게 합니다.

5. 데이터 누수: 전처리도 학습 데이터로만 맞춘다

직관(해설용 비유): 시험 전에 시험지의 평균 점수를 몰래 보고 공부 계획을 세우는 것과 같습니다. 테스트 데이터의 정보(평균, 표준편차, 범주 목록, 선택된 특성)가 학습 과정에 조금이라도 들어가면 점수가 실제보다 좋게 나옵니다.

scikit-learn '흔한 함정' 문서의 규칙은 간단합니다. 테스트 데이터에는 절대 fit을 부르지 않습니다. 먼저 나누고, 학습 데이터로 fit한 변환기를 테스트에는 transform만 합니다. 같은 문서의 예에서는, 무작위 특성 1만 개 · 표본 200개 · 무작위 라벨로 만든 데이터에서 전체 데이터로 특성을 고른 뒤 나누면 정확도가 약 0.76으로 나왔지만, 먼저 나누고 학습 데이터로만 고르면 우연 수준인 약 0.5가 나왔습니다. 라벨이 무작위인데 0.76이 나왔다는 것 자체가 누수의 증거입니다.

누수가 흔히 생기는 곳

  • 전체 데이터로 스케일러 · 대치값 · 특성 선택을 맞춘 뒤 나누기
  • 시간 순서가 있는 데이터에서 미래 정보를 쓰는 특성(예: "다음 달 해지 여부"를 예측하면서 "해지 처리일"을 특성으로 넣기)
  • 같은 고객 · 같은 문서가 학습과 테스트에 나뉘어 들어감

6. Pipeline과 ColumnTransformer: 한 번에 묶기

직관: 전처리 단계와 모델을 한 줄로 묶어 두면 fit은 학습 데이터에만, predict는 같은 변환을 거쳐 실행됩니다. 교차 검증을 돌려도 각 겹(fold)마다 전처리가 그 겹의 학습 부분으로만 다시 맞춰집니다.

mermaid
flowchart LR
  A[원본 표 데이터] --> B[학습 · 테스트로 먼저 나누기]
  B --> C[ColumnTransformer]
  C --> D[숫자 열: 중앙값 대치 → 표준화]
  C --> E[범주 열: 원-핫 인코딩]
  C --> F[순서 열: 순서 인코딩]
  D --> G[합친 특성 행렬]
  E --> G
  F --> G
  G --> H[모델 학습 fit]
  H --> I[테스트: 같은 변환 transform → 예측]
python
import numpy as np, pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, OrdinalEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, cross_val_score

# 설명용 가짜 데이터 400행: 나이 · 월소득(일부 빈칸) · 도시 · 등급 → 구매 여부
rng = np.random.default_rng(0); n = 400
df = pd.DataFrame({
    "age": rng.integers(20, 65, n).astype(float),
    "income": np.round(rng.lognormal(5.5, 0.6, n)),
    "city": rng.choice(["Seoul", "Busan", "Daegu"], n),
    "grade": rng.choice(["S", "M", "L"], n),
})
logit = 0.05*(df["age"]-40) + 0.004*(df["income"]-300) + (df["city"]=="Seoul")*0.8 - 0.4
y = (rng.random(n) < 1/(1+np.exp(-logit))).astype(int)
df.loc[rng.choice(n, 30, replace=False), "income"] = np.nan   # 빈칸 30개

pre = ColumnTransformer([
    ("num", Pipeline([("imp", SimpleImputer(strategy="median")),
                      ("sc", StandardScaler())]), ["age", "income"]),
    ("cat", OneHotEncoder(handle_unknown="ignore"), ["city"]),
    ("ord", OrdinalEncoder(categories=[["S", "M", "L"]]), ["grade"]),
])
model = Pipeline([("pre", pre), ("clf", LogisticRegression())])

X_tr, X_te, y_tr, y_te = train_test_split(df, y, test_size=0.25, random_state=0, stratify=y)
print(cross_val_score(model, X_tr, y_tr, cv=5).round(3))   # 학습 데이터 안에서 5겹 교차 검증
model.fit(X_tr, y_tr)
print(round(model.score(X_te, y_te), 3))                   # 테스트 정확도
print(model[:-1].get_feature_names_out())                  # 변환 후 특성 이름

실제 실행 결과(Python 3.14 · scikit-learn 1.9.0 · pandas 3.0.3, 시드 0):

코드
[0.75  0.683 0.717 0.65  0.7  ]
0.58
['num__age' 'num__income' 'cat__city_Busan' 'cat__city_Daegu'
 'cat__city_Seoul' 'ord__grade']

결과 읽기: 교차 검증 평균은 0.70인데 테스트 100행에서는 0.58이 나왔습니다. 일부러 잡음을 많이 넣은 작은 가짜 데이터라 평가 데이터를 어떻게 떼느냐에 따라 점수가 크게 흔들린다는 것을 보여 줍니다. 그래서 작은 데이터에서는 한 번의 테스트 점수보다 교차 검증의 평균과 흔들림을 함께 봐야 합니다. 특성 이름을 보면 원-핫이 도시 3칸을 만들었고 숫자 2칸, 순서 1칸으로 총 6칸이 된 것을 확인할 수 있습니다.

연습 문제

1. 학습 데이터 키(cm)가 (160,170,180)(160, 170, 180)일 때 표준화 계수는? 테스트의 키 190은 어떻게 바뀌나요?

풀이: μ=170\mu = 170, σ=(100+0+100)/3≈8.165\sigma = \sqrt{(100 + 0 + 100)/3} \approx 8.165. 테스트 190은 학습 기준으로 (190−170)/8.165≈2.449(190 - 170)/8.165 \approx 2.449입니다. 테스트 데이터로 평균을 다시 재지 않습니다.

2. 혈액형(A, B, O, AB)에는 어떤 인코딩을 쓰나요?

풀이: 순서가 없으므로 원-핫(4칸)입니다. 순서 인코딩은 "AB > O" 같은 가짜 순서를 만듭니다(트리 모델은 예외적으로 쓰기도 함).

3. 값 (2,4,빈칸,100)(2, 4, \text{빈칸}, 100)의 평균 대치값과 중앙값 대치값은?

풀이: 평균 =106/3≈35.3= 106/3 \approx 35.3, 중앙값 =4= 4. 이상치 100 때문에 평균 대치가 크게 왜곡됩니다.

4. 다음 코드의 문제는? X = StandardScaler().fit_transform(X_all); X_tr, X_te = train_test_split(X)

풀이: 테스트 데이터까지 포함한 평균 · 표준편차로 스케일러를 맞춰 정보가 샙니다. 먼저 나누고, 스케일러를 학습 데이터로만 fit하거나 Pipeline으로 묶어야 합니다.

스스로 점검

  1. 랜덤 포레스트에는 왜 표준화가 거의 필요 없나요?
    • 답: 트리는 한 특성을 임곗값과 비교해 나눌 뿐이라, 크기를 바꾸는 단조 변환이 어느 쪽으로 갈지를 바꾸지 않기 때문입니다.
  2. handle_unknown='ignore'는 언제 꼭 필요하나요?
    • 답: 서비스 중 학습 때 없던 범주가 들어올 수 있을 때입니다. 기본값('error')이면 예측이 오류로 멈춥니다.
  3. Pipeline을 쓰면 교차 검증에서 무엇이 좋아지나요?
    • 답: 겹마다 전처리를 그 겹의 학습 부분으로만 다시 맞추므로, 검증 부분의 정보가 새지 않습니다.

용어 정리

용어쉬운 뜻
특성 공학(feature engineering)원래 데이터로 모델이 쓰기 좋은 새 특성을 만드는 일
표준화(standardization)평균 0, 표준편차 1로 옮기기
최소-최대 스케일링(min-max scaling)[0, 1] 범위로 맞추기
로그 변환(log transform)길게 늘어진 분포를 로그로 펴기
원-핫 인코딩(one-hot encoding)범주마다 0/1 칸을 하나씩
순서 인코딩(ordinal encoding)순서 있는 범주를 0, 1, 2…로
결측값 대치(imputation)빈칸을 평균 · 중앙값 등으로 채우기
데이터 누수(data leakage)평가 데이터 정보가 학습에 새어 들어감
파이프라인(Pipeline)전처리와 모델을 한 줄로 묶은 객체
ColumnTransformer열마다 다른 전처리를 적용하는 도구

더 깊이 보려면

scikit-learn '7.3 Preprocessing data'에는 강건 스케일러(이상치에 강함), 분위수 · 거듭제곱 변환, 이산화, 다항 특성 생성까지 정리되어 있습니다. '11. Common pitfalls and recommended practices'는 이 수업의 5절 내용 — 일관되지 않은 전처리, 데이터 누수, 난수 제어 — 을 코드로 보여 줍니다.

다음에 볼 것

라이선스

이 글은 원문 번역이 아니라 KAITABASE가 직접 쓴 해설이며, 숫자 예와 예제 데이터는 설명용으로 만든 것입니다(코드는 직접 실행해 결과를 붙임). 참고 자료 scikit-learn 문서는 BSD-3-Clause 라이선스입니다.

출처

AI 원리부터 이해하기 · 머신러닝 모듈

머신러닝 모듈 학습

이 글은 머신러닝 모듈의 학습 자료입니다. 전체 17개 자료 중 4번째입니다.

AI 기초 과정은 현재 진도 저장을 지원하지 않습니다.

출처·원문

원문 보기

요약만

본문 자체 작성 · 참고 자료 scikit-learn 사용자 안내서(BSD-3)