scikit-learn을 사용한 표 형식 데이터 전처리 메모: 결측치, 범주형 변수, 스케일링 및 Pipeline
요약
scikit-learn을 활용하여 표 형식 데이터 전처리 과정을 체계적으로 정리한 가이드입니다. 결측치 처리(SimpleImputer), 범주형 변수 인코딩, 스케일링 등을 `ColumnTransformer`와 `Pipeline`으로 통합하는 방법을 다룹니다. 특히 이상치에 강한 중앙값 사용과 '결측 여부'를 특징량화하는 전략을 강조합니다.
핵심 포인트
- scikit-learn은 (n_samples, n_features) 형태의 2차원 데이터를 요구함.
- 이상치가 있을 경우 평균보다 중앙값을 사용하는 것이 안전함.
- SimpleImputer로 결측 여부를 나타내는 지표 컬럼 생성 가능.
- 결정 트리 모델에서는 One-Hot Encoding 대신 OrdinalEncoder가 효율적일 수 있음.
표 형식 데이터로 기계 학습 모델을 만들 때의 전처리를 scikit-learn 도구에 맞춰 정리한 메모입니다. 결측값 처리, 범주형 변수의 인코딩, 스케일링, 그리고 이들을 ColumnTransformer와 Pipeline으로 묶는 방법, 그리고 전처리 전에 수행하는 탐색적 데이터 분석(EDA)의 기본을 다룹니다. scikit-learn을 사용해 본 경험은 있지만, 전처리의 정석적인 절차를 한 번에 확인하고 싶은 분들을 상정했습니다.
전제: scikit-learn이 기대하는 데이터 형태
scikit-learn 추정기의 fit(X, y)는 다음 형태의 데이터를 받습니다.
X: 모양이(n_samples, n_features)인 배열 (또는 희소 행렬, DataFrame)y: 모양이(n_samples,)이며, 다중 출력이 필요한 경우(n_samples, n_targets)
특징량이 하나만 있는 경우에도 X는 2차원이어야 합니다. 이 때문에 `df[
데이터 전체 건수도 중요합니다. 수만 건이라면 어느 정도의 행을 버려도 영향은 작지만, 수십~수백 건밖에 없을 경우에는 한 줄도 낭비하고 싶지 않기 때문에 보완(imputation)을 우선합니다.
또 하나 중요한 것은 결측이 발생한 이유입니다. 결측이 무작위로 발생하는 것이 아니라 특정 조건에서 발생하기 쉬운 경우(예: 고소득자일수록 연봉을 응답하지 않는 경우), 단순 삭제나 평균값으로 보완하는 것은 결과에 편향을 가져옵니다.
SimpleImputer의 전략 선택 방법
from sklearn.impute import SimpleImputer
SimpleImputer(strategy="mean") # 평균값 (기본값)
SimpleImputer(strategy="median") # 중앙값
...
missing_values의 기본값은 np.nan이며, None이나 pd.NA, 특정 숫자/문자열을 결측으로 처리하도록 지정할 수도 있습니다.
어떤 전략을 선택할지는 결측의 의미로 판단합니다.
- 평균값/중앙값으로 채우는 경우: 본래 값이 존재해야 하는데, 측정 오류/기입 누락/시스템 오류 등으로 우연히 빈칸이 된 경우
- 상수로 채우는 경우: 빈칸이라는 것에 '애초에 존재하지 않음', '해당 없음'이라는 명확한 의미가 있는 경우(예: '지난번 구매로부터 경과 일수'가 빈칸 = 구매 이력 없음)
평균값과 중앙값 중 무엇을 사용할까
평균값은 이상치(outlier)의 영향을 강하게 받습니다. 예를 들어 5명의 연봉이 '300만, 400만, 500만, NaN, 1억'이었다면, NaN 이외의 4명의 평균값은
가 되어 실제와 동떨어진 값으로 보완되어 버립니다. 중앙값이라면 400만과 500만 사이인 450만이 됩니다.
- 분포가 좌우 대칭에 가까울 때: 평균값으로 충분합니다.
- 이상치나 긴 꼬리(tail)의 분포가 있을 때: 중앙값이 더 안전합니다.
실무 데이터에서는 후자의 경우가 많기 때문에, 망설여진다면 중앙값을 사용하는 것이 무난하다고 생각합니다.
결측이었다는 사실 자체를 특징량으로 만들기
'결측이었다'는 사실이 예측에 도움이 되는 경우도 있습니다. SimpleImputer(add_indicator=True)로 설정하면, 보완된 값 외에도 '결측이었는지'를 나타내는 2진 컬럼이
지정하지 않으면 알파벳 순서로 번호가 부여되므로, 순서에 의미가 있는 경우에는 반드시 명시합니다. 변환된 값은 필요에 따라 스케일링의 대상이 될 수 있습니다.
참고로, 결정 트리 계열 모델(랜덤 포레스트, 그래디언트 부스팅 등)에서는 순서가 없는 범주형 데이터라도 OrdinalEncoder를 사용해 정수화하는 것만으로도 충분히 기능하는 경우가 많아 One-Hot Encoding을 사용할 때 발생하는 컬럼 수 증가를 피할 수 있습니다.
LabelEncoder
은 목적 변수에 사용합니다.
LabelEncoder는 이름이 비슷하지만, 목적 변수 y의 레이블을 정수로 변환하는 용도로, 1차원 입력을 받아 항상 ndarray를 반환합니다. 특징량(feature) 변환에는 OrdinalEncoder를 사용해야 합니다.## 스케일링
어떤 모델에서 스케일링이 필요한가
- 필요성이 높은 경우: 거리 또는 내적에 기반한 모델(k-최근접 이웃, SVM), 정규화가 적용된 선형 모델(로지스틱 회귀, Ridge, Lasso), 경사 하강법으로 학습하는 모델(신경망), PCA
- 거의 불필요한 경우: 결정 트리 계열 모델(분할의 임계값은 값의 스케일에 의존하지 않기 때문에)
StandardScaler는 각 컬럼을 평균 0, 표준편차 1로 변환합니다.
One-Hot Encoding 컬럼은 스케일링하지 않습니다
수치형 컬럼과 범주형 컬럼이 혼재하는 경우, 일반적으로 수치형 컬럼만 표준화하고, One-Hot Encoding된 컬럼은 0/1 상태로 결합합니다. 0/1 컬럼을 표준화해도 큰 문제는 없지만, 해석성이 떨어지고 희소 행렬(sparse matrix)의 특성을 잃게 됩니다.
목적 변수의 변환
회귀의 목적 변수 y는 기본적으로 그대로 두어도 무방합니다. 다만, 매출이나 구매 금액처럼 대부분은 수천 원 정도인데 극히 일부에 100만 원과 같은 자릿수 차이가 나는 긴 꼬리 분포(long-tailed distribution)인 경우, 로그 변환이 자주 사용됩니다.
y_train_log = np.log1p(y_train) # log(1 + y). y = 0에서도 계산 가능
model.fit(X_train, y_train_log)
y_pred = np.expm1(model.predict(X_test)) # exp(y) - 1로 원래 스케일로 복원
로그 변환은 큰 값을 압축하여 일부 거대한 값에 의해 제곱 오차(squared error)가 지배되는 것을 방지합니다. 다만, 이 경우 모델이 실질적으로 '로그 스케일에서의 오차'를 최소화하고 있다는 점에는 주의해야 합니다. scikit-learn에는 이러한 변환과 역변환을 자동으로 수행하는 TransformedTargetRegressor도 있습니다.
참고로, 신경망으로 회귀할 때는 목적 변수를 표준화해 두면 학습이 안정되는 경우가 있습니다.
ColumnTransformer와 Pipeline로 묶기
ColumnTransformer: 컬럼별로 다른 전처리를 병렬 적용하기
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
preprocessor = ColumnTransformer(
...
각 요소는 '이름(임의의 문자열)', '적용할 변환기', '대상 컬럼'의 3가지 조합으로 이루어집니다. 지정하지 않은 컬럼은 기본적으로 삭제됩니다(`remainder=
사용하면, fit 시에는 학습 데이터만으로 각 단계의 파라미터가 결정되고, predict 시에는 그것이 테스트 데이터에 그대로 적용됩니다. cross_val_score나 GridSearchCV에 Pipeline을 통째로 전달하면, 교차 검증의 각 분할에서도 동일한 과정이 자동으로 지켜집니다.
하이퍼파라미터 탐색에서는 `
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn ML의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기