튜토리얼
06. Feature Engineering

06. Feature Engineering 실전 가이드

인코딩, 스케일링, 결측치 처리, 파생변수


학습 목표

이 튜토리얼을 완료하면 다음을 할 수 있습니다:

  • 범주형 인코딩: One-Hot, Label, Target, Frequency Encoding의 차이를 이해하고 상황에 맞게 선택
  • 수치형 변환: Log 변환, Binning, 다양한 스케일러를 적용하여 데이터 분포 개선
  • 결측치 처리: Simple, KNN, Iterative Imputer와 Missing Indicator 활용
  • 파생변수 생성: 도메인 지식을 활용한 새로운 feature 생성
  • Feature Selection: 상관관계, F-Score, 모델 기반 방법으로 중요 변수 선택
  • Pipeline 구축: Data Leakage를 방지하는 안전한 전처리 파이프라인 설계

핵심 개념

왜 Feature Engineering이 중요한가?

머신러닝 성능에 영향을 미치는 요소들:

요소영향
데이터 품질가장 중요 - "Garbage in, garbage out"
Feature Engineering핵심 요소 - 같은 데이터로 성능 차이를 만듦
알고리즘 선택중요하지만 feature보다 영향력 낮음
하이퍼파라미터마지막 미세 조정 단계

Kaggle 대회에서 상위권 솔루션들의 공통점: 대부분 비슷한 알고리즘(XGBoost, LightGBM)을 사용하지만, feature engineering에서 차이가 납니다. Andrew Ng: "Applied ML is basically feature engineering."


1. 범주형 인코딩

방식설명적합한 상황
One-Hot이진 벡터범주 수 적을 때, 선형 모델
Label정수 매핑트리 모델
Target타겟 평균고카디널리티
Frequency빈도수Leakage 방지

One-Hot Encoding

범주형 변수를 이진 벡터로 변환합니다. 범주 간 순서가 없을 때 사용합니다.

# pandas 방식
df_encoded = pd.get_dummies(df, columns=['sex'], drop_first=True)
 
# sklearn 방식
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(sparse_output=False, drop='first')
encoded = encoder.fit_transform(df[['sex']])
print(f'Feature names: {encoder.get_feature_names_out()}')
⚠️

범주가 많으면 차원 폭발(Curse of Dimensionality)이 발생합니다! 8개 이상의 고유값이 있다면 Target이나 Frequency Encoding을 고려하세요.

Label Encoding

범주를 정수로 매핑합니다. 트리 기반 모델에서 효과적입니다.

from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df['sex_le'] = le.fit_transform(df['sex'])
⚠️

Label Encoding은 순서 관계를 암시할 수 있습니다. 선형 모델에서는 주의가 필요하지만, 트리 모델에서는 문제없습니다.

Target Encoding (with Smoothing)

범주별 타겟의 평균값으로 인코딩합니다. 고카디널리티 범주에 효과적입니다.

def target_encode_smoothed(df, col, target, m=10):
    """Smoothing으로 과적합 방지"""
    global_mean = df[target].mean()
    agg = df.groupby(col)[target].agg(['mean', 'count'])
    smoothed = (agg['count'] * agg['mean'] + m * global_mean) / (agg['count'] + m)
    return df[col].map(smoothed)
 
# 사용 예시
df['deck_smoothed'] = target_encode_smoothed(df, 'deck', 'survived', m=10)

Smoothing 파라미터 m은 샘플이 적은 범주를 전체 평균으로 당깁니다. m=10이 일반적인 시작점입니다.

Frequency Encoding

범주의 출현 빈도로 인코딩합니다. Target 정보를 사용하지 않아 Leakage가 없습니다.

freq_map = df['deck'].value_counts() / len(df)
df['deck_freq'] = df['deck'].map(freq_map)

2. 수치형 변환

스케일러 비교

스케일러특징사용 시점
StandardScaler평균=0, 표준편차=1일반적인 경우
MinMaxScaler[0,1] 범위신경망, 거리 기반 모델
RobustScaler중앙값, IQR 사용이상치 존재 시
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
 
# 이상치가 있다면 RobustScaler
scaler = RobustScaler()
X_scaled = scaler.fit_transform(X)

Log 변환

왜곡된 분포를 정규분포에 가깝게 변환합니다.

# Skewness 확인
print(f'원본 Skewness: {df["fare"].skew():.2f}')
 
# Log 변환 (0이 포함된 경우 log1p 사용)
df['fare_log'] = np.log1p(df['fare'])
print(f'변환 후 Skewness: {df["fare_log"].skew():.2f}')

Skewness가 -1 ~ 1 범위를 벗어나면 Log 변환을 고려하세요. np.log1p()는 log(1+x)로 0값도 안전하게 처리합니다.

Binning (구간화)

연속형 변수를 범주형으로 변환합니다.

# 등간격 (Equal Width)
pd.cut(df['age'], bins=5)
 
# 등빈도 (Equal Frequency)
pd.qcut(df['age'], q=5)
 
# 도메인 기반 커스텀 구간
df['age_group'] = pd.cut(df['age'],
                         bins=[0, 12, 18, 35, 60, 100],
                         labels=['Child', 'Teen', 'Young', 'Adult', 'Senior'])

3. 결측치 처리

방법장점단점
평균/중앙값빠름, 간단함분산 감소
KNN Imputer변수 간 관계 고려계산 비용 높음
Iterative Imputer가장 정교함복잡, 느림
from sklearn.impute import SimpleImputer, KNNImputer
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
 
