🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

범주형 변수 인코딩 완벽 가이드 - 슬라이드 1/9
A

AI Generated

2025. 11. 29. · 13 Views

범주형 변수 인코딩 완벽 가이드

머신러닝 모델이 이해할 수 있도록 문자 데이터를 숫자로 변환하는 범주형 변수 인코딩 기법을 알아봅니다. 레이블 인코딩부터 원-핫 인코딩, 타겟 인코딩까지 실무에서 바로 활용할 수 있는 핵심 기법을 다룹니다.


목차

  1. 범주형_변수란_무엇인가
  2. 레이블_인코딩
  3. 원핫_인코딩
  4. 타겟_인코딩
  5. 빈도_인코딩
  6. 순서형_인코딩
  7. 이진_인코딩
  8. 인코딩_방법_선택_가이드

1. 범주형 변수란 무엇인가

김개발 씨는 첫 머신러닝 프로젝트를 맡게 되었습니다. 고객 데이터를 분석하려고 보니 '성별', '지역', '직업' 같은 컬럼이 가득합니다.

이 데이터를 모델에 넣으려고 하자 에러가 발생했습니다. "왜 문자 데이터는 안 되는 거죠?"

범주형 변수는 정해진 카테고리 중 하나의 값을 가지는 데이터입니다. 마치 객관식 시험 문제처럼, 여러 선택지 중 하나를 고르는 것과 같습니다.

성별(남/여), 혈액형(A/B/O/AB), 학년(1/2/3/4) 등이 대표적인 예시입니다. 머신러닝 모델은 숫자만 이해할 수 있기 때문에 이런 범주형 변수를 숫자로 변환하는 과정이 반드시 필요합니다.

다음 코드를 살펴봅시다.

import pandas as pd

# 고객 데이터 예시
data = {
    'customer_id': [1, 2, 3, 4, 5],
    'gender': ['남성', '여성', '여성', '남성', '여성'],
    'region': ['서울', '부산', '서울', '대구', '부산'],
    'membership': ['골드', '실버', '브론즈', '골드', '골드']
}

df = pd.DataFrame(data)

# 범주형 변수 확인
print("데이터 타입 확인:")
print(df.dtypes)
print("\n각 컬럼의 고유값:")
for col in ['gender', 'region', 'membership']:
    print(f"{col}: {df[col].unique()}")

김개발 씨는 데이터 분석팀에 배치된 지 일주일 된 신입 개발자입니다. 팀장님이 첫 과제로 고객 이탈 예측 모델을 만들어보라고 하셨습니다.

의욕 넘치게 데이터를 불러왔는데, 막상 모델에 넣으려니 에러 메시지가 뜹니다. "ValueError: could not convert string to float: '남성'" 당황한 김개발 씨 옆으로 선배 박시니어 씨가 다가왔습니다.

"아, 범주형 변수 때문에 그래요. 모델은 숫자밖에 모르거든요." 그렇다면 범주형 변수란 정확히 무엇일까요?

쉽게 비유하자면, 범주형 변수는 마치 객관식 시험 문제의 보기와 같습니다. "다음 중 하나를 고르시오"라는 문제처럼, 미리 정해진 선택지 중에서 하나의 값을 가집니다.

성별이라면 '남성' 또는 '여성', 혈액형이라면 'A', 'B', 'O', 'AB' 중 하나인 것처럼 말입니다. 이와 반대되는 개념이 수치형 변수입니다.

나이, 키, 몸무게처럼 연속적인 숫자 값을 가지는 데이터죠. 수치형 변수는 그 자체로 크기 비교나 연산이 가능합니다.

하지만 범주형 변수는 다릅니다. '서울'이 '부산'보다 크다고 말할 수 없으니까요.

범주형 변수는 다시 두 가지로 나뉩니다. 첫 번째는 명목형 변수입니다.

성별, 지역, 색상처럼 순서나 크기 개념이 없는 카테고리입니다. 두 번째는 순서형 변수입니다.

학년, 등급, 만족도처럼 카테고리 간에 순서가 존재합니다. '골드' 등급이 '실버'보다 높고, '실버'가 '브론즈'보다 높은 것처럼요.

그런데 왜 머신러닝 모델은 문자 데이터를 이해하지 못할까요? 머신러닝 모델의 핵심은 수학적 연산입니다.

가중치를 곱하고, 더하고, 미분하는 과정을 통해 학습합니다. "서울 곱하기 0.5"는 계산이 불가능하지만, "1 곱하기 0.5"는 가능합니다.

그래서 모든 범주형 변수를 숫자로 바꿔야 하는 것입니다. 위 코드를 살펴보면, pandas로 고객 데이터를 만들었습니다.

gender, region, membership 컬럼이 모두 문자열 타입(object)으로 되어 있는 것을 확인할 수 있습니다. 각 컬럼의 고유값을 출력해보면 어떤 카테고리들이 있는지 파악할 수 있죠.

