타이타닉에 대한 예제는 어느정도 진행했으므로 이번엔 <Porto Seguro's Safe Driver Prediction> 에 대해 공부해보려 한다.
https://www.kaggle.com/c/porto-seguro-safe-driver-prediction
먼저 이유한님의 커리큘럼에서 첫 번째 노트북인 <Data Preparation & Exploration> 을 보려한다. 이 노트북은 Bert Carremans이 3년 전에 작성하였고, 2021년 1월 12일 기준 42,118회 의 조회수와 366표 의 투표를 받은 노트북이다.
https://www.kaggle.com/bertcarremans/data-preparation-exploration
이 노트북은 결과 도출이 목적이 아닌 데이터 모델링을 위한 팁을 제공한다고 생각하면 된다. 노트북의 주요 섹션은 아래와 같다.
import pandas as pd # 0.25.1
import numpy as np # 1.18.5
import matplotlib.pyplot as plt # 3.2.2
import seaborn as sns # 0.10.1
# scikit-learn 0.23.1
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.feature_selection import VarianceThreshold, SelectFromModel
from sklearn.utils import shuffle
from sklearn.ensemble import RandomForestClassifier
import warnings
warnings.filterwarnings('ignore')
pd.set_option('display.max_columns', 100) # 표를 출력할 때 최대 열 수를 설정
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
Kaggle 데이터 설명에 대한 발췌:
먼저 이 정보들을 알아보기 위해 head를 이용해서 확인해보자.
train.head()
id | target | ps_ind_01 | ps_ind_02_cat | ps_ind_03 | ps_ind_04_cat | ps_ind_05_cat | ps_ind_06_bin | ps_ind_07_bin | ps_ind_08_bin | ps_ind_09_bin | ps_ind_10_bin | ps_ind_11_bin | ps_ind_12_bin | ps_ind_13_bin | ps_ind_14 | ps_ind_15 | ps_ind_16_bin | ps_ind_17_bin | ps_ind_18_bin | ps_reg_01 | ps_reg_02 | ps_reg_03 | 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 | ps_car_11 | ps_car_12 | ps_car_13 | ps_car_14 | ps_car_15 | ps_calc_01 | ps_calc_02 | ps_calc_03 | ps_calc_04 | ps_calc_05 | ps_calc_06 | ps_calc_07 | ps_calc_08 | ps_calc_09 | ps_calc_10 | ps_calc_11 | ps_calc_12 | ps_calc_13 | ps_calc_14 | ps_calc_15_bin | ps_calc_16_bin | ps_calc_17_bin | ps_calc_18_bin | ps_calc_19_bin | ps_calc_20_bin | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 7 | 0 | 2 | 2 | 5 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 11 | 0 | 1 | 0 | 0.7 | 0.2 | 0.718070 | 10 | 1 | -1 | 0 | 1 | 4 | 1 | 0 | 0 | 1 | 12 | 2 | 0.400000 | 0.883679 | 0.370810 | 3.605551 | 0.6 | 0.5 | 0.2 | 3 | 1 | 10 | 1 | 10 | 1 | 5 | 9 | 1 | 5 | 8 | 0 | 1 | 1 | 0 | 0 | 1 |
1 | 9 | 0 | 1 | 1 | 7 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 0 | 0 | 1 | 0.8 | 0.4 | 0.766078 | 11 | 1 | -1 | 0 | -1 | 11 | 1 | 1 | 2 | 1 | 19 | 3 | 0.316228 | 0.618817 | 0.388716 | 2.449490 | 0.3 | 0.1 | 0.3 | 2 | 1 | 9 | 5 | 8 | 1 | 7 | 3 | 1 | 1 | 9 | 0 | 1 | 1 | 0 | 1 | 0 |
2 | 13 | 0 | 5 | 4 | 9 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 12 | 1 | 0 | 0 | 0.0 | 0.0 | -1.000000 | 7 | 1 | -1 | 0 | -1 | 14 | 1 | 1 | 2 | 1 | 60 | 1 | 0.316228 | 0.641586 | 0.347275 | 3.316625 | 0.5 | 0.7 | 0.1 | 2 | 2 | 9 | 1 | 8 | 2 | 7 | 4 | 2 | 7 | 7 | 0 | 1 | 1 | 0 | 1 | 0 |
3 | 16 | 0 | 0 | 1 | 2 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 8 | 1 | 0 | 0 | 0.9 | 0.2 | 0.580948 | 7 | 1 | 0 | 0 | 1 | 11 | 1 | 1 | 3 | 1 | 104 | 1 | 0.374166 | 0.542949 | 0.294958 | 2.000000 | 0.6 | 0.9 | 0.1 | 2 | 4 | 7 | 1 | 8 | 4 | 2 | 2 | 2 | 4 | 9 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 17 | 0 | 0 | 2 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 1 | 0 | 0 | 0.7 | 0.6 | 0.840759 | 11 | 1 | -1 | 0 | -1 | 14 | 1 | 1 | 2 | 1 | 82 | 3 | 0.316070 | 0.565832 | 0.365103 | 2.000000 | 0.4 | 0.6 | 0.0 | 2 | 2 | 6 | 3 | 10 | 2 | 12 | 3 | 1 | 1 | 3 | 0 | 0 | 0 | 1 | 1 | 0 |
변수의 특징:
train.shape
(595212, 59)
현재 train 셋에는 59개의 변수와 595212개의 행이 있다. 중복된 행이 있는지 알아보자.
train.drop_duplicates()
train.shape
(595212, 59)
중복된 행은 없는 것으로 보인다.
test.shape
(892816, 58)
test의 변수가 하나 누락된 것을 알 수 있는데 이는 target 변수일 것이다.
train.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 595212 entries, 0 to 595211
Data columns (total 59 columns):
id 595212 non-null int64
target 595212 non-null int64
ps_ind_01 595212 non-null int64
ps_ind_02_cat 595212 non-null int64
ps_ind_03 595212 non-null int64
ps_ind_04_cat 595212 non-null int64
ps_ind_05_cat 595212 non-null int64
ps_ind_06_bin 595212 non-null int64
ps_ind_07_bin 595212 non-null int64
ps_ind_08_bin 595212 non-null int64
ps_ind_09_bin 595212 non-null int64
ps_ind_10_bin 595212 non-null int64
ps_ind_11_bin 595212 non-null int64
ps_ind_12_bin 595212 non-null int64
ps_ind_13_bin 595212 non-null int64
ps_ind_14 595212 non-null int64
ps_ind_15 595212 non-null int64
ps_ind_16_bin 595212 non-null int64
ps_ind_17_bin 595212 non-null int64
ps_ind_18_bin 595212 non-null int64
ps_reg_01 595212 non-null float64
ps_reg_02 595212 non-null float64
ps_reg_03 595212 non-null float64
ps_car_01_cat 595212 non-null int64
ps_car_02_cat 595212 non-null int64
ps_car_03_cat 595212 non-null int64
ps_car_04_cat 595212 non-null int64
ps_car_05_cat 595212 non-null int64
ps_car_06_cat 595212 non-null int64
ps_car_07_cat 595212 non-null int64
ps_car_08_cat 595212 non-null int64
ps_car_09_cat 595212 non-null int64
ps_car_10_cat 595212 non-null int64
ps_car_11_cat 595212 non-null int64
ps_car_11 595212 non-null int64
ps_car_12 595212 non-null float64
ps_car_13 595212 non-null float64
ps_car_14 595212 non-null float64
ps_car_15 595212 non-null float64
ps_calc_01 595212 non-null float64
ps_calc_02 595212 non-null float64
ps_calc_03 595212 non-null float64
ps_calc_04 595212 non-null int64
ps_calc_05 595212 non-null int64
ps_calc_06 595212 non-null int64
ps_calc_07 595212 non-null int64
ps_calc_08 595212 non-null int64
ps_calc_09 595212 non-null int64
ps_calc_10 595212 non-null int64
ps_calc_11 595212 non-null int64
ps_calc_12 595212 non-null int64
ps_calc_13 595212 non-null int64
ps_calc_14 595212 non-null int64
ps_calc_15_bin 595212 non-null int64
ps_calc_16_bin 595212 non-null int64
ps_calc_17_bin 595212 non-null int64
ps_calc_18_bin 595212 non-null int64
ps_calc_19_bin 595212 non-null int64
ps_calc_20_bin 595212 non-null int64
dtypes: float64(10), int64(49)
memory usage: 267.9 MB
현재 _cat이 붙은 변수가 14개가 있고, 이를 나중에 더미 변수를 생성해줄 수 있을 것이다. _bin이 붙은 변수는 이미 이진 변수이므로 더미 변수를 생성할 필요는 없다.
데이터는 49개의 정수형 데이터와 10개의 실수형 데이터로 되어있고, Null 값은 -1로 대체되어 없는 것을 확인할 수 있다.
데이터 관리를 용이하게 하기 위해 변수에 대한 메타 정보를 데이터 프레임에 저장하려한다.
data = []
for f in train.columns:
# Defining the role
if f =='target':
role = 'target'
elif f == 'id':
role = 'id'
else:
role = 'input'
# Defining the level
if 'bin' in f or f == 'target':
level = 'binary'
elif 'cat' in f or f =='id':
level = 'nominal'
elif train[f].dtype == float:
level = 'interval'
elif train[f].dtype == int:
level = 'ordinal'
# Initialize keep to True for all variables except for id
keep = True
if f == 'id':
keep = False
# Defining the data type
dtype = train[f].dtype
# Creating a Dict that contains all the metadata for the variable
f_dict = {
'varname': f,
'role': role,
'level': level,
'keep': keep,
'dtype': dtype
}
data.append(f_dict)
meta = pd.DataFrame(data, columns=['varname', 'role', 'level', 'keep', 'dtype'])
meta.set_index('varname', inplace=True)
meta
role | level | keep | dtype | |
---|---|---|---|---|
varname | ||||
id | id | nominal | False | int64 |
target | target | binary | True | int64 |
ps_ind_01 | input | ordinal | True | int64 |
ps_ind_02_cat | input | nominal | True | int64 |
ps_ind_03 | input | ordinal | True | int64 |
ps_ind_04_cat | input | nominal | True | int64 |
ps_ind_05_cat | input | nominal | True | int64 |
ps_ind_06_bin | input | binary | True | int64 |
ps_ind_07_bin | input | binary | True | int64 |
ps_ind_08_bin | input | binary | True | int64 |
ps_ind_09_bin | input | binary | True | int64 |
ps_ind_10_bin | input | binary | True | int64 |
ps_ind_11_bin | input | binary | True | int64 |
ps_ind_12_bin | input | binary | True | int64 |
ps_ind_13_bin | input | binary | True | int64 |
ps_ind_14 | input | ordinal | True | int64 |
ps_ind_15 | input | ordinal | True | int64 |
ps_ind_16_bin | input | binary | True | int64 |
ps_ind_17_bin | input | binary | True | int64 |
ps_ind_18_bin | input | binary | True | int64 |
ps_reg_01 | input | interval | True | float64 |
ps_reg_02 | input | interval | True | float64 |
ps_reg_03 | input | interval | True | float64 |
ps_car_01_cat | input | nominal | True | int64 |
ps_car_02_cat | input | nominal | True | int64 |
ps_car_03_cat | input | nominal | True | int64 |
ps_car_04_cat | input | nominal | True | int64 |
ps_car_05_cat | input | nominal | True | int64 |
ps_car_06_cat | input | nominal | True | int64 |
ps_car_07_cat | input | nominal | True | int64 |
ps_car_08_cat | input | nominal | True | int64 |
ps_car_09_cat | input | nominal | True | int64 |
ps_car_10_cat | input | nominal | True | int64 |
ps_car_11_cat | input | nominal | True | int64 |
ps_car_11 | input | ordinal | True | int64 |
ps_car_12 | input | interval | True | float64 |
ps_car_13 | input | interval | True | float64 |
ps_car_14 | input | interval | True | float64 |
ps_car_15 | input | interval | True | float64 |
ps_calc_01 | input | interval | True | float64 |
ps_calc_02 | input | interval | True | float64 |
ps_calc_03 | input | interval | True | float64 |
ps_calc_04 | input | ordinal | True | int64 |
ps_calc_05 | input | ordinal | True | int64 |
ps_calc_06 | input | ordinal | True | int64 |
ps_calc_07 | input | ordinal | True | int64 |
ps_calc_08 | input | ordinal | True | int64 |
ps_calc_09 | input | ordinal | True | int64 |
ps_calc_10 | input | ordinal | True | int64 |
ps_calc_11 | input | ordinal | True | int64 |
ps_calc_12 | input | ordinal | True | int64 |
ps_calc_13 | input | ordinal | True | int64 |
ps_calc_14 | input | ordinal | True | int64 |
ps_calc_15_bin | input | binary | True | int64 |
ps_calc_16_bin | input | binary | True | int64 |
ps_calc_17_bin | input | binary | True | int64 |
ps_calc_18_bin | input | binary | True | int64 |
ps_calc_19_bin | input | binary | True | int64 |
ps_calc_20_bin | input | binary | True | int64 |
예를 들어 nominal 데이터를 아래처럼 추출할 수 있다.
meta[(meta.level == 'nominal') & (meta.keep)].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', name='varname')
role, level에 따른 갯수도 구해볼 수 있다.
pd.DataFrame({'count': meta.groupby(['role', 'level'])['role'].size()}).reset_index()
role | level | count | |
---|---|---|---|
0 | id | nominal | 1 |
1 | input | binary | 17 |
2 | input | interval | 10 |
3 | input | nominal | 14 |
4 | input | ordinal | 16 |
5 | target | binary | 1 |
생성해둔 메타데이터로 기술 통계를 구하기 수월하다.
v = meta[(meta.level == 'interval') & (meta.keep)].index
train[v].describe()
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 | |
---|---|---|---|---|---|---|---|---|---|---|
count | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 |
mean | 0.610991 | 0.439184 | 0.551102 | 0.379945 | 0.813265 | 0.276256 | 3.065899 | 0.449756 | 0.449589 | 0.449849 |
std | 0.287643 | 0.404264 | 0.793506 | 0.058327 | 0.224588 | 0.357154 | 0.731366 | 0.287198 | 0.286893 | 0.287153 |
min | 0.000000 | 0.000000 | -1.000000 | -1.000000 | 0.250619 | -1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
25% | 0.400000 | 0.200000 | 0.525000 | 0.316228 | 0.670867 | 0.333167 | 2.828427 | 0.200000 | 0.200000 | 0.200000 |
50% | 0.700000 | 0.300000 | 0.720677 | 0.374166 | 0.765811 | 0.368782 | 3.316625 | 0.500000 | 0.400000 | 0.500000 |
75% | 0.900000 | 0.600000 | 1.000000 | 0.400000 | 0.906190 | 0.396485 | 3.605551 | 0.700000 | 0.700000 | 0.700000 |
max | 0.900000 | 1.800000 | 4.037945 | 1.264911 | 3.720626 | 0.636396 | 3.741657 | 0.900000 | 0.900000 | 0.900000 |
전체적으로 등간 변수의 범위가 다소 작은데 데이터의 익명화를 위해 변환(log 등)이 이미 적용되었을 수도 있다.
v = meta[(meta.level == 'ordinal') & (meta.keep)].index
train[v].describe()
ps_ind_01 | ps_ind_03 | ps_ind_14 | ps_ind_15 | ps_car_11 | ps_calc_04 | ps_calc_05 | ps_calc_06 | ps_calc_07 | ps_calc_08 | ps_calc_09 | ps_calc_10 | ps_calc_11 | ps_calc_12 | ps_calc_13 | ps_calc_14 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 |
mean | 1.900378 | 4.423318 | 0.012451 | 7.299922 | 2.346072 | 2.372081 | 1.885886 | 7.689445 | 3.005823 | 9.225904 | 2.339034 | 8.433590 | 5.441382 | 1.441918 | 2.872288 | 7.539026 |
std | 1.983789 | 2.699902 | 0.127545 | 3.546042 | 0.832548 | 1.117219 | 1.134927 | 1.334312 | 1.414564 | 1.459672 | 1.246949 | 2.904597 | 2.332871 | 1.202963 | 1.694887 | 2.746652 |
min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | -1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 2.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
25% | 0.000000 | 2.000000 | 0.000000 | 5.000000 | 2.000000 | 2.000000 | 1.000000 | 7.000000 | 2.000000 | 8.000000 | 1.000000 | 6.000000 | 4.000000 | 1.000000 | 2.000000 | 6.000000 |
50% | 1.000000 | 4.000000 | 0.000000 | 7.000000 | 3.000000 | 2.000000 | 2.000000 | 8.000000 | 3.000000 | 9.000000 | 2.000000 | 8.000000 | 5.000000 | 1.000000 | 3.000000 | 7.000000 |
75% | 3.000000 | 6.000000 | 0.000000 | 10.000000 | 3.000000 | 3.000000 | 3.000000 | 9.000000 | 4.000000 | 10.000000 | 3.000000 | 10.000000 | 7.000000 | 2.000000 | 4.000000 | 9.000000 |
max | 7.000000 | 11.000000 | 4.000000 | 13.000000 | 3.000000 | 5.000000 | 6.000000 | 10.000000 | 9.000000 | 12.000000 | 7.000000 | 25.000000 | 19.000000 | 10.000000 | 13.000000 | 23.000000 |
v = meta[(meta.level == 'binary') & (meta.keep)].index
train[v].describe()
target | ps_ind_06_bin | ps_ind_07_bin | ps_ind_08_bin | ps_ind_09_bin | ps_ind_10_bin | ps_ind_11_bin | ps_ind_12_bin | ps_ind_13_bin | ps_ind_16_bin | ps_ind_17_bin | ps_ind_18_bin | ps_calc_15_bin | ps_calc_16_bin | ps_calc_17_bin | ps_calc_18_bin | ps_calc_19_bin | ps_calc_20_bin | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 | 595212.000000 |
mean | 0.036448 | 0.393742 | 0.257033 | 0.163921 | 0.185304 | 0.000373 | 0.001692 | 0.009439 | 0.000948 | 0.660823 | 0.121081 | 0.153446 | 0.122427 | 0.627840 | 0.554182 | 0.287182 | 0.349024 | 0.153318 |
std | 0.187401 | 0.488579 | 0.436998 | 0.370205 | 0.388544 | 0.019309 | 0.041097 | 0.096693 | 0.030768 | 0.473430 | 0.326222 | 0.360417 | 0.327779 | 0.483381 | 0.497056 | 0.452447 | 0.476662 | 0.360295 |
min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
25% | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
50% | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 |
75% | 0.000000 | 1.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 0.000000 |
max | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
앞서 언급한 것처럼 target이 1인 레코드 비율보다 target이 0인 레코드의 비율이 월등하게 높다. 이렇게하면 예측에 어려움이 있을 수 있으므로, 두 가지 방법 중 한 가지를 적용해서 해결 할 수 있다:
이 부분의 설명은 예전에 머신러닝 완벽 가이드에서 공부했을 때의 자료를 참고해볼 수 있다. 오버샘플링과 언더샘플링 설명
그 때 언더 샘플링은 "너무 많은 정상 레이블 데이터를 감소시켜 정상 레이블의 경우 오히려 제대로 된 학습을 할 수 없다는 단점이 있어 잘 적용하지 않는다." 라고 했는데 여기선 언더 샘플링을 적용한다.
desired_apriori = 0.10
# target의 인덱스 추출
idx_0 = train[train.target == 0].index
idx_1 = train[train.target == 1].index
# target당 갯수 추출
nb_0 = len(train.loc[idx_0])
nb_1 = len(train.loc[idx_1])
# 언더샘플링 비율 및 target=0의 갯수 계산
undersampling_rate = ((1-desired_apriori) * nb_1)/(nb_0 * desired_apriori)
undersampled_nb_0 = int(undersampling_rate * nb_0)
print(f'Rate to undersample records with target=0: {undersampling_rate}')
print(f'Number of records with target=0 after undersampling: {undersampled_nb_0}')
# 랜덤으로 target=0인 값 추출
undersampled_idx = shuffle(idx_0, random_state=37, n_samples=undersampled_nb_0)
# 리스트 재생성
idx_list = list(undersampled_idx) + list(idx_1)
# 언더샘플링된 데이터 프레임 리턴
train = train.loc[idx_list].reset_index(drop=True)
Rate to undersample records with target=0: 0.34043569687437886
Number of records with target=0 after undersampling: 195246
-1인 값은 결측값을 의미한다.
vars_with_missing = []
for f in train.columns:
missings = train[train[f] == -1][f].count()
if missings > 0:
vars_with_missing.append(f)
missings_perc = missings/train.shape[0]
print(f'변수 {f} 는 {missings} 개의 결측값을 가지고 있으며, 퍼센트는 {missings_perc:.2%} 이다.')
print(f'전체 결측값 칼럼 갯수: {len(vars_with_missing)}')
변수 ps_ind_02_cat 는 103 개의 결측값을 가지고 있으며, 퍼센트는 0.05% 이다.
변수 ps_ind_04_cat 는 51 개의 결측값을 가지고 있으며, 퍼센트는 0.02% 이다.
변수 ps_ind_05_cat 는 2256 개의 결측값을 가지고 있으며, 퍼센트는 1.04% 이다.
변수 ps_reg_03 는 38580 개의 결측값을 가지고 있으며, 퍼센트는 17.78% 이다.
변수 ps_car_01_cat 는 62 개의 결측값을 가지고 있으며, 퍼센트는 0.03% 이다.
변수 ps_car_02_cat 는 2 개의 결측값을 가지고 있으며, 퍼센트는 0.00% 이다.
변수 ps_car_03_cat 는 148367 개의 결측값을 가지고 있으며, 퍼센트는 68.39% 이다.
변수 ps_car_05_cat 는 96026 개의 결측값을 가지고 있으며, 퍼센트는 44.26% 이다.
변수 ps_car_07_cat 는 4431 개의 결측값을 가지고 있으며, 퍼센트는 2.04% 이다.
변수 ps_car_09_cat 는 230 개의 결측값을 가지고 있으며, 퍼센트는 0.11% 이다.
변수 ps_car_11 는 1 개의 결측값을 가지고 있으며, 퍼센트는 0.00% 이다.
변수 ps_car_14 는 15726 개의 결측값을 가지고 있으며, 퍼센트는 7.25% 이다.
전체 결측값 칼럼 갯수: 12
# 너무 높은 비중의 결측값이 있는 데이터 제거
vars_to_drop = ['ps_car_03_cat', 'ps_car_05_cat']
train.drop(vars_to_drop, inplace=True, axis=1)
# 메타데이터 업데이트
meta.loc[(vars_to_drop), 'keep'] = False
# 평균, 최빈값으로 대체
mean_imp = SimpleImputer(missing_values=-1, strategy='mean')
mode_imp = SimpleImputer(missing_values=-1, strategy='most_frequent')
train['ps_reg_03'] = mean_imp.fit_transform(train[['ps_reg_03']]).ravel()
train['ps_car_14'] = mean_imp.fit_transform(train[['ps_car_14']]).ravel()
train['ps_car_11'] = mode_imp.fit_transform(train[['ps_car_11']]).ravel()
카테고리형 변수로 더미 변수를 생성하기 전에 각 카테고리 변수에 몇 개의 값들이 있는지 알아보자.
v = meta[(meta.level == 'nominal') & (meta.keep)].index
for f in v:
dist_values = train[f].value_counts().shape[0]
print(f'변수 {f} 는 {dist_values} 개의 값을 가지고 있다.')
변수 ps_ind_02_cat 는 5 개의 값을 가지고 있다.
변수 ps_ind_04_cat 는 3 개의 값을 가지고 있다.
변수 ps_ind_05_cat 는 8 개의 값을 가지고 있다.
변수 ps_car_01_cat 는 13 개의 값을 가지고 있다.
변수 ps_car_02_cat 는 3 개의 값을 가지고 있다.
변수 ps_car_04_cat 는 10 개의 값을 가지고 있다.
변수 ps_car_06_cat 는 18 개의 값을 가지고 있다.
변수 ps_car_07_cat 는 3 개의 값을 가지고 있다.
변수 ps_car_08_cat 는 2 개의 값을 가지고 있다.
변수 ps_car_09_cat 는 6 개의 값을 가지고 있다.
변수 ps_car_10_cat 는 3 개의 값을 가지고 있다.
변수 ps_car_11_cat 는 104 개의 값을 가지고 있다.
ps_car_11_cat이 많은 값을 가지고 있다. 여기서 필자는 oliver 이 만든 커널을 적용한다.
def add_noise(series, noise_level):
return series * (1 + noise_level * np.random.randn(len(series)))
def target_encode(trn_series=None, tst_series=None, target=None, min_samples_leaf=1, smoothing=1, noise_level=0):
'''
trn_series: train의 카테고리 피쳐(pd.Series)
tst_series: test의 카테고리 피쳐(pd.Series)
target: target 데이터(pd.Series)
min_samples_leaf(int): 카테고리 평균을 고려할 최소 샘플 수
smoothing(int): 범주형 평균과 이전의 평균의 균형을 유지하는 평활 효과
'''
# assert: 가정설정문, 어떤 조건을 테스트하는 디버깅 보조 도구
assert len(trn_series) == len(target)
assert trn_series.name == tst_series.name
temp = pd.concat([trn_series, target], axis=1)
# target 평균 계산
averages = temp.groupby(by=trn_series.name)[target.name].agg(['mean', 'count'])
# smoothing 계산
smoothing = 1 / (1 + np.exp(-(averages['count'] - min_samples_leaf) / smoothing))
# 모든 target에 평균 적용
prior = target.mean()
# count가 클수록 full_avg가 적게 고려된다.
averages[target.name] = prior * (1 - smoothing) + averages['mean'] * smoothing
averages.drop(['mean', 'count'], axis=1, inplace=True)
# trn에 평균 적용 및 인덱스 복구
ft_trn_series = pd.merge(trn_series.to_frame(trn_series.name),
averages.reset_index().rename(columns={'index': target.name, target.name: 'average'}),
on=trn_series.name,
how='left')['average'].rename(trn_series.name + '_mean').fillna(prior)
ft_trn_series.index = trn_series.index
# tst에 평균 적용 및 인덱스 복구
ft_tst_series = pd.merge(tst_series.to_frame(tst_series.name),
averages.reset_index().rename(columns={'index': target.name, target.name: 'average'}),
on=tst_series.name,
how='left')['average'].rename(trn_series.name + '_mean').fillna(prior)
ft_tst_series.index = tst_series.index
return add_noise(ft_trn_series, noise_level), add_noise(ft_tst_series, noise_level)
train_encoded, test_encoded = target_encode(train['ps_car_11_cat'],
test['ps_car_11_cat'],
target=train.target,
min_samples_leaf=100,
smoothing=10,
noise_level=0.01)
train['ps_car_11_cat_te'] = train_encoded
train.drop('ps_car_11_cat', axis=1, inplace=True)
meta.loc['ps_car_11_cat', 'keep'] = False
test['ps_car_11_cat_te'] = test_encoded
test.drop('ps_car_11_cat', axis=1, inplace=True)
카테고리형 변수와 target이 1인 고객의 분포를 확인해보자.
v = meta[(meta.level == 'nominal') & (meta.keep)].index
for f in v:
plt.figure()
fig, ax = plt.subplots(figsize=(20, 10))
cat_perc = train[[f, 'target']].groupby([f], as_index=False).mean()
cat_perc.sort_values(by='target', ascending=False, inplace=True)
sns.barplot(ax=ax, x=f, y='target', data=cat_perc, order=cat_perc[f], palette='rocket')
plt.ylabel('% target', fontsize=18)
plt.xlabel(f, fontsize=18)
plt.tick_params(axis='both', which='major', labelsize=18)
결측값이 있는 고객은 보험 청구를 요청할 확률이 훨씬 높은 것으로 보인다.(낮은 경우도 있음) 이를 최빈값으로 교체하는 등 방법이 다양하게 있을 것이다.
간격 변수들의 상관 관계를 알아보자. 아래 코드는 an example by Michael Waskom에 기반하여 작성하였다.
def corr_heatmap(v):
correlations = train[v].corr()
cmap = sns.diverging_palette(220, 10, as_cmap=True)
fig, ax = plt.subplots(figsize=(10, 10))
sns.heatmap(correlations, cmap=cmap, fmt='.2f', square=True, linewidths=.5, annot=True, cbar_kws={'shrink': .75})
v = meta[(meta.level == 'interval') & (meta.keep)].index
corr_heatmap(v)
높은 상관관계를 가진 변수들:
# 빠른 프로세싱을 위해 샘플 추출
s = train.sample(frac=0.1)
아래 회귀 직선을 확인하면 피쳐들이 선형 관계가 있다는 것을 알 수 있고, hue 로 회귀 직선이 target=0 일 때와 1일 때를 볼 수 있다.
sns.lmplot('ps_reg_02', 'ps_reg_03', data=s, hue='target', palette='Set1', scatter_kws={'alpha': 0.3})
sns.lmplot('ps_car_12', 'ps_car_13', data=s, hue='target', palette='Set2', scatter_kws={'alpha': 0.3})
sns.lmplot('ps_car_12', 'ps_car_14', data=s, hue='target', palette='Set3', scatter_kws={'alpha': 0.3})
sns.lmplot('ps_car_13', 'ps_car_15', data=s, hue='target', palette='summer_r', scatter_kws={'alpha': 0.3})
그래프 형태가 더 눈에 띌 수 있게 x, y 축 값을 바꾸자.
sns.lmplot('ps_car_15', 'ps_car_13', data=s, hue='target', palette='winter', scatter_kws={'alpha': 0.3})
v = meta[(meta.level == 'ordinal') & (meta.keep)].index
corr_heatmap(v)
서수형 변수들에선 상관관계가 높게 나타나지 않는다. 하지만 우리는 목표값별로 그룹화할 때 분포가 어떻게 되는지 알아볼 수 있다.
카테고리형 변수에서 1과 2는 값이 두 배임을 의미하진 않는다. 그러므로 더미형 변수를 만들어줘야 한다. 첫 번째 더미 변수를 삭제하는 이유는 원래 변수의 범주에 대해 생성된 다른 더미 변수에서 파생될 수 있기 때문이다.
v = meta[(meta.level == 'nominal') & (meta.keep)].index
print(f'더미화 전에 train 세트에 있는 변수의 갯수: {train.shape[1]}')
train = pd.get_dummies(train, columns=v, drop_first=True)
print(f'더미화 후에 train 세트에 있는 변수의 갯수: {train.shape[1]}')
더미화 전에 train 세트에 있는 변수의 갯수: 57
더미화 후에 train 세트에 있는 변수의 갯수: 109
52개의 더미 변수가 추가되었다.
drop_first에 대한 추가 설명: 타이타닉 예제에서 예를 들면 Pclass가 1, 2, 3이 있었고, 이에 더미 변수를 생성하면 Pclass_1, Pclass_2, Pclass_3이 나온다. 여기서 drop_first
를 하면 Pclass_2, Pclass_3 만 나온다. 이렇게해도 두 값이 0이라면 Pclass가 1임을 알 수 있기 때문이다.
PolynomialFeatures함수는 데이터를 다항식 형태로 변경한다.
v = meta[(meta.level == 'interval') & (meta.keep)].index
poly = PolynomialFeatures(degree=2, interaction_only=False, include_bias=False)
interactions = pd.DataFrame(data=poly.fit_transform(train[v]), columns=poly.get_feature_names(v))
interactions.drop(v, axis=1, inplace=True)
print(f'교호작용 전에 train 세트에 있는 변수의 갯수: {train.shape[1]}')
train = pd.concat([train, interactions], axis=1)
print(f'교호작용 후에 train 세트에 있는 변수의 갯수: {train.shape[1]}')
교호작용 전에 train 세트에 있는 변수의 갯수: 109
교호작용 후에 train 세트에 있는 변수의 갯수: 164
55개의 교호작용 변수가 추가되었다.
분산이 없거나 매우 낮은 피쳐를 제거할 수 있는데, sklearn의 VarianceThreshold 를 이용해서 제거할 수 있다. 기본적으로는 분산이 0인 피쳐를 제거하는데 이전 단계에서 분산이 0인 변수가 없었으므로, 1%미만인 피쳐를 제거하게 설정할 수 있다.
Vectorize는 매트릭스 구조의 데이터의 연산을 일괄적으로 처리할 수 있도록 Series, DataFrame, array 등과 같이 시퀀스형 자료를 함수의 매개변수로 포함시킬 수 있게 하는 것을 말한다.
selector = VarianceThreshold(threshold=0.01)
selector.fit(train.drop(['id', 'target'], axis=1))
f = np.vectorize(lambda x: not x)
v = train.drop(['id', 'target'], axis=1).columns[f(selector.get_support())] # get_support()는 선택된 특성을 boolean 값으로 표시해 어떤 특성이 선택되었는지 확인 가능
print(f'{len(v)} 개의 변수가 너무 낮은 분산을 갖고 있다.')
print(f'변수 리스트:\n{list(v)}')
28 개의 변수가 너무 낮은 분산을 갖고 있다.
변수 리스트:
['ps_ind_10_bin', 'ps_ind_11_bin', 'ps_ind_12_bin', 'ps_ind_13_bin', 'ps_car_12', 'ps_car_14', 'ps_car_11_cat_te', 'ps_ind_05_cat_2', 'ps_ind_05_cat_5', 'ps_car_01_cat_1', 'ps_car_01_cat_2', 'ps_car_04_cat_3', 'ps_car_04_cat_4', 'ps_car_04_cat_5', 'ps_car_04_cat_6', 'ps_car_04_cat_7', 'ps_car_06_cat_2', 'ps_car_06_cat_5', 'ps_car_06_cat_8', 'ps_car_06_cat_12', 'ps_car_06_cat_16', 'ps_car_06_cat_17', 'ps_car_09_cat_4', 'ps_car_10_cat_1', 'ps_car_10_cat_2', 'ps_car_12^2', 'ps_car_12 ps_car_14', 'ps_car_14^2']
만약 분산을 바탕으로 선택을 하면 많은 변수가 없어질 수 있다. 그러나 우리가 지금 변수를 많이 갖고있기에 분류기가 선택하도록 하자. sklearn의 SelectFromModel 메서드를 사용하면 최상의 피쳐를 선택하게 할 수 있다. 아래에서 랜덤 포레스트를 통해 방법을 설명한다.
랜덤 포레스트의 feature importances 를 이용해서 선택하도록 하자. SelectFromModel 로 유지할 변수의 수를 지정할 수 있고, feature importances에 대한 임곗값을 수동으로 설정할 수 있다. 여기선 간단하게 상위 50%만을 설정한다.
이 코드는 GitHub repo of Sebastian Raschka 에서 참고했다.
X_train = train.drop(['id', 'target'], axis=1)
y_train = train['target']
feat_labels = X_train.columns
rf = RandomForestClassifier(n_estimators=1000, random_state=0, n_jobs=-1)
rf.fit(X_train, y_train)
importances = rf.feature_importances_
indices = np.argsort(rf.feature_importances_)[::-1]
for f in range(X_train.shape[1]):
print("%2d) %-*s %f" % (f + 1, 30, feat_labels[indices[f]], importances[indices[f]]))
1) ps_car_11_cat_te 0.021010
2) ps_car_13 0.017335
3) ps_car_13^2 0.017320
4) ps_car_12 ps_car_13 0.017257
5) ps_car_13 ps_car_14 0.017169
6) ps_reg_03 ps_car_13 0.017106
7) ps_reg_01 ps_car_13 0.016905
8) ps_car_13 ps_car_15 0.016836
9) ps_reg_03 ps_car_14 0.016238
10) ps_reg_03 ps_car_12 0.015469
11) ps_reg_03 ps_car_15 0.015133
12) ps_car_14 ps_car_15 0.015055
13) ps_car_13 ps_calc_02 0.014721
14) ps_car_13 ps_calc_01 0.014721
15) ps_reg_02 ps_car_13 0.014694
16) ps_reg_01 ps_reg_03 0.014691
17) ps_car_13 ps_calc_03 0.014675
18) ps_reg_01 ps_car_14 0.014328
19) ps_reg_03^2 0.014262
20) ps_reg_03 0.014198
21) ps_reg_03 ps_calc_02 0.013798
22) ps_reg_03 ps_calc_03 0.013758
23) ps_reg_03 ps_calc_01 0.013680
24) ps_calc_10 0.013613
25) ps_car_14 ps_calc_02 0.013576
26) ps_car_14 ps_calc_01 0.013515
27) ps_car_14 ps_calc_03 0.013464
28) ps_calc_14 0.013387
29) ps_car_12 ps_car_14 0.012966
30) ps_ind_03 0.012918
31) ps_reg_02 ps_car_14 0.012796
32) ps_car_14^2 0.012751
33) ps_car_14 0.012726
34) ps_calc_11 0.012645
35) ps_reg_02 ps_reg_03 0.012537
36) ps_ind_15 0.012112
37) ps_car_12 ps_car_15 0.010960
38) ps_car_15 ps_calc_03 0.010928
39) ps_car_15 ps_calc_02 0.010836
40) ps_car_15 ps_calc_01 0.010827
41) ps_car_12 ps_calc_01 0.010496
42) ps_calc_13 0.010493
43) ps_car_12 ps_calc_03 0.010317
44) ps_car_12 ps_calc_02 0.010316
45) ps_reg_02 ps_car_15 0.010211
46) ps_reg_01 ps_car_15 0.010167
47) ps_calc_02 ps_calc_03 0.010091
48) ps_calc_01 ps_calc_02 0.010048
49) ps_calc_01 ps_calc_03 0.009998
50) ps_calc_07 0.009883
51) ps_calc_08 0.009807
52) ps_reg_01 ps_car_12 0.009439
53) ps_reg_02 ps_car_12 0.009312
54) ps_reg_02 ps_calc_01 0.009260
55) ps_reg_02 ps_calc_03 0.009243
56) ps_reg_02 ps_calc_02 0.009178
57) ps_reg_01 ps_calc_03 0.009062
58) ps_reg_01 ps_calc_01 0.009057
59) ps_calc_06 0.009052
60) ps_reg_01 ps_calc_02 0.009048
61) ps_calc_09 0.008799
62) ps_ind_01 0.008649
63) ps_calc_05 0.008303
64) ps_calc_04 0.008178
65) ps_calc_12 0.008049
66) ps_reg_01 ps_reg_02 0.008034
67) ps_car_15^2 0.006150
68) ps_car_15 0.006109
69) ps_calc_03^2 0.005977
70) ps_calc_01^2 0.005969
71) ps_calc_03 0.005948
72) ps_calc_01 0.005948
73) ps_calc_02^2 0.005940
74) ps_calc_02 0.005928
75) ps_car_12 0.005318
76) ps_car_12^2 0.005307
77) ps_reg_02 0.004983
78) ps_reg_02^2 0.004959
79) ps_reg_01 0.004173
80) ps_reg_01^2 0.004134
81) ps_car_11 0.003803
82) ps_ind_05_cat_0 0.003586
83) ps_ind_17_bin 0.002851
84) ps_calc_17_bin 0.002687
85) ps_calc_16_bin 0.002609
86) ps_calc_19_bin 0.002552
87) ps_calc_18_bin 0.002487
88) ps_ind_16_bin 0.002409
89) ps_car_01_cat_11 0.002382
90) ps_ind_04_cat_0 0.002370
91) ps_ind_04_cat_1 0.002368
92) ps_car_09_cat_2 0.002325
93) ps_ind_07_bin 0.002312
94) ps_ind_02_cat_1 0.002260
95) ps_car_09_cat_0 0.002093
96) ps_car_01_cat_7 0.002093
97) ps_ind_02_cat_2 0.002089
98) ps_calc_20_bin 0.002056
99) ps_ind_06_bin 0.002051
100) ps_car_06_cat_1 0.001999
101) ps_calc_15_bin 0.001989
102) ps_car_07_cat_1 0.001955
103) ps_ind_08_bin 0.001945
104) ps_car_09_cat_1 0.001820
105) ps_car_06_cat_11 0.001803
106) ps_ind_18_bin 0.001726
107) ps_ind_09_bin 0.001718
108) ps_car_01_cat_10 0.001606
109) ps_car_01_cat_9 0.001598
110) ps_car_06_cat_14 0.001566
111) ps_car_01_cat_6 0.001552
112) ps_car_01_cat_4 0.001550
113) ps_ind_05_cat_6 0.001503
114) ps_ind_02_cat_3 0.001424
115) ps_car_07_cat_0 0.001388
116) ps_car_01_cat_8 0.001356
117) ps_car_02_cat_1 0.001348
118) ps_car_08_cat_1 0.001348
119) ps_car_02_cat_0 0.001308
120) ps_car_06_cat_4 0.001237
121) ps_ind_05_cat_4 0.001217
122) ps_ind_02_cat_4 0.001163
123) ps_car_01_cat_5 0.001145
124) ps_car_06_cat_6 0.001118
125) ps_car_06_cat_10 0.001054
126) ps_car_04_cat_1 0.001046
127) ps_ind_05_cat_2 0.001017
128) ps_car_04_cat_2 0.000991
129) ps_car_06_cat_7 0.000990
130) ps_car_01_cat_3 0.000903
131) ps_car_01_cat_0 0.000877
132) ps_car_09_cat_3 0.000873
133) ps_ind_14 0.000850
134) ps_car_06_cat_15 0.000839
135) ps_car_06_cat_9 0.000807
136) ps_ind_05_cat_1 0.000736
137) ps_car_06_cat_3 0.000696
138) ps_car_10_cat_1 0.000694
139) ps_ind_12_bin 0.000672
140) ps_ind_05_cat_3 0.000667
141) ps_car_09_cat_4 0.000619
142) ps_car_04_cat_8 0.000554
143) ps_car_01_cat_2 0.000549
144) ps_car_06_cat_17 0.000513
145) ps_car_06_cat_16 0.000482
146) ps_car_04_cat_9 0.000435
147) ps_car_06_cat_12 0.000434
148) ps_car_06_cat_13 0.000399
149) ps_car_01_cat_1 0.000391
150) ps_ind_05_cat_5 0.000319
151) ps_car_06_cat_5 0.000285
152) ps_ind_11_bin 0.000210
153) ps_car_04_cat_6 0.000196
154) ps_ind_13_bin 0.000150
155) ps_car_04_cat_3 0.000147
156) ps_car_06_cat_2 0.000138
157) ps_car_06_cat_8 0.000096
158) ps_car_04_cat_5 0.000096
159) ps_car_04_cat_7 0.000085
160) ps_ind_10_bin 0.000075
161) ps_car_10_cat_2 0.000062
162) ps_car_04_cat_4 0.000041
sfm = SelectFromModel(rf, threshold='median', prefit=True)
print(f'Selection 전 피쳐의 수: {X_train.shape[1]}')
n_features = sfm.transform(X_train).shape[1]
print(f'Selection 후 피쳐의 수: {n_features}')
selected_vars = list(feat_labels[sfm.get_support()])
Selection 전 피쳐의 수: 162
Selection 후 피쳐의 수: 81
train = train[selected_vars + ['target']]
앞서 언급한 것과 같이 train 세트에 standard scaling을 할 수 있고, 이는 몇몇 분류기의 성능을 향상시킬 수 있다.
scaler = StandardScaler()
scaler.fit_transform(train.drop(['target'], axis=1))
array([[-0.45941104, -1.26665356, 1.05087653, ..., -0.72553616,
-1.01071913, -1.06173767],
[ 1.55538958, 0.95034274, -0.63847299, ..., -1.06120876,
-1.01071913, 0.27907892],
[ 1.05168943, -0.52765479, -0.92003125, ..., 1.95984463,
-0.56215309, -1.02449277],
...,
[-0.9631112 , 0.58084336, 0.48776003, ..., -0.46445747,
0.18545696, 0.27907892],
[-0.9631112 , -0.89715418, -1.48314775, ..., -0.91202093,
-0.41263108, 0.27907892],
[-0.45941104, -1.26665356, 1.61399304, ..., 0.28148164,
-0.11358706, -0.72653353]])