Post

2. Categorical Feature Encoding Challenge

2. Categorical Feature Encoding Challenge



난이도2   
경진대회명범주형 데이터 이진분류 경진대회   
미션다양한 범주형 데이터를 활용해 타겟값 1에 속할 확률 예측   
문제유형이진분류평가지표ROC AUC 
제출시 사용한 모델로지스틱 회귀   
파이썬 버전3.7.10   
사용 라이브러리 버전numpy == 1.19.5
pandas == 1.3.2
seaborn == 0.11.2
matplotlib == 3.4.3
sklearn == 0.32.2
scipy ==1.7.1
   


학습 목표

  • 범주형 데이터를 활용해 이진분류하는 경진대회에 참가
  • 피처 구성을 이해하기 위해 탐색적 데이터 분석을 자세히 학습
  • 데이터 특성에 따른 맞춤형 인코딩 방법 학습
  • 최종적으로 프라이빗 리더보드에서 2등을 기록하는 모델 제작


학습 순서

  1. 경진대회 이해
  2. 탐색적 데이터 분석
  3. 베이스라인 모델(로지스틱 회귀)
  4. 성능개선 I (피처 엔지니어링 강화)
  5. 성능 개선 II (검증 데이터까지 훈련에 이용)


학습 키워드

  • 유형 및 평가 지표 : 이진분류, ROC AUC
  • 탐색적 데이터 분석 : 피처 요약표, 타겟값 분포, 이진/명목형/순서형/날짜 피처 분포
  • 머신러닝 모델 : 로지스틱 회귀
  • 피처 앤지니어링 : 원-핫 인코딩, 피처 맞춤 인코딩, 피처 스케일링
  • 하이퍼파라미터 최적화 : 그리드 서치





1. 경진대회 이해

  • 본 대회의 목표는 범주형 feature 23개를 활용해 해당 데이터가 타겟값 1에 속할 확률을 예측하는 것이다.


  • 본 경진대회의 특징
    • 본 대회는 인위적으로 만든 데이터를 제공한다. (연습용으로는 인공 데이터가 오히려 좋다.)

    • 각 피처와 타겟값의 의미를 알 수 없다. 자전거 대여 수요 예측은 다르게 날씨가 좋을수록 자전거 대여 수량이 많을것이라 예상할 수 있었지만 이런 경우 활용할 수 있는 배경 지식이 없기 때문에 순전히 데이터만 보고 접근해야 한다.

    • 제공되는 데이터가 모두 범주형이다. 값이 두개로만 구성된 데이터부터 순서형 데이터(ordinal data), 명목형 데이터(nominal data), 날짜 데이터까지 다양하게 제공된다.
      • bin_로 시작하는 피처는 이진 피처, nom_로 시작하는 피처는 명목형 피처, ord_로 시작하는 피처는 순서형 피처이다.
      • 순서형 피처 중 ord_3, ord_4, ord_5 는 알파벳순으로 고윳값 순서가 매져있다.
    • 타겟값도 범주형 데이터 이다. 0과 1 두개로 구성되어 있다.


확률 예측

분류 문제에서는 타겟값이 0이냐 1이냐가 아니라 ‘1일 확률’을 예측한다. 보통 음성 값일 확률보다는 양성 값일 확률로 예측한다. 일반적으로 0은 음성, 1은 양성을 나타낸다. 스팸 메일을 거르는 문제라면 0은 일반 메일, 1은 스팸 메일을 뜻한다. 암을 진단하는 문제에서도 0은 정상, 1은 암 진단을 의미한다. 이런 문제들에서 우리가 알고싶은건 스팸 메일일 확률이나 암일 확률이기 때문에 양성값인 1일 확률을 예측한다.




2. 탐색적 데이터 분석

분석 과정은 아래와 같은 순서로 진행된다.


2.1 탐색적 데이터 분석

kaggle competitions download -c cat-in-the-dat



데이터 로드 및 shape 확인

데이터를 읽어올 때 index_col 파라미터에 데이터가 가지고 있는 id를 전달했다. 열 이름을 전달하면 해당 열을 인덱스로 지정하며 명시하지 않으면 0부터 시작하는 새로운 열을 생성해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd
import numpy as np

data_path = 'datasets/'

train = pd.read_csv(data_path + 'train.csv', index_col = 'id')
test = pd.read_csv(data_path + 'test.csv', index_col = 'id')
submission = pd.read_csv(data_path + 'sample_submission.csv', index_col = 'id')

