Py) ML - One-Hot Encoding

Py) ML - One-Hot Encoding

sklearnPandas 라이브러리를 활용하여 범주형 변수를 원핫 인코딩(One-Hot Encoding)을 실시하는 방법에 대해 알아본다.


이론

개요

기본적으로 통계 및 머신러닝 모델링에서 범주형 데이터를 그대로 모델에 넣지 않는다. 간단하게 말해서 특정 알고리즘을 기반으로 계산을 하는데 사칙연산을 하는 와중에 갑자기 “A”라는 문자가 있다면 어떻게 처리할 방법이 없다. 그래서 범주형 데이터는 보통 숫자로 바꾼 후에 모델을 통한 학습 및 평가를 실시하게 된다.

원핫 인코딩(One-Hot Encoding)의 대상이 되는 범주형 변수는 해당 변수 범주 개수(n) 만큼의 신규 변수로 쪼개어지며 각 신규 변수는 원소가 0 또는 1이 있는 이진(binary)변수이다. 그리고 해당 이진변수를 가변수 또는 더미(dummy)변수 라고 지칭한다.
※ 간혹 index 변수나 flag 변수라고 하기도 한다.

생성되는 가변수는 원핫 인코딩의 대상이 되는 범주형 변수의 위치에 맞춰 1과 0으로 변환된다. 예를 들어 신규 가변수가 “A”라는 범주를 대상으로 변환된 것이라면, 대상 변수의 원소가 A가 위치한 곳에는 1이 기록되고 나머지는 모두 0으로 기록된다.

기본 예시

다음과 같이 두 범주가 있는 범주형 변수가 있을 때 원핫 인코딩을 실시한 결과는 다음과 같다.
두 범주가 있는 범주형 변수의 원핫 인코딩 예시

신규 생성된 변수 “v1”은 기존 “v”변수의 범주 “a”가 위치한 행에 “1”이 있고 나머지는 “0”으로 되어있는 것을 볼 수 있으며, “v2”의 경우 기존 “v”변수의 범주 “b”가 위치한 행에 “1”이 있고 나머지는 “0”으로 되어있는 것을 볼 수 있다.

세 범주가 있는 범주형 변수를 원핫 인코딩 하는 경우 다음과 같다.
세 범주가 있는 범주형 변수의 원핫 인코딩 예시

원핫 인코딩 변환 규칙은 두 범주의 경우와 같고, 이번에는 대상 변수 “v”의 범주 개수가 3개이기 때문에 가변수가 3개 생성되었고 “v1”, “v2”, “v3” 변수는 각각 범주 “e”, “f”, “g”를 담당하고 있다.

1개 범주를 제외하는 경우

원핫 인코딩의 경우 앞에서 설명한대로 대상 변수의 범주 개수(n) 만큼의 가변수로 쪼개어지는 것이 기본이나 범주 개수보다 한 개 적은 n-1개의 가변수만 있어도 된다. 다음의 예시를 보자.
1개 가변수를 제거한 원핫 인코딩 예시

대상 변수 “v”에 범주가 3개 있지만 생성된 가변수는 “v1”과 “v2”이며 각 변수는 범주 “e”와 “f”에 대한 가변수이다. 그래도 두 가변수는 대상 변수 “v”의 모든 정보를 포함하고 있다. 왜냐하면 “v1”과 “v2”가 모두 0인 경우는 범주 “g”에 해당하는 정보이기 때문이다. 그래서 원핫 인코딩을 실시하는 경우 모든 가변수를 생성하고 분석 모델에 반영하기 보다는 1개 범주를 제외하기도 하며 특히 선형회귀분석을 실시하는 경우 “완전 공선성”문제 때문에 원핫 인코딩으로 생성된 가변수를 모델에 포함하는 경우 반드시 1개 이상의 가변수를 제거한다.

Pandas