실제 현업에서는 데이터의 대부분이 범주형 변수인 경우도 많습니다. 쇼핑몰 데이터만 해도 상품 카테고리, 결제 방법, 배송 지역, 고객 등급 등 수많은 범주형 변수가 존재합니다.

이런 데이터를 어떻게 변환하느냐에 따라 모델 성능이 크게 달라질 수 있습니다. 박시니어 씨가 말했습니다.

"범주형 변수 인코딩은 머신러닝의 기본 중 기본이에요. 이것만 제대로 이해해도 데이터 전처리의 절반은 끝난 거예요." 김개발 씨는 고개를 끄덕이며 노트에 메모했습니다.

범주형 변수를 숫자로 바꾸는 방법, 그것이 바로 인코딩이었습니다.

실전 팁

💡 - 데이터를 받으면 먼저 df.dtypes로 각 컬럼의 타입을 확인하세요

  • nunique() 메서드로 각 범주형 컬럼의 고유값 개수를 파악하면 인코딩 방법 선택에 도움이 됩니다

2. 레이블 인코딩

범주형 변수를 숫자로 바꿔야 한다는 것은 이해했습니다. 김개발 씨는 가장 직관적인 방법을 떠올렸습니다.

"그냥 각 카테고리에 번호를 붙이면 되지 않을까요?" 바로 그것이 레이블 인코딩의 핵심 아이디어입니다.

레이블 인코딩은 각 카테고리에 고유한 정수를 부여하는 가장 단순한 인코딩 방법입니다. 마치 출석부에 번호를 매기듯, '남성'은 0, '여성'은 1처럼 순차적으로 숫자를 할당합니다.

구현이 간단하고 메모리 효율이 좋지만, 의도치 않은 순서 관계가 생길 수 있다는 점을 주의해야 합니다.

다음 코드를 살펴봅시다.

from sklearn.preprocessing import LabelEncoder
import pandas as pd

# 샘플 데이터
df = pd.DataFrame({
    'region': ['서울', '부산', '대구', '서울', '부산', '인천']
})

# 레이블 인코더 생성 및 학습
encoder = LabelEncoder()
df['region_encoded'] = encoder.fit_transform(df['region'])

print("인코딩 결과:")
print(df)
print(f"\n클래스 매핑: {dict(zip(encoder.classes_, range(len(encoder.classes_))))}")

# 새로운 데이터 변환
new_data = ['서울', '대구']
encoded = encoder.transform(new_data)
print(f"\n새로운 데이터 변환: {new_data} -> {encoded}")

김개발 씨가 처음 시도한 방법은 단순했습니다. 엑셀에서 찾아 바꾸기를 하듯, '서울'은 0, '부산'은 1, '대구'는 2로 바꾸면 되지 않을까?

이 직관적인 아이디어가 바로 레이블 인코딩입니다. 레이블 인코딩은 마치 학교 출석부에 번호를 매기는 것과 같습니다.

김철수는 1번, 이영희는 2번, 박민수는 3번. 이름 대신 번호로 부르는 것이죠.

컴퓨터도 마찬가지로 '서울' 대신 0, '부산' 대신 1로 기억합니다. sklearn 라이브러리의 LabelEncoder를 사용하면 이 작업을 아주 쉽게 할 수 있습니다.

fit_transform 메서드 하나로 학습과 변환을 동시에 처리합니다. 내부적으로는 알파벳 또는 가나다 순으로 정렬하여 번호를 부여합니다.

위 코드에서 classes_ 속성을 확인하면 어떤 카테고리가 어떤 숫자로 매핑되었는지 알 수 있습니다. 이 매핑 정보를 저장해두면 나중에 예측 결과를 다시 원래 카테고리로 되돌릴 수도 있습니다.

그런데 박시니어 씨가 경고했습니다. "레이블 인코딩에는 함정이 있어요." 무슨 말일까요?

서울이 0, 부산이 1, 대구가 2로 인코딩되었다고 가정해봅시다. 머신러닝 모델 입장에서는 이 숫자들 사이에 크기 관계가 있다고 해석할 수 있습니다.

대구(2)가 서울(0)보다 2배 크다거나, 부산(1)이 서울(0)과 대구(2)의 중간이라고 착각할 수 있는 것이죠. 실제로 지역 간에는 이런 수학적 관계가 없습니다.

서울과 부산 사이에 순서나 크기 개념이 없으니까요. 이것을 순서 편향이라고 부릅니다.

그렇다면 레이블 인코딩은 언제 사용해야 할까요? 첫째, 트리 기반 모델을 사용할 때입니다.

의사결정나무, 랜덤포레스트, XGBoost 같은 모델은 숫자의 크기가 아니라 분기점으로 데이터를 나누기 때문에 순서 편향의 영향을 덜 받습니다. 둘째, 순서형 변수를 인코딩할 때입니다.

'브론즈 < 실버 < 골드' 같은 등급 데이터는 원래 순서가 있으므로 레이블 인코딩이 오히려 적절합니다. 브론즈를 0, 실버를 1, 골드를 2로 인코딩하면 원래의 순서 관계를 그대로 유지할 수 있습니다.

