섹션 1: 수치형 피처 (Numerical Features)
1.1 스케일링 (Scaling)
대부분의 머신러닝 알고리즘은 데이터의 스케일에 민감합니다. 0부터 1,000,000까지의 범위를 가진 열은 0부터 1까지의 범위를 가진 열보다 훨씬 큰 영향을 미치게 됩니다.
StandardScaler: 데이터가 대체로 정규분포를 따를 때 사용합니다. 가장 일반적인 선택입니다.
MinMaxScaler: 값을 0과 1 사이로 변환해야 할 때 사용합니다. 신경망(Neural Network)에 적합합니다.
RobustScaler: 이상치가 많이 포함된 경우 사용합니다. 평균(mean)이 아닌 중앙값(median)과 IQR(사분위 범위)을 사용합니다.
from sklearn.preprocessing import RobustScaler
df['salary_scaled'] = RobustScaler().fit_transform(df[['salary']])
⚠️ 스케일러는 반드시 학습 데이터(training data)에만 적합(fit)해야 합니다. 전체 데이터셋에 적합하면 정보 누수(data leakage)가 발생합니다.
1.2 로그 변환 (Log Transformation)
소득, 가격, 매출과 같이 수치형 열이 강하게 오른쪽으로 치우친(right-skewed) 경우 사용합니다.
import numpy as np
df['revenue_log'] = np.log1p(df['revenue']) # log1p는 0 값을 안전하게 처리
1.3 구간화 (Binning)
연속형 숫자를 범주형 변수로 변환하는 것이 더 유용한 경우가 있습니다.
동일 폭 구간(Equal-width bins): 데이터가 고르게 분포되어 있을 때 pd.cut()을 사용합니다.
분위수 구간(Quantile bins): 데이터가 치우쳐 있을 때 pd.qcut()을 사용합니다. 각 구간은 동일한 수의 샘플을 갖습니다.
df['age_group'] = pd.cut(
df['age'],
bins=[0, 18, 35, 55, 100],
labels=['teen', 'young_adult', 'adult', 'senior']
)
1.4 상호작용 피처 (Interaction Features)
두 개의 피처를 결합하면 각각을 단독으로 사용하는 것보다 더 강력한 예측력을 가질 수 있습니다.
df['price_per_sqft'] = df['price'] / df['sqft']
df['debt_to_income'] = df['debt'] / df['income']
선형 모델에서는 다항식 피처(Polynomial Features)를 사용하여 비선형 관계를 포착할 수 있습니다.
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2, include_bias=False)
# 생성되는 피처:
# age, salary, age², salary², age × salary
1.5 이상치 클리핑 (Clipping Outliers)
이상치를 제거하는 대신 적절한 분위수(percentile)에서 값을 제한할 수 있습니다.
lower = df['salary'].quantile(0.01)
upper = df['salary'].quantile(0.99)
df['salary_clipped'] = df['salary'].clip(
lower=lower,
upper=upper
)
섹션 2: 범주형 피처 (Categorical Features)
2.1 원-핫 인코딩 (One-Hot Encoding)
각 범주를 별도의 0/1 열로 변환합니다. 순서가 없는 명목형 범주(nominal categories)에 가장 적합합니다.
df_encoded = pd.get_dummies(df, columns=['city'], drop_first=True)
⚠️ 열에 500개의 고유 범주가 있다면 500개의 열이 생성됩니다. 이 경우에는 대신 타깃 인코딩(Target Encoding)을 사용하십시오.
2.2 레이블 인코딩 (Label Encoding)
각 범주에 정수를 할당합니다. 실제로 순서가 존재하는 순서형 데이터(ordinal data)에만 사용해야 합니다.
df['education'] = df['education'].map({
'High School': 0,
'Bachelor': 1,
'Master': 2,
'PhD': 3
})
도시 이름과 같은 명목형 데이터에는 레이블 인코딩을 적용하지 마십시오. 모델이 런던(London) > 뭄바이(Mumbai)와 같은 잘못된 순서 관계를 가정하게 됩니다.
2.3 타깃 인코딩 (Target Encoding)
각 범주를 해당 그룹의 타깃 변수 평균값으로 대체합니다. 고 카디널리티(high-cardinality) 열에서 강력한 성능을 보입니다.
from category_encoders import TargetEncoder
df['city_encoded'] = TargetEncoder().fit_transform(
df['city'],
df['churn']
)
⚠️ 위험 요소: 데이터 누수(data leakage)가 발생할 수 있습니다. 실제 운영 환경에서는 교차 검증 기반(cross-fold) 타깃 인코딩을 사용하십시오.
2.4 빈도 인코딩 (Frequency Encoding)
각 범주를 해당 범주의 출현 빈도로 대체합니다. 단순하지만 트리 기반 모델에서 의외로 효과적인 경우가 많습니다.
freq_map = df['city'].value_counts(normalize=True)
df['city_freq'] = df['city'].map(freq_map)
2.5 바이너리 인코딩 (Binary Encoding)
레이블 인코딩과 원-핫 인코딩의 중간 방식입니다. 높은 카디널리티를 가진 데이터에서 열 폭증(column explosion)을 줄이는 데 효과적입니다.
from category_encoders import BinaryEncoder
df_encoded = BinaryEncoder().fit_transform(df[['city']])
100개의 범주 → 단 7개의 이진 열
섹션 3: 날짜 및 시간 피처 (Datetime Features)
대부분의 모델에게 원시 날짜 데이터는 거의 쓸모가 없습니다. 의미 있는 구성 요소를 추출해야 합니다.
3.1 기본 추출 (Standard Extractions)
df['order_date'] = pd.to_datetime(df['order_date'])
df['month'] = df['order_date'].dt.month
df['day_of_week'] = df['order_date'].dt.dayofweek
df['is_weekend'] = df['day_of_week'].isin([5, 6]).astype(int)
df['quarter'] = df['order_date'].dt.quarter
df['days_since'] = (
df['order_date'] - pd.Timestamp('2024-01-01')
).dt.days
3.2 순환 인코딩 (Cyclical Encoding) 🔄
월(month)을 단순한 숫자로 두면 모델은 12월(12)과 1월(1)이 서로 멀리 떨어져 있다고 생각합니다. 하지만 실제로는 그렇지 않습니다.
import numpy as np
df['month_sin'] = np.sin(2 * np.pi * df['month'] / 12)
df['month_cos'] = np.cos(2 * np.pi * df['month'] / 12)
하루 중 시간(hour)에도 동일한 패턴을 적용할 수 있습니다. 이 경우 24로 나누어 사용합니다.
3.3 비즈니스 캘린더 피처 (Business Calendar Features)
import holidays
indian_holidays = holidays.India(years=2025)
df['is_holiday'] = (
df['order_date']
.apply(lambda d: d in indian_holidays)
.astype(int)
)
df['is_month_end'] = (
df['order_date']
.dt.is_month_end
.astype(int)
)
섹션 4: 텍스트 피처 (Text Features)
4.1 기본 통계 피처 (Basic Statistical Features)
NLP를 적용하기 전에 간단한 통계 정보를 추출해 보십시오. 예상보다 훨씬 유용한 경우가 많습니다.
df['word_count'] = df['review'].str.split().str.len()
df['avg_word_len'] = df['review'].str.len() / df['word_count']
df['has_question'] = df['review'].str.contains(r'\\?').astype(int)
df['uppercase_ratio'] = df['review'].apply(
lambda x: sum(c.isupper() for c in str(x)) / max(len(str(x)), 1)
)
4.2 TF-IDF
단어의 중요도를 반영하여 텍스트를 수치형 피처로 변환합니다.
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(
max_features=100,
ngram_range=(1, 2),
stop_words='english'
)
X_tfidf = tfidf.fit_transform(df['review'])
4.3 감성 점수 (Sentiment Score)
from textblob import TextBlob
df['sentiment'] = df['review'].apply(
lambda x: TextBlob(str(x)).sentiment.polarity
)
-1 (매우 부정적)부터 1 (매우 긍정적)까지의 값을 가집니다.
4.4 문장 임베딩 (Sentence Embeddings)
현대적인 접근 방식입니다. 텍스트의 의미적 정보를 밀집 벡터(dense vector)로 표현합니다. 딥러닝 모델에서는 TF-IDF보다 훨씬 강력한 성능을 보입니다.
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = model.encode(df['review'].tolist())
Shape: (n_rows, 384)
→ 각 행은 384개의 수치형 피처로 변환됩니다.
섹션 5: 공간 정보 피처 (Geospatial Features)
5.1 거리 피처 (Distance Features)
각 지점이 중요한 위치로부터 얼마나 떨어져 있는지를 측정합니다.
from math import radians, sin, cos, sqrt, atan2
def haversine(lat1, lon1, lat2, lon2):
R = 6371
lat1, lon1, lat2, lon2 = map(
radians,
[lat1, lon1, lat2, lon2]
)
a = (
sin((lat2 - lat1) / 2) ** 2 +
cos(lat1) * cos(lat2) *
sin((lon2 - lon1) / 2) ** 2
)
return R * 2 * atan2(sqrt(a), sqrt(1 - a))
city_centre = (28.6139, 77.2090)
df['dist_to_centre_km'] = df.apply(
lambda r: haversine(
r['lat'],
r['lon'],
*city_centre
),
axis=1
)
5.2 지오해싱 (Geohashing)
좌표를 짧은 문자열로 인코딩합니다. 각 접두사(prefix)는 특정 지리적 영역을 나타냅니다. 위치를 그룹화하는 데 매우 유용합니다.
import pygeohash as pgh
df['geohash_5'] = df.apply(
lambda r: pgh.encode(
r['lat'],
r['lon'],
precision=5
),
axis=1
)
precision=5
→ 대략 5km 범위의 지역을 나타냅니다.
섹션 6: 집계 피처 (Aggregation Features)
이들은 실제 운영 환경의 머신러닝에서 가장 강력한 피처 중 하나이며, 특히 고객 데이터나 거래 데이터에서 매우 효과적입니다.
6.1 그룹 집계 (Group Aggregations)
stats = df.groupby('customer_id').agg(
total_orders=('order_id', 'count'),
total_spent=('amount', 'sum'),
avg_order_value=('amount', 'mean'),
max_order=('amount', 'max')
).reset_index()
df = df.merge(
stats,
on='customer_id',
how='left'
)
6.2 시차 및 이동 집계 피처 (Lag and Rolling Features)
순차 데이터에서는 최근 N개 기간 동안 발생한 일이 미래를 예측하는 가장 강력한 신호인 경우가 많습니다.
df = df.sort_values(
['customer_id', 'order_date']
)
df['prev_order_amount'] = (
df.groupby('customer_id')['amount']
.shift(1)
)
df['amount_change'] = (
df['amount'] -
df['prev_order_amount']
)
df['rolling_30d_spend'] = (
df.groupby('customer_id')['amount']
.transform(
lambda x: x.rolling(3).sum()
)
)
섹션 7: 피처 선택 (Feature Selection)
피처를 생성하는 것은 작업의 절반에 불과합니다. 실제로 유용한 피처만 남기는 것이 나머지 절반입니다.
7.1 분산이 낮은 피처 제거 (Drop Low Variance Features)
모든 행에서 거의 동일한 값을 가지는 열은 모델에 아무런 정보를 제공하지 못합니다.
from sklearn.feature_selection import VarianceThreshold
selector = VarianceThreshold(threshold=0.01)
X_reduced = selector.fit_transform(X)
7.2 상관관계가 높은 피처 제거 (Drop Correlated Features)
상관관계가 매우 높은 피처들은 중복된 정보를 담고 있습니다. 하나만 남기고 나머지는 제거합니다.
corr = df.corr().abs()
upper = corr.where(
np.triu(
np.ones(corr.shape),
k=1
).astype(bool)
)
to_drop = [
col
for col in upper.columns
if any(upper[col] > 0.95)
]
df.drop(
columns=to_drop,
inplace=True
)
7.3 피처 중요도 (Feature Importance)
트리 기반 모델을 사용하여 피처의 중요도를 평가할 수 있습니다. 중요도가 거의 0에 가까운 피처는 제거를 고려할 수 있습니다.
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(
n_estimators=100,
random_state=42
)
model.fit(X_train, y_train)
importance = pd.Series(
model.feature_importances_,
index=X_train.columns
)
print(
importance
.sort_values(ascending=False)
.head(20)
)
7.4 SHAP 값 (SHAP Values)
SHAP은 어떤 피처가 중요한지를 알려줄 뿐만 아니라, 각 피처가 개별 예측에 어떤 영향을 미쳤는지도 설명해 줍니다.
import shap
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_train)
shap.summary_plot(
shap_values,
X_train
)
섹션 8: 자동화된 피처 엔지니어링 (Automated Feature Engineering)
가능한 조합이 많을 때는 프로그래밍 방식으로 피처를 생성하고, 피처 선택 단계에서 무엇을 남길지 결정하게 합니다.
import featuretools as ft
es = ft.EntitySet(id='orders')
es = es.add_dataframe(
dataframe_name='orders',
dataframe=df,
index='order_id',
time_index='order_date'
)
feature_matrix, feature_defs = ft.dfs(
entityset=es,
target_dataframe_name='orders',
agg_primitives=['sum', 'mean', 'count', 'max', 'std'],
trans_primitives=['month', 'weekday', 'is_weekend'],
max_depth=2
)
print(f"Generated {len(feature_defs)} features automatically")
이를 실행한 다음, 출력 결과를 분산 필터와 상관관계 필터에 통과시키고, 이후 중요도 점수를 확인합니다.
결론
피처 엔지니어링은 도메인 지식과 기술적 역량이 만나는 지점입니다.
아무리 강력한 알고리즘이라도 잘못 준비된 피처를 보완해 주지는 못합니다.
꾸준히 최고의 모델을 만드는 엔지니어들은 가장 많은 알고리즘을 아는 사람들이 아닙니다. 그들은 자신의 데이터를 가장 깊이 이해하는 사람들입니다.
단순하게 시작하고, 영향을 측정한 뒤, 더 단순한 버전으로 충분하지 않을 때만 복잡성을 단계적으로 추가하십시오.
'데이터 사이언스 & 데이터 엔지니어링' 카테고리의 다른 글
| 2026년 모든 데이터 엔지니어가 반드시 알아야 할 12가지 데이터 아키텍처 패턴 (0) | 2026.06.27 |
|---|---|
| 더 나은 데이터 과학자로 만들어주는 세 가지 디자인 패턴 (0) | 2026.06.08 |
| 피처 엔지니어링 입문: 스케일링, 정규화, 표준화를 쉽게 이해하기 (0) | 2026.05.30 |
| 데이터를 이동, 변환, 신뢰하는 방법에 대한 완전 가이드 (0) | 2026.05.27 |
| 데이터 과학자에서 AI 아키텍트로(From Data Scientist to AI Architect) (0) | 2026.05.19 |
댓글