원핫 인코딩을 실시하기 위해 Pandas라이브러리의 get_dummies() 함수를 사용할 수 있다. 이 함수를 쓰는 경우는 1회성으로 간편하게 원핫 인코딩을 실시하고자 할 때 사용하며 Pandas 2.0.0 버전 부터는 기본 반환값이 10이 아닌 TrueFalse이며 정수 반환값을 원하는 경우 “dtype” 인자에 “int”를 할당해야 한다. 참고로 statsmodels 라이브러리 기반의 모델링을 실시하는 경우 입력값이 TrueFalse일 때 에러가 발생할 수 있으니 주의하도록 한다.

sklearn

원핫 인코딩을 실시하기 위해 sklearn라이브러리의 OneHotEncoder() 클래스를 사용할 수 있다. 이 경우는 .fit() 메서드를 사용하는 경우 원핫 인코딩을 실시하는 모델 객체를 생성할 수 있기 때문에 Pandasget_dummies() 함수와 다르게 다회성으로 원핫 인코딩을 실시할 때 적극 활용할 수 있다. 좀 더 정확하게 말하자면 원핫 인코딩 규칙을 학습하는 객체가 있고 변환해야 하는 객체가 별도로 있을 때 사용하기 편리한 클래스이다. 그렇기 때문에 머신러닝 모델링을 위해 학습/평가 데이터세트로 분할하는 경우 학습 데이터세트로 원핫 인코딩 규칙을 학습 및 변환하고 해당 규칙을 평가 데이터세트 변환에 사용할 수 있겠다. 보다 쉬운 이해는 실습 부분을 참고하자.

실습

실습을 위해 다음과 같이 데이터프레임 객체 “df1”과 “df2”를 준비하자.

1
2
df1 = pd.DataFrame(dict(v = ["a", "b", "c", "c"]))
df1
v
0 a
1 b
2 c
3 c
1
2
3
4
df2 = pd.DataFrame(dict(cat1 = ["a", "b", "c", "c"],
cat2 = ["dog", "cat", "cat", "rat"],
num1 = [100, 100, 200, 200]))
df2
cat1 cat2 num1
0 a dog 100
1 b cat 100
2 c cat 200
3 c rat 200

기본 코드

원핫 인코딩을 위한 전문 함수나 클래스의 도움 없이 기본 Pandas 객체 핸들링 코드를 사용하여 구현하면 다음과 같다.

1
2
3
pd.DataFrame(dict(v0 = (df1["v"] == df1["v"].unique()[0]) + 0,
v1 = (df1["v"] == df1["v"].unique()[1]) + 0,
v2 = (df1["v"] == df1["v"].unique()[2]) + 0))
v0 v1 v2
0 1 0 0
1 0 1 0
2 0 0 1
3 0 0 1

물론 NumPywhere() 함수를 사용해도 되고, 반복문을 추가하여 많은 수의 범주가 있는 변수를 대상으로 할 때는 반복문을 활용할 수도 있겠다. 하지만 보다 편리한 원핫 인코딩을 위해 Pandassklearn 라이브러리에서 함수와 클래스를 제공하기 때문에 특별한 경우가 아니라면 이를 이용하는 것이 좋다.

Pandas

Pandasget_dummies()를 사용하여 원핫 인코딩을 실시해보자.

1
pd.get_dummies(df1)
v_a v_b v_c
0 True False False
1 False True False
2 False False True
3 False False True

get_dummies() 함수는 Pandas 1.0.0 버전부터 “object” 자료형 변수를 대상으로 자동 변환을 지원한다. 그리고 앞에도 언급했지만 Pandas 2.0.0 버전부터 원핫 인코딩 결과가 10이 아닌 TrueFalse로 나오기 때문에 정수 반환값을 원하는 경우 “dtype” 인자에 “int”를 할당해야 하며 다음을 참고하도록 하자.

1
pd.get_dummies(df1, dtype = "int")
v_a v_b v_c
0 1 0 0
1 0 1 0
2 0 0 1
3 0 0 1

그리고 범주형 변수가 2개 이상이 있는 경우 다음과 같이 결과를 확인할 수 있으며 수치형 변수 “num1”에 대한 가변수는 생성이 되지 않은 것을 알 수 있다.

