[Intermediate Machine Learning] 범주형 변수(Categorical Variables)

서동진·2022년 7월 9일
0

Kaggle Courses

목록 보기
8/11

이글은 Categorical Variable을 번역한 글입니다.

이번에는 범주형 변수(Categorical Variables)가 무었인지, 어떻게 범주형 변수를 다뤄야 하는지를 배우게 될 것입니다.

Introduction

범주형 변수(categorical variable)은 변수가 가질수 있는 값이 정해져있습니다.

  • 얼마나 자주 아침을 먹는지에 대한 "먹지않음(Never)", "가끔 먹음(Rarely)", "자주 먹음(Most day)", "매일 먹음(Every Day)" 이라는 선택지가 주어진 설문조사를 생각해봅시다. 이 경우에 대답은 주어진 범주안에서 대답 되어질것이기 때문에, 데이터는 범주형(categorical)입니다.
  • 만약 어떤 브랜드의 차를 소유하고 있습니까? 라는 설문조사에 답한다면, 대답은 아마 "KIA", "Ford", "HYUNDAI" 등 으로 대답할 것입니다. 이러한 경우도 데이터는 범주형(Categorical)입니다.

만약 당신이 이러한 변수들을 대부분에 machine learning 모델에 어떠한 처리 없이 그냥 넣어주게 된다면 에러를 마주하게 될 것입니다.

3가지 방법

1) 범주형 변수 제거하기(Drop Categorical Variables)

범주형 변수를 다루는 가장 쉬운 방법은 데이터셋에서 범주형 변수를 제거하는 것입니다. 이러한 방법은 해당 변수가 유용한 정보를 담지 않을 경우에만 효과가 있을 것입니다.

2) Ordianl Encoding

Ordinal encoding은 각각의 고유한 값에 다른 정수를 할당하는 것입니다.

이러한 방법은 범주의 순서를 가정합니다: "Never" (0) < "Rarely" (1) < "Most days" (2) < "Every day" (3).
위 예시에서 각 범주가 순서가 있다고 판단되기에 가정이 타당하다고 할 수 있습니다. 모든 범주형 변수가 값에서 순서가 명확한건 아니지만, 순서가 있는 범주형 변수를 ordinal variables라고 부릅니다. 트리 기반의 모델들(Decision tree, Random Forests)에서 ordinal encoding이 ordinal variables과 잘 작동할 것입니다.

3) One-Hot Encoding

One-hot encoding은 원래 데이터에서 나올 수 있는 각 값의 존재 유무를 나타내는 새로운 column을 만듭니다. 예시는 다음과 같습니다.

원래 데이터셋에서, "Color"는 3가지의 범주("Red", "Yellow", "Green")를 갖고있는 범주형 변수입니다. 각 범주마다 one-hot encoding된 column을 갖고 있고, 원래 데이터셋에서 각각의 row는 하나의 row를 표현하게 됩니다. 원래값이 "Red"일 때마다 "Red" Column에 1을 집어 넣습니다. 만약 원래값이 "Yellow"라면 "Yellow" Column에 1을 넣습니다. 이 과정을 모든 범주에 반복합니다.

ordinal encoding과는 달리 one-hot encoding은 범주의 순서를 가정하지 않습니다. 그러므로 이러한 방법은 범주형 데이터에 순서가 명확하게 보이지 않을 때 잘 작동할 것입니다.(즉 "Red"는 "Yellow"보다 크지도 작지도 않습니다.) 이렇게 명확한 순서가 없는 범주형 변수를 nominal varaibles라 부릅니다.

One-hot encoding은 일반적으로 범주의 수가 큰 경우에는 잘 작동하지 않습니다.(일반적으로 범주의 수가 15개 이상인 경우 사용하지 않는 것이 좋습니다.)

Example

이전의 예시와 마찬가지로 Melbourne Housing Dataset을 이용하여 연습해보겠습니다.

import pandas as pd
from sklearn.model_selection import train_test_split

# 데이터 불러오기
data = pd.read_csv('../input/melbourne-housing-snapshot/melb_data.csv')

