8  연속형 데이터 범주화

Keywords

python, 전처리, 통계, 가설검정, 기계학습, 회귀, 분류, 군집, 모델 학습, 모델 평가

연속형 데이터 범주화(Discretization 또는 Binning)는 연속형 변수를 구간별로 나누어 범주형 변수로 변환하는 과정이다. 이 과정은 정보 손실을 수반하지만, 모델의 해석력을 높이고 비선형 관계를 단순화하며 노이즈를 감소시키는 효과가 있다. 특히 도메인 전문가와의 커뮤니케이션이나 규칙 기반 의사결정 시스템에서 유용하다. 이 장에서는 등간격 구간화, 등빈도 구간화, 사용자 정의 구간화, k-means 기반 구간화, Decision Tree 기반 구간화 등 다양한 범주화 기법을 학습한다.

예제: 데이터 로드

import pandas as pd
import seaborn as sns
import numpy as np

# 데이터 로드
df = sns.load_dataset("penguins")

# 연속형 변수 확인
print("연속형 변수 기초 통계:")
print(df[["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g"]].describe())
연속형 변수 기초 통계:
       bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g
count      342.000000     342.000000         342.000000   342.000000
mean        43.921930      17.151170         200.915205  4201.754386
std          5.459584       1.974793          14.061714   801.954536
min         32.100000      13.100000         172.000000  2700.000000
25%         39.225000      15.600000         190.000000  3550.000000
50%         44.450000      17.300000         197.000000  4050.000000
75%         48.500000      18.700000         213.000000  4750.000000
max         59.600000      21.500000         231.000000  6300.000000

8.1 범주화의 필요성과 효과

연속형 데이터를 범주화하면 다음과 같은 효과를 얻을 수 있다.

범주화의 주요 효과

효과 설명 예시
해석력 증가 숫자 대신 의미 있는 구간명 사용 “40.5mm” → “보통 길이”
노이즈 감소 작은 변동을 무시하고 큰 패턴에 집중 측정 오차 완화
비선형 관계 단순화 복잡한 관계를 구간별 규칙으로 표현 나이-위험도 관계
이상치 영향 완화 극단값을 구간 경계로 제한 최상위 구간으로 포함
규칙 추출 용이 의사결정 규칙 명확화 “A구간이면 승인”

다만, 정보 손실이 불가피하므로 데이터가 충분히 많고 모델의 예측 성능보다 해석력이 중요한 경우에 사용하는 것이 적절하다.

8.2 등간격 구간화 (Equal-width Binning)

등간격 구간화는 전체 값의 범위를 동일한 간격으로 k개의 구간으로 나누는 방법이다. 구현이 간단하고 직관적이지만, 데이터의 분포를 고려하지 않으므로 일부 구간에 데이터가 몰리거나 거의 없을 수 있다.

구간 너비 계산 공식

\[ \text{bin width} = \frac{\max(x) - \min(x)}{k} \]

여기서 k는 구간의 개수이다.

예제: 등간격 구간화

# 4개의 등간격 구간으로 분할
df["bill_length_bin"] = pd.cut(df["bill_length_mm"], bins=4)

print("등간격 구간화 결과:")
print(df["bill_length_bin"].value_counts().sort_index())
print("\n구간 정보:")
print(df["bill_length_bin"].unique())
등간격 구간화 결과:
bill_length_bin
(32.072, 38.975]     79
(38.975, 45.85]     124
(45.85, 52.725]     129
(52.725, 59.6]       10
Name: count, dtype: int64

구간 정보:
[(38.975, 45.85], NaN, (32.072, 38.975], (45.85, 52.725], (52.725, 59.6]]
Categories (4, interval[float64, right]): [(32.072, 38.975] < (38.975, 45.85] < (45.85, 52.725] < (52.725, 59.6]]

8.2.1 범주 이름 지정

숫자 구간 대신 의미 있는 이름을 부여하면 해석력이 더욱 향상된다.