셋째, 타겟 변수를 인코딩할 때입니다. 분류 문제에서 예측하려는 클래스를 숫자로 바꿀 때는 레이블 인코딩을 사용합니다.

김개발 씨가 물었습니다. "그러면 명목형 변수는 어떻게 해요?" 박시니어 씨가 미소 지으며 답했습니다.

"그럴 때 쓰는 게 원-핫 인코딩이에요."

실전 팁

💡 - 트리 기반 모델(XGBoost, LightGBM, 랜덤포레스트)에서는 레이블 인코딩을 사용해도 무방합니다

  • 순서가 있는 변수는 수동으로 순서를 지정하여 인코딩하는 것이 좋습니다

3. 원핫 인코딩

지역 데이터를 레이블 인코딩했더니 모델이 "대구가 서울보다 2배 중요하다"고 학습해버렸습니다. 김개발 씨는 당황했습니다.

순서가 없는 범주형 변수를 어떻게 해야 순서 편향 없이 인코딩할 수 있을까요? 해답은 원-핫 인코딩에 있었습니다.

원-핫 인코딩은 각 카테고리를 별도의 이진 컬럼으로 변환하는 방법입니다. 마치 OX 퀴즈처럼, 해당 카테고리면 1, 아니면 0으로 표시합니다.

순서 편향 문제를 완전히 해결할 수 있지만, 카테고리가 많으면 컬럼 수가 급격히 늘어나는 단점이 있습니다.

다음 코드를 살펴봅시다.

import pandas as pd
from sklearn.preprocessing import OneHotEncoder

# 샘플 데이터
df = pd.DataFrame({
    'customer_id': [1, 2, 3, 4],
    'region': ['서울', '부산', '대구', '서울']
})

# pandas의 get_dummies 사용 (가장 간단한 방법)
df_encoded = pd.get_dummies(df, columns=['region'], prefix='region')
print("pandas get_dummies 결과:")
print(df_encoded)

# sklearn OneHotEncoder 사용 (ML 파이프라인용)
encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore')
encoded_array = encoder.fit_transform(df[['region']])
feature_names = encoder.get_feature_names_out(['region'])
print(f"\nsklearn 결과 (컬럼명: {feature_names}):")
print(encoded_array)

박시니어 씨가 화이트보드에 그림을 그리기 시작했습니다. "원-핫 인코딩을 이해하려면 먼저 원-핫이 뭔지 알아야 해요." **원-핫(One-Hot)**은 디지털 회로에서 온 용어입니다.

여러 개의 비트 중에서 딱 하나만 1이고 나머지는 모두 0인 상태를 말합니다. 마치 여러 개의 전구 중에서 딱 하나만 켜져 있는 것과 같습니다.

원-핫 인코딩은 이 원리를 그대로 적용합니다. '서울', '부산', '대구' 세 개의 카테고리가 있다면, 세 개의 새로운 컬럼을 만듭니다.

그리고 해당하는 카테고리의 컬럼에만 1을 넣고, 나머지에는 0을 넣습니다. 예를 들어 '서울'이라면 [1, 0, 0], '부산'이라면 [0, 1, 0], '대구'라면 [0, 0, 1]이 됩니다.

이렇게 하면 어떤 지역도 다른 지역보다 크거나 작지 않습니다. 완전히 동등한 관계를 유지하게 되죠.

김개발 씨가 고개를 끄덕였습니다. "아, 그래서 순서 편향이 없는 거군요!" pandas에서는 get_dummies 함수로 아주 쉽게 원-핫 인코딩을 할 수 있습니다.

columns 파라미터에 변환할 컬럼을 지정하고, prefix로 새로 만들어질 컬럼의 접두어를 설정합니다. 결과를 보면 region_서울, region_부산, region_대구 세 개의 컬럼이 생성된 것을 확인할 수 있습니다.

sklearn의 OneHotEncoder는 머신러닝 파이프라인에서 사용하기 좋습니다. handle_unknown='ignore' 옵션을 설정하면 학습 데이터에 없던 새로운 카테고리가 나타나도 에러 없이 처리할 수 있습니다.

하지만 박시니어 씨가 주의를 줬습니다. "원-핫 인코딩에도 문제가 있어요." 바로 차원의 폭발입니다.

카테고리가 100개면 컬럼이 100개가 됩니다. 우편번호처럼 수천 개의 카테고리가 있다면?

컬럼이 수천 개로 늘어나 메모리를 잡아먹고, 모델 학습 속도도 느려집니다. 또 다른 문제는 다중공선성입니다.

원-핫 인코딩된 컬럼들은 서로 완벽한 상관관계를 가집니다. region_서울 + region_부산 + region_대구 = 1이 항상 성립하니까요.

이는 선형 회귀 같은 모델에서 문제를 일으킬 수 있습니다. 이 문제를 해결하기 위해 drop_first 옵션을 사용하기도 합니다.

첫 번째 카테고리의 컬럼을 제거하는 것이죠. 세 개의 지역이 있다면 두 개의 컬럼만 만들어도 세 가지 경우를 모두 표현할 수 있습니다.

