3. 안전 운전자 예측
3. 안전 운전자 예측
난이도 | 2 | |||
---|---|---|---|---|
경진대회명 | 포르투 세구로 안전 운전자 예측 경진대회 | |||
미션 | 포르투 세구로 보험사에서 제공한 고객 데이터를 활용해 운전자가 보험을 청구할 확률 예측 | |||
문제유형 | 이진분류 | 평가지표 | 정규화된 지니계수 | |
제출시 사용한 모델 | LightGBM와 XGBoost의 앙상블 | |||
파이썬 버전 | 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 - missingno == 0.4.2 - lightgbm == 3.2.1 - xgboost == 1.4.2. - bayes_opt == 1.2.0 |
학습 목표
- 실제 기업 데이터를 활용한 안전 운전자 예측 경진대회에 참가
- 베이스라인 모델에 시작해 성능이 좋은 모델로 발전
- 실제로 많이 활용되는 여러 가지 고급 모델링 기법 학습
학습 순서
- 경진대회 이해
- 탐색적 데이터 분석
- 베이스라인 모델(LightGBM)
- 성능 개선 I (LightGBM + 피처 엔지니어링 강화, 하이퍼파라미터 최적화)
- 성능 개선 II (XGBoost)
- 성능 개선 III (앙상블)
학습 키워드
- 유형 및 평가 지표 : 이진분류, 정규화된 지니계수
- 탐색적 데이터 분석 : 피처 요약표 응용, 결측값 시각화, 결측값 처리
- 머신러닝 모델 : OOF 예측, LightGBM, XGBoost, 앙상블
- 피처 엔지니어링 : 창의적 피처 엔지니어링
- 하이퍼파라미터 최적화 : 베이지안 최적화
1. 경진대회 이해
포르투 세구로는 지난 20년 간 머신러니을 활용해왔지만, 자동차 보험과 관련해서 보다 정확한 예측 모델을 만들고자 본 대회를 열었다.
사고를 낼 가능성이 낮은 안전 운전자에게는 보험료를 적게 청구하고, 사고 가능성이 높은 난폭 운전자에게는 많은 보험료를 청구해야 한다.
데이터셋 특징
- 운전자가 보험금을 청구할 확률을 정확히 예측하는 모델을 만드는게 본 경진대회의 목표이다.
- 주어진 데이터에는 결측값이 많다. 결측값은 -1로 기록돼 있다.
- 타겟값은 0 또는 1이다. 값이 0이면 운전자가 보험금을 청구하지 않는다는 뜻이고, 1이면 청구한다는 뜻이다.
2. 탐색적 데이터 분석
분석 과정은 아래와 같은 순서로 진행된다.
2.1 데이터 둘러보기
kaggle competitions download -c porto-seguro-safe-driver-prediction
- 본 경진대회에서 예측해야 하는 값은 타겟값이 1일 확률이다.
- 타겟값 0은 운전자가 보험금을 청구하지 않는 경우, 타겟값 1은 청구하는 경우를 의미한다.
- 즉 운전자가 보험금을 청구할 확률이 얼마나 되는지를 예측한다.
데이터 로드 및 shape 확인
데이터를 읽어올 때 index_col 파라미터에 데이터가 가지고 있는 id를 전달한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd
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')
train.shape, test.shape
'''
((595212, 58), (892816, 57))
'''
1
train.head().T
id | 7 | 9 | 13 | 16 | 17 |
---|---|---|---|---|---|
target | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_01 | 2.000000 | 1.000000 | 5.000000 | 0.000000 | 0.000000 |
ps_ind_02_cat | 2.000000 | 1.000000 | 4.000000 | 1.000000 | 2.000000 |
ps_ind_03 | 5.000000 | 7.000000 | 9.000000 | 2.000000 | 0.000000 |
ps_ind_04_cat | 1.000000 | 0.000000 | 1.000000 | 0.000000 | 1.000000 |
ps_ind_05_cat | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_06_bin | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 1.000000 |
ps_ind_07_bin | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_08_bin | 0.000000 | 1.000000 | 1.000000 | 0.000000 | 0.000000 |
ps_ind_09_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_10_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_11_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_12_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_13_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_14 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_15 | 11.000000 | 3.000000 | 12.000000 | 8.000000 | 9.000000 |
ps_ind_16_bin | 0.000000 | 0.000000 | 1.000000 | 1.000000 | 1.000000 |
ps_ind_17_bin | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_18_bin | 0.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_reg_01 | 0.700000 | 0.800000 | 0.000000 | 0.900000 | 0.700000 |
ps_reg_02 | 0.200000 | 0.400000 | 0.000000 | 0.200000 | 0.600000 |
ps_reg_03 | 0.718070 | 0.766078 | -1.000000 | 0.580948 | 0.840759 |
ps_car_01_cat | 10.000000 | 11.000000 | 7.000000 | 7.000000 | 11.000000 |
ps_car_02_cat | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
ps_car_03_cat | -1.000000 | -1.000000 | -1.000000 | 0.000000 | -1.000000 |
ps_car_04_cat | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_car_05_cat | 1.000000 | -1.000000 | -1.000000 | 1.000000 | -1.000000 |
ps_car_06_cat | 4.000000 | 11.000000 | 14.000000 | 11.000000 | 14.000000 |
ps_car_07_cat | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
ps_car_08_cat | 0.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
ps_car_09_cat | 0.000000 | 2.000000 | 2.000000 | 3.000000 | 2.000000 |
ps_car_10_cat | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
ps_car_11_cat | 12.000000 | 19.000000 | 60.000000 | 104.000000 | 82.000000 |
ps_car_11 | 2.000000 | 3.000000 | 1.000000 | 1.000000 | 3.000000 |
ps_car_12 | 0.400000 | 0.316228 | 0.316228 | 0.374166 | 0.316070 |
ps_car_13 | 0.883679 | 0.618817 | 0.641586 | 0.542949 | 0.565832 |
ps_car_14 | 0.370810 | 0.388716 | 0.347275 | 0.294958 | 0.365103 |
ps_car_15 | 3.605551 | 2.449490 | 3.316625 | 2.000000 | 2.000000 |
ps_calc_01 | 0.600000 | 0.300000 | 0.500000 | 0.600000 | 0.400000 |
ps_calc_02 | 0.500000 | 0.100000 | 0.700000 | 0.900000 | 0.600000 |
ps_calc_03 | 0.200000 | 0.300000 | 0.100000 | 0.100000 | 0.000000 |
ps_calc_04 | 3.000000 | 2.000000 | 2.000000 | 2.000000 | 2.000000 |
ps_calc_05 | 1.000000 | 1.000000 | 2.000000 | 4.000000 | 2.000000 |
ps_calc_06 | 10.000000 | 9.000000 | 9.000000 | 7.000000 | 6.000000 |
ps_calc_07 | 1.000000 | 5.000000 | 1.000000 | 1.000000 | 3.000000 |
ps_calc_08 | 10.000000 | 8.000000 | 8.000000 | 8.000000 | 10.000000 |
ps_calc_09 | 1.000000 | 1.000000 | 2.000000 | 4.000000 | 2.000000 |
ps_calc_10 | 5.000000 | 7.000000 | 7.000000 | 2.000000 | 12.000000 |
ps_calc_11 | 9.000000 | 3.000000 | 4.000000 | 2.000000 | 3.000000 |
ps_calc_12 | 1.000000 | 1.000000 | 2.000000 | 2.000000 | 1.000000 |
ps_calc_13 | 5.000000 | 1.000000 | 7.000000 | 4.000000 | 1.000000 |
ps_calc_14 | 8.000000 | 9.000000 | 7.000000 | 9.000000 | 3.000000 |
ps_calc_15_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_calc_16_bin | 1.000000 | 1.000000 | 1.000000 | 0.000000 | 0.000000 |
ps_calc_17_bin | 1.000000 | 1.000000 | 1.000000 | 0.000000 | 0.000000 |
ps_calc_18_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 |
ps_calc_19_bin | 0.000000 | 1.000000 | 1.000000 | 0.000000 | 1.000000 |
ps_calc_20_bin | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
1
test.head().T
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
ps_ind_01 | 0.000000 | 4.000000 | 5.000000 | 0.000000 | 5.000000 |
ps_ind_02_cat | 1.000000 | 2.000000 | 1.000000 | 1.000000 | 1.000000 |
ps_ind_03 | 8.000000 | 5.000000 | 3.000000 | 6.000000 | 7.000000 |
ps_ind_04_cat | 1.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_05_cat | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_06_bin | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 0.000000 |
ps_ind_07_bin | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_08_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_09_bin | 0.000000 | 1.000000 | 1.000000 | 0.000000 | 1.000000 |
ps_ind_10_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_11_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_12_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_13_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_14 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_15 | 12.000000 | 5.000000 | 10.000000 | 4.000000 | 4.000000 |
ps_ind_16_bin | 1.000000 | 1.000000 | 0.000000 | 1.000000 | 1.000000 |
ps_ind_17_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_ind_18_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_reg_01 | 0.500000 | 0.900000 | 0.400000 | 0.100000 | 0.900000 |
ps_reg_02 | 0.300000 | 0.500000 | 0.000000 | 0.200000 | 0.400000 |
ps_reg_03 | 0.610328 | 0.771362 | 0.916174 | -1.000000 | 0.817771 |
ps_car_01_cat | 7.000000 | 4.000000 | 11.000000 | 7.000000 | 11.000000 |
ps_car_02_cat | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
ps_car_03_cat | -1.000000 | -1.000000 | -1.000000 | -1.000000 | -1.000000 |
ps_car_04_cat | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_car_05_cat | -1.000000 | 0.000000 | -1.000000 | -1.000000 | -1.000000 |
ps_car_06_cat | 1.000000 | 11.000000 | 14.000000 | 1.000000 | 11.000000 |
ps_car_07_cat | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
ps_car_08_cat | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
ps_car_09_cat | 2.000000 | 0.000000 | 2.000000 | 2.000000 | 2.000000 |
ps_car_10_cat | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
ps_car_11_cat | 65.000000 | 103.000000 | 29.000000 | 40.000000 | 101.000000 |
ps_car_11 | 1.000000 | 1.000000 | 3.000000 | 2.000000 | 3.000000 |
ps_car_12 | 0.316228 | 0.316228 | 0.400000 | 0.374166 | 0.374166 |
ps_car_13 | 0.669556 | 0.606320 | 0.896239 | 0.652110 | 0.812914 |
ps_car_14 | 0.352136 | 0.358329 | 0.398497 | 0.381445 | 0.385097 |
ps_car_15 | 3.464102 | 2.828427 | 3.316625 | 2.449490 | 3.316625 |
ps_calc_01 | 0.100000 | 0.400000 | 0.600000 | 0.100000 | 0.900000 |
ps_calc_02 | 0.800000 | 0.500000 | 0.600000 | 0.500000 | 0.600000 |
ps_calc_03 | 0.600000 | 0.400000 | 0.600000 | 0.500000 | 0.800000 |
ps_calc_04 | 1.000000 | 3.000000 | 2.000000 | 2.000000 | 3.000000 |
ps_calc_05 | 1.000000 | 3.000000 | 3.000000 | 1.000000 | 4.000000 |
ps_calc_06 | 6.000000 | 8.000000 | 7.000000 | 7.000000 | 7.000000 |
ps_calc_07 | 3.000000 | 4.000000 | 4.000000 | 3.000000 | 1.000000 |
ps_calc_08 | 6.000000 | 10.000000 | 6.000000 | 12.000000 | 10.000000 |
ps_calc_09 | 2.000000 | 2.000000 | 3.000000 | 1.000000 | 4.000000 |
ps_calc_10 | 9.000000 | 7.000000 | 12.000000 | 13.000000 | 12.000000 |
ps_calc_11 | 1.000000 | 2.000000 | 4.000000 | 5.000000 | 4.000000 |
ps_calc_12 | 1.000000 | 0.000000 | 0.000000 | 1.000000 | 0.000000 |
ps_calc_13 | 1.000000 | 3.000000 | 2.000000 | 0.000000 | 0.000000 |
ps_calc_14 | 12.000000 | 10.000000 | 4.000000 | 5.000000 | 4.000000 |
ps_calc_15_bin | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 0.000000 |
ps_calc_16_bin | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 |
ps_calc_17_bin | 1.000000 | 1.000000 | 0.000000 | 1.000000 | 1.000000 |
ps_calc_18_bin | 0.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_calc_19_bin | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
ps_calc_20_bin | 1.000000 | 1.000000 | 0.000000 | 0.000000 | 1.000000 |
1
submission.head()
target | |
---|---|
id | |
0 | 0.0364 |
1 | 0.0364 |
2 | 0.0364 |
3 | 0.0364 |
4 | 0.0364 |
2.2 데이터 분석
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
train.info()
'''
<class 'pandas.core.frame.DataFrame'>
Index: 595212 entries, 7 to 1488027
Data columns (total 58 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 target 595212 non-null int64
1 ps_ind_01 595212 non-null int64
2 ps_ind_02_cat 595212 non-null int64
3 ps_ind_03 595212 non-null int64
4 ps_ind_04_cat 595212 non-null int64
5 ps_ind_05_cat 595212 non-null int64
6 ps_ind_06_bin 595212 non-null int64
7 ps_ind_07_bin 595212 non-null int64
8 ps_ind_08_bin 595212 non-null int64
9 ps_ind_09_bin 595212 non-null int64
10 ps_ind_10_bin 595212 non-null int64
11 ps_ind_11_bin 595212 non-null int64
12 ps_ind_12_bin 595212 non-null int64
13 ps_ind_13_bin 595212 non-null int64
14 ps_ind_14 595212 non-null int64
15 ps_ind_15 595212 non-null int64
16 ps_ind_16_bin 595212 non-null int64
17 ps_ind_17_bin 595212 non-null int64
18 ps_ind_18_bin 595212 non-null int64
19 ps_reg_01 595212 non-null float64
20 ps_reg_02 595212 non-null float64
21 ps_reg_03 595212 non-null float64
22 ps_car_01_cat 595212 non-null int64
23 ps_car_02_cat 595212 non-null int64
24 ps_car_03_cat 595212 non-null int64
25 ps_car_04_cat 595212 non-null int64
26 ps_car_05_cat 595212 non-null int64
27 ps_car_06_cat 595212 non-null int64
28 ps_car_07_cat 595212 non-null int64
29 ps_car_08_cat 595212 non-null int64
30 ps_car_09_cat 595212 non-null int64
31 ps_car_10_cat 595212 non-null int64
32 ps_car_11_cat 595212 non-null int64
33 ps_car_11 595212 non-null int64
34 ps_car_12 595212 non-null float64
35 ps_car_13 595212 non-null float64
36 ps_car_14 595212 non-null float64
37 ps_car_15 595212 non-null float64
38 ps_calc_01 595212 non-null float64
39 ps_calc_02 595212 non-null float64
40 ps_calc_03 595212 non-null float64
41 ps_calc_04 595212 non-null int64
42 ps_calc_05 595212 non-null int64
43 ps_calc_06 595212 non-null int64
44 ps_calc_07 595212 non-null int64
45 ps_calc_08 595212 non-null int64
46 ps_calc_09 595212 non-null int64
47 ps_calc_10 595212 non-null int64
48 ps_calc_11 595212 non-null int64
49 ps_calc_12 595212 non-null int64
50 ps_calc_13 595212 non-null int64
51 ps_calc_14 595212 non-null int64
52 ps_calc_15_bin 595212 non-null int64
53 ps_calc_16_bin 595212 non-null int64
54 ps_calc_17_bin 595212 non-null int64
55 ps_calc_18_bin 595212 non-null int64
56 ps_calc_19_bin 595212 non-null int64
57 ps_calc_20_bin 595212 non-null int64
dtypes: float64(10), int64(48)
memory usage: 267.9 MB
'''
- 데이터 타입: int64, float64
- 피처명은 비식별화 되어있어 각 피처가 어떤 의미인지 알 수 없지만 일정한 형식 있음
- 맨 처음은 모두 ps로 시작
- 분류: ind, reg, car, calc
- 분류 후 해당 분류에서의 일련번호 나옴
- 데이터 종류가 bin이면 이진 피처
- cat이면 명목형 피처
- 데이터 종류가 생략돼 있으면 순서형 피처 또는 연속형 피처
여기서 분류와 일련번호는 비식별화되어 있어 각 분류가 무슨 의미인지 알 수 없다.
info 결과를 보면 모든 피처에 결측값이 없다고 나오지만, 실제로는 값이 누락된 곳에 -1이 입력되어 있다. 이런 경우엔 피처별 결측값 수는 -1 을 np.NaN으로 변환한 다음 갯수를 세면 된다.
훈련 데이터에 결측값이 얼마나 있는지 시각화
피처가 많다보니 결측값을 시각화해서 한눈에 보는게 편하다. 이럴 때 missingno 패키지를 사용한다.
missingno는 결측값을 시각화하는 패키지이다. missingno가 제공하는 bar() 함수를 사용해 훈련 데이터에 결측값이 얼마나 있는지 살펴보자. bar() 함수는 결측값을 막대 그래프 형태로 시각화해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
import missingno as msno
# 훈련 데이터 복사본에서 -1을 np.NaN으로 변환
train_copy = train.copy().replace(-1, np.NaN)
# 결측값을 시각화(처음 28개만)
msno.bar(df = train_copy.iloc[:, 1:29], figsize=(13, 6));
'''
불필요한 출력(리턴 값)을 숨기기 위해서 matplotlib 같은 시각화 함수 뒤에 ;를 붙인다.
맨 끝에 ;를 붙이면, 이 리턴값 출력만 숨겨주고 그래프는 정상 출력된다.
'''
먼저 -1을 np.NaN으로 바꿨다. 훈련 데이터 값을 직접 바꾸면 안 되기 때문에 중간에 Copy() 함수로 복사본을 만들어 진행한다. 피처가 57개나 되기 때문에 피처를 반으로 나눠 처음 28개를 시각화한다.
막대 그래프 높이가 낮을수록 결측값이 많다는 뜻이다. 그래프 아래 피처명이 개재돼 있고 그래프 위에는 정상 값이 몇 개인지가 표시되어 있다.
ps_reg_03, ps_car_03_cat, ps_car_05_cat 피처에 결측값이 많은것을 확인할 수 있다.
나머지 29개 이후 피처들의 결측값 확인
1
msno.bar(df=train_copy.iloc[:, 29:], figsize=(13, 6));
ps_car_14에 결측값이 좀 있고 나머지 피처에는 거의 없다.
결측값을 매트릭스 형태로 시각화
결측값을 매트릭스 형태로 시각화할 수도 있다. bar()대신 matrix() 함수를 사용하면 된다.
1
msno.matrix(df=train_copy.iloc[:, 1:29], figsize=(13, 6));
오른쪽 막대는 결측값의 상대적인 분포를 보여준다. 검은색으로 뾰족하게 튀어나온 부분이 결측값이 몰려있는 행을 의미한다. 왼쪽에 표시된 22는 결측값이 없는 열 개수를, 오른쪽의 28은 전체 열 개수를 뜻한다.
피처 요약표
피처 요약표가 있으면 데이터 관리도 편하고, 추후 그래프를 그릴 때도 활용할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def resumetable(df):
print(f'데이터셋 형상: {df.shape}')
summary = pd.DataFrame(df.dtypes, columns=['데이터 타입'])
summary['결측값 개수'] = (df == -1).sum().values # 피처별 -1 개수 [1]
summary['고윳값 개수'] = df.nunique().values
summary['데이터 종류'] = None
for col in df.columns: # [2]
if 'bin' in col or col == 'target':
summary.loc[col, '데이터 종류'] = '이진형'
elif 'cat' in col:
summary.loc[col, '데이터 종류'] = '명목형'
elif df[col].dtype == float:
summary.loc[col, '데이터 종류'] = '연속형'
elif df[col].dtype == int:
summary.loc[col, '데이터 종류'] = '순서형'
return summary
결측값이 -1 이므로 결측값 개수를 구하려면 -1의 개수를 구해야한다.
[1] 처럼 (df == -1).sum().values 코드로 피처별 -1 개수를 구할 수 있다.
[2] 에서는 for문을 순회하며 데이터 종류를 추가했다. 피처명에 ‘bin’이 포함돼 있거나 타겟 열이면 이진형 데이터이고, ‘cat’이 포함돼 있으면 명목형이다. 데이터 타입이 float면 연속형 데이터 이며, int면 순서형 데이터이다.
1
2
3
4
5
6
summary = resumetable(train)
summary
'''
데이터셋 형상: (595212, 58)
'''
데이터 타입 | 결측값 개수 | 고윳값 개수 | 데이터 종류 | |
---|---|---|---|---|
target | int64 | 0 | 2 | 이진형 |
ps_ind_01 | int64 | 0 | 8 | 순서형 |
ps_ind_02_cat | int64 | 216 | 5 | 명목형 |
ps_ind_03 | int64 | 0 | 12 | 순서형 |
ps_ind_04_cat | int64 | 83 | 3 | 명목형 |
ps_ind_05_cat | int64 | 5809 | 8 | 명목형 |
ps_ind_06_bin | int64 | 0 | 2 | 이진형 |
ps_ind_07_bin | int64 | 0 | 2 | 이진형 |
ps_ind_08_bin | int64 | 0 | 2 | 이진형 |
ps_ind_09_bin | int64 | 0 | 2 | 이진형 |
ps_ind_10_bin | int64 | 0 | 2 | 이진형 |
ps_ind_11_bin | int64 | 0 | 2 | 이진형 |
ps_ind_12_bin | int64 | 0 | 2 | 이진형 |
ps_ind_13_bin | int64 | 0 | 2 | 이진형 |
ps_ind_14 | int64 | 0 | 5 | 순서형 |
ps_ind_15 | int64 | 0 | 14 | 순서형 |
ps_ind_16_bin | int64 | 0 | 2 | 이진형 |
ps_ind_17_bin | int64 | 0 | 2 | 이진형 |
ps_ind_18_bin | int64 | 0 | 2 | 이진형 |
ps_reg_01 | float64 | 0 | 10 | 연속형 |
ps_reg_02 | float64 | 0 | 19 | 연속형 |
ps_reg_03 | float64 | 107772 | 5013 | 연속형 |
ps_car_01_cat | int64 | 107 | 13 | 명목형 |
ps_car_02_cat | int64 | 5 | 3 | 명목형 |
ps_car_03_cat | int64 | 411231 | 3 | 명목형 |
ps_car_04_cat | int64 | 0 | 10 | 명목형 |
ps_car_05_cat | int64 | 266551 | 3 | 명목형 |
ps_car_06_cat | int64 | 0 | 18 | 명목형 |
ps_car_07_cat | int64 | 11489 | 3 | 명목형 |
ps_car_08_cat | int64 | 0 | 2 | 명목형 |
ps_car_09_cat | int64 | 569 | 6 | 명목형 |
ps_car_10_cat | int64 | 0 | 3 | 명목형 |
ps_car_11_cat | int64 | 0 | 104 | 명목형 |
ps_car_11 | int64 | 5 | 5 | 순서형 |
ps_car_12 | float64 | 1 | 184 | 연속형 |
ps_car_13 | float64 | 0 | 70482 | 연속형 |
ps_car_14 | float64 | 42620 | 850 | 연속형 |
ps_car_15 | float64 | 0 | 15 | 연속형 |
ps_calc_01 | float64 | 0 | 10 | 연속형 |
ps_calc_02 | float64 | 0 | 10 | 연속형 |
ps_calc_03 | float64 | 0 | 10 | 연속형 |
ps_calc_04 | int64 | 0 | 6 | 순서형 |
ps_calc_05 | int64 | 0 | 7 | 순서형 |
ps_calc_06 | int64 | 0 | 11 | 순서형 |
ps_calc_07 | int64 | 0 | 10 | 순서형 |
ps_calc_08 | int64 | 0 | 11 | 순서형 |
ps_calc_09 | int64 | 0 | 8 | 순서형 |
ps_calc_10 | int64 | 0 | 26 | 순서형 |
ps_calc_11 | int64 | 0 | 20 | 순서형 |
ps_calc_12 | int64 | 0 | 11 | 순서형 |
ps_calc_13 | int64 | 0 | 14 | 순서형 |
ps_calc_14 | int64 | 0 | 24 | 순서형 |
ps_calc_15_bin | int64 | 0 | 2 | 이진형 |
ps_calc_16_bin | int64 | 0 | 2 | 이진형 |
ps_calc_17_bin | int64 | 0 | 2 | 이진형 |
ps_calc_18_bin | int64 | 0 | 2 | 이진형 |
ps_calc_19_bin | int64 | 0 | 2 | 이진형 |
ps_calc_20_bin | int64 | 0 | 2 | 이진형 |
피처 요약표에서 명목형 피처 추출
1
2
3
4
5
6
7
8
9
summary[summary['데이터 종류'] == '명목형'].index
'''
Index(['ps_ind_02_cat', 'ps_ind_04_cat', 'ps_ind_05_cat', 'ps_car_01_cat',
'ps_car_02_cat', 'ps_car_03_cat', 'ps_car_04_cat', 'ps_car_05_cat',
'ps_car_06_cat', 'ps_car_07_cat', 'ps_car_08_cat', 'ps_car_09_cat',
'ps_car_10_cat', 'ps_car_11_cat'],
dtype='object')
'''
피처 요약표에서 데이터 타입이 실수형인 피처 추출
1
2
3
4
5
summary[summary['데이터 타입'] == 'float64'].index
'''
Index(['ps_reg_01', 'ps_reg_02', 'ps_reg_03', 'ps_car_12', 'ps_car_13', 'ps_car_14', 'ps_car_15', 'ps_calc_01', 'ps_calc_02', 'ps_calc_03'], dtype='object')
'''
2.3 데이터 시각화
타겟값 분포를 활용해 타겟값이 얼마나 불균형한지 알아본다. 또한 이진 피처, 명목형 피처, 순서형 피처의 고윳값별 타겟값 비유을 알아본다.
고윳값별 타겟값 비율을 보면 모델링 시 필요한 피처와 필요 없는 피처를 구분할 수 있다.
시각화 라이브러리 임포트
1
2
3
import seaborn as sns
import matplotlib as mpl
import matplotlib.pyplot as plt
타겟값 분포 확인
write_percent 함수 생성
폰트 : 15
전체 크기: 7x6
비율 표시
제목: Target Distribution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def write_percent(ax, total_size):
for patch in ax.patches:
width = patch.get_width()
height = patch.get_height()
left_coord = patch.get_x()
percent = height / total_size * 100
ax.text(x = left_coord + width / 2,
y = height + total_size * 0.001,
s = '{:1.1f}%'.format(percent),
ha = 'center')
mpl.rc('font', size=15)
plt.figure(figsize=(7, 6))
ax = sns.countplot(data=train, x='target')
write_percent(ax, len(train))
ax.set_title('Target Distribution');
타겟값 0은 96.4%를 차지하며 1은 단 3.6% 만 차지한다. 전체 운전자 중 3.6%만 보험금을 청구했다는 뜻이다. 차 사고가 그리 흔하게 나진 않기 때문에 소수의 운전자만 보험금을 청구했다.(타겟값 불균형)
타겟값이 불균형하기 때문에 비율이 작은 타겟값 1을 잘 예측하는 게 중요하다. 따라서 이번에는 각 피처의 분포를 알아보기 보다는, 각 피처의 고윳값별 타겟값 1 비율을 알아본다. 고윳값별 타겟값 1 비율을 통해 해당 피처가 모델링에 필요한 피처인지 확인할 수 있다.
타겟값의 비율 차이가 크면 비율이 작은 타겟값을 잘 예측하는게 중요하다.
예를들어 피처 A에 고윳값 a, b 가 있다고 하자. 이때 고윳값 a, b별로 타겟값 1 비율이 얼마나 되는지 살펴보려는 것이다. 고윳값별로 타겟값 1 비율이 똑같거나 통계적 유효성이 떨어지면, 즉 통계적으로 유의미한 차이가 없다면 피처 A로는 무언가를 분별하기 어려우므로 예측에 도움이 되지 않는다. 즉 고윳값에 따라 타겟값 비율이 달라야 그 피처가 타겟값 예측에 도움을 준다.
각각의 고윳값마다 타겟값 비율이 다른 피처여야 모델링에 도움이 된다.
타겟값 1 비율의 통계적 유효성이 떨어져도 불필요한 피처가 될 수 있다. 통계적 유효성은 barplot()을 그릴 때 나타나는 신뢰구간으로 판단한다. 신뢰구간이 좁다면 통계적으로 어느 정도 유효하다고 보고, 구간이 넓다면 신뢰하기 어렵다고 보는것이다.
A의 키는 170 ~ 175 정도 돼 vs A의 키는 150 ~ 200 정도 돼
통계적 유효성이 높아야(신뢰구간이 좁아야) 모델링에 도움이 된다.
종합하면, 고윳값별 타겟값 1 비율이 충분히 차이가 나고 신뢰구간도 작은 피처여야 모델링에 도움이 된다. 그렇지 않은 피처는 제거하는게 좋다.
이진 피처 타겟값 분포 확인
위 내용에 유념해 이전 피처의 고윳값별 타겟값 비율을 구해본다.
plot_target_ratio_by_features 함수 생성
df, features, nrows, ncols, size=(12, 18)
폰트: 9
상하 좌우 여백 :0.3, 0.3
색상: Set2
데이터 종류가 이진형인 피처들만 그래프 출력
범례 미표시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.gridspec as gridspec
def plot_target_ratio_by_features(df, features, nrows, ncols, size=(12, 18)):
mpl.rc('font', size=9)
plt.figure(figsize=size)
grid = gridspec.GridSpec(nrows, ncols)
plt.subplots_adjust(hspace=0.3, wspace=0.3)
for idx, feature in enumerate(features):
ax = plt.subplot(grid[idx])
sns.barplot(data=df, x=feature, y='target', hue=feature, palette='Set2', legend=False, ax=ax);
bin_features = summary[summary['데이터 종류'] == '이진형'].index
plot_target_ratio_by_features(train, bin_features, 6, 3)
ps_ind_06_bin 피처 그래프를 먼저 살펴보면 이 피처는 값이 0일 때 타겟값이 1인 비율이 4%(0.04)정도 이다. 나머지 96%는 타겟값이 0이다. 값이 1일 때는 타겟값 1이 2.8% 정도 차지한다.
고윳값 | 타겟값 1비율 | 타겟값 0 비율 |
---|---|---|
0 | 4% | 96% |
1 | 2.8% | 97.2% |
종합하면 ps_ind_06_bin 피처는 고윳값별로 타겟값 비율이 다르다. 따라서 타겟값을 추정하는 예측력이 있다. 추가로 신뢰구간도 좁아 모델링에 도움 되는 피처이다.
제거할 피처 표
피처명 | 제거해야하는 이유 |
---|---|
ps_ind_10_bin ~ ps_ind_13_bin | 신뢰구간이 넓어 통계적 유효성이 떨어짐 |
ps_calc_15_bin ~ ps_calc_20_bin | 고윳값별 타겟값 비율 차이가 없어 타겟값 예측력이 없음 |
위 표가 정답은 아니다. 신뢰구간이 얼마나 넓어야 제거할 피처로 볼지, 고윳값별 타겟값 비율이 얼마나 달라야 제거할 피처로 볼지는 사람마다 다르게 판단할 수 있기 때문이다. 또한 제거할 피처를 찾기 위한 다른 방법도 많다. 피처 선택(또는 피처 제거)에 꼭 한 가지 방법만 쓰는 게 아니다. ‘이런 논리로 제거할 피처를 골라낼 수 있구나’ 정도로 알아두면 된다.
명목형 피처 타겟값 분포 확인
명목형 피차 14개로 동일한 그래프를 그린다. 명목형 피처는 고윳값 개수가 이진 피처보다 많다. 고윳값이 많으면 그래프가 가로로 길어지기 때문에 서브플롯을 2열로 그린다.
- 데이터 종류: 명목형
- 7행 2열 배치
1
2
nom_features = summary[summary['데이터 종류'] == '명목형'].index
plot_target_ratio_by_features(train, nom_features, 7, 2)
-1은 결측값이다. 만약 결측값이 많지 않다면 다른 값으로 대체하고, 결측값이 많다면 해당 피처 자체를 제거한다. 하지만 결측값 자체가 타겟값 예측에 도움을 주는 경우도 있다.
ps_ind_02_cat 피처를 보면 결측값 -1이 다른 고윳값들보다 타겟값 1 비율이 크다. 신뢰구간이 넓다는 점을 감안해도 비율이 확실히 크다. 이런 결측값을 다른 값으로 대체하면 모델 성능이 더 나빠질 수 있다. 결측값 자체가 타겟값에 대한 예측력이 있기 때문이다. 따라서 결측값을 그래도 모델링한다.(-1도 하나의 고윳값이라 간주)
ps_car_02_cat 피처를 보면 -1일 때 타겟값 1 비율은 0%이다. 이 경우만 놓고볼 때 ps_car_02_cat 피처 값이 -1이면 타겟값이 0이라고 판단할 수 있다. 이 역시 결측값이 타겟값을 예측하는데 도움을 준다. 결측값을 포함하는 다른 피처도 비슷한 양상을 보인다.
결측값 자체에 타깃값 예측력이 있다면 고윳값으로 간주(결측값 처리 필요 없음)
ps_ind_02_cat, ps_ind_04_cat, ps_car_01_cat 그래프를 보면 -1을 제외하고 나머지 고윳값은 타겟값 1 비율이 비슷하며 -1일 때의 신뢰구간이 상대적으로 넓다.
그렇더라도 -1의 신뢰하한과 다른 고윳값들의 신뢰상한 간 차이가 크다. 따라서 -1의 신뢰 구간이 넓다는 점을 감안해도 -1과 다른 고윳값들 간 타겟값 1 비율에 차이가 있다. 즉, 고윳값 간 타겟값 1 비율에 차이가 있으므로 모델링에 필요한 피처이다.
그러므로 ps_ind_02_cat, ps_ind_04_cat, ps_car_01_cat 피처는 신뢰구간이 넓더라도 제거하지 않는다.
ps_car_10_cat 피처를 보면 세 고윳값은 타겟값 1의 평균 비율이 비슷하다. 그리고 고윳값 2의 신뢰구간이 유독 넓은것을 확인할 수 있다. 이 피처는 상위권 캐글러 중 ps_car_10_cat 피처를 제거한 사람도 있고, 그대로 둔 사람도 있다. 이럴 때는 해당 피처를 제거한 경우와 제거하지 않은 경우에 성능이 어떻게 다른지 비교해 보는것도 좋다.
여기서는 ps_car_10_cat 피처를 제거하지 않는다.
분석결과 : 명목형 피처는 모두 모델링에 이용
순서형 피처 타겟값 분포 확인
데이터 종류: 순서형
8행 2열
size = 12 x 20
1
2
ord_features = summary[summary['데이터 종류'] == '순서형'].index
plot_target_ratio_by_features(train, ord_features, 8, 2, (12, 20))
ps_ind_14 피처를 보면 고윳값 0, 1, 2, 3의 타겟값 비율은 큰 차이가 없고, 고윳값 4의 신뢰구간은 넓은것을 확인할 수 있다. 신뢰구간이 넓어 통계적 유효성이 떨어지므로 제거한다.
ps_calc_04 부터 ps_calc_14까지는 모두 고윳값별 타겟값 비율이 거의 비슷하다. 타겟값 비율이 다른 고윳값도 있긴 하겠지만, 그 고윳값은 신뢰구간이 넓어 통계적 유효성이 떨어진다. 따라서 이 피처들은 모두 제거한다.
앞서 ‘이진 피처’ 절에서 calc 분류에 속하는 이진 피처는 모두 제거하기로 했다. 순서형 피처도 마찬가지로 calc 분류의 모든 피처를 제거한다.
(분석 결과) 제거해야 할 순서형 피처
피처 명 | 제거해야 하는 이유 |
---|---|
ps_ind_14 | 타겟값 비율의 신뢰구간이 넓어 통계적 유효성이 떨어짐 |
ps_calc_04 ~ ps_calc_14 | 고윳값별 타겟값 비율 차이가 없음. 타겟값 비율이 다르더라도 신뢰구간이 넓어 통계적 유효성이 떨어짐 |
연속형 피처 타겟값 분포 확인
연속형 피처는 연속된 값이므로 고윳값이 굉장히 많아 고윳값별 타겟값 1 비율을 구하기가 힘들다. 따라서 값을 몇 개의 구간으로 나눠서 구간별 타겟값 1 비율을 알아본다.
연속형 데이터를 구간으로 나누려면 판다스의 cut() 함수를 활용하면 딘다. 다음은 cut() 함수를 활용해 여러개의 값을 3개 구간으로 나누는 예이다.
고윳값이 아주 많을 때는 몇 개의 구간으로 나눠 구간별 비율을 알아볼 수 있다.
1
2
3
4
5
6
pd.cut([1.0, 1.5, 2.1, 2.7, 3.5, 4.0], 3)
'''
[(0.997, 2.0], (0.997, 2.0], (2.0, 3.0], (2.0, 3.0], (3.0, 4.0], (3.0, 4.0]]
Categories (3, interval[float64, right]): [(0.997, 2.0] < (2.0, 3.0] < (3.0, 4.0]]
'''
입력과 결과를 비교해보면 다음 그림처럼 변환된 것을 알 수 있다.
이 방식은 연속형 데이터를 범주형 데이터로 바꾸는 효과가 있다.
연속형 피처의 구간별 타겟값 1 비율을 구한다.
1
2
3
4
5
6
7
8
9
10
11
12
cont_features = summary[summary['데이터 종류'] == '연속형'].index
plt.figure(figsize=(12, 16))
grid = gridspec.GridSpec(5, 2)
plt.subplots_adjust(hspace=0.4, wspace=0.2)
for idx, cont_feature in enumerate(cont_features):
train[cont_feature] = pd.cut(train[cont_feature], 5)
ax = plt.subplot(grid[idx])
sns.barplot(data=train, x=cont_feature, y='target', hue=cont_feature, legend=False, palette = 'Set2', ax=ax)
ax.tick_params(axis='x', labelrotation=10)
(분석 결과) 제거해야 할 연속형 피처
피처명 | 제거해야 하는 이유 |
---|---|
ps_calc_01~ps_calc_03 | 구간별 타겟값 비율 차이가 없음 |
clac 분류의 피처는 데이터 종류에 상관없이 모두 제거해야 한다는 결론이 났다. 이 피처들은 고윳값 혹은 구간별로 타겟값 비율이 거의 같다. 즉 타겟값을 구분하는 예측 능력이 떨어진다는 뜻이다.
연속형 피처 상관관계 확인
일반적으로 강한 상관관계를 보이는 두 피처가 있으면 둘 중 하나를 제거하는게 좋다. 상관관계가 강하면 타겟값 예측력도 비슷하다.
상관관계가 강한 피처들은 예측력도 비슷하므로 하나만 남겨두는게 좋다.
상관관계가 얼마나 강한 피처를 제거해야 하는가?
상관계수가 얼마 이상일 때 제거하는 게 좋은지 정해진 기준은 없다. 상황과 모델에 따라 다르다. 더불어, 강한 상관관계를 보이는 두 피처 중 하나를 제거한다고 해서 반드시 성능이 향상되는 것도 아니다.
고려해볼 만한 요소 정도로 생각하면 된다. 아래는 두 피처 간 상관계수에 따른 상관관계를 나타낸 표이다.
피어슨 상관계수 | 상관관계 |
---|---|
0.00 ~ 0.19 | 아주 약함 |
0.20 ~ 0.39 | 약함 |
0.40 ~ 0.59 | 보통 |
0.60 ~ 0.79 | 강함 |
0.80 ~ 1.0 | 아주 강함 |
0.8 이상의 ‘아주 강한’ 상관관게를 보이는 피처가 있다면 제거를 고려해보는 것도 좋은 방법이다.
피처 간 상관관계를 파악하기 위해 히트맵을 그린다. 그 전에 현재 피처에 결측값이 있으므로 결측값을 제거해야 한다.
-1을 np.NaN 으로 변환한 train_copy에서 np.NaN을 제거하면 된다. dropna() 는 np.NaN을 제거해주는 함수이다.
1
train_copy = train_copy.dropna() # np.NaN값 삭제
1
2
3
plt.figure(figsize=(10, 8))
cont_corr = train_copy[cont_features].corr()
sns.heatmap(cont_corr, annot=True, cmap='OrRd');
가장 강한 상관관계를 보이는 피처는 ps_car_12와 ps_car_14이다. 두 피처 간 상관계수는 0.77이다. 둘 중 하나를 제거해야 할 만큼 강한 상관관계를 보이는건 아지만 테스트 결과 ps_car_14 피처를 제거하니 성능이 더 좋아졌다. 성능 향상을 위해 ps_car_14 피처를 추가로 제거한다.
ps_reg_02와 ps_reg_03 피처 간 상관계수는 0.76이다. 두 번째로 강한 상관관계이다. 그러나 테스트 결과 둘 중 하나를 제거하니 오히려 성능이 떨어졌으므로 두 피처는 그대로 남겨둔다.
분석 결과: 상관관계가 강한 피처 제거(ps_car_14)