예제: 구간에 라벨 지정

# 구간에 의미 있는 이름 부여
df["bill_length_bin"] = pd.cut(
    df["bill_length_mm"],
    bins=4,
    labels=["짧음", "보통", "김", "매우 김"]
)

print("라벨이 지정된 구간화 결과:")
print(df["bill_length_bin"].value_counts())
라벨이 지정된 구간화 결과:
bill_length_bin
김       129
보통      124
짧음       79
매우 김     10
Name: count, dtype: int64

장단점

장점 단점
구현이 간단하고 직관적 이상치에 민감 (범위가 크게 늘어남)
구간 너비가 일정하여 해석 용이 데이터 밀도 불균형 발생 가능
계산 속도가 빠름 데이터 분포를 고려하지 않음

적용 상황

  • 데이터가 비교적 균등하게 분포된 경우
  • 구간 너비가 도메인상 의미가 있는 경우 (예: 10년 단위 연령 구간)
  • 빠른 프로토타이핑이 필요한 경우

8.3 등빈도 구간화 (Equal-frequency Binning, Quantile Binning)

등빈도 구간화는 각 구간에 동일한 개수의 데이터가 포함되도록 분할하는 방법이다. 데이터의 분포를 고려하여 구간을 나누므로, 등간격 구간화의 불균형 문제를 해결할 수 있다.

예제: 등빈도 구간화

# 4개의 등빈도 구간으로 분할 (사분위수)
df["bill_length_qbin"] = pd.qcut(
    df["bill_length_mm"],
    q=4
)

print("등빈도 구간화 결과:")
print(df["bill_length_qbin"].value_counts().sort_index())
print("\n각 구간의 데이터 개수:")
for bin_range in df["bill_length_qbin"].unique():
    count = (df["bill_length_qbin"] == bin_range).sum()
    print(f"{bin_range}: {count}개")
등빈도 구간화 결과:
bill_length_qbin
(32.099000000000004, 39.225]    86
(39.225, 44.45]                 85
(44.45, 48.5]                   87
(48.5, 59.6]                    84
Name: count, dtype: int64

각 구간의 데이터 개수:
(32.099000000000004, 39.225]: 86개
(39.225, 44.45]: 85개
nan: 0개
(44.45, 48.5]: 87개
(48.5, 59.6]: 84개

8.3.1 라벨 지정

사분위수 구간에 Q1, Q2, Q3, Q4와 같은 표준 라벨을 부여할 수 있다.

예제: 사분위수 라벨 지정

# 사분위수 라벨 지정
df["bill_length_qbin"] = pd.qcut(
    df["bill_length_mm"],
    q=4,
    labels=["Q1", "Q2", "Q3", "Q4"]
)

print("사분위수 구간 분포:")
print(df["bill_length_qbin"].value_counts())
사분위수 구간 분포:
bill_length_qbin
Q3    87
Q1    86
Q2    85
Q4    84
Name: count, dtype: int64

장단점

장점 단점
데이터 불균형 문제 해결 동일한 값이 많으면 에러 발생 가능
분포를 자동으로 반영 구간 너비가 일정하지 않아 해석이 복잡할 수 있음
이상치의 영향이 적음 도메인 지식과 무관하게 분할됨

적용 상황

  • 데이터가 편향되어 있는 경우
  • 각 구간에 충분한 샘플이 필요한 경우
  • 순위 기반 분석이 중요한 경우

주의사항

동일한 값이 많이 나타나면 qcut이 정확히 같은 개수로 나누지 못해 오류가 발생할 수 있다. 이 경우 duplicates='drop' 옵션을 사용하거나 구간 개수를 줄인다.

# 중복값이 많을 때 처리
try:
    df["bill_length_qbin_safe"] = pd.qcut(
        df["bill_length_mm"],
        q=10,
        duplicates='drop'
    )
except Exception as e:
    print(f"오류 발생: {e}")

8.4 사용자 정의 구간화