[0, 0]이면 서울, [1, 0]이면 부산, [0, 1]이면 대구로 해석하면 됩니다. 김개발 씨가 메모했습니다.

"카테고리 수가 적으면 원-핫, 많으면 다른 방법을 찾아보자."

실전 팁

💡 - 카테고리가 10개 이하일 때 원-핫 인코딩이 효과적입니다

  • 선형 모델을 사용할 때는 drop_first=True 옵션으로 다중공선성을 줄이세요

4. 타겟 인코딩

김개발 씨는 고민에 빠졌습니다. 고객 데이터에 '우편번호' 컬럼이 있는데, 카테고리가 무려 500개가 넘습니다.

원-핫 인코딩을 하면 컬럼이 500개나 생기고, 레이블 인코딩을 하면 순서 편향이 생깁니다. 이럴 때 사용할 수 있는 비밀 무기가 있습니다.

타겟 인코딩은 각 카테고리를 해당 카테고리의 타겟 변수 평균값으로 대체하는 방법입니다. 마치 각 지역의 "평균 집값"으로 지역을 표현하는 것과 같습니다.

고차원 범주형 변수를 단일 컬럼으로 변환할 수 있어 효율적이지만, 과적합 위험이 있어 적절한 정규화가 필요합니다.

다음 코드를 살펴봅시다.

import pandas as pd
import numpy as np
from sklearn.model_selection import KFold

# 샘플 데이터 (지역별 구매 여부)
df = pd.DataFrame({
    'region': ['서울', '부산', '서울', '대구', '부산', '서울', '대구', '부산'],
    'purchased': [1, 0, 1, 0, 1, 1, 0, 0]
})

# 기본 타겟 인코딩: 각 지역의 구매율로 변환
target_mean = df.groupby('region')['purchased'].mean()
df['region_target_encoded'] = df['region'].map(target_mean)

print("지역별 구매율:")
print(target_mean)
print("\n타겟 인코딩 결과:")
print(df)

# Smoothing을 적용한 타겟 인코딩 (과적합 방지)
global_mean = df['purchased'].mean()
smoothing = 3  # smoothing 파라미터
counts = df.groupby('region')['purchased'].count()
smoothed = (counts * target_mean + smoothing * global_mean) / (counts + smoothing)
print(f"\nSmoothing 적용 결과:\n{smoothed}")

"우편번호가 500개나 되는데 어떻게 하죠?" 김개발 씨의 질문에 박시니어 씨가 웃으며 답했습니다. "그럴 때 쓰는 게 타겟 인코딩이에요.

캐글 대회에서 자주 쓰는 기법이죠." 타겟 인코딩의 아이디어는 간단합니다. 각 카테고리를 그 카테고리에 속한 샘플들의 타겟 변수 평균으로 바꾸는 것입니다.

비유를 들어볼까요? 부동산 데이터를 분석한다고 가정해봅시다.

'강남구', '마포구', '관악구' 같은 지역명 대신, 각 지역의 평균 아파트 가격으로 대체하는 것입니다. 강남구는 15억, 마포구는 10억, 관악구는 7억처럼요.

이렇게 하면 지역이라는 범주형 변수가 하나의 숫자 컬럼으로 변환됩니다. 위 코드에서는 구매 여부를 예측하는 상황을 가정했습니다.

서울 지역 고객들의 구매율이 100%라면 '서울'은 1.0으로 인코딩됩니다. 부산의 구매율이 33%라면 '부산'은 0.33으로 인코딩되는 식이죠.

타겟 인코딩의 장점은 명확합니다. 카테고리가 500개든 5000개든 단 하나의 컬럼으로 변환됩니다.

원-핫 인코딩의 차원 폭발 문제를 완벽히 해결하죠. 하지만 박시니어 씨가 경고했습니다.

"타겟 인코딩에는 아주 치명적인 함정이 있어요." 바로 **타겟 누수(Target Leakage)**와 과적합 문제입니다. 학습 데이터의 타겟 정보를 그대로 피처로 사용하면, 모델이 정답을 미리 알고 있는 것과 같습니다.

특히 샘플 수가 적은 카테고리는 과적합이 심해집니다. 예를 들어 '제주도' 카테고리에 딱 한 명의 고객만 있고 그 고객이 구매했다면, 타겟 인코딩 값은 1.0이 됩니다.

하지만 이것이 정말 제주도의 특성일까요? 아니면 그냥 우연일까요?

이 문제를 해결하기 위해 스무딩(Smoothing) 기법을 사용합니다. 코드의 smoothed 계산 부분을 보면, 전체 평균(global_mean)을 섞어주는 것을 알 수 있습니다.

샘플 수가 적은 카테고리일수록 전체 평균에 가깝게, 샘플 수가 많을수록 해당 카테고리의 평균에 가깝게 조정됩니다. 또 다른 방법은 K-Fold 타겟 인코딩입니다.