1
pd.get_dummies(df2, dtype = "int")
num1 cat1_a cat1_b cat1_c cat2_cat cat2_dog cat2_rat
0 100 1 0 0 0 1 0
1 100 0 1 0 1 0 0
2 200 0 0 1 1 0 0
3 200 0 0 1 0 0 1

수치형 변수도 변환되도록 지정하고자 한다면 다음과 같이 “columns” 변수에 원핫 인코딩을 실시하고자 하는 대상 변수를 지정해주어야 한다.

1
pd.get_dummies(df2, columns = ["cat1", "cat2", "num1"], dtype = "int")
cat1_a cat1_b cat1_c cat2_cat cat2_dog cat2_rat num1_100 num1_200
0 1 0 0 0 1 0 1 0
1 0 1 0 1 0 0 1 0
2 0 0 1 1 0 0 0 1
3 0 0 1 0 0 1 0 1

그리고 get_dummies() 함수의 경우 다른 함수와 크게 다른 부분이 있는데 원핫 인코딩 대상 변수를 지정하는 경우 하나만 지정하더라도 반드시 리스트로 넣어야 한다. 대부분의 함수나 클래스의 경우 특정 인자에 입력되는 값이 1개이면 굳이 리스트를 사용하지 않아도 알아서 처리해주는 반면 이 함수만 특이하다.

1
pd.get_dummies(df2, columns = ["num1"], dtype = "int")
cat1 cat2 num1_100 num1_200
0 a dog 1 0
1 b cat 1 0
2 c cat 0 1
3 c rat 0 1

원핫 인코딩으로 생성되는 가변수를 (범주형 변수 마다)하나 제거하고자 한다면 “drop_first” 인자에 True를 할당하면 된다.
※ 마지막 가변수 제거는 지원하지 않으며 필요시 직접 제거해야함.

1
pd.get_dummies(df2, dtype = "int", drop_first = True)
num1 cat1_b cat1_c cat2_dog cat2_rat
0 100 0 0 1 0
1 100 1 0 0 0
2 200 0 1 0 0
3 200 0 1 0 1

첫 번째 가변수가 제거되는데 이는 사전순(또는 ASCII 코드순) 으로 제거가 된다.

sklearn

다회성 원핫 인코딩 변환에는 sklearn 라이브러리의 OneHotEncoder() 클래스를 사용할 수 있다. 앞에도 설명했지만 이 클래스로 원핫 인코딩 규칙이 담긴 모델 객체를 생성할 수 있으며 .fit().transform() 메서드를 사용하는 예시는 다음과 같다.

1
2
3
4
5
6
model_ohe = OneHotEncoder().fit(df1)
model_ohe.transform(df1).toarray()
## array([[1., 0., 0.],
## [0., 1., 0.],
## [0., 0., 1.],
## [0., 0., 1.]])

sklearn의 정규화 클래스와 다르게 OneHotEncoder() 클래스는 transform() 메서드 뒤에 .toarray() 메서드를 연쇄적으로 작성하여야 반환값을 NumPy 어레이 객체로 받을 수 있으니 꼭 숙지하도록 한다. 그리고 .fit() 메서드 안에 원핫 인코딩 변환규칙을 학습하고자 하는 객체를 넣으면 되고 그 학습된 규칙이 담긴 “model_ohe” 객체를 살펴보면 다음과 같다.

1
2
3
4
5
model_ohe.categories_ 
## [array(['a', 'b', 'c'], dtype=object)]

model_ohe.feature_names_in_
## array(['v'], dtype=object)

많은 내용이 있지만 주요 어트리뷰트를 꼽자면 상기와 같이 .categories_.feature_names_in_ 어트리뷰트가 있으며 입력된 변수의 범주와 해당 변수명을 확인할 수 있다. 그리고 이를 활용해서 Pandasget_dummies()의 결과처럼 데이터프레임으로 만들고자 한다면 다음과 같이 코드를 작성할 수 있다.

