이번 주제는 Porto serqruo safe prediction 로,
목표는 운전자가 내년에 자동차 보험 청구를 시작할 확률울 예측하는 모델을 구축 하는 것이다.
이번 필사는 Bert Carremans님의 코드를 참고하였다.
총 2가지 포스트로 내용을 나누었고, 순서는 아래와 같다.
Porto serqruo safe prediction(Bert Carremans) (1)
더보기
1.데이터확인
2. Metadata
3. 기술 통계(Description Statistics)
4. 불균형 클래스 처리
5. 데이터 품질검사
Porto serqruo safe prediction(Bert Carremans) (2)
더보기
6. EDA Visualization
7. Feature Engineering
8. Feature 선택
9. Feature 확장
Porto-serqruo-safe-prediction
- Porto-serqruo는 보험회사 이름이다.
- 이 보험회사는 자동차 보험 청구 예측을 통해 보험 비용을 관리하려고 한다.
- 예측이 부정확하면 좋은 운전자의 보험 비용이 상승하고, 예측이 정확하면 나쁜 운전자의 경우 가격이 낮아진다.
- 목표 : 운전자가 내년에 자동차 보험 청구를 시작할 확률울 예측하는 모델을 구축 해야함.
In [22]:
# Loading package
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Imputer was deprecated 3 versions ago and remove in 0.22
# from sklearn.preprocessing import Imputer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import VarianceThreshold
from sklearn.feature_selection import SelectFromModel
from sklearn.utils import shuffle
from sklearn.ensemble import RandomForestClassifier
pd.set_option('display.max_columns',100)
1. 데이터 확인
- Loading data
In [23]:
train = pd.read_csv('../data/porto-seguro-safe-driver-prediction/train.csv')
test = pd.read_csv('../data/porto-seguro-safe-driver-prediction/test.csv')
- 데이터 설명
- 자동차 보험 계약자가 클레임을 제기 할 확률을 예측
- 유사한 그룹화에 속하는 변수(Feature)는 변수 이름에 태그가 지정된다.(예: ind, reg, car, calc)
- 변수 이름에는 이항 변수를 나타내는
bin
전두사가 포함되어 있거나, 범주형 변수을 나타내는cat
등이 있다. - 이러한 지정이 없는 형상은 연속형 또는 순서형이다.
- -1 값은 관측치에서 형상이 누락되었음을 나타낸다.
- target 열은 해당 정책 보유자에 대한 청구가 제기되었는지 여부를 나타낸다.
In [24]:
train.head()
Out[24]:
In [25]:
train.tail()
Out[25]:
- 위의 데이터 설명에서 참고할 점 다시 요약
- 이항 변수
- 범주화 값이 숫자인 범주화 변수
int
orfloat
값인 변수-1
누락값- target 변수 과 ID 변수
In [26]:
# 데이터 갯수 확인
train.shape
Out[26]:
In [27]:
# 동일한 변수값이 들어가 있을 경우, 삭제하고 다시 데이터 갯수 확인
train.drop_duplicates()
train.shape
Out[27]:
In [28]:
# test 데이터의 row가 하나 없는 이유는 target 변수가 생략되었기 때문이다. 이 부분이 우리가 예측해야 할 부분이다.
test.shape
Out[28]:
In [29]:
# 14개의 범주형의 더미변수를 만들어보자. `bin` 변수는 이미 이상 변수여서 더미화 할 필요없다.
train.info()
2. Metadata
- 데이터 관리를 용이하게 하기 위해 Feature에 대한 메타 정보를 DataFrame에 저장한다.
- 분석, 시각화, 모델링 등을 위해 특정 변수를 선택하고자 할 때 유용할 것이다.
- 명확하게 분리해보자.
- role : input, ID, target
- level : nomial, interval, ordinal, binary
- keep : True or False
- dtype : int, float, str
In [30]:
data = []
for f in train.columns:
# Define the role
if f == 'target':
role = 'target'
elif f == 'id':
role = 'id'
else:
role = 'input'
# Define the level
if 'bin' in f or f == 'target':
level = 'binary' # 이상 변수
elif 'cat' in f or f == 'id':
level = 'nominal' # 방향, 숫자의미 없이 카테고리 변수 , 명목 척도
elif train[f].dtype == 'float64':
level = 'interval' # 등간척도, 대표적 예는 온도, 0도가 열이 없는건 아니다. cf) 비율 척도 ratio scale, 예 : 키 몸무게, 0은 아무것도 없는 것을 의미함.
elif train[f].dtype == 'int64':
level = 'ordinal' # 랭킹, 순서, 스케일링등 숫자가 의미 있는 변수, 서열 척도
# id value는 제외
keep = True
if f == 'id':
keep = False
# data type 정의
dtype = train[f].dtype
# 변수의 메타데이터를 포함한 dict 만들기
f_dict = {
'varname': f,
'role': role,
'level': level,
'keep': keep,
'dtype': dtype
}
level = ''
data.append(f_dict)
meta = pd.DataFrame(data, columns=['varname', 'role', 'level','keep','dtype'])
meta.set_index('varname', inplace=True)
In [31]:
meta
Out[31]:
- 삭제되지 않은 모든 nominal variables를 추출하는 예제
In [32]:
meta[(meta.level == 'nominal') & (meta.keep)].index
Out[32]:
In [33]:
# 역할 및 수준별 변수의 갯수를 볼수 있음.
In [34]:
pd.DataFrame({'count' : meta.groupby(['role', 'level'])['role'].size()}).reset_index()
Out[34]:
3. 기술 통계(Descriptive Statistics)
- 데이터 프레임에 기술 통계을 적용
- 범주형 변수와 ID 변수에 대한 평균, std, ...를 계산하는 것은 옳지않으므로, 나중에 시각적으로 탐구
- 메타 파일을 활용하여 기술 통계량을 계산할 변수를 쉽게 선택 가능
In [35]:
# Interval variables
v = meta[(meta.level == 'interval') & (meta.keep)].index # interval : 데이터 형식이 float, keep : id 컬럼 제외
train[v].describe()
Out[35]:
In [36]:
# Oridinal variable
v = meta[(meta.level == 'ordinal') & (meta.keep)].index
train[v].describe()
Out[36]:
In [37]:
# Binary variable
v = meta[(meta.level == 'binary') & (meta.keep)].index
train[v].describe()
Out[37]:
- train 데이터의 분포는 3.645%로, 균형이 매우 맞지 않는다.
- 평균을 통해 대부분의 변수의 값이 대부분의 경우 0이라고 결론을 내릴 수 있다.
4. 불균형 클래스 처리
위에서 언급했듯이 목표=1을 가진 기록의 비율은 목표=0보다 훨씬 적다. 이것은 정확성은 크지만 실제에 있어서 어떠한 부가 가치를 지닌 모델을 이끌어낼 수 있다. 이 문제를 해결하기 위한 두 가지 가능한 전략은 다음과 같다.
- 타겟=1로 레코드를 oversampling(오버샘플링)
- 타겟=0으로 레코드 undersampling(과소 샘플링)
물론 더 많은 전략들이 있고 MachineLearningMastery.com은 멋진 개요를 제공한다. 우리는 훈련 세트가 꽤 크기 때문에 undersampling(과소 샘플링)으로 갈 수 있다.
In [38]:
desired_apriori = 0.10
# target value(목표값) 당 지수 가져오기
idx_0 = train[train.target == 0].index
idx_1 = train[train.target == 1].index
# target value(목표값)당 원래 레코드 수 가져오기
nb_0 = len(train.loc[idx_0])
nb_1 = len(train.loc[idx_1])
# 언더샘플링 비율 과 타켓이 0 일때 레코드 갯수 결과 계산
undersampling_rate = ((1-desired_apriori)*nb_1)/(nb_0*desired_apriori)
undersampled_nb_0 = int(undersampling_rate*nb_0)
print('Rate to undersample records with target=0: {}'.format(undersampling_rate))
print('Number of records with target=0 after undersampling: {}'.format(undersampled_nb_0))
# 목표값=0이 있는 레코드를 임의로 선택하여 원하는 사전 정보를 얻으십시오.
undersampled_idx = shuffle(idx_0, random_state = 37, n_samples=undersampled_nb_0)
#나머지 인덱스를 사용하여 list 구성
idx_list = list(undersampled_idx) + list(idx_1)
# 언더샘플링할 결과 다시 담기
train = train.loc[idx_list].reset_index(drop=True)
5. 데이터 품질 검사
- missing values 검사
- missing value는
-1
로 표현함
- missing value는
In [39]:
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('Variable {} has {} records ({:.2%}) with missing values'.format(f, missings, missings_perc))
print('\n In total, there are {} variables with missing values'.format(len(vars_with_missing)))
- ps_car_03_cat(68.39%), ps_car_05_cat(44.26%)은 missing value(결측치)비율이 높다. 정확하게 측정할 수 없으므로 변수에서 제거한다.
- 대부분의 범주형 변수(categorical variable)은 -1로 둔다.
- 연속형 변수 mean 값으로, 순서형 변수는 mode값으로 대체한다.
In [40]:
# 결측치 비율이 높은 두 항목을 삭제 한다.
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
In [41]:
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_11'] = mean_imp.fit_transform(train[['ps_car_11']]).ravel()
train['ps_car_14'] = mean_imp.fit_transform(train[['ps_car_14']]).ravel()
train['ps_car_02_cat'] = mode_imp.fit_transform(train[['ps_car_02_cat']]).ravel()
- 범주형 변수의 집합원 갯수 체크
- 카디널리티는 변수에서 다른 값의 수를 가리킨다.
- 나중에 범주형 변수에서 더미 변수를 만들 것이기 때문에 많은 구별되는 값을 갖는 변수가 있는지 확인할 필요가 있다.
- 이러한 변수들이 많은 더미 변수를 야기할 수 있기 때문에 다르게 다루어야 한다.
In [42]:
v = meta[(meta.level == 'nominal') & (meta.keep)].index
for f in v:
dist_values = train[f].value_counts().shape[0]
print('Variable {} has {} distinct values'.format(f, dist_values))
- ps_car_11_cat 는 104개로 유니크한 값이 존재하지만, 여전히 논리적(reasonable)이다.
In [43]:
# 아래 코드는 ps_car_11_cat과 같이 유니크한 값을 데이터를 분리하여 새로운 데이터를 생성해주는 코드이다.
# Script by https://www.kaggle.com/ogrellier
# Code: https://www.kaggle.com/ogrellier/python-target-encoding-for-categorical-features
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):
"""
Smoothing is computed like in the following paper by Daniele Micci-Barreca
https://kaggle2.blob.core.windows.net/forum-message-attachments/225952/7441/high%20cardinality%20categoricals.pdf
trn_series : training categorical feature as a pd.Series
tst_series : test categorical feature as a pd.Series
target : target data as a pd.Series
min_samples_leaf (int) : minimum samples to take category average into account
smoothing (int) : smoothing effect to balance categorical average vs prior
"""
assert len(trn_series) == len(target)
assert trn_series.name == tst_series.name
temp = pd.concat([trn_series, target], axis=1)
# Compute target mean
averages = temp.groupby(by=trn_series.name)[target.name].agg(["mean", "count"])
# Compute smoothing
smoothing = 1 / (1 + np.exp(-(averages["count"] - min_samples_leaf) / smoothing))
# Apply average function to all target data
prior = target.mean()
# The bigger the count the less full_avg is taken into account
averages[target.name] = prior * (1 - smoothing) + averages["mean"] * smoothing
averages.drop(["mean", "count"], axis=1, inplace=True)
# Apply averages to trn and tst series
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)
# pd.merge does not keep the index so restore it
ft_trn_series.index = trn_series.index
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)
# pd.merge does not keep the index so restore it
ft_tst_series.index = tst_series.index
return add_noise(ft_trn_series, noise_level), add_noise(ft_tst_series, noise_level)
In [44]:
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_te', 'keep'] = False # meta 정보에도 업데이트
test['ps_car_11_cat_te'] =test_encoded
test.drop('ps_car_11_cat', axis=1, inplace=True)
In [79]:
# 원본데이터에도 지워졌으니, meta에서도 삭제
meta.drop(['ps_car_11_cat'], inplace=True)
'Competition > Kaggle' 카테고리의 다른 글
[kaggle] Porto serqruo safe prediction(Bert Carremans) (2) (0) | 2020.09.06 |
---|---|
[kaggle] Titanic: Machine Learning from Disaster (4) (0) | 2020.09.04 |
[kaggle] Titanic: Machine Learning from Disaster (3) (0) | 2020.09.02 |