데이터를 여러 폴드로 나누고, 각 폴드의 데이터를 인코딩할 때 해당 폴드를 제외한 나머지 데이터의 평균을 사용합니다. 이렇게 하면 타겟 누수를 효과적으로 방지할 수 있습니다.

김개발 씨가 메모했습니다. "타겟 인코딩은 강력하지만, 반드시 스무딩이나 K-Fold 기법과 함께 사용해야 한다."

실전 팁

💡 - 타겟 인코딩은 반드시 Cross-Validation과 함께 사용하여 타겟 누수를 방지하세요

  • category_encoders 라이브러리의 TargetEncoder는 스무딩을 자동으로 적용해줍니다

5. 빈도 인코딩

김개발 씨는 타겟 인코딩의 개념은 이해했지만, 타겟 누수가 걱정되었습니다. 좀 더 안전하면서도 고차원 범주형 변수를 효과적으로 처리할 방법이 없을까요?

이때 사용할 수 있는 것이 바로 빈도 인코딩입니다.

빈도 인코딩은 각 카테고리를 해당 카테고리가 데이터에서 나타나는 빈도(횟수 또는 비율)로 대체하는 방법입니다. 마치 인기 순위처럼, 자주 등장하는 카테고리는 높은 값, 드물게 등장하는 카테고리는 낮은 값을 가집니다.

타겟 누수 없이 간단하게 적용할 수 있다는 장점이 있습니다.

다음 코드를 살펴봅시다.

import pandas as pd

# 샘플 데이터
df = pd.DataFrame({
    'product_category': ['전자기기', '의류', '전자기기', '식품', '의류',
                         '전자기기', '전자기기', '식품', '의류', '기타']
})

# 빈도(출현 횟수) 인코딩
freq_count = df['product_category'].value_counts()
df['category_freq_count'] = df['product_category'].map(freq_count)

# 비율 인코딩 (0~1 사이 값으로 정규화)
freq_ratio = df['product_category'].value_counts(normalize=True)
df['category_freq_ratio'] = df['product_category'].map(freq_ratio)

print("카테고리별 빈도:")
print(freq_count)
print("\n인코딩 결과:")
print(df)
print(f"\n희귀 카테고리 '기타'의 빈도: {freq_count['기타']} ({freq_ratio['기타']:.1%})")

박시니어 씨가 새로운 방법을 알려주었습니다. "타겟 인코딩이 부담스러우면 빈도 인코딩을 먼저 시도해봐요.

더 안전하고 간단하거든요." 빈도 인코딩은 말 그대로 각 카테고리가 데이터에 몇 번 등장하는지를 값으로 사용합니다. '전자기기'가 100번, '의류'가 50번, '식품'이 30번 나타났다면, 각각 100, 50, 30으로 인코딩하는 것이죠.

이 방법의 아이디어는 빈도 자체가 유의미한 정보라는 것입니다. 자주 등장하는 카테고리는 그만큼 중요하거나 인기 있는 경우가 많습니다.

쇼핑몰에서 '전자기기' 카테고리가 가장 많이 팔린다면, 그 빈도 정보 자체가 예측에 도움이 될 수 있습니다. 위 코드를 보면 두 가지 방식을 사용했습니다.

첫째는 출현 횟수를 그대로 사용하는 방법입니다. value_counts()로 각 카테고리의 개수를 세고, map()으로 매핑합니다.

둘째는 비율로 정규화하는 방법입니다. normalize=True 옵션을 주면 0과 1 사이의 비율 값을 얻을 수 있습니다.

빈도 인코딩의 가장 큰 장점은 타겟 누수가 없다는 것입니다. 타겟 변수를 전혀 사용하지 않기 때문에 과적합 위험이 낮습니다.

또한 구현이 매우 간단하고 이해하기 쉽습니다. 하지만 단점도 있습니다.

빈도가 같은 카테고리들은 같은 값으로 인코딩됩니다. 만약 '의류'와 '식품'이 모두 50번씩 등장한다면, 둘은 구분이 되지 않습니다.

이런 경우 다른 인코딩 방법과 조합하여 사용하는 것이 좋습니다. 실무에서 빈도 인코딩은 특히 희귀 카테고리 탐지에 유용합니다.

인코딩 값이 아주 작은 데이터는 드물게 나타나는 케이스이므로, 이상치일 가능성이 있습니다. 이 정보를 모델이 활용할 수 있게 되는 것이죠.

김개발 씨가 물었습니다. "빈도 인코딩이랑 타겟 인코딩을 같이 쓰면 안 되나요?" 박시니어 씨가 고개를 끄덕였습니다.

"물론이죠! 오히려 여러 인코딩을 조합하면 모델이 더 다양한 관점에서 데이터를 볼 수 있어요.

이것을 피처 엔지니어링이라고 하죠."

실전 팁

💡 - 빈도 인코딩과 타겟 인코딩을 함께 사용하면 서로 보완적인 정보를 제공합니다

  • 테스트 데이터에 학습 데이터에 없던 카테고리가 나타나면 0 또는 전체 평균으로 대체하세요

6. 순서형 인코딩