# target값 분리하기 
y = data.Price
X = data.drop(['Price'], axis=1)

# 학습데이터와 검증 데이터로 데이터 나누기
X_train_full, X_valid_full, y_train, y_valid = train_test_split(X, y, train_size=0.8, test_size=0.2,
                                                                random_state=0)

# 결측치가 존재하는 columns 제거 
cols_with_missing = [col for col in X_train_full.columns if X_train_full[col].isnull().any()] 
X_train_full.drop(cols_with_missing, axis=1, inplace=True)
X_valid_full.drop(cols_with_missing, axis=1, inplace=True)

# "Cardinality"는 컬럼에 존재하는 uniuqe한 값의 수를 의미합니다.
# 상대적으로 낮은 Cardinality을 갖는 범주형 변수를 선택합니다.(편의성을 위함)
low_cardinality_cols = [cname for cname in X_train_full.columns if X_train_full[cname].nunique() < 10 and 
                        X_train_full[cname].dtype == "object"]

# 수치형 변수 선택
numerical_cols = [cname for cname in X_train_full.columns if X_train_full[cname].dtype in ['int64', 'float64']]

# 선택된 columns만 이용
my_cols = low_cardinality_cols + numerical_cols
X_train = X_train_full[my_cols].copy()
X_valid = X_valid_full[my_cols].copy()
X_train.head()

다음으로 학습 데이터에서 범주형 변수 리스트를 확인해보겠습니다. 이는 각 컬럼의 데이터 타입(dtype)을 확인함으로써 알 수 있습니다. object dtype은 text를 갖고 있다는 것을 의미합니다.(이론적으로 다른 이유가 있을 수 있지만 여기서는 일단 넘어갑니다.) 해당 데이터셋에서 text를 갖고있는 columns는 범주형 변수를 의미합니다.

# 범주형 변수 리스트 얻기
s = (X_train.dtypes == 'object')
object_cols = list(s[s].index)

print("Categorical variables:")
print(object_cols)

각 방법의 성능 측정을 위한 함수 정의
score_dataset()함수를 정의하여 범주형 변수를 다루는 세가지 방법의 성능을 비교하겠습니다. 이 함수는 random forest 모델의 평균절대오차(MAE: mean absolute error) 를 계산 해줍니다.

from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error

# Function for comparing different approaches
def score_dataset(X_train, X_valid, y_train, y_valid):
    model = RandomForestRegressor(n_estimators=100, random_state=0)
    model.fit(X_train, y_train)
    preds = model.predict(X_valid)
    return mean_absolute_error(y_valid, preds)

방법 1의 성능(범주형 변수 제거하기)

select_dtypes()함수를 이용하여 object type을 갖는 columns를 제거합니다.

drop_X_train = X_train.select_dtypes(exclude=['object'])
drop_X_valid = X_valid.select_dtypes(exclude=['object'])

print("MAE from Approach 1 (Drop categorical variables):")
print(score_dataset(drop_X_train, drop_X_valid, y_train, y_valid))

방법 2의 성능(Ordinal Encoding)

scikit-learn에 OrdinalEncoder 클래스를 이용하여 ordinal encoding을 할 수 있습니다. loop 문을 이용하여 각각의 범주형 변수에 해당하는 column에 ordinal encoder를 적용하겠습니다.

from sklearn.preprocessing import OrdinalEncoder

# 원본 데이터를 바꾸지 않기위해 복사본 생성
label_X_train = X_train.copy()
label_X_valid = X_valid.copy()

# 범주형 변수를 갖는 각 column에 ordinal encoder를 적용 
ordinal_encoder = OrdinalEncoder()
label_X_train[object_cols] = ordinal_encoder.fit_transform(X_train[object_cols])
label_X_valid[object_cols] = ordinal_encoder.transform(X_valid[object_cols])

print("MAE from Approach 2 (Ordinal Encoding):") 
print(score_dataset(label_X_train, label_X_valid, y_train, y_valid))