print(train.shape, test.shape)

'''
(300000, 24) (200000, 23)
'''



train, test, submission의 첫 5행 출력

1
train.head()
bin_0bin_1bin_2bin_3bin_4nom_0nom_1nom_2nom_3nom_4...nom_9ord_0ord_1ord_2ord_3ord_4ord_5daymonthtarget
id
0000TYGreenTriangleSnakeFinlandBassoon...2f4cb3d512GrandmasterColdhDkr220
1010TYGreenTrapezoidHamsterRussiaPiano...f83c56c211GrandmasterHotaAbF780
2000FYBlueTrapezoidLionRussiaTheremin...ae6800dd01ExpertLava HothRJc720
3010FYRedTrapezoidSnakeCanadaOboe...8270f0d711GrandmasterBoiling HotiDkW211
4000FNRedTrapezoidLionCanadaOboe...b164b72a71GrandmasterFreezingaRqP780

5 rows × 24 columns



train.head()로 출력하면 중간에 피처가 생략된 상태로 출력돼 보기 불편하다. 이때 T 메서드를 호출하면 한눈에 보기 편하게 행과 열의 위치가 바뀐다.

1
train.head().T
id01234
bin_000000
bin_101010
bin_200000
bin_3TTFFF
bin_4YYYYN
nom_0GreenGreenBlueRedRed
nom_1TriangleTrapezoidTrapezoidTrapezoidTrapezoid
nom_2SnakeHamsterLionSnakeLion
nom_3FinlandRussiaRussiaCanadaCanada
nom_4BassoonPianoThereminOboeOboe
nom_550f116bcfb3b4d25d03263bdce5f122465925b0f5acd5
nom_63ac1b8814fbcb50fc10922e3cb850d7ad46a1fe17a1fd
nom_768f6ad3e93b6dd5612a6a36f527ec69236eb04ddac2be
nom_8c389000ab4cd920251de9c9f6844ade6ab69cb43ab175
nom_92f4cb3d51f83c56c21ae6800dd08270f0d71b164b72a7
ord_021111
ord_1GrandmasterGrandmasterExpertGrandmasterGrandmaster
ord_2ColdHotLava HotBoiling HotFreezing
ord_3hahia
ord_4DARDR
ord_5krbFJckWqP
day27727
month28218
target00010



테스트 데이터 인덱스가 300,000 부터 시작하기 때문에 submission id는 300,000 부터 시작한다.

1
submission.head()
target
id
3000000.5
3000010.5
3000020.5
3000030.5
3000040.5



1
test.head().T
id300000300001300002300003300004
bin_000100
bin_100001
bin_210111
bin_3TTFTF
bin_4YNYYN
nom_0BlueRedBlueRedRed
nom_1TriangleSquareSquareStarTrapezoid
nom_2AxolotlLionDogCatDog
nom_3FinlandCanadaChinaChinaChina
nom_4PianoPianoPianoPianoPiano
nom_50870b0a5da5c276589568550f04c5725677ee70a6270d
nom_69ceb19dd61ad7442421fe17a1fda6542cec097b6a3518
nom_7530f8ecc312e6161c927d6df03f30c63bd0ca42386065
nom_89d117320c46ae3059cb759e21f00b6ec68fff91f3b1ee
nom_93c49b42b82857710756f323c53fb5de3dcc4967cfa9c9
ord_021213
ord_1NoviceMasterExpertContributorGrandmaster
ord_2WarmLava HotFreezingLava HotLava Hot
ord_3jlabl
ord_4PAGQW
ord_5beRPtPkeqK
day57124
month11512311




2.2 피처 요약표 생성

피처 요약표는 피처별 데이터 타입, 결측값 개수, 고윳값 개수, 실제 입력값 등을 정리한 표이다.


피처 요약표를 만드는 3단계

1. 피처별 데이터 타입 DataFrame 생성*

2. 인덱스 재설정 후 열 이름 변경

3. 결측값 개수, 고윳값 개수, 1~3행 입력값 추가


1. 피처별 데이터 타입 DataFrame 생성

DataFrame 객체에서 dtypes를 호출하면 피처별 데이터 타입을 반환해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
train.dtypes