사용자 정의 구간화는 도메인 지식이나 비즈니스 규칙에 따라 직접 구간 경계를 설정하는 방법이다. 가장 해석력이 높지만, 주관이 개입되므로 기준에 대한 명확한 설명이 필요하다.

예제: 도메인 지식 기반 구간화

# 도메인 전문가가 정의한 구간 경계
bins = [30, 40, 45, 50, 60]
labels = ["매우 짧음", "짧음", "보통", "김"]

df["bill_length_custom"] = pd.cut(
    df["bill_length_mm"],
    bins=bins,
    labels=labels
)

print("사용자 정의 구간화 결과:")
print(df["bill_length_custom"].value_counts())

# 구간 통계
print("\n구간별 평균 체중:")
print(df.groupby("bill_length_custom")["body_mass_g"].mean())
사용자 정의 구간화 결과:
bill_length_custom
보통       113
매우 짧음    100
짧음        77
김         52
Name: count, dtype: int64

구간별 평균 체중:
bill_length_custom
매우 짧음    3558.000000
짧음       4114.935065
보통       4657.079646
김        4578.846154
Name: body_mass_g, dtype: float64

장단점

장점 단점
해석력이 가장 높음 주관이 개입될 수 있음
도메인 지식을 직접 반영 데이터 분포를 무시할 수 있음
비즈니스 규칙과 일치 구간 설정 근거 설명 필요

적용 상황

  • 명확한 도메인 기준이 있는 경우 (예: 의학적 기준, 법적 기준)
  • 비즈니스 규칙이 정해진 경우 (예: 신용등급, 고객 등급)
  • 기존 시스템과의 호환성이 필요한 경우

8.5 k-means 기반 구간화

k-means 기반 구간화는 값의 분포를 고려하여 군집 중심을 기준으로 구간을 나누는 방법이다. 비선형적인 데이터 구조를 반영할 수 있어 복잡한 분포에서도 효과적이다.

예제: k-means 기반 구간화

from sklearn.cluster import KMeans

# 결측치 제거
x = df[["bill_length_mm"]].dropna()

# k-means 군집화 (3개 구간)
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
clusters = kmeans.fit_predict(x)

# 원본 데이터프레임에 결과 저장
df.loc[x.index, "bill_length_kbin"] = clusters

print("k-means 기반 구간화 결과:")
print(df["bill_length_kbin"].value_counts().sort_index())

# 각 군집의 중심값
print("\n군집 중심값:")
for i, center in enumerate(kmeans.cluster_centers_):
    print(f"군집 {i}: {center[0]:.2f}mm")

# 군집별 통계
print("\n군집별 범위:")
for i in range(3):
    cluster_data = x[clusters == i]
    print(f"군집 {i}: {cluster_data['bill_length_mm'].min():.2f} ~ {cluster_data['bill_length_mm'].max():.2f}mm")
k-means 기반 구간화 결과:
bill_length_kbin
0.0    114
1.0     94
2.0    134
Name: count, dtype: int64

군집 중심값:
군집 0: 45.09mm
군집 1: 50.66mm
군집 2: 38.20mm

군집별 범위:
군집 0: 41.70 ~ 47.80mm
군집 1: 48.10 ~ 59.60mm
군집 2: 32.10 ~ 41.60mm

장단점

장점 단점
데이터 분포를 자동으로 반영 해석이 상대적으로 어려움
비선형 구조를 포착 가능 랜덤 초기화로 인한 변동성
이상치 영향 완화 구간 개수 결정 필요

적용 상황

  • 데이터의 자연스러운 군집이 예상되는 경우
  • 분포가 복잡하거나 다봉형(multi-modal)인 경우
  • 데이터 기반 자동화가 필요한 경우

8.6 Decision Tree 기반 구간화

Decision Tree 기반 구간화는 타겟 변수를 기준으로 정보이득을 최대화하는 구간을 찾는 지도학습 방법이다. 타겟과의 관계를 직접 반영하므로 예측 모델에 효과적이지만, 데이터 누수에 주의해야 한다.

