97dingdong 2022. 12. 20. 17:29

데이터에 범주형 변수가 포함되어 있을 때 문제점

● 데이터에 범주형 변수가 포함되어 있을 경우 대다수의 지도학습 모델이 학습되지 않거나 비정상적으로 학습이 된다.

▷str 타입의 범주형 변수가 포함되면 대다수의 지도학습 모델 자체가 학습이 되지 않는다. str 타입의 범주형 변수가 포함되더라도 그나마 정상적으로 학습되는 모델은 트리 계열 모델밖에 없다.

▷int 혹은 float 타입의 범주형 변수 또한 모델 학습이 가능하지만 비정상적으로 학습이 되기 때문에 적절한 범주형 변수 처리를 해야 한다. 

 

● 모델 학습을 위해 범주형 변수는 반드시 숫자로 변환되어야 하지만, 임의로 설정하는 것은 매우 부적절하다.

예시) 종교 변수: 기독교=1, 불교=2, 천주교=3

→불교는 기독교의 2배라는 등의 대수 관계가 실제로 존재하지 않지만, 이런식으로 변환하면 비정상적인 관계가 생기기 때문에 모델에 악영향을 끼칠수 밖에 없다.

 

● 코드화된 범주형 변수도 적절한 숫자로 변환해줘야 한다.

 

결론: 문자로 된 범주형 변수, 코드화된 범주형 변수를 적절한 숫자로 변환해줘야 한다.

 


범주형 변수 판별 방법

범주형 변수는 상태 공간의 크기가 유한한 변수이다. 

주의할 점은 int 혹은 float 타입으로 정의된 변수는 반드시 연속형 변수가 아닐 수 있다.

즉, 데이터의 type을 보고 "type이 float이니까 연속형, type이 str이니까 범주형" 이런식으로 판단하면 안된다.

그래서 반드시 도메인이나 변수의 상태 공간을 바탕으로 판단해야 한다.

 

예를 들어, 월(month), 시간(hour) 숫자로 되어있지만 범주형 변수이다.

 


범주형 변수 변환 방법 1. 더미화

가장 일반적인 범주형 변수를 변환하는 방법으로, 범주형 변수가 특정 값을 취하는지 여부를 나타내는 더미변수를 생성하는 방법이다.

위에 그림을 부과적으로 설명하자면,

불교 변수는 나머지 변수로 완벽히 추론 가능하므로 기독교, 천주교, 불교 이 세 변수 간에는 상관성이 완벽하게 존재한다. 특징 간 상관성이 완벽하게 존재하는 경우 정상적인 모델 학습이 어려울수 있기 때문에 상관성 제거를 위해 불교 변수를 제거한다.(기독교, 천주교, 불교 제거 / 기독교, 불교, 천주교 제거 / 천주교, 불교, 기독교 제거 중 하나로) 

예외로, 트리계열의 모델을 사용하는 경우에는 설명력을 위해서 변수를 제거 안하는 경우가 있다. 이런 경우 설명력을 위해서 그런거지 예측력을 위해서는 좋은 접근이 아니다.

 

단점: 한 범주형 변수가 상태 공간이 클 때 더미화를 할 경우 추가되는 변수가 엄청나게 많아질수 있다. 예를 들어, 어떤 범주형 변수의 상태 공간의 크기가 100일 때 100개의 변수가 추가된다. 이렇게 변수가 많아지면 차원의 저주 문제로 이어질수 있다. 그리고 데이터가 sparse(희소)해 질수도 있다.

결론적으로 범주형 변수의 상태 공간이 클때는 더미화를 하는게 적합하지 않다.

 

 

범주형 변수 변환 방법 2. 연속형 변수로 치환

범주형 변수의 상태 공간 크기가 클때, 더미화는 과하게 많은 변수를 추가해서 차원의 저주 문제로 이어질 수 있다.

 

라벨 정보(분류, 예측 둘다 가능)를 활용하여 범주 변수를 연속형 변수로 치환하면 기존 변수가 가지는 정보가 일부 손실될 수 있고 활용이 어렵다는 단점이 있으나, 차원의 크기가 변하지 않으며 더 효율적인 변수로 변활할 수 있다는 장점이 있다.

 이 방법을 쓸 때 장점은 더미화를 하는 것과 다르게 차원이 늘어나지 않는다. 그리고 범주형 변수를 라벨과 관련된 연속형 변수로 치환을 해주기 때문에 즉, 라벨을 고려해서 변환했기 때문에 모델의 성능을 높일 수 있다. 단점은 온전한 기존 정보가 손실이 어느정도 일어날 것이다. 위의 그림을 보면 만약에 A가 200, B도 200이라면 A와 B가 같은 값으로 인식하게 되어 정보 손실이 일어날 수도 있다.

 

결론: 상태 공간의 크기가 작으면 더미화, 상태 공간의 크기가 크면 연속형 변수로 치환하는 방법을 사용한다.

 


● 관련 문법: Series.unique()

Series에 포함된 유니크한 값을 반환해주는 함수로, 상태 공간을 확인하는데 사용한다.

 