1
2
3
4
in_cat = model_ohe.categories_
in_name = model_ohe.feature_names_in_
pd.DataFrame(model_ohe.transform(df1).toarray(),
columns = [in_name + "_" + c for c in in_cat])
v_a v_b v_c
0 1.0 0.0 0.0
1 0.0 1.0 0.0
2 0.0 0.0 1.0
3 0.0 0.0 1.0

기존 학습된 정보를 토대로 새로운 객체를 변환하려면 .transform() 메서드를 다음과 같이 사용할 수 있겠다.

1
2
3
4
df_new = pd.DataFrame(dict(v = ["a", "c"]))
model_ohe.transform(df_new).toarray()
## array([[1., 0., 0.],
## [0., 0., 1.]])

모델 객체를 만들지 않고 한 번에 변환하려면 .fit_transform()을 사용할 수 있으며 다음과 같다.

1
2
3
4
5
OneHotEncoder().fit_transform(df1).toarray()
## array([[1., 0., 0.],
## [0., 1., 0.],
## [0., 0., 1.],
## [0., 0., 1.]])

이제 두 개 이상의 범주형 변수를 OneHotEncoder() 클래스를 사용하여 변환해보자.

1
2
3
4
5
6
model_ohe_m = OneHotEncoder().fit(df2)
model_ohe_m.transform(df2).toarray()
## array([[1., 0., 0., 0., 1., 0., 1., 0.],
## [0., 1., 0., 1., 0., 0., 1., 0.],
## [0., 0., 1., 1., 0., 0., 0., 1.],
## [0., 0., 1., 0., 0., 1., 0., 1.]])

Pandasget_dummies() 함수와는 다르게 각 변수명이 기록되어있지 않다보니 어떤 변수가 어떤 변수의 어떤 범주에 대한 가변수인지 알아보기 어렵다.

일단 “ondel_ohe_m” 객체를 살펴보자.

1
2
3
4
5
6
7
model_ohe_m.feature_names_in_
## array(['cat1', 'cat2', 'num1'], dtype=object)

model_ohe_m.categories_
## [array(['a', 'b', 'c'], dtype=object),
## array(['cat', 'dog', 'rat'], dtype=object),
## array([100, 200], dtype=int64)]

이를 Pandas 데이터프레임 객체로 만들고자 한다면 다음과 같이 코드를 작성할 수 있다. 먼저 각 변수명을 만들어보자.

1
2
3
4
5
6
7
8
9
10
11
12
aa = model_ohe_m.feature_names_in_
bb = model_ohe_m.categories_
cc = [aa[n] + "_" + pd.Series(bb[n]).astype("str") for n in range(len(aa))]
pd.Series(cc).explode()
## 0 cat1_a
## 0 cat1_b
## 0 cat1_c
## 1 cat2_cat
## 1 cat2_dog
## 1 cat2_rat
## 2 num1_100
## 2 num1_200

상기 코드에서 확보한 변수명을 사용하여 데이터프레임을 생성하면 다음과 같다.

1
2
pd.DataFrame(model_ohe_m.transform(df2).toarray(), 
columns = pd.Series(cc).explode())
cat1_a cat1_b cat1_c cat2_cat cat2_dog cat2_rat num1_100 num1_200
0 1.0 0.0 0.0 0.0 1.0 0.0 1.0 0.0
1 0.0 1.0 0.0 1.0 0.0 0.0 1.0 0.0
2 0.0 0.0 1.0 1.0 0.0 0.0 0.0 1.0
3 0.0 0.0 1.0 0.0 0.0 1.0 0.0 1.0

sklearnOneHotEncoder() 클래스를 사용하는 경우 아무래도 Pandasget_dummies() 함수보다 운용시 번거로운 부분이 많다. 그래서 변수명을 붙이는 부분은 상기에 제시한 코드를 기반으로 별도의 사용자 정의 함수를 만들어서 활용하는 것도 방법이다.

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×