# 단순 대체
simple_imp = SimpleImputer(strategy='median')
 
# KNN 기반 (변수 간 관계 고려)
knn_imp = KNNImputer(n_neighbors=5)
 
# Iterative (가장 정교함)
iter_imp = IterativeImputer(max_iter=10, random_state=42)

Missing Indicator

결측 자체가 정보일 수 있습니다. 결측 여부를 별도 feature로 추가합니다.

# 결측 여부 표시
df['age_missing'] = df['age'].isna().astype(int)
 
# 결측치 대체
df['age'].fillna(df['age'].median(), inplace=True)
 
# 결측 여부별 타겟 분석
print(df.groupby('age_missing')['survived'].mean())

결측이 무작위가 아니라면(예: 고령 승객의 나이 누락) Missing Indicator가 예측에 도움이 될 수 있습니다.


4. 파생변수 생성

도메인 지식을 활용하여 새로운 feature를 만듭니다.

Titanic 예시

# 가족 크기
df['family_size'] = df['sibsp'] + df['parch'] + 1
 
# 혼자 여행 여부
df['is_alone'] = (df['family_size'] == 1).astype(int)
 
# 가족 규모 그룹
df['family_group'] = pd.cut(df['family_size'],
                            bins=[0, 1, 4, 11],
                            labels=['Alone', 'Small', 'Large'])
 
# 1인당 요금
df['fare_per_person'] = df['fare'] / df['family_size']
 
# 연령대
df['age_group'] = pd.cut(df['age'],
                         bins=[0, 12, 18, 35, 60, 100],
                         labels=['Child', 'Teen', 'Young', 'Adult', 'Senior'])

파생변수의 효과는 모델에 따라 다릅니다. 선형 모델은 비선형 관계를 표현하는 파생변수에서 큰 이득을 얻고, 트리 모델은 이미 비선형성을 처리하므로 효과가 적을 수 있습니다.


5. Feature Selection

다양한 방법 비교

from sklearn.feature_selection import SelectKBest, f_classif, RFE
from sklearn.ensemble import RandomForestClassifier
 
# 1. 상관계수 기반
corr = X.corrwith(y).abs().sort_values(ascending=False)
print('상관계수 순위:', corr.head())
 
# 2. F-Score (통계적 유의성)
selector = SelectKBest(score_func=f_classif, k=10)
X_selected = selector.fit_transform(X, y)
scores = pd.Series(selector.scores_, index=X.columns).sort_values(ascending=False)
 
# 3. RFE (Recursive Feature Elimination)
rfe = RFE(estimator=RandomForestClassifier(), n_features_to_select=10)
X_rfe = rfe.fit_transform(X, y)
 
# 4. Model-based (Feature Importance)
rf = RandomForestClassifier(n_estimators=100).fit(X, y)
importances = pd.Series(rf.feature_importances_, index=X.columns).sort_values(ascending=False)
⚠️

각 방법은 다른 관점에서 중요도를 측정합니다. 상관계수는 선형 관계만, F-Score는 통계적 유의성을, RF Importance는 예측 기여도를 봅니다. 여러 방법을 함께 활용하세요!


6. Pipeline 구축 (Leakage 방지)

🚫

Data Leakage 주의!

잘못된 방식: 전체 데이터 스케일링 후 train/test 분리

올바른 방식: 분리 후 train으로만 fit하고 각각 transform

Pipeline을 사용하면 자동으로 방지됩니다!

from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
 
num_features = ['age', 'sibsp', 'parch', 'fare']
cat_features = ['pclass', 'sex', 'embarked']
 
# 수치형 전처리
num_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])
 
# 범주형 전처리
cat_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
])
 
# 결합
preprocessor = ColumnTransformer([
    ('num', num_transformer, num_features),
    ('cat', cat_transformer, cat_features)
])
 
# 전체 파이프라인
pipeline = Pipeline([
    ('preprocessor', preprocessor),
    ('classifier', RandomForestClassifier())
])
 
# 사용
pipeline.fit(X_train, y_train)
score = pipeline.score(X_test, y_test)

권장 사항 요약

상황권장 방법
범주 적음 + 선형모델One-Hot
트리 모델Label/Ordinal
고카디널리티Target/Frequency
왜곡 분포Log 변환
이상치RobustScaler
결측치 관계 중요KNN Imputer
Leakage 방지Pipeline 필수!

면접 질문 맛보기

  1. Target Encoding의 장단점과 주의사항은?
  2. Feature Scaling은 언제 필요하고 언제 불필요한가요?
  3. Pipeline을 사용하는 이유는?

더 많은 면접 질문은 Premium Interviews (opens in a new tab)에서 확인하세요.


실습 노트북

노트북에서는 추가로 다음 내용을 실습할 수 있습니다:

  • Titanic과 California Housing 데이터셋을 활용한 실전 예제
  • 인코딩 방법별 성능 비교 실험 (One-Hot vs Label vs Target)
  • Imputation 방법별 정확도 비교
  • 파생변수 추가에 따른 모델 성능 변화 측정
  • 회귀 문제에서의 Log 변환 효과 비교
  • 여러 모델(LogReg, RF, GBM, SVM)을 Pipeline으로 비교

이전: 05. Ensemble Methods | 다음: 07. Clustering