김개발 씨가 새로운 데이터를 받았습니다. 고객 등급 컬럼에 '브론즈', '실버', '골드', '플래티넘' 값이 들어있습니다.

분명 순서가 있는 데이터인데, 그냥 레이블 인코딩을 해도 될까요? 아니면 원-핫 인코딩을 해야 할까요?

이런 순서형 변수에는 특별한 처리가 필요합니다.

순서형 인코딩은 카테고리 간에 명확한 순서가 있을 때 그 순서를 반영하여 숫자로 변환하는 방법입니다. 브론즈를 1, 실버를 2, 골드를 3으로 인코딩하면 원래의 등급 관계가 숫자에도 그대로 반영됩니다.

레이블 인코딩과 비슷하지만, 순서를 명시적으로 지정한다는 점이 다릅니다.

다음 코드를 살펴봅시다.

import pandas as pd
from sklearn.preprocessing import OrdinalEncoder

# 고객 등급 데이터
df = pd.DataFrame({
    'customer_id': [1, 2, 3, 4, 5],
    'membership': ['실버', '골드', '브론즈', '플래티넘', '실버'],
    'satisfaction': ['불만족', '보통', '만족', '매우만족', '보통']
})

# 순서를 명시적으로 지정
membership_order = ['브론즈', '실버', '골드', '플래티넘']
satisfaction_order = ['불만족', '보통', '만족', '매우만족']

# OrdinalEncoder 사용
encoder = OrdinalEncoder(categories=[membership_order, satisfaction_order])
df[['membership_encoded', 'satisfaction_encoded']] = encoder.fit_transform(
    df[['membership', 'satisfaction']]
)

print("순서형 인코딩 결과:")
print(df)
print(f"\n등급 순서: {membership_order}")
print(f"만족도 순서: {satisfaction_order}")

박시니어 씨가 질문했습니다. "김개발 씨, '골드'가 '실버'보다 높은 등급이라는 건 알죠?" 김개발 씨가 당연하다는 듯 고개를 끄덕였습니다.

"그렇다면 이 순서 정보를 모델에게도 알려줘야 하지 않을까요?" 순서형 변수는 범주형 변수의 특수한 경우입니다. 카테고리 간에 명확한 선후 관계가 존재합니다.

등급(브론즈 < 실버 < 골드), 학년(1학년 < 2학년 < 3학년), 만족도(불만족 < 보통 < 만족) 같은 데이터가 이에 해당합니다. 일반 레이블 인코딩을 사용하면 알파벳 순으로 숫자가 부여됩니다.

그러면 '골드'가 0, '브론즈'가 1이 될 수도 있습니다. 원래의 순서 관계가 완전히 뒤바뀌는 것이죠.

이것은 모델에게 잘못된 정보를 주는 것입니다. 순서형 인코딩은 이 문제를 해결합니다.

개발자가 직접 순서를 지정하고, 그 순서대로 숫자를 부여합니다. sklearn의 OrdinalEncoder를 사용할 때 categories 파라미터에 순서가 있는 리스트를 전달하면 됩니다.

위 코드에서 membership_order를 보면 ['브론즈', '실버', '골드', '플래티넘'] 순으로 지정했습니다. 이렇게 하면 브론즈는 0, 실버는 1, 골드는 2, 플래티넘은 3으로 인코딩됩니다.

원래의 등급 순서가 숫자에도 정확히 반영되는 것이죠. 순서형 인코딩의 장점은 순서 정보를 활용할 수 있다는 것입니다.

모델은 플래티넘(3)이 브론즈(0)보다 크다는 것을 알게 되고, 이 정보를 예측에 활용할 수 있습니다. 예를 들어 "등급이 높을수록 구매율이 높다"는 패턴을 학습할 수 있죠.

반면 원-핫 인코딩을 사용하면 이 순서 정보가 사라집니다. 골드와 브론즈가 단지 "다른 카테고리"일 뿐, 어느 쪽이 더 높은 등급인지 모델은 알 수 없습니다.

김개발 씨가 물었습니다. "그러면 순서가 있는 변수는 무조건 순서형 인코딩을 해야 하나요?" 박시니어 씨가 답했습니다.

"대부분의 경우 그렇죠. 하지만 때로는 원-핫 인코딩이 더 좋을 수도 있어요.

예를 들어 각 등급별로 완전히 다른 특성이 있다면요. 결국은 실험을 통해 더 좋은 성능을 내는 방법을 선택하면 됩니다."

실전 팁

💡 - 순서형 변수는 반드시 순서를 명시적으로 지정하세요. 알파벳 순이 아닌 비즈니스 로직에 맞는 순서를 사용합니다

  • 등급 간 간격이 동일하지 않다면(브론즈-실버 차이와 골드-플래티넘 차이가 다르다면) 커스텀 값을 직접 지정하는 것도 방법입니다

7. 이진 인코딩

김개발 씨는 국가 코드를 처리해야 하는데, 카테고리가 무려 100개가 넘습니다. 원-핫 인코딩을 하면 100개 이상의 컬럼이 생기고, 레이블 인코딩은 순서 편향이 걱정됩니다.