'''
bin_0      int64
bin_1      int64
bin_2      int64
bin_3     object
bin_4     object
nom_0     object
nom_1     object
nom_2     object
nom_3     object
nom_4     object
nom_5     object
nom_6     object
nom_7     object
nom_8     object
nom_9     object
ord_0      int64
ord_1     object
ord_2     object
ord_3     object
ord_4     object
ord_5     object
day        int64
month      int64
target     int64
dtype: object
'''



이 값을 입력으로 DataFrame을 새로 생성하면 피처별 데이터 타입이 입력된 DataFrame이 만들어진다. 이 때 다음과 같이 columns 파라미터로 원하는 열 이름을 설정할 수 있다.

1
2
summary = pd.DataFrame(train.dtypes, columns = ['데이터 타입'])
summary.head()
데이터 타입
bin_0int64
bin_1int64
bin_2int64
bin_3object
bin_4object



2. 인덱스 재설정 후 열 이름 변경

현재 피처 이름들이 인덱스로 사용중이기 떄문에 현재 인덱스를 열로 옮기고 새로운 인덱스를 만든다.

reset_index()를 호출하면 현재 인덱스를 열로 옮기고 새로운 인덱스를 만든다. 새로운 인덱스는 0부터 시작해 1씩 증가하는 정수이며, 옮겨진 열의 이름은 ‘index’가 된다.

1
2
3
summary = summary.reset_index()

summary.head()
index데이터 타입
0bin_0int64
1bin_1int64
2bin_2int64
3bin_3object
4bin_4object



현재 피처 이름이 포함된 열 이름이 index 이기 때문에 rename()함수를 사용해 열 이름을 ‘피처’로 바꾼다.

1
2
3
summary = summary.rename(columns={'index':'피처'})

summary.head()
피처데이터 타입
0bin_0int64
1bin_1int64
2bin_2int64
3bin_3object
4bin_4object



3. 결측값 개수, 고유값 개수, 1~3행 입력값 추가

DataFrame에 결측값 개수, 고윳값 개수, 첫 세 개 행에 입력된 값을 추가해 보자.

  1. 피처별 결측값 개수를 DataFrame에 추가한다. isnull()은 결측값 포함 여부를 True, False로 반환하는 함수이다. True는 1, False는 0으로 간주되어 isnull()을 적용한 DataFrame에 sum() 함수를 호출하면 True의 개수, 즉 피처별 결측값 개수를 구해준다.

  2. 피처별 고유값 개수를 추가한다. nunique()는 피처별 고윳값 개수를 구하는 함수이다.

  3. 훈련 데이터 1~3행에 입력된 값을 요약표 DataFrame에 추가한다. 각 피처에 실제 어떤 값들이 들어있는지 확인하기 위함이다. loc[0]은 첫 번째 행, loc[1]은 두번째 행, loc[2]는 세번째 행의 값을 의미한다.

values 를 적용하지 않으면 반환 타입인 Series는 인덱스(bin_0, bin_1 등)과 값(0 등)의 쌍으로 이루어져 있다. 따라서 values를 호출해 값만 추출하여 summary에 추가한다.


axis 값기준 방향작동 방향의미
axis=0행(row) 기준위→아래 방향열(column) 단위 계산
axis=1열(column) 기준왼→오 방향행(row) 단위 계산
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 피처별 결측값 개수
summary['결측값 개수'] = train.isnull().sum().values
'''
- isnull() ➝ 원소 단위로 작동해서 True / False 값을 가진 동일 크기의 DataFrame을 반환
- sum() ➝ 기본적으로 열 단위(axis=0)로 작동해서 각 열의 결측값 개수를 반환
'''

# 2. 피처별 고윳값 개수
summary['고윳값 개수'] = train.nunique().values
'''
nunique()는 기본적으로 열(axis=0) 단위로 작동
'''
# 3. 1~3행에 입력되어 있는 값
summary['첫 번째 값'] = train.loc[0].values
summary['두 번째 값'] = train.loc[1].values
summary['세 번째 값'] = train.loc[1].values

summary.head()
피처데이터 타입결측값 개수고윳값 개수첫 번째 값두 번째 값세 번째 값
0bin_0int6402000
1bin_1int6402011
2bin_2int6402000
3bin_3object02TTT
4bin_4object02YYY



