데이터 분석에서 범주형 변수를 다루는 것은 중요한 과제입니다. 이 글에서는 범주형 변수를 효과적으로 숫자로 변환하고, 모델 학습에 적용하는 다양한 방법을 소개합니다. 더미화와 연속형 치환의 장단점을 비교하며, 실제 데이터에 적용하는 예시를 통해 이해를 돕습니다.
주요내용
- 데이터 분석에서 범주형 변수의 올바른 처리는 모델의 성능에 큰 영향을 미칩니다. 📊
- 더미화와 연속형 변수로의 치환은 범주형 변수를 다루는 두 가지 주요 방법입니다. 🔑
- 더미화는 변수를 0과 1의 이진값으로 변환하는 반면, 연속형 치환은 변수를 실제 레이블과의 관계를 반영하는 연속값으로 변환합니다. 🔄
- 각 방법은 상황에 따라 장단점이 있으며, 데이터의 특성과 모델의 요구사항에 맞게 적절히 선택해야 합니다. ✅
범주형 변수 문제
- 데이터에
범주형 변수
가 포함되어 있어, 대다수의 지도학습 모델이 학습되지 않거나,비정상적으로
학습되는 문제
예시)
-
1) str타입의 범주형 변수가 포함되면 대다수의 지도 학습 모델 자체가 학습이 되지 않는다.
-
2) int혹은 float 타입의 범주형 변수는 (타이타닉의 Pclass) 모델 학습은 되지만 비정상적으로 학습되는 것이 대부분이다.
-
적절한 모델학습을 위해서
범주형 변수는 반드시 숫자로 변환
되어야 한다. -
하지만 그 변환을 분석가가 임의로 설정하는 것은 부적절하다.
예시)
1
2
3
- 종교 변수가 있을 때, 천주교 =1, 기독교=2, 불교=3 이라고 하는 경우
- (가정) 불교는 천주교의 3배인 것이 아니지만, 숫자로 임의 변경이 되어 모델이 잘못된 인식을 할 수 있다.
특히 코드화된 범주형 변수
는 적절한 숫자로 변환을 해줘야 한다
범주형 변수 판별 방법
-
범주형 변수는
상태 공간의 크기가 유한한
변수를 의미합니다. -
반드시 도메인 지식이나, 변수의 상태공간을 바탕으로 판별해야 합니다.
-
특히,
int 혹은 float 타입의 변수는 무조건 연속형 변수다
라는 고정관념에 주의해야 합니다.
e.g)
-
월(month)는 숫자이지만, 범주형 변수입니다.
-
타이타닉의 Pclass는 unique() 값이 1,2,3으로 범주형 변수입니다.
관련 문법 : Series.unique() with len()
-
Series에 포함된 고유한 값을 ndarray 형태로 반환해주는 함수
-
주로, 상태 공간을 확인하는데 사용한다.
범주형 변수 변환 방법을 선택하는 기준
-
범주형 변수의 크기를 판별했을 때
-
상태 공간의 크기가 작다면 => Dummy화(방법1)
-
상태 공간의 크기가 크다면 => 연속형 변수로 치환(방법2)
-
이 코드는 os
와 pandas
라이브러리를 임포트하고, Python에서 발생할 수 있는 불필요한 경고 메시지를 필터링하여 표시하지 않도록 설정합니다. 또한, 현재 작업 중인 디렉토리의 경로를 얻어와서 이를 파일 로드 경로로 설정합니다. 이를 통해 사용자는 현재 작업 중인 디렉토리 내의 파일을 쉽게 접근하고 사용할 수 있습니다.
1
2
3
4
5
6
7
8
9
import os
import pandas as pd
# 불필요한 경고 표시를 생략합니다.
import warnings
warnings.filterwarnings(action = 'ignore')
a=%pwd # 현재 작업 경로를 변수 a에 할당합니다.
os.chdir(a) # 현재 작업 경로로 파일 로드 경로를 설정합니다.
데이터 로드
- 중고차 품질을 판단하는 실습용 data
이 함수는 pandas
라이브러리의 read_csv
함수를 사용하여 "car-good.csv"
파일을 읽고, 결과를 df
변수에 저장합니다. pandas
는 데이터 분석과 조작을 위한 고수준의 Python 라이브러리로, CSV 파일을 읽는 것은 데이터 분석 작업에서 흔히 수행되는 초기 단계입니다.
1
2
# car-good.csv 파일을 pandas 라이브러리를 사용하여 읽어옵니다.
df = pd.read_csv("car-good.csv")
df.head()
함수는 데이터프레임에서 처음 다섯 행을 반환합니다. 이 함수는 데이터의 구조를 빠르게 확인할 때 유용하며, 인자를 전달하여 반환할 행의 수를 조정할 수도 있습니다.
1
df.head() # 데이터프레임의 처음 다섯 행을 반환합니다.
본 코드는 데이터프레임 df
에서 특징(features)과 라벨(labels)을 분리하는 과정을 보여줍니다. df.drop('Class', axis=1)
을 사용하여 ‘Class’ 열을 제외한 모든 열을 X
변수에 할당합니다. 이는 특징 집합을 나타냅니다. df['Class']
를 사용하여 ‘Class’ 열만을 Y
변수에 할당함으로써 라벨 집합을 구성합니다.
1
2
3
# 특징과 라벨 분리
X = df.drop('Class', axis = 1) # 'Class' 열을 제외한 모든 열을 X에 할당
Y = df['Class'] # 'Class' 열만 Y에 할당
train_test_split
함수는 데이터를 학습용과 평가용으로 분리합니다. 이 함수는 sklearn.model_selection
모듈에 포함되어 있으며, 입력 데이터(X
)와 타겟 데이터(Y
)를 받아 학습 데이터(Train_X
, Train_Y
)와 평가 데이터(Test_X
, Test_Y
)로 나눕니다.
1
2
3
# 학습 데이터와 평가 데이터 분리
from sklearn.model_selection import train_test_split
Train_X, Test_X, Train_Y, Test_Y = train_test_split(X, Y)
Train_Y.value_counts()
함수는 Train_Y
데이터셋 내의 레이블 값들의 분포를 확인하는 데 사용됩니다. 이 함수는 각 레이블 값이 데이터셋에 몇 번 등장하는지를 나타내는 카운트를 반환합니다.
1
2
# label 데이터 개수 확인
Train_Y.value_counts()
이 함수는 Train_Y
와 Test_Y
데이터셋에서 문자열 레이블을 숫자로 치환합니다. replace
메소드를 사용하여 "negative"
는 -1
로, "positive"
는 1
로 변환합니다. inplace=True
파라미터는 변환된 데이터를 원본 데이터셋에 바로 적용함을 의미합니다.
1
2
3
4
# 문자 label을 숫자로 치환
# inplace = True 로 바로 원본데이터에 적용
Train_Y.replace({"negative":-1, "positive":1}, inplace = True)
Test_Y.replace({"negative":-1, "positive":1}, inplace = True)
본 코드는 데이터 프레임의 상위 5개 행을 출력하며, Buying
, Maint
, Lug_boot
, safety
변수들을 범주형 변수로 간주하고 있습니다. 추가적으로, 이 변수들이 단순한 범주형보다는 순서가 있는 서열형 변수임을 지적합니다.
1
2
3
Train_X.head()
# Buying, Maint, Lug_boot, safety 변수는 범주형 변수로 간주됩니다.
# 엄밀히 말하면, 이들은 서열형 변수입니다.
unique함수로 상태공간 크기 확인
상태공간의 크기를 확인하는 것은 데이터 분석에서 중요한 단계입니다. 이를 위해 unique
함수를 사용할 수 있습니다. 예를 들어, pandas
라이브러리를 사용하여 데이터프레임의 특정 열에서 고유한 값들을 찾아내고, 그 개수를 세어 상태공간의 크기를 알아낼 수 있습니다.
이 함수는 Train_X
데이터프레임의 각 열에 대해 고유한 값의 수를 출력하여, 해당 열이 범주형 변수인지 여부를 판단하는 데 도움을 줍니다. 고유한 값의 수가 전체 데이터 수에 비해 상대적으로 작으면, 그 열은 범주형 변수로 간주될 수 있습니다.
1
2
3
# 모든 변수가 범주형인지 확인하기 위한 코드 (고유한 데이터의 수가 원본 데이터 수에 비해 매우 작은지 확인)
for col in Train_X.columns:
print(col, len(Train_X[col].unique()))
범주형 변수 변환방법(1) - 더미화
-
가장 일반적인 범주형 변수를 변환하는 방법
-
범주형 변수가 특정 값을 취하는지 여부를 0 또는 1로 하는 dummy변수를 생성하는 방식
-
일반적으로 마지막 dummy는 제거한다.
-
경우의 수로 나머지 dummy들로 추론이 가능함
-
변수간의 상관성 제거를 할 수 있다.
-
계산량 감소를 기대할 수 있다.
-
-
예외적으로, Tree 기반 모델 (Decision Tree, Random Forest 등)에서는 ‘설명력’을 높이기 위해 마지막 dummy를 그대로 사용하기도 한다.
관련 문법 : feature_engine.encoding.OneHotCategoricalEncoder
-
더미화를 하기 위한 함수
-
sklearn의 preprocessing model을 통한 결측치 처리 과정과 비슷 (모델 생성 -> 학습 -> train/test 데이터에 transform)
-
Feature-engine은 활발한 개발 중이기 때문에 버전에 따라 import 함수명이 바뀔 수 있다.
파라미터
-
variables : 더미화 대상이 되는 범주형 변수의 이름 목록(
해당 변수는 반드시 str/object 타입이어야만 한다.
) -
drop_last : 한 범주 변수로 만든 더미 변수 중 마지막에 생성한 더미 변수를 제거할지 여부
-
top_categories : 한 범주 변수로 만든 더미 변수 개수를 설정 (빈도를 기준으로 자른다.)
- 한 범주 변수에서 가지는 상태 공간이 100이면, 그중 빈도가 높은 상위 10개만 사용하는 식
-
장점
- 사용이 간편하다.(크게 고려해야 할 사항 없음)
-
단점
-
범주 변수가 가지는 값이 많을 때, 더미화 하는 과정에서 추가되는 변수가 너무 많아질 수 있다.(차원의 저주 문제)
-
한 범주 변수가 가지는 값이 너무 많을 때(상태 공간이 크면), 더미화를 할 때 데이터가 너무 희소해질 수 있다.
-
희소한 데이터와 연속형 데이터가 같이 있으면 모델링을 하는 것이 상당히 어렵다.
-
FYI
-
pandas.get_dummies() 함수로도 동일하게 더미화를 할 수 있다.
-
하지만 train data에 포함된 이 방식은
범주형 변수를 처리한 방식
으로새로 들어온 데이터에 적용이 불가능
하기 때문에 실제적인 활용이 어렵다. (pandas 함수들은 인스턴스화되어 처리하는 방식이 아니기 때문)- 위의 그림을 기준으로 볼 때 좌측에 있는 데이터가 train data이고(종교가 다양), 새로 들어온 데이터는
천주교
만 있다고 할 때, 기존 train data로 생성한 get_dummies와 새로 들어온 데이터로 생성한 get_dummies는 다른 형태가 된다.
- 위의 그림을 기준으로 볼 때 좌측에 있는 데이터가 train data이고(종교가 다양), 새로 들어온 데이터는
전체 데이터가 모두 범주형이므로 더미화를 위해 전부 문자열 타입으로 변경해준다.
변수 Train_X
의 데이터 타입을 문자열(str
)로 변환합니다. 이는 데이터 전처리 과정에서 특정 데이터셋의 특성을 문자열 형태로 일관되게 관리하기 위해 사용됩니다.
1
Train_X = Train_X.astype(str) # Train_X 변수의 데이터 타입을 문자열로 변환합니다.
이 코드는 feature_engine
라이브러리의 OneHotEncoder
를 사용하여 범주형 변수를 원-핫 인코딩합니다. OneHotEncoder
는 모델을 생성하고, 훈련 데이터(Train_X
)의 모든 범주형 변수를 원-핫 인코딩된 형태로 변환하는 과정을 포함합니다. drop_last=True
옵션을 사용하여 각 범주형 변수에 대한 마지막 더미 변수를 제외시키는 방식으로 다중공선성 문제를 방지합니다. 이후, 생성된 인코더 모델을 사용하여 훈련 데이터(Train_X
)와 테스트 데이터(Test_X
)를 변환합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from feature_engine.encoding import OneHotEncoder as OHE
# 모델 생성(인스턴스화)
# 맨 마지막 더미 변수는 생략
# feature_engine의 variables는 리스트 형태이다
dummy_model = OHE(variables = Train_X.columns.tolist(), # 훈련 데이터의 변수명을 리스트로 변환
drop_last = True)
# 모델 학습
dummy_model.fit(Train_X)
# 변환
# 훈련 데이터와 테스트 데이터를 변환
d_Train_X = dummy_model.transform(Train_X)
d_Test_X = dummy_model.transform(Test_X)
이 코드는 더미 변수를 사용하여 K-최근접 이웃(KNN) 모델을 학습하고, 테스트 데이터에 대한 예측을 수행한 후, 실제값과 예측값 사이의 f1_score
를 계산합니다. 먼저, sklearn.neighbors
의 KNeighborsClassifier
를 KNN
으로 임포트하고, 학습 데이터(d_Train_X
, Train_Y
)를 사용하여 모델을 학습시킵니다. 그 다음, 테스트 데이터(d_Test_X
)에 대한 예측을 수행합니다. 마지막으로, sklearn.metrics
의 f1_score
함수를 사용하여 실제 테스트 레이블(Test_Y
)과 예측 레이블(pred_Y
) 사이의 F1 점수를 출력합니다.
1
2
3
4
5
6
7
8
9
# 더미화를 한 뒤의 모델 효과 테스트
# KNN 활용
from sklearn.neighbors import KNeighborsClassifier as KNN
model = KNN().fit(d_Train_X, Train_Y)
pred_Y = model.predict(d_Test_X)
# 실제값과 예측값의 차이 확인 (f1 score)
from sklearn.metrics import f1_score
print("f1_score of KNN with dummy variables :", f1_score(Test_Y, pred_Y))
-
아래의 연속형 변수와의 절대적 비교를 위해 KNN의 방법을 기본값으로 두었습니다.
-
더미화한 변수는 차원이 늘어나고, 희소해지는 경향이 있기 때문에 KNN 성향이 떨어질 가능성이 상대적으로 더 큽니다.
-
따라서 더미화한 변수로 반드시 KNN을 써야 하는 경우라면, 위의 model처럼 KNN()으로 기본값을 두기보다는, metric으로 jaccard 등을 사용하면 현재 상태보다는 조금 더 높은 성능을 기대해 볼 수 있을 것입니다.
범주형 변수 변환방법(2) - 연속형 변수로 치환
-
범주형 변수의 상태 공간 크기가 클 때, dummy화는
과하게 많은 변수를 추가
해서차원의 저주
문제로 이어지는 문제가 있다. -
label정보를 활용해 범주 변수를
연속형 변수로 치환
하면 기존 변수가 가지는정보가 일부 손실될 우려
가 있고,활용이 어렵다
는 단점이 있으나, 차원의 크기가 변하지 않으며더 효율적인 변수
로 치환할 수 있다는 장점이 있다.
정리하자면,
장점
-
지도학습의 경우 Y를 고려한 변환이기 때문에 모델 성능이 좋아지는 경우가 종종 있다.
-
차원을 변화시키기 않기 때문에
차원의 저주 문제
에 빠지지 않는다. -
범주형 변수를 연속형 변수로 변환시키기 때문에
더 많은 정보를 포함할 가능성
이 생긴다.
단점
-
기존 변수가 가지는 정보를 손실할 가능성이 있다.
-
아래의 그림에서 X 에 따른 Y평균값이 모두 다르지만, 만약 동일한 값이 존재한다면? (A도 200, B도 200이면?)
- A와 B 는 서로 다른 데이터이지만, 동일한 값이 입력되어 정보 손실이 발생한다.
- group by 를 통해 X에 따른 Y의 평균을 계산할 수 있다.
X에 따른 Y의 평균을 계산해야 하므로 train data의 X, Y값을 다시 이어 붙인다.
이 코드는 훈련 데이터 세트(Train_X
)와 타겟 변수(Train_Y
)를 결합하여 새로운 데이터프레임(Train_df
)을 생성합니다. 그 후, 모든 범주형 변수에 대해 각 변수의 값에 따른 타겟 변수(Class
)의 평균을 계산하고, 이를 사전(dict
) 형태로 저장합니다. 이 사전을 사용하여 훈련 데이터 세트의 해당 변수 값을 그 변수의 값에 따른 Class
의 평균 값으로 치환합니다. 마지막으로, 테스트 데이터 세트(Test_X
)에 대해서도 동일한 치환 작업을 수행합니다. 이 과정은 변수의 값을 그 변수가 타겟 변수에 미치는 평균적인 영향으로 대체하는 특징 엔지니어링 기법의 일종입니다.
1
2
3
4
5
6
7
8
9
Train_df = pd.concat([Train_X, Train_Y], axis = 1)
for col in Train_X.columns: # 전체 변수 중 범주 변수만 순회
# col에 따른 Class의 평균을 나타내는 사전 생성
temp_dict = Train_df.groupby(col)['Class'].mean().to_dict()
Train_df[col] = Train_df[col].replace(temp_dict) # 변수 치환
# 테스트 데이터도 같은 방식으로 치환
Test_X[col] = Test_X[col].astype(str).replace(temp_dict)
Train_df.head()
함수는 Train_df
데이터프레임의 상위 5개 행을 반환합니다. 이는 데이터의 구조를 빠르게 확인하기 위해 사용되며, 주석은 변수값들이 연속형으로 변경되었음을 나타냅니다.
1
2
# 변수값들이 연속형으로 변경되었다.
Train_df.head()
해당 변수들은 dummy화한 변수들에 비해 Class값과의 관련성이 더욱 명확하다.
위에서 dummy화 했던 변수의 값을 보면 아래와 같다.
-
이때, index 56의 값을 보면 Buying이 vhigh(매우 높음)이다. 그리고 연속형으로 변환한 결과를 보면 동일 index의 Class에 대한 Buying의 평균값이 -1이다.
-
그리고 Class는 1 또는 -1의 값 뿐이다.
-
따라서 “Buying이 vhigh이면 무조건 -1이다.”라고 추론할 수 있다.
-
이처럼 범주형 변수를 연속형 변수로 변환하면 레이블과 관련된 직접적인 정보를 포함하고 있음을 알 수 있다.
모델 학습을 위해 Train / Test 분할
본 코드는 데이터 프레임 Train_df
에서 특정 열('Class'
)을 기준으로 입력 데이터(Train_X
)와 타겟 데이터(Train_Y
)를 분리하는 과정을 보여줍니다. Train_X
는 'Class'
열을 제외한 모든 열을 포함하며, Train_Y
는 오직 'Class'
열만을 포함합니다. 이는 머신러닝 모델 학습에 있어 입력 변수와 타겟 변수를 분리하는 표준 절차를 따릅니다.
1
2
Train_X = Train_df.drop('Class', axis = 1) # 'Class' 열을 제외한 모든 데이터를 Train_X에 할당합니다.
Train_Y = Train_df['Class'] # 'Class' 열의 데이터만 Train_Y에 할당합니다.
이 코드는 KNN 모델을 사용하여 연속 변수를 포함한 데이터에 대해 학습을 수행하고, 테스트 데이터에 대한 예측을 진행합니다. 그 후, f1_score
함수를 사용하여 실제 테스트 레이블과 예측된 레이블 간의 F1 점수를 계산하고 출력합니다. 라벨을 고려한 전처리 방식이 더미화 방식보다 우수한 결과를 가져옴을 확인할 수 있으며, 이는 더미화를 적용했을 때의 F1 점수가 0임을 통해 강조됩니다.
1
2
3
4
5
6
# 모델을 학습시키고 테스트 데이터에 대한 예측을 수행
model = KNN().fit(Train_X, Train_Y)
pred_Y = model.predict(Test_X)
print("f1_score of KNN with continous variable :", f1_score(Test_Y, pred_Y))
# 라벨을 고려한 전처리를 통해 더미화보다 좋은 결과를 얻을 수 있음을 확인 (더미화한 f1 점수는 0)
댓글남기기