● 관련 문법: feature_engine.categorical_encoders.OneHotCategoricalEncoder

변경:feature_engine.encoding.OneHotEncoder 

더미화를 하기 위한 함수로, 활용 방법은 sklearn의 인스턴스의 활용 방법과 유사하다.

 

-주요 입력

▷variables: 더미화 대상이 되는 범주형 변수의 이름 목록(주의: 해당 변수는 반드시 str 타입이어야 함, 숫자로 된 범주형 변수인 경우 astype 함수를 사용해서 자료형을 str으로 바꾸고 사용해야 함) → variables에 리스트를 넣어줌

▷drop_last: 한 범주 변수로부터 만든 더미변수 가운데 마지막 더미변수를 제거할 지를 결정

▷top_categories: 한 범주 변수로부터 만든 더미변수 개수를 설정하며, 빈도 기준으로 자름(예를들어, 한 범주형 변수의 상태 공간이 100개일때 100개의 특징을 다 쓰기 어렵기 때문에 그 중에 빈도가 높은 상위 10개만 쓰는 방식이다.)

 

참고사항: pandas.get_dummies()는 이 함수보다 사용이 훨씬 간단하지만, 인스턴스로 저장되는 것이 아니기 때문에 학습 데이터와 새로 들어온 데이터를 똑같이 처리 할 수 없다.

학습 데이터에 포함된 범주형 변수를 처리한 방식으로 새로 들어온 데이터에 적용이 불가능하기 때문에, 실제적으로 활용이 어려워서 머신러닝 모델을 구축할때는 잘 쓰이지 않는다.

 


## 코드 실습 ##

import os
os.chdir(r"C:\Users\82102\Desktop\데이터전처리\머신러닝을 위한 필수 전처리\Part 4. 머신러닝을 위한 필수 전처리\데이터")

import pandas as pd
df= pd.read_csv("car-good.csv")
df.head()

# 특징과 라벨 분리
X = df.drop('Class', axis=1)
Y = df['Class']
# 학습 데이터와 평가 데이터 분리
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(X, Y)
y_train.value_counts()

# 문자 라벨을 숫자로 치환
y_train.replace({"negative":-1, "positive":1}, inplace=True)
y_test.replace({"negative":-1, "positive":1}, inplace=True)
x_train.head() # Buying, Maint, Lug_boot, Safety 변수가 범주형 변수로 판단됨

# 자세한 범주형 변수 판별 -> 모든 변수가 범주형임을 확인
for col in x_train.columns:
    print(col, x_train[col].unique(), len(x_train[col].unique()))

 

  • 더미화를 이용한 범주 변수 처리
x_train = x_train.astype(str) # 모든 변수가 범주형이므로, 더미화를 위해 전부 string 타입으로 변환
from feature_engine.encoding import OneHotEncoder as OHE

# 인스턴스화
dummy_model = OHE(variables=x_train.columns.tolist(),
                 drop_last=True) 
# 더미화 대상 컬럼은 모든 컬럼이기 때문에 x_train.columns를 입력
# variables로 list를 받음->tolist를 이용해서 리스트로 바꿈

# 학습(fit)
dummy_model.fit(x_train)

# transform
d_x_train = dummy_model.transform(x_train)
d_x_test = dummy_model.transform(x_test)
# 더미화를 한 뒤의 모델 테스트
from sklearn.neighbors import KNeighborsClassifier as KNN
model = KNN().fit(d_x_train, y_train)
pred_y = model.predict(d_x_test)

from sklearn.metrics import f1_score
f1_score(y_test, pred_y)

# 더미화를 해서 데이터가 sparse 해지는 경향이 있기 때문에
# 모델을 KNN을 써서 성능을 올리고 싶으면 metric으로 'jaccard'를 쓰면 좀 더 좋은 성능을 기대할 수 있다.
from sklearn.neighbors import KNeighborsClassifier as KNN
model = KNN(metric='jaccard').fit(d_x_train, y_train)
pred_y = model.predict(d_x_test)

from sklearn.metrics import f1_score
f1_score(y_test, pred_y)
# 성능이 조금 오름

 

  • 연속형 변수로 치환
# 라벨 정보를 활용하기 위해서 분할했던 데이터프레임 합침
train_df = pd.concat([x_train, y_train], axis=1)
train_df.head()

# x_train 컬럼들이 모두 범주형 변수이므로 x_train.columns를 for문에 사용
# 만약, 범주형이 아닌 변수가 있을 경우 분할해서 연속형 변수로 치환 후 합침
# x_train_cate = x_train[['범주형 변수'...]], x_test_cate = x_test[['범주형 변수'...]]
for col in x_train.columns:
    # col에 따른 Class의 평균을 나타내는 사전(replace를 쓰기 위해, 사전으로 만듦)
    temp_dict = train_df.groupby(col)['Class'].mean().to_dict()
    #print(temp_dict)
    
    #print('\n', '*'*100, '\n')
    
    # 변수 치환
    train_df[col] = train_df[col].replace(temp_dict)
    #print(train_df[col])
    
    #print('\n', '*'*100, '\n')
    
    # 테스트 데이터도 같이 치환해줘야 함
    x_test[col] = x_test[col].astype(str).replace(temp_dict)
    #print(x_test[col])
    
    #print('\n', '*'*100, '\n')
{'high': -1.0, 'low': -0.8375, 'med': -0.9090909090909091, 'vhigh': -1.0}

 **************************************************************************************************** 