4. 피처 요약표 생성함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def resumetable(df):
    print(f'데이터셋 형상: {df.shape}')

    # 스텝 1: 피처별 데이터 타입 DataFrame 생성
    summary = pd.DataFrame(df.dtypes, columns=['데이터 타입'])
    
    # 스텝 2: 인덱스 재설정 후 열 이름 변경
    # 2-1: 인덱스 재설정
    summary = summary.reset_index()
    # 2-2: 열 이름 변경
    summary = summary.rename(columns={'index': '피처'})
    
    # 스텝 3: 결측값 개수, 고윳값 개수, 1~3행 입력값 추가
    # 피처별 결측값 개수
    summary['결측값 개수'] = df.isnull().sum().values    
    # 피처별 고윳값 개수
    summary['고윳값 개수'] = df.nunique().values
    # 1~3행에 입력되어 있는 값
    summary['첫 번째 값'] = df.loc[0].values
    summary['두 번째 값'] = df.loc[1].values
    summary['세 번째 값'] = df.loc[2].values

    return summary

resumetable(train)

데이터셋 형상: (300000, 24)

피처데이터 타입결측값 개수고윳값 개수첫 번째 값두 번째 값세 번째 값
0bin_0int6402000
1bin_1int6402010
2bin_2int6402000
3bin_3object02TTF
4bin_4object02YYY
5nom_0object03GreenGreenBlue
6nom_1object06TriangleTrapezoidTrapezoid
7nom_2object06SnakeHamsterLion
8nom_3object06FinlandRussiaRussia
9nom_4object04BassoonPianoTheremin
10nom_5object022250f116bcfb3b4d25d03263bdce5
11nom_6object05223ac1b8814fbcb50fc10922e3cb8
12nom_7object0122068f6ad3e93b6dd5612a6a36f527
13nom_8object02215c389000ab4cd920251de9c9f684
14nom_9object0119812f4cb3d51f83c56c21ae6800dd0
15ord_0int6403211
16ord_1object05GrandmasterGrandmasterExpert
17ord_2object06ColdHotLava Hot
18ord_3object015hah
19ord_4object026DAR
20ord_5object0192krbFJc
21dayint6407277
22monthint64012282
23targetint6402000




2.3 피처 요약표 해석

  1. 이진(binary) 피처: bin_0 ~ bin_4

  2. 명목형(nominal) 피처: nom_0 ~ nom_9

  3. 순서형(ordinal) 피처: ord_0 ~ ord_5

  4. 그 외 피처: day, month, target


1. 이진(binary) 피처: bin_0 ~ bin_4

  • 이진 피처들은 고윳값이 모두 2개이다. 이중 bin_0, bin_1, bin_2 는 데이터 타입이 int64고, 실젯값이 0 또는 1로 구성돼 있다.

  • bin_3, bin_4는 object 타입이고, 실젯값은 T 또는 F(bin_3 피처), Y 또는 N(bin_4 피처) 이다.

  • 따라서 T와 Y는 1로, F와 N은 0으로 인코딩 한다.


2. 명목형(nominal) 피처: nom_0 ~ nom_9

  • 명목형 피처는 모두 object 타입이고 결측값은 없다. nom_0 부터 nom_4는 고윳값이 6개 이하인데, nom_5 부터 nom_9는 고윳값이 많으며 의미를 알 수 없는 값을 확인할 수 있다.


3. 순서형(ordinal) 피처: ord_0 ~ ord_5

  • ord_0 피처만 int64 타입이고 나머지는 object 타입니다. 명목형 데이터와 다르게 순서형 데이터는 순서가 매우 중요하다.

  • 순서에 따라 타겟값에 미치는 영향이 다르기 때문에 순서에 유의하여 인코딩 해야한다.



ord_0, ord_1, ord_2 feature 고윳값 확인

unique()함수를 통해 고윳값을 구할 수 있다.

1
2
3
4
5
6
7
8
9
for i in range(3):
	feature = 'ord_' + str(i)
	print(f'{feature} 고윳값: {train[feature].unique()}')

''' 
ord_0 고윳값: [2 1 3]
ord_1 고윳값: ['Grandmaster' 'Expert' 'Novice' 'Contributor' 'Master']
ord_2 고윳값: ['Cold' 'Hot' 'Lava Hot' 'Boiling Hot' 'Freezing' 'Warm']
'''
  • unique() 함수는 고윳값이 등장한 순으로 출력한다. ord_0 피처의 고윳값은 숫자 크기에 순서를 맞추면 되고 ord_1 피처의 고윳값은 등급에 따라 Novice, Contributor, Expert, Master, Grandmaster 순으로 맞춘다.

  • ord_2 피처는 춥고 더움 정도를 나타내므로 Freezing, Cold, Warm, Hot, Boiling Hot, Lava Hot 순으로 맞춘다.

