원핫인코딩에서 첫 번째 범주를 드롭해도 되는 이유
범주형 변수가 Red, Blue, Green 세 가지 값을 가진다고 해보겠습니다.
원핫인코딩을 하면 다음처럼 표현됩니다.
| 색상 | Red | Blue | Green |
|---|---|---|---|
| Red | 1 | 0 | 0 |
| Blue | 0 | 1 | 0 |
| Green | 0 | 0 | 1 |
여기서 중요한 점은 세 열 중 두 열의 값만 알면 나머지 한 열도 자동으로 알 수 있다는 것입니다.
예를 들어 Red 열을 제거하면:
| 색상 | Blue | Green |
|---|---|---|
| Red | 0 | 0 |
| Blue | 1 | 0 |
| Green | 0 | 1 |
이제도 원래 범주를 완전히 구분할 수 있습니다.
Blue=0, Green=0→ RedBlue=1, Green=0→ BlueBlue=0, Green=1→ Green
즉, 첫 번째 범주의 열을 삭제한다고 해서 범주에 대한 정보가 사라지는 것은 아닙니다.
삭제된 범주는 나머지 더미변수가 모두 0인 경우로 표현되며, 이를 기준 범주(reference category 또는 baseline category)라고 합니다.
왜 굳이 하나를 제거하는가?
특히 선형회귀나 로지스틱 회귀처럼 절편(intercept)을 사용하는 선형 모델에서는 모든 원핫 열을 그대로 넣으면 문제가 생길 수 있습니다.
위 예에서는 항상 다음 관계가 성립합니다.
Red + Blue + Green = 1
그리고 선형 모델에서 절편을 사용하면 이미 모든 행에 대해 값이 1인 항이 존재합니다.
따라서 Red, Blue, Green을 모두 사용하면 한 변수가 다른 변수들로 정확하게 설명되는 완전 다중공선성(perfect multicollinearity)이 발생합니다.
이를 흔히 더미 변수 함정(dummy variable trap)이라고 합니다.
그래서 범주가 3개라면 보통 3개가 아니라 2개의 더미 변수만 사용합니다.
첫 번째 범주를 제거하면 계수는 어떻게 해석하는가?
예를 들어 Red를 제거한 상태에서 선형회귀를 한다고 하겠습니다.
특성은 다음 두 개만 남습니다.
BlueGreen
이때 모델의 계수는 대략 다음 의미로 해석할 수 있습니다.
- 절편: 기준 범주인
Red의 예측 기준값 Blue의 계수:Red에 비해Blue일 때 얼마나 달라지는지Green의 계수:Red에 비해Green일 때 얼마나 달라지는지
따라서 제거된 범주는 없어진 것이 아니라 비교 기준이 된 것입니다.
scikit-learn에서는 어떻게 하는가?
scikit-learn에서는 OneHotEncoder의 drop 옵션을 사용할 수 있습니다.
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(drop='first')
drop='first'를 사용하면 각 범주형 변수마다 첫 번째 범주를 하나씩 제거합니다. scikit-learn 공식 문서에서도 완전한 공선성이 문제가 되는 상황에서 사용할 수 있는 옵션으로 설명합니다. (Scikit-learn)
예를 들어:
from sklearn.preprocessing import OneHotEncoder
X = [
['Red'],
['Blue'],
['Green']
]
encoder = OneHotEncoder(
drop='first',
sparse_output=False
)
encoded = encoder.fit_transform(X)
print(encoder.categories_)
print(encoded)
categories_를 통해 어떤 범주들이 학습되었는지 확인할 수 있습니다.
다만 여기서 말하는 "first"는 데이터에서 처음 등장한 값이라는 뜻으로 생각하면 안 됩니다. OneHotEncoder가 결정한 categories_의 첫 번째 범주가 제거됩니다.
그래서 실제로 무엇이 제거됐는지는 다음처럼 확인하는 것이 가장 확실합니다.
print(encoder.categories_)
print(encoder.drop_idx_)
drop_idx_에는 각 특성에서 어떤 범주의 인덱스가 제거되었는지가 들어 있습니다. (Scikit-learn)
이진 범주만 제거하고 싶다면
scikit-learn에는 다음 옵션도 있습니다.
encoder = OneHotEncoder(drop='if_binary')
drop='if_binary'는 범주가 정확히 2개인 변수에서만 하나를 제거합니다.
예를 들어:
- 성별:
Male,Female→ 하나 제거 - 색상:
Red,Blue,Green→ 전부 유지
이 방식은 이진 범주를 굳이 두 개의 열로 표현할 필요가 없을 때 유용합니다. (Scikit-learn)
항상 drop='first'를 해야 하는 것은 아니다
이 부분이 중요합니다.
1. 선형회귀 / 로지스틱 회귀
절편을 사용하는 전통적인 선형 모델에서는 하나를 제거하는 방식이 자연스럽습니다.
OneHotEncoder(drop='first')
특히 다중공선성을 피하거나 계수를 기준 범주 대비 효과로 해석하고 싶을 때 유용합니다.
2. Ridge, Lasso, 규제가 적용된 로지스틱 회귀
무조건 첫 번째 범주를 제거하는 것이 항상 더 좋은 것은 아닙니다.
scikit-learn 공식 문서에서도 범주 하나를 제거하면 원래 대칭적인 표현이 깨지며, penalized linear model에서는 이것이 편향을 유발할 수 있다고 명시하고 있습니다. (Scikit-learn)
따라서 규제가 들어간 모델에서는 목적에 따라 다음처럼 모든 범주를 유지하기도 합니다.
OneHotEncoder(drop=None)
참고로 OneHotEncoder의 기본값도 drop=None입니다. (Scikit-learn)
3. Decision Tree / Random Forest / Gradient Boosting
트리 계열 모델은 선형회귀처럼 설계행렬을 역산하는 방식이 아니기 때문에 더미 변수 함정이 핵심적인 문제가 아닙니다.
따라서 일반적으로 굳이 첫 번째 범주를 제거할 필요가 없습니다.
OneHotEncoder(drop=None)
을 사용해도 됩니다.
pandas와 scikit-learn 비교
pandas에서는:
pd.get_dummies(
df,
drop_first=True
)
scikit-learn에서는:
OneHotEncoder(
drop='first'
)
가 비슷한 역할을 합니다.
다만 머신러닝 파이프라인에서는 일반적으로 OneHotEncoder가 더 편리합니다.
예를 들어 ColumnTransformer, Pipeline과 바로 연결할 수 있습니다.
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
preprocessor = ColumnTransformer(
transformers=[
(
'category',
OneHotEncoder(
drop='first',
handle_unknown='ignore'
),
['color', 'city']
)
],
remainder='passthrough'
)
model = Pipeline([
('preprocessing', preprocessor),
('regression', LinearRegression())
])
이렇게 하면 학습 데이터에서 결정한 범주 체계를 그대로 테스트 데이터에도 적용할 수 있다는 장점이 있습니다.
한 문장으로 정리
범주가 여러 개인 원핫인코딩에서는 하나의 더미변수를 제거해도 나머지 값만으로 제거된 범주를 구분할 수 있으므로 정보가 사라지지 않으며, 절편을 사용하는 선형 모델에서는 오히려 하나를 제거함으로써 완전 다중공선성을 피할 수 있다.
scikit-learn에서는 보통 OneHotEncoder(drop='first')로 구현하며, 선형 모델에서는 유용하지만 모든 머신러닝 모델에서 반드시 해야 하는 것은 아니다는 점까지 같이 기억하면 됩니다. (Scikit-learn)