예제: 타겟 기반 구간화

from sklearn.tree import DecisionTreeRegressor

# 결측치 제거
x = df[["bill_length_mm"]].dropna()
y = df.loc[x.index, "body_mass_g"]

# Decision Tree로 최적 구간 찾기
tree = DecisionTreeRegressor(
    max_leaf_nodes=4,  # 4개 구간
    random_state=42
)
tree.fit(x, y)

# 리프 노드 ID를 구간으로 사용
df.loc[x.index, "bill_length_treebin"] = tree.apply(x)

print("Decision Tree 기반 구간화 결과:")
print(df["bill_length_treebin"].value_counts().sort_index())

# 각 구간의 타겟 평균
print("\n구간별 평균 체중:")
print(df.groupby("bill_length_treebin")["body_mass_g"].mean().sort_index())

# 구간 경계 확인 (트리 분할 지점)
print("\n트리 분할 정보:")
print(f"사용된 특성: {tree.feature_importances_}")
Decision Tree 기반 구간화 결과:
bill_length_treebin
3.0     82
4.0     65
5.0     88
6.0    107
Name: count, dtype: int64

구간별 평균 체중:
bill_length_treebin
3.0    3480.792683
4.0    3901.923077
5.0    4406.534091
6.0    4767.990654
Name: body_mass_g, dtype: float64

트리 분할 정보:
사용된 특성: [1.]

데이터 누수 주의

Decision Tree 기반 구간화는 타겟 정보를 사용하므로, 반드시 학습 데이터로만 구간을 결정하고 테스트 데이터에 적용해야 한다.

예제: 데이터 누수 방지

from sklearn.model_selection import train_test_split

# 학습/테스트 분리
df_clean = df[["bill_length_mm", "body_mass_g"]].dropna()
X_train, X_test, y_train, y_test = train_test_split(
    df_clean[["bill_length_mm"]],
    df_clean["body_mass_g"],
    test_size=0.2,
    random_state=42
)

# 학습 데이터로만 트리 학습
tree_safe = DecisionTreeRegressor(max_leaf_nodes=4, random_state=42)
tree_safe.fit(X_train, y_train)

# 학습/테스트 데이터에 동일한 구간 적용
train_bins = tree_safe.apply(X_train)
test_bins = tree_safe.apply(X_test)

print("학습 데이터 구간 분포:")
print(pd.Series(train_bins).value_counts().sort_index())
print("\n테스트 데이터 구간 분포:")
print(pd.Series(test_bins).value_counts().sort_index())
학습 데이터 구간 분포:
3    66
4    50
5    68
6    89
Name: count, dtype: int64

테스트 데이터 구간 분포:
3    16
4    18
5    17
6    18
Name: count, dtype: int64

장단점

장점 단점
타겟 정보를 직접 반영 데이터 누수 위험 (주의 필요)
예측 성능 향상 가능 과적합 위험
비선형 관계 포착 해석이 복잡할 수 있음

적용 상황

  • 예측 모델의 성능이 중요한 경우
  • 타겟과의 비선형 관계가 예상되는 경우
  • 특성 공학(feature engineering)의 일환으로 사용

8.7 범주화 후 인코딩 연결

범주화된 변수는 여전히 범주형이므로, 모델 학습을 위해 인코딩이 필요하다.

예제: 범주화 후 One-Hot Encoding

# 범주화된 변수를 One-Hot Encoding
df_bin_encoded = pd.get_dummies(
    df,
    columns=["bill_length_bin"],
    drop_first=True
)

print("인코딩된 컬럼:")
print([col for col in df_bin_encoded.columns if "bill_length_bin" in col])
print("\n인코딩 결과 샘플:")
print(df_bin_encoded.filter(like='bill_length_bin').head())
인코딩된 컬럼:
['bill_length_bin_보통', 'bill_length_bin_김', 'bill_length_bin_매우 김']