미리 정리해놔야 인코딩할 때 순서에 맞게 매핑하기 편하다.



고윳값 개수가 많은 ord_3, ord_4, ord_5 feature 고윳값 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
for i in range(3, 6):
	feature = 'ord_' + str(i)
	print(f'{feature} 고윳값 : {train[feature].unique()}', end='\n\n')

'''
ord_3 고윳값 : ['h' 'a' 'i' 'j' 'g' 'e' 'd' 'b' 'k' 'f' 'l' 'n' 'o' 'c' 'm']

ord_4 고윳값 : ['D' 'A' 'R' 'E' 'P' 'K' 'V' 'Q' 'Z' 'L' 'F' 'T' 'U' 'S' 'Y' 'B' 'H' 'J'
 'N' 'G' 'W' 'I' 'O' 'C' 'X' 'M']

ord_5 고윳값 : ['kr' 'bF' 'Jc' 'kW' 'qP' 'PZ' 'wy' 'Ed' 'qo' 'CZ' 'qX' 'su' 'dP' 'aP'
 'MV' 'oC' 'RL' 'fh' 'gJ' 'Hj' 'TR' 'CL' 'Sc' 'eQ' 'kC' 'qK' 'dh' 'gM'
 'Jf' 'fO' 'Eg' 'KZ' 'Vx' 'Fo' 'sV' 'eb' 'YC' 'RG' 'Ye' 'qA' 'lL' 'Qh'
 'Bd' 'be' 'hT' 'lF' 'nX' 'kK' 'av' 'uS' 'Jt' 'PA' 'Er' 'Qb' 'od' 'ut'
 'Dx' 'Xi' 'on' 'Dc' 'sD' 'rZ' 'Uu' 'sn' 'yc' 'Gb' 'Kq' 'dQ' 'hp' 'kL'
 'je' 'CU' 'Fd' 'PQ' 'Bn' 'ex' 'hh' 'ac' 'rp' 'dE' 'oG' 'oK' 'cp' 'mm'
 'vK' 'ek' 'dO' 'XI' 'CM' 'Vf' 'aO' 'qv' 'jp' 'Zq' 'Qo' 'DN' 'TZ' 'ke'
 'cG' 'tP' 'ud' 'tv' 'aM' 'xy' 'lx' 'To' 'uy' 'ZS' 'vy' 'ZR' 'AP' 'GJ'
 'Wv' 'ri' 'qw' 'Xh' 'FI' 'nh' 'KR' 'dB' 'BE' 'Bb' 'mc' 'MC' 'tM' 'NV'
 'ih' 'IK' 'Ob' 'RP' 'dN' 'us' 'dZ' 'yN' 'Nf' 'QM' 'jV' 'sY' 'wu' 'SB'
 'UO' 'Mx' 'JX' 'Ry' 'Uk' 'uJ' 'LE' 'ps' 'kE' 'MO' 'kw' 'yY' 'zU' 'bJ'
 'Kf' 'ck' 'mb' 'Os' 'Ps' 'Ml' 'Ai' 'Wc' 'GD' 'll' 'aF' 'iT' 'cA' 'WE'
 'Gx' 'Nk' 'OR' 'Rm' 'BA' 'eG' 'cW' 'jS' 'DH' 'hL' 'Mf' 'Yb' 'Aj' 'oH'
 'Zc' 'qJ' 'eg' 'xP' 'vq' 'Id' 'pa' 'ux' 'kU' 'Cl']
'''

ord_3, ord_4, ord_5 피처는 알파벳순으로 정렬되어 있다. 이 피처들은 알파벳순으로 인코딩 한다.



그 외 features: day, month, target

1
2
3
4
5
6
7
8
9
print('day 고윳값: ', train['day'].unique())
print('month 고윳값: ', train['month'].unique())
print('target 고윳값: ', train['target'].unique())

'''
day 고윳값:  [2 7 5 4 3 1 6]
month 고윳값:  [ 2  8  1  4 10  3  7  9 12 11  5  6]
target 고윳값:  [0 1]
'''
  • day 피처의 고윳값이 7개 이다. 요일을 나타낸다고 짐작할 수 있다.

  • month 피처의 고윳값은 1부터 12이다. 월을 나타낸다.

  • 타겟값은 0 또는 1로 구성돼 있다.

This post is licensed under CC BY 4.0 by the author.