"중간 지점이 없을까요?" 박시니어 씨가 이진 인코딩을 소개해주었습니다.

이진 인코딩은 카테고리를 먼저 정수로 변환한 후, 그 정수를 이진수로 표현하는 방법입니다. 마치 컴퓨터가 숫자를 0과 1로 저장하는 것처럼, 32개의 카테고리를 단 5개의 컬럼으로 표현할 수 있습니다.

원-핫 인코딩보다 차원이 적고, 레이블 인코딩보다 순서 편향이 적은 절충안입니다.

다음 코드를 살펴봅시다.

import pandas as pd
import numpy as np

# 샘플 데이터 (8개 카테고리)
df = pd.DataFrame({
    'country': ['한국', '미국', '일본', '중국', '독일', '프랑스', '영국', '호주']
})

# 수동 이진 인코딩 구현
# 1. 먼저 정수로 변환 (0부터 시작)
country_to_int = {country: i for i, country in enumerate(df['country'].unique())}
df['country_int'] = df['country'].map(country_to_int)

# 2. 정수를 이진수로 변환 (3비트면 8개 표현 가능: 2^3 = 8)
n_bits = int(np.ceil(np.log2(len(country_to_int))))
for i in range(n_bits):
    df[f'country_bit_{i}'] = (df['country_int'] >> i) & 1

print(f"카테고리 수: {len(country_to_int)}, 필요한 비트 수: {n_bits}")
print(f"원-핫 인코딩: {len(country_to_int)}개 컬럼 vs 이진 인코딩: {n_bits}개 컬럼")
print("\n이진 인코딩 결과:")
print(df)

박시니어 씨가 화이트보드에 숫자를 쓰기 시작했습니다. "컴퓨터가 숫자를 어떻게 저장하는지 알아요?" 김개발 씨가 대답했습니다.

"0과 1, 이진수로요." "맞아요! 그 원리를 인코딩에 적용한 게 바로 이진 인코딩이에요." 이진 인코딩의 핵심 아이디어는 간단합니다.

8개의 카테고리를 원-핫 인코딩하면 8개의 컬럼이 필요합니다. 하지만 이진수를 사용하면?

000, 001, 010, 011, 100, 101, 110, 111로 3개의 컬럼만으로 8가지를 표현할 수 있습니다. 수학적으로 n개의 카테고리를 이진 인코딩하면 log2(n)개의 컬럼이 필요합니다.

100개의 카테고리라면 원-핫은 100개 컬럼, 이진 인코딩은 단 7개 컬럼(2^7 = 128 > 100)으로 충분합니다. 차원이 로그 스케일로 줄어드는 것이죠.

위 코드를 단계별로 살펴보겠습니다. 먼저 각 국가를 0부터 시작하는 정수로 매핑합니다.

한국은 0, 미국은 1, 일본은 2... 이렇게요.

그 다음 각 정수를 이진수로 변환합니다. 비트 연산자(>>와 &)를 사용하여 각 자릿수의 값을 추출합니다.

결과를 보면 '한국'(0)은 [0, 0, 0], '미국'(1)은 [1, 0, 0], '일본'(2)은 [0, 1, 0]으로 인코딩됩니다. 각 비트 컬럼이 마치 특성을 나타내는 것처럼 작동합니다.

이진 인코딩의 장점은 차원 효율성입니다. 카테고리가 많아도 컬럼 수가 크게 늘어나지 않습니다.

원-핫 인코딩의 메모리 문제를 해결하면서도, 레이블 인코딩처럼 단일 숫자로 압축하지 않아 순서 편향이 적습니다. 하지만 완벽한 방법은 아닙니다.

이진 인코딩된 각 비트 컬럼이 실제로 의미 있는 정보를 담고 있는지는 보장되지 않습니다. 같은 비트를 공유하는 카테고리들(예: bit_0이 1인 모든 국가)이 실제로 비슷한 특성을 가지는 건 아니니까요.

실무에서는 category_encoders 라이브러리의 BinaryEncoder를 사용하면 간편합니다. 이 라이브러리는 이진 인코딩뿐 아니라 다양한 인코딩 방법을 제공합니다.

김개발 씨가 정리했습니다. "카테고리가 많을 때 원-핫과 레이블의 중간 지점으로 이진 인코딩을 고려해볼 수 있군요!"

실전 팁

💡 - category_encoders 라이브러리의 BinaryEncoder를 사용하면 더 편리하게 이진 인코딩을 적용할 수 있습니다

  • 해싱 인코딩(HashingEncoder)도 비슷한 목적으로 사용되며, 새로운 카테고리에 더 유연합니다

8. 인코딩 방법 선택 가이드

김개발 씨는 지금까지 배운 인코딩 방법들을 정리하며 생각에 잠겼습니다. 레이블, 원-핫, 타겟, 빈도, 순서형, 이진...

도대체 언제 어떤 방법을 써야 할까요? 박시니어 씨가 실무에서 사용하는 의사결정 가이드를 알려주었습니다.