276   -1.000000
859   -0.837500
766   -0.837500
51    -1.000000
708   -0.837500
         ...   
373   -1.000000
275   -1.000000
446   -0.909091
536   -0.909091
749   -0.837500
Name: Buying, Length: 648, dtype: float64

 **************************************************************************************************** 

65    -1.000000
436   -0.909091
814   -0.837500
229   -1.000000
682   -0.837500
         ...   
392   -1.000000
91    -1.000000
128   -1.000000
790   -0.837500
364   -1.000000
Name: Buying, Length: 216, dtype: float64

 **************************************************************************************************** 

{'high': -1.0, 'low': -0.7908496732026143, 'med': -0.9375, 'vhigh': -1.0}

 **************************************************************************************************** 

276   -1.00000
859   -0.79085
766   -0.93750
51    -1.00000
708   -1.00000
        ...   
373   -0.93750
275   -1.00000
446   -1.00000
536   -1.00000
749   -1.00000
Name: Maint, Length: 648, dtype: float64

 **************************************************************************************************** 

65    -1.00000
436   -1.00000
814   -0.79085
229   -1.00000
682   -1.00000
        ...   
392   -0.79085
91    -1.00000
128   -0.93750
790   -0.93750
364   -0.93750
Name: Maint, Length: 216, dtype: float64

 **************************************************************************************************** 

{'2': -0.9345794392523364, '3': -0.9285714285714286, '4': -0.9428571428571428}

 **************************************************************************************************** 

276   -0.934579
859   -0.942857
766   -0.934579
51    -0.942857
708   -0.934579
         ...   
373   -0.942857
275   -0.934579
446   -0.934579
536   -0.942857
749   -0.942857
Name: Doors, Length: 648, dtype: float64

 **************************************************************************************************** 

65    -0.934579
436   -0.934579
814   -0.934579
229   -0.934579
682   -0.928571
         ...   
392   -0.934579
91    -0.942857
128   -0.928571
790   -0.928571
364   -0.942857
Name: Doors, Length: 216, dtype: float64

 **************************************************************************************************** 

{'2': -1.0, '4': -0.8679245283018868}

 **************************************************************************************************** 

276   -1.000000
859   -0.867925
766   -0.867925
51    -0.867925
708   -1.000000
         ...   
373   -0.867925
275   -1.000000
446   -0.867925
536   -0.867925
749   -0.867925
Name: Persons, Length: 648, dtype: float64

 **************************************************************************************************** 

65    -0.867925
436   -1.000000
814   -1.000000
229   -0.867925
682   -0.867925
         ...   
392   -0.867925
91    -1.000000
128   -1.000000
790   -0.867925
364   -1.000000
Name: Persons, Length: 216, dtype: float64

 **************************************************************************************************** 

{'big': -0.9444444444444444, 'med': -0.9369369369369369, 'small': -0.9238095238095239}

 **************************************************************************************************** 

276   -0.944444
859   -0.936937
766   -0.923810
51    -0.944444
708   -0.944444
         ...   
373   -0.936937
275   -0.936937
446   -0.936937
536   -0.936937
749   -0.923810
Name: Lug_boot, Length: 648, dtype: float64

 **************************************************************************************************** 

65    -0.923810
436   -0.936937
814   -0.936937
229   -0.936937
682   -0.944444
         ...   
392   -0.936937
91    -0.923810
128   -0.923810
790   -0.944444
364   -0.936937
Name: Lug_boot, Length: 216, dtype: float64

 **************************************************************************************************** 

{'high': -0.8823529411764706, 'low': -1.0, 'med': -0.9252336448598131}

 **************************************************************************************************** 

276   -1.000000
859   -0.925234
766   -0.925234
51    -1.000000
708   -1.000000
         ...   
373   -0.925234
275   -0.882353
446   -0.882353
536   -0.882353
749   -0.882353
Name: Safety, Length: 648, dtype: float64

 **************************************************************************************************** 

65    -0.882353
436   -0.925234
814   -0.925234
229   -0.925234
682   -0.925234
         ...   
392   -0.882353
91    -0.925234
128   -0.882353
790   -0.925234
364   -0.925234
Name: Safety, Length: 216, dtype: float64

 **************************************************************************************************** 
# 잘 바뀜
train_df.head()

# 모델링을 위해 학습 데이터를 다시 라벨과 분리
x_train = train_df.drop('Class', axis=1)
y_train = train_df['Class']
# 연속형 변수로 치환한 뒤의 모델 테스트
model = KNN().fit(x_train, y_train)
pred_y = model.predict(x_test)

f1_score(y_test, pred_y)
# 라벨을 고려한 전처리이므로 더미화보다 좋은 결과가 나온 것을 확인
# 차원도 줄고 모델의 성능이 더 좋아짐