python, 전처리, 통계, 가설검정, 기계학습, 회귀, 분류, 군집, 모델 학습, 모델 평가
피처 엔지니어링(Feature Engineering)은 기존 데이터를 변형하거나 조합하여 모델이 더 잘 학습할 수 있는 새로운 입력 변수를 만드는 과정이다. 머신러닝에서 “좋은 피처”는 복잡한 알고리즘보다 더 큰 영향을 미치며, 실무 데이터 과학자의 역량이 가장 잘 드러나는 단계이다. 피처 엔지니어링은 도메인 지식과 데이터 탐색을 결합하여 모델이 패턴을 더 쉽게 학습할 수 있도록 돕는다. 이 장에서는 비율 피처, 조합 피처, 변환 피처, 집계 피처 등 다양한 피처 생성 기법과 모델별 전략을 학습한다.
“모델 성능의 상당 부분은 알고리즘이 아니라 피처에서 결정된다.”
예제: 데이터 로드
import seaborn as snsimport pandas as pdimport numpy as np# 데이터 로드 및 결측치 제거df = sns.load_dataset("penguins").dropna()print("데이터 크기:", df.shape)print("\n사용 가능한 컬럼:")print(df.columns.tolist())
데이터 크기: (333, 7)
사용 가능한 컬럼:
['species', 'island', 'bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g', 'sex']
10.1 피처 엔지니어링의 중요성
피처 엔지니어링이 중요한 이유는 다음과 같다.
피처 엔지니어링의 영향력
관점
설명
예시
성능 차이의 핵심
같은 모델도 피처에 따라 성능 천차만별
기본 피처 80% → 파생 피처 95%
단순함의 강력함
좋은 피처 + 단순 모델 > 나쁜 피처 + 복잡 모델
선형 회귀 + 좋은 피처 > 복잡한 신경망
도메인 지식 반영
전문가 지식이 모델에 직접 반영되는 단계
의료: 체질량지수(BMI), 금융: 부채비율
해석력 향상
의미 있는 피처는 모델 설명에 도움
“연령대”가 “나이 제곱”보다 이해 쉬움
차원 축소 효과
여러 변수의 관계를 하나로 압축
길이/너비 대신 종횡비 사용
실무 관점
캐글 경진대회에서 상위권 진입의 핵심 요소
프로덕션 환경에서 모델 유지보수성 향상
컴퓨팅 리소스 절약 (단순 모델 사용 가능)
비즈니스 이해관계자와의 커뮤니케이션 개선
10.2 파생 변수 생성 기법
파생 변수는 기존 변수를 수학적으로 변환하거나 조합하여 새로운 정보를 추출하는 방법이다.
10.2.1 비율(Ratio) 피처
두 변수의 비율은 각 변수의 절대값보다 더 의미 있는 정보를 담을 수 있다. 특히 크기가 다른 개체를 비교할 때 유용하다.
예제: 부리 비율 피처 생성
# 부리 길이 대 깊이 비율df["bill_ratio"] = df["bill_length_mm"] / df["bill_depth_mm"]print("부리 비율 피처:")print(df["bill_ratio"].describe())# 종별 비율 비교print("\n종별 평균 부리 비율:")print(df.groupby("species")["bill_ratio"].mean().sort_values())
부리 비율 피처:
count 333.000000
mean 2.607228
std 0.495436
min 1.639810
25% 2.162651
50% 2.576531
75% 3.096970
max 3.612676
Name: bill_ratio, dtype: float64
종별 평균 부리 비율:
species
Adelie 2.121478
Chinstrap 2.653756
Gentoo 3.176602
Name: bill_ratio, dtype: float64
비율 피처는 다음과 같은 상황에서 효과적이다.
신체 비례나 형태를 나타낼 때 (예: 부리 형태, 체형)
스케일이 다른 개체를 비교할 때
효율성이나 밀도를 나타낼 때 (예: 연비, 인구밀도)
추가 예시
# 체중 대 날개 길이 비율 (체중 효율성)df["body_flipper_ratio"] = df["body_mass_g"] / df["flipper_length_mm"]# 전체 부리 크기 대비 길이 비율df["bill_total"] = df["bill_length_mm"] + df["bill_depth_mm"]df["bill_length_proportion"] = df["bill_length_mm"] / df["bill_total"]print("\n생성된 비율 피처:")print(df[["bill_ratio", "body_flipper_ratio", "bill_length_proportion"]].head())
# 체중과 날개 길이의 상호작용 (전체적인 신체 크기)df["body_flipper_interaction"] = df["body_mass_g"] * df["flipper_length_mm"]print("상호작용 피처:")print(df["body_flipper_interaction"].describe())# 종별 상호작용 피처 비교print("\n종별 평균 상호작용:")print(df.groupby("species")["body_flipper_interaction"].mean())
상호작용 피처:
count 3.330000e+02
mean 8.553021e+05
std 2.206116e+05
min 5.158500e+05
25% 6.796500e+05
50% 8.075000e+05
75% 1.021250e+06
max 1.392300e+06
Name: body_flipper_interaction, dtype: float64
종별 평균 상호작용:
species
Adelie 7.059329e+05
Chinstrap 7.327592e+05
Gentoo 1.108586e+06
Name: body_flipper_interaction, dtype: float64
조합 피처 유형
연산
의미
예시
곱셈 (A × B)
상호작용, 면적, 부피
가로 × 세로 = 면적
덧셈 (A + B)
전체 크기, 합계
총 점수, 총 길이
차이 (A - B)
변화량, 격차
가격 변동, 수입-지출
평균 ((A + B) / 2)
중심 경향
평균 성적, 평균 온도
추가 예시
# 전체 부리 크기 (길이 + 깊이)df["bill_total_size"] = df["bill_length_mm"] + df["bill_depth_mm"]# 날개와 부리 길이 차이df["flipper_bill_diff"] = df["flipper_length_mm"] - df["bill_length_mm"]print("\n조합 피처 샘플:")print(df[["bill_total_size", "flipper_bill_diff"]].head())
# 체중의 로그 변환df["log_body_mass"] = np.log(df["body_mass_g"])print("로그 변환 전후 비교:")print("원본 왜도:", df["body_mass_g"].skew().round(3))print("로그 왜도:", df["log_body_mass"].skew().round(3))# 분포 비교print("\n원본 통계:")print(df["body_mass_g"].describe())print("\n로그 변환 통계:")print(df["log_body_mass"].describe())
로그 변환 전후 비교:
원본 왜도: 0.472
로그 왜도: 0.175
원본 통계:
count 333.000000
mean 4207.057057
std 805.215802
min 2700.000000
25% 3550.000000
50% 4050.000000
75% 4775.000000
max 6300.000000
Name: body_mass_g, dtype: float64
로그 변환 통계:
count 333.000000
mean 8.326667
std 0.188465
min 7.901007
25% 8.174703
50% 8.306472
75% 8.471149
max 8.748305
Name: log_body_mass, dtype: float64
변환의 효과
스케일 안정화: 큰 값과 작은 값의 차이 완화
거리 기반 모델 성능 향상: KNN, SVM 등에서 중요
선형 관계 강화: 지수적 관계를 선형으로 변환
이상치 영향 감소: 극단값을 상대적으로 축소
10.3.2 표준화/정규화 피처
변수를 일정 범위로 변환하여 스케일을 통일한다.
예제: 표준화 피처 생성
from sklearn.preprocessing import StandardScaler# 주요 수치형 변수들을 표준화num_cols = ["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g"]scaler = StandardScaler()df_scaled = pd.DataFrame( scaler.fit_transform(df[num_cols]), columns=[f"{col}_scaled"for col in num_cols], index=df.index)# 원본 데이터프레임에 추가df = pd.concat([df, df_scaled], axis=1)print("표준화 피처 샘플:")print(df[[num_cols[0], f"{num_cols[0]}_scaled"]].head())
# 체중을 3개 구간으로 분류df["body_mass_group"] = pd.cut( df["body_mass_g"], bins=[0, 3500, 4500, 7000], labels=["small", "medium", "large"])print("체중 구간 분포:")print(df["body_mass_group"].value_counts())# 구간별 평균 날개 길이print("\n구간별 평균 날개 길이:")print(df.groupby("body_mass_group")["flipper_length_mm"].mean())
체중 구간 분포:
body_mass_group
medium 146
large 112
small 75
Name: count, dtype: int64
구간별 평균 날개 길이:
body_mass_group
small 188.506667
medium 195.383562
large 216.589286
Name: flipper_length_mm, dtype: float64
구간화의 효과
연속값을 해석 가능한 범주로 변환
비선형 관계를 단계적으로 표현
트리 모델에서 분할 힌트 제공
이상치의 영향 완화
추가 예시: 등빈도 구간화
# 사분위수 기준 구간화df["body_mass_quantile"] = pd.qcut( df["body_mass_g"], q=4, labels=["Q1", "Q2", "Q3", "Q4"])print("사분위수 구간 분포:")print(df["body_mass_quantile"].value_counts())
# 종 내 체중 순위df["body_mass_rank_species"] = df.groupby("species")["body_mass_g"].rank(ascending=False)# 종 내 체중 백분위수df["body_mass_pct_species"] = df.groupby("species")["body_mass_g"].rank(pct=True)print("종 내 순위와 백분위수:")print(df[["species", "body_mass_g", "body_mass_rank_species", "body_mass_pct_species"]].head(10))
# 주요 수치형 변수 선택num_cols = ["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g"]# 행 단위 평균 (개체의 전반적인 크기)df["num_mean"] = df[num_cols].mean(axis=1)# 행 단위 표준편차 (측정값의 변동성)df["num_std"] = df[num_cols].std(axis=1)# 행 단위 최댓값df["num_max"] = df[num_cols].max(axis=1)# 행 단위 최솟값df["num_min"] = df[num_cols].min(axis=1)# 행 단위 범위 (최댓값 - 최솟값)df["num_range"] = df["num_max"] - df["num_min"]print("통계 요약 피처:")print(df[["num_mean", "num_std", "num_range"]].describe())
통계 요약 피처:
num_mean num_std num_range
count 333.000000 333.000000 333.000000
mean 1117.295420 2061.465442 4189.892192
std 204.948885 400.090276 806.147181
min 738.875000 1309.655694 2683.400000
25% 951.400000 1736.198835 3532.500000
50% 1075.325000 1981.053650 4030.100000
75% 1258.550000 2345.624922 4756.000000
max 1646.350000 3103.740721 6284.800000
통계 요약의 효과
개체의 전체적인 특성을 하나의 값으로 압축
변수 간 일관성 또는 변동성 파악
차원 축소 효과
노이즈 감소 (평균화 효과)
10.7 범주형 조합 피처
여러 범주형 변수를 결합하여 세분화된 그룹을 만든다.
예제: 종과 성별 조합
# 종과 성별을 결합한 새로운 범주df["species_sex"] = df["species"].astype(str) +"_"+ df["sex"].astype(str)print("종-성별 조합 분포:")print(df["species_sex"].value_counts())# 조합별 평균 체중print("\n종-성별 조합별 평균 체중:")print(df.groupby("species_sex")["body_mass_g"].mean().sort_values())
1. 데이터 로드
↓
2. 결측치 처리 (제거/대체)
↓
3. 이상치 탐지 및 처리
↓
4. 스케일링 (변수 크기 통일)
↓
5. 분포 변환 (왜도 정규화)
↓
6. 인코딩 (범주형 → 수치형)
↓
7. 불균형 데이터 처리
↓
8. 피처 엔지니어링 ← 현재 장
↓
9. 피처 선택 (중요 피처 추출)
↓
10. 모델링
단계별 피처 생성 시점
결측치 처리 전: X (결측치가 계산을 방해)
이상치 처리 전: △ (이상치가 비율/평균에 영향)
스케일링 전: O (원본 값으로 비율 계산 후 스케일링)
인코딩 후: O (범주형 조합 피처 생성 가능)
피처 선택 전: O (모든 후보 피처 생성 후 선택)
10.12 요약
이 장에서는 피처 엔지니어링의 개념과 다양한 기법을 학습했다. 주요 내용은 다음과 같다.
피처 엔지니어링 기법 정리
기법
방법
효과
예시
비율
A / B
상대적 크기, 형태
부리 길이/깊이 비율
조합
A × B, A + B
상호작용, 전체 크기
체중 × 날개 길이
변환
log, sqrt, 제곱
분포 정규화, 비선형 관계
log(체중)
구간화
cut, qcut
비선형 포착, 해석력
체중 그룹(소/중/대)
집계
groupby + transform
그룹 내 상대적 위치
종별 평균 대비 편차
통계 요약
mean, std, max
전체 특성 압축
측정값 평균
범주 조합
문자열 결합
세분화된 그룹
종_성별 조합
시계열
lag, rolling, diff
시간 패턴, 추세
7일 이동평균
피처 엔지니어링 모범 사례
EDA 먼저: 데이터 탐색을 통해 패턴 발견 후 피처 생성
도메인 지식 활용: 전문가 의견이나 논문의 아이디어 반영
단순하게 시작: 복잡한 피처보다 직관적인 피처부터
반복적 개선: 모델 성능을 확인하며 점진적으로 추가
과적합 주의: 학습 데이터로만 통계량 계산, 테스트 누수 방지
문서화: 피처 생성 논리와 의미를 명확히 기록
재현성 확보: 파이프라인으로 자동화하여 일관성 유지
피처 엔지니어링 체크리스트
피처 엔지니어링은 데이터 과학의 핵심 기술이자 예술이다. 도메인 지식, 창의성, 그리고 반복적인 실험을 통해 모델 성능을 크게 향상시킬 수 있다. 좋은 피처는 복잡한 모델보다 강력하며, 실무에서 데이터 과학자의 역량을 가장 잘 드러내는 단계이다. 다음 단계로는 생성된 피처를 바탕으로 모델을 학습하고 평가하는 과정을 진행할 수 있다.