코드셀에서 각 column에 고유한 값에 서로 다른 정수를 무작위로 할당하였습니다. 이러한 방법은 custom labels를 만드는 것보다는 더 간단하고 자주 사용되는 방법입니다. 그러나 만약 모든 ordinal variables에 대해 더 정확한 정보를 표현하는 label을 만든다면 더 좋은 모델 성능을 보일 수도 있습니다.

방법 3의 접근법(One-Hot encoding)

scikit learn의 OneHotEncoder class를 사용한다면 one-hot-encoding을 할 수 있습니다 OneHotEncoder 사용하기 전에 몇가지 parameter들을 설정해 줘야 합니다.

  • handle_unknown='ignore'을 설정하여 검증 데이터에서 학습데이터에서는 존재하지 않는 class들을 만났을대 발생하는 오류를 방지합니다.
  • sparse=False는 encode된 columns이 sparse matrix 대신에 numpy array 형태로 리턴 되도록 설정해줍니다.

encoder를 사용하기 위해 오직 one-hot encoded되길 원하는 범주형 columns만 제공해야 합니다. 예시로 학습데이터를 encode 하기 위해 X_train[object_cols]를 제공 합니다.(object_cols는 범주형 데이터를 갖는 column 이름 리스트 입니다. 그러므로 X_train[object_cols]는 학습데이터의 모든 범주형 데이터를 담고 있습니다.)

from sklearn.preprocessing import OneHotEncoder

# 범주형 데이터를 갖는 각 column에 one-hot encoder를 적용
OH_encoder = OneHotEncoder(handle_unknown='ignore', sparse=False)
OH_cols_train = pd.DataFrame(OH_encoder.fit_transform(X_train[object_cols]))
OH_cols_valid = pd.DataFrame(OH_encoder.transform(X_valid[object_cols]))

# One-hot encoding은 index를 제거하기 때문에 다시 되돌려준다.
OH_cols_train.index = X_train.index
OH_cols_valid.index = X_valid.index

# 범주형 변수를 제거(one-hot encoding 대체)
num_X_train = X_train.drop(object_cols, axis=1)
num_X_valid = X_valid.drop(object_cols, axis=1)

# one-hot encoded된 변수(columns)를 수치형 변수에 붙임
OH_X_train = pd.concat([num_X_train, OH_cols_train], axis=1)
OH_X_valid = pd.concat([num_X_valid, OH_cols_valid], axis=1)

print("MAE from Approach 3 (One-Hot Encoding):") 
print(score_dataset(OH_X_train, OH_X_valid, y_train, y_valid))

어떠한 접근법이 가장 좋습니까?

위 예시에서는, 범주형 변수를 제거하는(방법1)것이 가장 안 좋은 결과를 보였습니다. 다른 두 방법(방법2, 방법3)에 대해서는 MAE 점수가 비슷하기 때문에 어떤 방법이 더 좋다고 결론 내리는 것은 의미 없습니다.

일반적으로 one-hot encoding(방법3)이 가장 좋은 성능을 보이고, 범주형 변수를 제거하는 것이 (방법1) 가장 안 좋은 성능을 보입니다. 하지만 이는 때에 따라 달라질 수 있습니다.

sdj4819개인적인의견sdj4819 개인적인 의견

현실 데이터에서는 범주의 종류가 매우 많을 때가 많습니다. 따라서 one-hot encoding은 잘 안하는 경우가 많고, tree 기반 모델에서는 순서가 없는 변수에 위 예시에서와 같이 ordinal encoding(label encoding)을 적용했을 때 one-hot encoding과 비슷한 성능을 보이는 경우가 종종 있기에 label encoding을 그냥 사용하곤 합니다.

Concolusion

현실 세계에는 수많은 범주형 데이터가 있습니다. 이 일반적인 데이터 타입을 어떻게 다루는지 안다면 더욱더 효과적인 데이터 과학자가 될 수 있을것 입니다.

Your Turn

다음 예시로 연습해 보세요

Reference

profile
으쌰으쌰

0개의 댓글