인코딩 방법 선택은 변수의 특성, 카테고리 수, 사용할 모델에 따라 달라집니다. 순서가 있으면 순서형 인코딩, 카테고리가 적으면 원-핫, 많으면 타겟 인코딩이나 이진 인코딩을 고려합니다.

트리 기반 모델은 레이블 인코딩도 잘 작동합니다.

다음 코드를 살펴봅시다.

def select_encoding_method(n_categories, has_order, model_type, has_target=True):
    """
    인코딩 방법 선택 가이드

    Parameters:
    - n_categories: 고유 카테고리 수
    - has_order: 순서가 있는 변수인지 (True/False)
    - model_type: 'tree' (트리 기반) 또는 'linear' (선형 모델/신경망)
    - has_target: 타겟 변수가 있는지 (지도학습 여부)
    """
    recommendations = []

    # 순서형 변수인 경우
    if has_order:
        recommendations.append("순서형 인코딩 (OrdinalEncoder)")

    # 트리 기반 모델
    elif model_type == 'tree':
        recommendations.append("레이블 인코딩 (LabelEncoder)")
        if has_target and n_categories > 10:
            recommendations.append("타겟 인코딩 (+ 스무딩)")

    # 선형 모델 또는 신경망
    else:
        if n_categories <= 10:
            recommendations.append("원-핫 인코딩 (OneHotEncoder)")
        elif n_categories <= 100:
            recommendations.append("이진 인코딩 (BinaryEncoder)")
            recommendations.append("타겟 인코딩 (+ Cross-Validation)")
        else:
            recommendations.append("타겟 인코딩 (필수: 스무딩 + CV)")
            recommendations.append("빈도 인코딩")

    return recommendations

# 사용 예시
print("케이스 1 - 지역(5개), 선형회귀:")
print(select_encoding_method(5, False, 'linear'))

print("\n케이스 2 - 상품카테고리(50개), XGBoost:")
print(select_encoding_method(50, False, 'tree'))

print("\n케이스 3 - 고객등급(4개), 순서있음:")
print(select_encoding_method(4, True, 'linear'))

박시니어 씨가 화이트보드에 의사결정 트리를 그리기 시작했습니다. "인코딩 방법을 선택하는 건 마치 요리에 맞는 양념을 고르는 것과 같아요.

상황에 따라 달라지죠." 첫 번째로 확인할 것은 순서의 유무입니다. 등급, 학년, 만족도처럼 카테고리 간에 명확한 순서가 있다면 순서형 인코딩을 사용합니다.

이렇게 하면 순서 정보를 모델이 활용할 수 있습니다. 만약 순서가 없는 명목형 변수라면 다음 질문으로 넘어갑니다.

두 번째로 확인할 것은 사용할 모델의 종류입니다. 랜덤포레스트, XGBoost, LightGBM 같은 트리 기반 모델은 데이터를 분기점으로 나누어 학습합니다.

숫자의 절대적인 크기보다 "어떤 값을 기준으로 나눌 것인가"가 중요하죠. 그래서 레이블 인코딩을 사용해도 순서 편향의 영향이 적습니다.

반면 선형회귀, 로지스틱회귀, 신경망 같은 모델은 숫자를 그대로 곱하고 더합니다. 이런 모델에 레이블 인코딩을 사용하면 순서 편향이 성능에 직접적인 영향을 미칩니다.

따라서 원-핫 인코딩이나 다른 방법을 고려해야 합니다. 세 번째로 확인할 것은 카테고리의 수입니다.

카테고리가 10개 이하로 적다면 원-핫 인코딩이 가장 안전합니다. 순서 편향도 없고, 컬럼 수도 감당할 만하니까요.

카테고리가 10~100개 정도면 이진 인코딩을 고려해볼 수 있습니다. 또는 타겟 변수가 있다면 타겟 인코딩을 스무딩과 함께 사용합니다.

카테고리가 100개를 넘어가면 원-핫 인코딩은 사실상 불가능합니다. 타겟 인코딩, 빈도 인코딩, 또는 둘의 조합을 사용합니다.

반드시 Cross-Validation으로 타겟 누수를 방지해야 합니다. 위 코드의 함수는 이 의사결정 과정을 자동화한 것입니다.

실제 프로젝트에서 참고용으로 사용할 수 있습니다. 하지만 박시니어 씨가 강조했습니다.

"이건 어디까지나 가이드라인이에요. 실제로는 여러 방법을 시도해보고 교차 검증으로 가장 좋은 성능을 내는 방법을 선택해야 해요." 김개발 씨가 고개를 끄덕였습니다.

결국 데이터 과학은 실험의 연속이었습니다.

실전 팁

💡 - 하나의 인코딩 방법만 고집하지 마세요. 여러 방법을 시도하고 교차 검증으로 비교하는 것이 가장 좋습니다

  • 같은 데이터셋에서 여러 인코딩 방법을 조합하여 새로운 피처를 만들 수도 있습니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#인코딩#머신러닝#전처리#피처엔지니어링#Data Science

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.