인코딩 결과 샘플:
   bill_length_bin_보통  bill_length_bin_김  bill_length_bin_매우 김
0                True              False                 False
1                True              False                 False
2                True              False                 False
3               False              False                 False
4               False              False                 False

범주화와 인코딩을 조합하면 연속형 변수를 비선형적으로 활용하면서도 선형 모델에 적용할 수 있다.

8.8 범주화 vs 스케일링

범주화와 스케일링은 서로 다른 목적과 효과를 가진 전처리 방법이다.

범주화와 스케일링 비교

항목 스케일링 범주화
목적 변수 간 크기 통일 연속형을 범주형으로 변환
정보 손실 없음 (변환만) 있음 (구간화로 인한 손실)
해석력 낮음 (숫자 그대로) 높음 (의미 있는 구간명)
모델 안정성 보통 높음 (노이즈 감소)
주 용도 거리 기반 모델, 경사하강법 규칙 기반 의사결정, 해석
적합 모델 선형, KNN, SVM, 신경망 트리, 규칙 기반 시스템

선택 기준

  • 예측 성능 우선: 스케일링 사용 (정보 손실 없음)
  • 해석력 우선: 범주화 사용 (도메인 전문가 커뮤니케이션)
  • 노이즈가 많은 경우: 범주화 고려 (노이즈 감소 효과)
  • 데이터 충분: 스케일링 사용 (범주화는 정보 손실)
  • 규칙 추출 필요: 범주화 사용 (명확한 의사결정 규칙)

8.9 요약

이 장에서는 연속형 데이터를 범주형으로 변환하는 다양한 범주화 기법을 학습했다. 주요 내용은 다음과 같다.

범주화 방법 비교

방법 원리 장점 단점 적용 상황
등간격 동일한 구간 너비 간단, 직관적 데이터 불균형 균등 분포, 도메인 기준 너비
등빈도 동일한 데이터 개수 분포 반영, 균형 너비 불균등, 중복값 문제 편향 분포, 순위 기반 분석
사용자 정의 도메인 지식 기반 해석력 최고, 비즈니스 반영 주관 개입, 근거 필요 명확한 기준 존재
k-means 군집 중심 기반 비선형 구조 반영 해석 어려움, 변동성 복잡한 분포, 자동화
Decision Tree 타겟 정보이득 기반 타겟 관계 반영, 성능 향상 데이터 누수 위험, 과적합 예측 성능 중요, 비선형 관계

범주화 적용 상황

상황 범주화 권장 여부 이유
모델 해석이 중요할 때 의미 있는 구간명으로 설명 가능
비선형 관계 단순화 복잡한 관계를 구간별 규칙으로 표현
데이터 노이즈가 큼 작은 변동 무시, 큰 패턴 포착
데이터가 충분히 많음 정보 손실이 아까움, 스케일링 선호
딥러닝 사용 연속형 그대로 사용이 더 효과적
규칙 추출 필요 명확한 의사결정 규칙 생성

범주화 의사결정 프로세스

  1. EDA 수행: 데이터 분포, 타겟과의 관계 파악
  2. 목적 확인: 예측 성능 vs 해석력 중 무엇이 우선인지 결정
  3. 방법 선택: 상황에 맞는 범주화 방법 선택
  4. 검증: 범주화 전후 모델 성능 및 해석력 비교
  5. 문서화: 구간 설정 근거와 결과 명확히 기록

범주화는 정보 손실을 감수하고 해석력을 얻는 선택이다. EDA 결과를 바탕으로 신중하게 결정해야 하며, 특히 도메인 전문가와의 협업이 중요한 경우 사용자 정의 구간화를 고려해야 한다. 다음 단계로는 범주화된 변수를 인코딩하여 모델에 적용하거나, 스케일링을 통해 연속형 변수를 그대로 활용하는 방법을 선택할 수 있다.