11. 범주형변수 가공¶
5장에서는 수치형변수를 처리하는 방법에 대해 살펴봤습니다. 이번 장에서는 범주형변수를 변환하는 방법에 대해 알아보겠습니다.
범주형변수는 성별처럼 변수의 값 사이에 관련이 없고 구분되어 있는 변수를 뜻합니다. 범주형변수는 모델 학습에 사용하기 위해 수치형 또는 이진 변수로 변환해야 합니다.
11.1 라이브러리 import 및 설정¶
%reload_ext autoreload
%autoreload 2
%matplotlib inline
import kaggler
from lightgbm import LGBMRegressor
from matplotlib import rcParams, pyplot as plt
import numpy as np
import pandas as pd
from pathlib import Path
from scipy.sparse import hstack
import seaborn as sns
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from warnings import simplefilter
rcParams['figure.figsize'] = (16, 8)
plt.style.use('fivethirtyeight')
simplefilter('ignore')
11.2 학습데이터 로드¶
이번 실습에 사용할 데이터도 5장에서 실습한 것 처럼 데이콘의 영화 관객수 예측 모델 개발 페이지에서 다운로드하여 ../data/movies/
폴더에 저장을 해둡니다. 해당 데이터는 영화의 장르, 개봉일, 상영시간 등의 데이터로 영화 총 관객수 (box_off_num
)를 예측하는 데이터입니다.
data_dir = Path('../data/movies/')
trn_file = data_dir / 'movies_train.csv'
seed = 42
target_col = 'box_off_num'
df = pd.read_csv(trn_file, index_col=0)
print(df.shape)
df.head()
(600, 11)
distributor | genre | release_time | time | screening_rat | director | dir_prev_bfnum | dir_prev_num | num_staff | num_actor | box_off_num | |
---|---|---|---|---|---|---|---|---|---|---|---|
title | |||||||||||
개들의 전쟁 | 롯데엔터테인먼트 | 액션 | 2012-11-22 | 96 | 청소년 관람불가 | 조병옥 | NaN | 0 | 91 | 2 | 23398 |
내부자들 | (주)쇼박스 | 느와르 | 2015-11-19 | 130 | 청소년 관람불가 | 우민호 | 1161602.50 | 2 | 387 | 3 | 7072501 |
은밀하게 위대하게 | (주)쇼박스 | 액션 | 2013-06-05 | 123 | 15세 관람가 | 장철수 | 220775.25 | 4 | 343 | 4 | 6959083 |
나는 공무원이다 | (주)NEW | 코미디 | 2012-07-12 | 101 | 전체 관람가 | 구자홍 | 23894.00 | 2 | 20 | 6 | 217866 |
불량남녀 | 쇼박스(주)미디어플렉스 | 코미디 | 2010-11-04 | 108 | 15세 관람가 | 신근호 | 1.00 | 1 | 251 | 2 | 483387 |
데이터를 확인해보면 distributor
, genre
, screening_rat
, 그리고 director
까지 총 4개의 범주형변수가 존재함을 알 수 있습니다.
11.3 EDA (Exploratory Data Analysis)¶
df.info()
<class 'pandas.core.frame.DataFrame'>
Index: 600 entries, 개들의 전쟁 to 베를린
Data columns (total 11 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 distributor 600 non-null object
1 genre 600 non-null object
2 release_time 600 non-null object
3 time 600 non-null int64
4 screening_rat 600 non-null object
5 director 600 non-null object
6 dir_prev_bfnum 270 non-null float64
7 dir_prev_num 600 non-null int64
8 num_staff 600 non-null int64
9 num_actor 600 non-null int64
10 box_off_num 600 non-null int64
dtypes: float64(1), int64(5), object(5)
memory usage: 56.2+ KB
info()
함수를 통해 변수별 데이터 타입을 확인해보겠습니다. 독립변수를 포함 총 6개의 수치형변수가 있으며 그 중 dir_prev_bfnum
은 결측값이 많음을 확인할 수 있습니다. 범주형변수는 distributor
, genre
, screening_rat
그리고 director
까지 총 4개가 있습니다. 이 때 release_time
은 시계열변수이지만 문자열(object
)로 인식하고 있습니다.
df['release_time'] = pd.to_datetime(df['release_time'])
print(df['release_time'].dtype)
datetime64[ns]
df.fillna(0, inplace=True)
df.info()
<class 'pandas.core.frame.DataFrame'>
Index: 600 entries, 개들의 전쟁 to 베를린
Data columns (total 11 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 distributor 600 non-null object
1 genre 600 non-null object
2 release_time 600 non-null datetime64[ns]
3 time 600 non-null int64
4 screening_rat 600 non-null object
5 director 600 non-null object
6 dir_prev_bfnum 600 non-null float64
7 dir_prev_num 600 non-null int64
8 num_staff 600 non-null int64
9 num_actor 600 non-null int64
10 box_off_num 600 non-null int64
dtypes: datetime64[ns](1), float64(1), int64(5), object(4)
memory usage: 56.2+ KB
release_time
을 시계열타입(datetime
)으로 변환하고 결측값을 0으로 대체했습니다.
num_cols = [x for x in df.columns if df[x].dtype in [np.int64, np.float64] and x != target_col]
cat_cols = ['distributor', 'genre', 'screening_rat', 'director']
print(f' numeric ({len(num_cols)}):\t{num_cols}')
print(f'categorical ({len(cat_cols)}):\t{cat_cols}')
numeric (5): ['time', 'dir_prev_bfnum', 'dir_prev_num', 'num_staff', 'num_actor']
categorical (4): ['distributor', 'genre', 'screening_rat', 'director']
11.3.1 범주형변수 EDA¶
print(cat_cols)
['distributor', 'genre', 'screening_rat', 'director']
pd.DataFrame(df['distributor'].value_counts())
distributor | |
---|---|
CJ 엔터테인먼트 | 54 |
롯데엔터테인먼트 | 52 |
(주)NEW | 30 |
(주)마운틴픽쳐스 | 29 |
인디스토리 | 26 |
... | ... |
(주)팝 파트너스 | 1 |
스폰지이엔티 | 1 |
(주)JK필름 | 1 |
디 씨드 | 1 |
스튜디오 블루 | 1 |
169 rows × 1 columns
범주형변수를 탐색할 때 유용하게 쓰이는 value_counts()
함수를 위에서 사용했습니다. value_counts()
는 데이터내에서 범주별로 등장한 횟수를 반환해줍니다. 위의 예시에서 CJ 엔터테인먼트
가 54개의 영화를 배급 했음을 알 수 있습니다. 또한 총 169개의 배급사가 존재하는 것도 하단에 나와있는 169 rows
를 통해 알 수 있습니다.
pd.DataFrame(df['genre'].value_counts())
genre | |
---|---|
드라마 | 221 |
다큐멘터리 | 93 |
멜로/로맨스 | 78 |
코미디 | 53 |
공포 | 42 |
액션 | 28 |
느와르 | 27 |
애니메이션 | 21 |
미스터리 | 17 |
SF | 13 |
뮤지컬 | 5 |
서스펜스 | 2 |
genre
에는 총 12개의 범주가 존재하며 그 중 드라마
가 221개로 가장 많은 것을 알 수 있습니다.
pd.DataFrame(df['screening_rat'].value_counts())
screening_rat | |
---|---|
청소년 관람불가 | 204 |
15세 관람가 | 202 |
12세 관람가 | 102 |
전체 관람가 | 92 |
pd.DataFrame(df['director'].value_counts())
director | |
---|---|
홍상수 | 7 |
노진수 | 4 |
장률 | 4 |
전규환 | 4 |
신재호 | 4 |
... | ... |
안상훈 | 1 |
한준희 | 1 |
이광호 | 1 |
김동빈 | 1 |
전윤수 | 1 |
472 rows × 1 columns
11.4 수치형/시계열변수 가공¶
수치형 독립변수 중 멱변환 분포를 따르는 변수에도 np.log1p()
변환을 적용하였다.
df[['dir_prev_bfnum', 'dir_prev_num', 'num_staff', 'num_actor']] = df[['dir_prev_bfnum', 'dir_prev_num', 'num_staff', 'num_actor']].apply(np.log1p)
df[num_cols].describe()
time | dir_prev_bfnum | dir_prev_num | num_staff | num_actor | |
---|---|---|---|---|---|
count | 600.000000 | 600.000000 | 600.000000 | 600.000000 | 600.000000 |
mean | 100.863333 | 5.305796 | 0.462197 | 4.026352 | 1.446130 |
std | 18.097528 | 6.254561 | 0.555570 | 1.789517 | 0.446256 |
min | 45.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
25% | 89.000000 | 0.000000 | 0.000000 | 2.890372 | 1.098612 |
50% | 100.000000 | 0.000000 | 0.000000 | 4.424829 | 1.386294 |
75% | 114.000000 | 12.837611 | 1.098612 | 5.579730 | 1.609438 |
max | 180.000000 | 16.684279 | 1.791759 | 6.768493 | 3.258097 |
df['year'] = df['release_time'].dt.year
df['month'] = df['release_time'].dt.month
df.head()
distributor | genre | release_time | time | screening_rat | director | dir_prev_bfnum | dir_prev_num | num_staff | num_actor | box_off_num | year | month | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
title | |||||||||||||
개들의 전쟁 | 롯데엔터테인먼트 | 액션 | 2012-11-22 | 96 | 청소년 관람불가 | 조병옥 | 0.000000 | 0.000000 | 4.521789 | 1.098612 | 23398 | 2012 | 11 |
내부자들 | (주)쇼박스 | 느와르 | 2015-11-19 | 130 | 청소년 관람불가 | 우민호 | 13.965312 | 1.098612 | 5.961005 | 1.386294 | 7072501 | 2015 | 11 |
은밀하게 위대하게 | (주)쇼박스 | 액션 | 2013-06-05 | 123 | 15세 관람가 | 장철수 | 12.304905 | 1.609438 | 5.840642 | 1.609438 | 6959083 | 2013 | 6 |
나는 공무원이다 | (주)NEW | 코미디 | 2012-07-12 | 101 | 전체 관람가 | 구자홍 | 10.081425 | 1.098612 | 3.044522 | 1.945910 | 217866 | 2012 | 7 |
불량남녀 | 쇼박스(주)미디어플렉스 | 코미디 | 2010-11-04 | 108 | 15세 관람가 | 신근호 | 0.693147 | 0.693147 | 5.529429 | 1.098612 | 483387 | 2010 | 11 |
num_cols += ['year', 'month']
print(num_cols)
['time', 'dir_prev_bfnum', 'dir_prev_num', 'num_staff', 'num_actor', 'year', 'month']
features = num_cols + cat_cols
print(features)
['time', 'dir_prev_bfnum', 'dir_prev_num', 'num_staff', 'num_actor', 'year', 'month', 'distributor', 'genre', 'screening_rat', 'director']
11.5 범주형변수 가공¶
rmse = lambda y, p: np.sqrt(mean_squared_error(y, p))
rmsle = lambda y, p: np.sqrt(mean_squared_error(np.log1p(y), np.log1p(p)))
11.5.1 Ordinal Encoding¶
가장 먼저 배워볼 변환 방법은 ordinal encoding 입니다. 각각의 범주를 0부터 n-1의 정수로 변환하는 방법입니다. 일반적으로 ordinal encoding은 범주간의 대소 관계가 존재할 때 해당 관계를 보존하는 식으로 변환하는 방법입니다. 예를 들어 옷 사이즈 small, medium, large가 있을 때 차례대로 0, 1, 2로 변환하는 것입니다. 하지만 Scikit-learn에서 Ordinal encoding시 사용하는 함수는 범주내의 대소 관계와 무관하게 수치 변환을 실시합니다. 또한 Scikit-learn에서 제공하는 label encoding 방법 또한 이와 유사하게 적용됩니다.
from sklearn.preprocessing import OrdinalEncoder
df_cat = df.copy()
oe = OrdinalEncoder()
df_cat[cat_cols] = oe.fit_transform(df[cat_cols])
df_cat[cat_cols].head()
distributor | genre | screening_rat | director | |
---|---|---|---|---|
title | ||||
개들의 전쟁 | 80.0 | 10.0 | 3.0 | 393.0 |
내부자들 | 21.0 | 2.0 | 3.0 | 252.0 |
은밀하게 위대하게 | 21.0 | 10.0 | 1.0 | 357.0 |
나는 공무원이다 | 6.0 | 11.0 | 2.0 | 17.0 |
불량남녀 | 96.0 | 11.0 | 1.0 | 218.0 |
OrdinalEncoder()
을 적용하니 범주가 수치로 변환된 것을 확인할 수 있습니다.
trn, tst = train_test_split(df_cat, test_size=.2, random_state=seed)
clf = LGBMRegressor(random_state=seed)
clf.fit(trn[features], np.log1p(trn[target_col]))
p = np.expm1(clf.predict(tst[features]))
print(f' RMSE:\t{rmse(tst[target_col], p):12.2f}')
print(f'RMSLE:\t{rmsle(tst[target_col], p):12.2f}')
RMSE: 1412591.02
RMSLE: 1.98
11.5.2 Label Encoding with Grouping¶
앞서 살펴본 encoding 방법은 개별 범주에 하나의 번호를 부여하기 때문에 한번만 등장하는 범주가 여러개일 때 각각 다른 숫자를 갖게 됩니다. 한번만 등장하지 않는데 개별 숫자로 encoding하는 것은 비효율적일 수 있습니다. 이번 절에서는 빈도 수에 기반해 범주를 묶어서 encoding하는 방법을 소개하겠습니다.
from kaggler.preprocessing import LabelEncoder
df_cat = df.copy()
le = LabelEncoder(min_obs=2)
df_cat[cat_cols] = le.fit_transform(df[cat_cols])
df_cat[cat_cols].head()
Using TensorFlow backend.
distributor | genre | screening_rat | director | |
---|---|---|---|---|
title | ||||
개들의 전쟁 | 2.0 | 5 | 0 | 0.0 |
내부자들 | 6.0 | 6 | 0 | 6.0 |
은밀하게 위대하게 | 6.0 | 5 | 1 | 0.0 |
나는 공무원이다 | 3.0 | 3 | 3 | 0.0 |
불량남녀 | 66.0 | 3 | 1 | 0.0 |
kaggler
라이브러리의 preprocessing
모듈에서 제공하는 LabelEncoder()
함수는 특정 횟수 미만으로 등장하는 범주들을 모두 하나의 그룹으로 묶어주는 기능을 제공합니다. min_obs
파라미터에 2를 명시했기 때문에 2번 미만으로 등장하는 범주는 모두 하나의 수치로 변환하게 됩니다.
trn, tst = train_test_split(df_cat, test_size=.2, random_state=seed)
clf = LGBMRegressor(random_state=seed)
clf.fit(trn[features], np.log1p(trn[target_col]))
p = np.expm1(clf.predict(tst[features]))
print(f' RMSE:\t{rmse(tst[target_col], p):12.2f}')
print(f'RMSLE:\t{rmsle(tst[target_col], p):12.2f}')
RMSE: 1182500.97
RMSLE: 1.92
Label encoding with grouping 방법을 통해 수치 변환을 한 경우 ordinal encoding을 했을 때 보다 평가지표인 rmse와 rmsle가 모두 줄어든 것을 확인할 수 있습니다. rmse와 rmsle은 낮을 수록 모델 성능이 높은 지표입니다.
11.5.3 One-Hot-Encoding¶
앞서 살펴본 ordinal/label encoding은 트리 기반의 모델을 사용할 때는 학습이 잘 되지만 선형회귀, 로지스틱회귀, 또는 딥러닝(신경망) 모델에 적용할 때는 학습이 잘 안될 수 있습니다. 후자에 언급한 알고리즘들은 숫자의 대소 관계에 영향을 받는데 ordinal/label encoding으로 변환한 수치값은 원래 없던 대소 관계를 임의로 부여한 것이기 때문에 모델에 악영향을 줄 수가 있습니다. 예를 들어 조병옥
감독과 우민호
감독은 서로 다른 사람인데 각각 1과 3으로 변환이 되면 수치상 조병옥
감독이 3명이 모이면 우민호
감독이 된다는 뜻입니다. 선형회귀, 로지스틱회귀, 그리고 딥러닝 모델은 이러한 관계를 반영할려고 하기 때문에 모델 학습이 잘 안될 수도 있습니다.
위와 같은 예시인 경우 ordinal encoding 보다 one-hot encoding방법을 적용할 수 있습니다. One-hot encoding은 각각의 범주의 존재 여부를 이진 변수로 변환해서 나타냅니다. sklearn.preprocessing
모듈의 OneHotEncoder()
함수를 통해 적용할 수 있습니다.
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder()
X = hstack((df[num_cols],
ohe.fit_transform(df[cat_cols])))
print(X.shape)
(600, 664)
One-hot encoding 변환 후에는 범주의 개수 만큼 열의 개수가 늘어나기 때문에 664개로 열이 늘어난 것을 확인할 수 있습니다.
X_trn, X_tst, y_trn, y_tst = train_test_split(X, df[target_col], test_size=.2, random_state=seed)
clf = LGBMRegressor(random_state=seed)
clf.fit(X_trn, np.log1p(y_trn))
p = np.expm1(clf.predict(X_tst))
print(f' RMSE:\t{rmse(tst[target_col], p):12.2f}')
print(f'RMSLE:\t{rmsle(tst[target_col], p):12.2f}')
RMSE: 1239343.52
RMSLE: 1.99
11.5.4 One-Hot-Encoding with Grouping¶
One-hot encoding 적용 전에도 특정 빈도 미만으로 출현하는 범주들은 하나의 그룹으로 묶고 나서 변환을 실시할 수 있습니다. kaggler
라이브러리를 사용해 그룹을 지은 후 one-hot encoding 적용이 가능합니다.
from kaggler.preprocessing import OneHotEncoder
ohe = OneHotEncoder(min_obs=2)
X = hstack((df[num_cols],
ohe.fit_transform(df[cat_cols])))
print(X.shape)
(600, 187)
min_obs
파라미터에 2를 주었기 때문에 2미만의 빈도를 가진 범주는 모두 하나의 범주로 묶게 됩니다. 범주를 묶지 않았을 때는 encoding 변환 후 664개의 열이 존재했는데 범주를 묶은 후에는 encoding 변환 후 187개의 열만 존재하는 것을 확인할 수 있습니다.
X_trn, X_tst, y_trn, y_tst = train_test_split(X, df[target_col], test_size=.2, random_state=seed)
clf = LGBMRegressor(random_state=seed)
clf.fit(X_trn, np.log1p(y_trn))
p = np.expm1(clf.predict(X_tst))
print(f' RMSE:\t{rmse(tst[target_col], p):12.2f}')
print(f'RMSLE:\t{rmsle(tst[target_col], p):12.2f}')
RMSE: 1183708.39
RMSLE: 1.96
rmse와 rmsle도 범주를 묶기 전보다 좋아진 것을 확인할 수 있습니다.
11.5.5 Target Encoding without Cross-Validation¶
Target encoding은 각각의 범주를 종속변수의 평균값으로 변환하는 방법입니다. 예를들어 모든 12세 관람가
영화의 관객수 평균이 84만이면 12세 관람가
를 84만으로 변환합니다.
Target encoding은 종속변수에 대한 정보를 활용해 변환하기 때문에 과적합에 취약합니다. 그러므로 cross-validation과 smoothing을 함께 사용해서 과적합을 방지해줄 필요가 있습니다. kaggler
라이브러리에서 제공하는 TargetEncoder()
함수는 cross-validation과 smoothing을 모두 지원합니다.
from kaggler.preprocessing import TargetEncoder
trn, tst = train_test_split(df, test_size=.2, random_state=seed)
te = TargetEncoder(cv=None)
trn[cat_cols] = te.fit_transform(trn[cat_cols], trn[target_col])
tst[cat_cols] = te.transform(tst[cat_cols])
trn[cat_cols].head()
distributor | genre | screening_rat | director | |
---|---|---|---|---|
title | ||||
돼지의 왕 | 721746.077154 | 1.998008e+05 | 3.511182e+05 | 723283.709843 |
청춘그루브 | 723430.057925 | 6.093409e+05 | 1.289088e+06 | 723430.057925 |
행복한 울릉인 | 722860.857509 | 7.472685e+04 | 1.529000e+05 | 723430.082604 |
옥희의 영화 | 718805.251941 | 6.093409e+05 | 3.511182e+05 | 711379.735399 |
권법형사 : 차이나타운 | 90921.244145 | 2.212935e+06 | 3.511182e+05 | 723429.982531 |
clf = LGBMRegressor(random_state=seed)
clf.fit(trn[features], np.log1p(trn[target_col]))
p = np.expm1(clf.predict(tst[features]))
print(f' RMSE:\t{rmse(tst[target_col], p):12.2f}')
print(f'RMSLE:\t{rmsle(tst[target_col], p):12.2f}')
RMSE: 1271836.20
RMSLE: 3.40
11.5.6 Target Encoding with Cross-Validation¶
trn, tst = train_test_split(df, test_size=.2, random_state=seed)
te = TargetEncoder()
trn[cat_cols] = te.fit_transform(trn[cat_cols], trn[target_col])
tst[cat_cols] = te.transform(tst[cat_cols])
trn[cat_cols].head()
distributor | genre | screening_rat | director | |
---|---|---|---|---|
title | ||||
돼지의 왕 | 723277.447850 | 267982.096480 | 348028.101562 | 723432.730281 |
청춘그루브 | 742425.489583 | 742425.489583 | 742425.489583 | 742425.489583 |
행복한 울릉인 | 768901.203125 | 10425.029522 | 122162.823956 | 768901.203125 |
옥희의 영화 | 722877.805490 | 813347.509259 | 380361.663366 | 721858.927057 |
권법형사 : 차이나타운 | 722864.097814 | 727096.433397 | 115692.865474 | 597129.065104 |
clf = LGBMRegressor(random_state=seed)
clf.fit(trn[features], np.log1p(trn[target_col]))
p = np.expm1(clf.predict(tst[features]))
print(f' RMSE:\t{rmse(tst[target_col], p):12.2f}')
print(f'RMSLE:\t{rmsle(tst[target_col], p):12.2f}')
RMSE: 1529036.45
RMSLE: 2.12
이번 실습에 사용하는 데이터셋은 샘플 수가 약 600개 정도 되는 작은 데이터셋이라서 cross-validation을 적용해도 과적합이 크게 개선되지는 않았습니다.
11.5.7 Frequency Encoding¶
Frequency encoding은 각 범주가 출현한 빈도 수로 범주를 변환합니다. 예를 들어 12세 관람가
가 102번 등장했으면 12세 관람가
범주를 102로 변환합니다. Frequency encoding은 ordinal/label encoding에 grouping을 적용한 것과 비슷한 효과를 가집니다. 출현 빈도가 1인 범주는 모두 1로 변환되기 때문에 하나의 그룹에 묶인 상태로 변환된 것과 같기 때문입니다. kaggler
라이브러리에서 제공하는 FrequencyEncoder()
함수를 통해 적용 가능합니다.
from kaggler.preprocessing import FrequencyEncoder
df_cat = df.copy()
fe = FrequencyEncoder()
df_cat[cat_cols] = fe.fit_transform(df[cat_cols])
df_cat[cat_cols].head()
distributor | genre | screening_rat | director | |
---|---|---|---|---|
title | ||||
개들의 전쟁 | 52 | 28 | 204 | 1 |
내부자들 | 26 | 27 | 204 | 4 |
은밀하게 위대하게 | 26 | 28 | 202 | 1 |
나는 공무원이다 | 30 | 53 | 92 | 1 |
불량남녀 | 2 | 53 | 202 | 1 |
trn, tst = train_test_split(df_cat, test_size=.2, random_state=seed)
clf = LGBMRegressor(random_state=seed)
clf.fit(trn[features], np.log1p(trn[target_col]))
p = np.expm1(clf.predict(tst[features]))
print(f' RMSE:\t{rmse(tst[target_col], p):12.2f}')
print(f'RMSLE:\t{rmsle(tst[target_col], p):12.2f}')
RMSE: 1258846.99
RMSLE: 1.96
간단하지만 준수한 성능을 보이는 방법임을 알 수 있습니다.
11.5.8 Hash Encoding¶
Hashing encoding은 각 범주에 hash 함수를 적용해서 나온 hash값으로 변환하는 방식입니다. Hash 함수를 사용하기 때문에 fit()
이 필요 없어서 빠르고 메모리를 적게 사용합니다. 그래서 범주의 개수가 많거나 데이터가 클 때 사용하면 효율적인 방식입니다. sklearn.feature_extraction
모듈의 FeatureHasher()
함수를 통해 적용 가능합니다.
from sklearn.feature_extraction import FeatureHasher
fh = FeatureHasher(n_features=128, input_type='string')
X = hstack([df[num_cols]] + [fh.fit_transform(df[col]) for col in cat_cols])
print(X.shape)
(600, 519)
FeatureHasher()
함수의 n_features
파라미터에 hash 값의 길이를 설정할 수 있는데 길이가 너무 짧으면 다른 범주 간의 hash 값 충돌이 발생할 수 있습니다. 그러므로 충분하게 큰 값을 설정해주어야 합니다. 실습 데이터는 범주의 개수가 적어서 128을 주었지만 일반적으로 2^10인 1024로 설정합니다.
X_trn, X_tst, y_trn, y_tst = train_test_split(X, df[target_col], test_size=.2, random_state=seed)
clf = LGBMRegressor(random_state=seed)
clf.fit(X_trn, np.log1p(y_trn))
p = np.expm1(clf.predict(X_tst))
print(f' RMSE:\t{rmse(tst[target_col], p):12.2f}')
print(f'RMSLE:\t{rmsle(tst[target_col], p):12.2f}')
RMSE: 1261559.18
RMSLE: 1.95