Data Preparation & Exploration

박신영·2023년 3월 25일
1

🔶Introduction

✔️ PorteSeguro Competition data에 대한 good insight를 얻는 것이 목표
✔️ 운전자가 내년에 자동차 보험 청구를 시작할 확률을 예측하는 모델 구축
✔️ predict_proba 함수를 통해 확률 값 예측
✔️ Normalized Gini Coefficient를 평가지표로 사용

  • ✔️ 메인 섹션
    1. 데이터에 대한 시각적 영감
    2. 메타데이터 정의
    3. Descriptive 통계량
    4. 불균형 classes 처리
    5. 데이터의 quality 확인
    6. Exploratory data visualization
    7. Feature engineering
    8. Feature 선택
    9. Feature scaling

Information about data

  • train set 59만 개, test set 89만 개
  • feature가 비식별화 되어있어 난이도 높음(실제 기업 데이터는 보통 비식별화)
  • feature이 그루핑 되어 있음
    • ind : 정수값 매핑
    • reg : region
    • car : 차에 관한 변수
    • calc : 실수 값
  • target은 이전에 보험 청구가 있었으면 1, 아니면 0

Loading packages

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# matplotlib의 기본 scheme이 아닌 seaborn scheme을 세팅하기
# graph의 font size를 지정하지 않고 seaborn의 font_scale 사용하면 편리

from sklearn.impute import SimpleImputer
# 결측치 대체를 위한 라이브러리(Imputer->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)

🔶데이터에 대한 시각적 영감

✔️ pandas는 테이블화 된 데이터 다루기 최적화, 많이 쓰이는 라이브러리
✔️ 데이터 셋의 간단한 통계적 분석 ~ 복잡한 처리들 사용 가능
✔️ 캐글에서 데이터셋은 보통 train과 test set으로 나뉘어짐

from google.colab import drive
drive.mount('/content/drive')

pwd
  • 대회 데이터 설명의 발췌록
    • 유사한 그룹의 feature는 ind, reg, car, calc 등의 피쳐이름과 같은 태그 지정됨
    • bin ➡️ binary feature & cat ➡️ categorical feature
      • 지정 X feature 은 연속형 or 순서형
    • 값이 -1이면 형상이 관측치에서 누락되었음을 의미(Null Value)
  • target 열은 해당 정책 소유자에 관한 클레임이 제기되었는지 여부를 나타냄
df_train = pd.read_csv('./drive/MyDrive/ECC 데과B/train_w2.csv')
df_test = pd.read_csv('./drive/MyDrive/ECC 데과B/test_w2.csv')
df_train.head()
# head()는 파일의 앞부분만 보여주는 것
# 괄호 안에 아무 입력이 없을 경우 기본인 5줄로 출력
indexidtargetps_ind_01ps_ind_02_catps_ind_03ps_ind_04_catps_ind_05_catps_ind_06_binps_ind_07_binps_ind_08_binps_ind_09_binps_ind_10_binps_ind_11_binps_ind_12_binps_ind_13_binps_ind_14ps_ind_15ps_ind_16_binps_ind_17_binps_ind_18_bin
0702251001000000011010
190117000010000003001
21305491000100000012100
3160012001000000008100
4170020101000000009100
df_train.tail()
# tail()은 파일의 뒷부분만 보여주는 것
# 괄호 안에 아무 입력이 없을 경우 기본인 5줄로 출력
indexidtargetps_ind_01ps_ind_02_catps_ind_03ps_ind_04_catps_ind_05_catps_ind_06_binps_ind_07_binps_ind_08_binps_ind_09_binps_ind_10_binps_ind_11_binps_ind_12_binps_ind_13_binps_ind_14ps_ind_15ps_ind_16_binps_ind_17_binps_ind_18_bin
5952071488013031100000010000013100
59520814880160513000001000006100
5952091488017011100010000000012100
595210148802105231000100000012100
59521114880270018001000000007100
  • 알아낸 데이터 형식
    ▫️ binary variables
    ▫️ 정수로 이루어진 Categorical 변수
    ▫️ 나머지는 int나 float 변수
    ▫️ -1인 결측지를 가지는 변수
    ▫️ target 변수와 ID 변수
# rows와 cols의 갯수 확인
df_train.shape

(595212, 59)

➡️ train data에는 29개 변수와 595212개의 관측치 존재

# 중복된 데이터 제거하기
df_train.drop_duplicates()
df_train.shape

(595212, 59)

➡️ 중복되는 데이터 X

df_test.shape

(892816, 58)

➡️ test set에서 target 변수가 줄어서 전체 변수 하나가 줄어든 것
➡️ 나중에 14개 범주형 변수들에 대한 dummy 변수를 만들 수 있고 빈 변수는 이미 binary 변수라 중복값 제거하지 않아도 됨

df_train.info()
# 데이터의 정보 확인

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 595212 entries, 0 to 595211
Data columns (total 59 columns):

NumColumnNon-Null CountDtype
0id595212 non-nullint64
1target595212 non-nullint64
2ps_ind_01595212 non-nullint64
3ps_ind_02_cat595212 non-nullint64
4ps_ind_03595212 non-nullint64
5ps_ind_04_cat595212 non-nullint64
6ps_ind_05_cat595212 non-nullint64
7ps_ind_06_bin595212 non-nullint64
8ps_ind_07_bin595212 non-nullint64
9ps_ind_08_bin595212 non-nullint64
10ps_ind_09_bin595212 non-nullint64
11ps_ind_10_bin595212 non-nullint64
12ps_ind_11_bin595212 non-nullint64
13ps_ind_12_bin595212 non-nullint64
14ps_ind_13_bin595212 non-nullint64
15ps_ind_14595212 non-nullint64
16ps_ind_15595212 non-nullint64
17ps_ind_16_bin595212 non-nullint64
18ps_ind_17_bin595212 non-nullint64
19ps_ind_18_bin595212 non-nullint64
20ps_reg_01595212 non-nullfloat64
21ps_reg_02595212 non-nullfloat64
22ps_reg_03595212 non-nullfloat64
23ps_car_01_cat595212 non-nullint64
24ps_car_02_cat595212 non-nullint64
25ps_car_03_cat595212 non-nullint64
26ps_car_04_cat595212 non-nullint64
27ps_car_05_cat595212 non-nullint64
28ps_car_06_cat595212 non-nullint64
29ps_car_07_cat595212 non-nullint64
30ps_car_08_cat595212 non-nullint64
31ps_car_09_cat595212 non-nullint64
32ps_car_10_cat595212 non-nullint64
33ps_car_11_cat595212 non-nullint64
34ps_car_11595212 non-nullint64
35ps_car_12595212 non-nullfloat64
36ps_car_13595212 non-nullfloat64
37ps_car_14595212 non-nullfloat64
38ps_car_15595212 non-nullfloat64
39ps_calc_01595212 non-nullfloat64
40ps_calc_02595212 non-nullfloat64
41ps_calc_03595212 non-nullfloat64
42ps_calc_04595212 non-nullint64
43ps_calc_05595212 non-nullint64
44ps_calc_06595212 non-nullint64
45ps_calc_07595212 non-nullint64
46ps_calc_08595212 non-nullint64
47ps_calc_09595212 non-nullint64
48ps_calc_10595212 non-nullint64
49ps_calc_11595212 non-nullint64
50ps_calc_12595212 non-nullint64
51ps_calc_13595212 non-nullint64
52ps_calc_14595212 non-nullint64
53ps_calc_15_bin595212 non-nullint64
54ps_calc_16_bin595212 non-nullint64
55ps_calc_17_bin595212 non-nullint64
56ps_calc_18_bin595212 non-nullint64
57ps_calc_19_bin595212 non-nullint64
58ps_calc_20_bin595212 non-nullint64

dtypes: float64(10), int64(49)
memory usage: 267.9 MB

  • data type이 주로 integer or float
  • null 값 X
    - 결측치는 -1로 대체됐기 때문

🔶메타데이터 정의

  • 데이터 관리를 용이하게 하기 위해 변수에 대한 메타 정보를 데이터 프레임에 저장
  • 분석, 시각화, 모델링을 위해 특정 변수를 선택할때 유용
  • 데이터 정리 방식(각 특징별 정리) :
    • role : input, ID, target
    • level : nominal, interval, ordinal, binary
    • keep : True or False (버릴지 아닐지)
    • dtype : int, float, str
# append를 위해 빈 list 생성
data = []

for f in df_train.columns:

  # 데이터의 역할을 지정 (독립변수, 종속변수, id (PM))
  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 df_train[f].dtype == float:
    level = 'interval'
  elif df_train[f].dtype == int:
    level = 'ordinal'

  # id는 False로 지정하여 버림, 나머지는 True로 가져감
  keep = True
  if f == 'id' :
    keep = False

  # Defining the data type
  dtype = df_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'])
# dataframe에 먼저 데이터 저장, 변수 이름을 인덱스로 함
meta.set_index('varname', inplace=True)
# 인덱스를 varname으로 지정
  • DataFrame 생성
  • 비어있는 리스트를 만들어 train 데이터 셋의 column을 반복문을 활용하여 처리
  • 이름이 bin, cat 등으로 분류되어있어 상세히 확인 가능
  • meta 생성했으므로 meta 확인
meta
varnamerolelevelkeepdtype
ididNaNfalseint64
targettargetNaNtrueint64
ps_ind_01inputNaNtrueint64
ps_ind_02_catinputNaNtrueint64
ps_ind_03inputNaNtrueint64
ps_ind_04_catinputNaNtrueint64
ps_ind_05_catinputNaNtrueint64
ps_ind_06_bininputNaNtrueint64
ps_ind_07_bininputNaNtrueint64
ps_ind_08_bininputNaNtrueint64
ps_ind_09_bininputNaNtrueint64
ps_ind_10_bininputNaNtrueint64
ps_ind_11_bininputNaNtrueint64
ps_ind_12_bininputNaNtrueint64
ps_ind_13_bininputNaNtrueint64
ps_ind_14inputNaNtrueint64
ps_ind_15inputNaNtrueint64
ps_ind_16_bininputNaNtrueint64
ps_ind_17_bininputNaNtrueint64
ps_ind_18_bininputNaNtrueint64
ps_reg_01inputNaNtruefloat64
ps_reg_02inputNaNtruefloat64
ps_reg_03inputNaNtruefloat64
ps_car_01_catinputNaNtrueint64
ps_car_02_catinputNaNtrueint64
ps_car_03_catinputNaNtrueint64
ps_car_04_catinputNaNtrueint64
ps_car_05_catinputNaNtrueint64
ps_car_06_catinputNaNtrueint64
ps_car_07_catinputNaNtrueint64
ps_car_08_catinputNaNtrueint64
ps_car_09_catinputNaNtrueint64
ps_car_10_catinputNaNtrueint64
ps_car_11_catinputNaNtrueint64
ps_car_11inputNaNtrueint64
ps_car_12inputNaNtruefloat64
ps_car_13inputNaNtruefloat64
ps_car_14inputNaNtruefloat64
ps_car_15inputNaNtruefloat64
ps_calc_01inputNaNtruefloat64
ps_calc_02inputNaNtruefloat64
ps_calc_03inputNaNtruefloat64
ps_calc_04inputNaNtrueint64
ps_calc_05inputNaNtrueint64
ps_calc_06inputNaNtrueint64
ps_calc_07inputNaNtrueint64
ps_calc_08inputNaNtrueint64
ps_calc_09inputNaNtrueint64
ps_calc_10inputNaNtrueint64
ps_calc_11inputNaNtrueint64
ps_calc_12inputNaNtrueint64
ps_calc_13inputNaNtrueint64
ps_calc_14inputNaNtrueint64
ps_calc_15_bininputNaNtrueint64
ps_calc_16_bininputNaNtrueint64
ps_calc_17_bininputNaNtrueint64
ps_calc_18_bininputNaNtrueint64
ps_calc_19_bininputNaNtrueint64
ps_calc_20_bininputNaNtrueint64

---

🤔 이렇게 만들어서 어떻게 활용할 수 있을까?

  • EX1] 버리지 않을 변수 중에서 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')

  • EX2] 각 level의 role과 level에 해당하는 변수가 몇개인지 확인하기
    • reset_index()는 컬럼명 인덱스가 아닌 행 번호 인덱스로 사용하고 싶은 경우 사용
    • groupby()는 데이터를 그룹별로 분할 & 독립된 그룹에 대해 별도로 데이터 처리하여 그룹별 통계량 확인할 때 유용
pd.DataFrame({'count' : meta.groupby(['role', 'level'])['role'].size()}).reset_index()
# 컬럼명으로 지정한 인덱스 제거(SQL / 사용)
indexrolelevelcount
0idnominal1
1inputbinary17
2inputinterval10
3inputnominal14
4inputordinal16
5targetbinary1

▶️ 데이터 정보를 알고 싶거나 인덱싱이 필요한 경우 사용가능!

🔶Descriptive statistics

  • 데이터 프레임에 설명 방법 적용 가능
  • 범주형 변수와 id 변수에 대한 평균, 표준 등을 계산하는 것은 의미가 없음 ➡️ 나중에 시각적으로 살펴보자
  • 메타파일을 통해 기술 통계량을 계산할 변수 쉽게 선택 가능
    ⟫ 데이터 유형별로 작업 수행

1. Interval Type

v = meta[(meta.level == 'interval') & (meta.keep)].index
df_train[v].describe()
# 각 interval feature가 가진 통계치량 확인
indexps_reg_01ps_reg_02ps_reg_03ps_car_12ps_car_13ps_car_14ps_car_15ps_calc_01ps_calc_02ps_calc_03
count595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0
mean0.61099137786200550.43918435784224790.55110184104256810.379944815094820370.81326467563635820.27625627413690963.0658994433648190.44975638932010790.449589221991492150.44984879337110145
std0.28764261915676140.40426428574511440.79350576731915110.058326955342681230.224588096359593870.357154033289627470.73136622585668120.287198086467864270.28689348184136930.2871529853105775
min0.00.0-1.0-1.00.2506190682-1.00.00.00.00.0
25%0.40.20.5250.3162277660.67086659140.3331666252.82842712470.20.20.2
50%0.70.30.72067676530.37416573870.76581129670.36878177833.31662479040.50.40.5
75%0.90.61.00.40.90619040130.3964845523.60555127550.70.70.7
max0.91.84.03794502191.26491106413.72062600260.63639610313.74165738680.90.90.9
  • 결측치 확인
    • 결측치 전부 -1로 대체된 상태
    • ps_reg_03, ps_car_12, ps_car_14 (calc 변수는 결측치 X)
    • 1} reg 변수
      • 'ps_reg_03'에만 결측값 존재
      • 변수 간의 범위(최소에서 최대)가 다름
        • 스케일링(ex. StandardScaler)을 적용할 수 있음
        • 사용할 분류기(classifier)에 따라 다름
    • 2} car 변수
      • 'ps_car_12'와 'ps_car_15'에 결측값 존재
      • reg 변수와 같이 범위가 다르고 스케일링 적용 가능
    • 3} calc 변수
      • 결측값이 없음
      • 최대치가 0.9
      • 모든 calc 세 변수는 분포가 매우 유사함
     
  • 변수 사이의 범위 확인
    • 범위가 변수들 사이 차이는 있지만 작은 정도
    • scaling을 할지 말지 고민해봐야 할 문제
  • 변수들의 숫자 크기 확인
    • 구간 변수 크기가 전부 작은 듯
    • 익명화를 위해 Log등의 변환을 해준게 아닌가 싶음
  • reg 변수
    • 'ps_reg_03'에만 결측값 존재
    • 변수 간의 범위(최소에서 최대)가 다름
      • 스케일링(ex. StandardScaler)을 적용할 수 있음
      • 사용할 분류기(classifier)에 따라 다름
  • car 변수
    • 'ps_car_12'와 'ps_car_15'에 결측값 존재
    • reg 변수와 같이 범위가 다르고 스케일링 적용 가능
  • calc 변수
    • 결측값이 없음
    • 최대치가 0.9
    • 모든 calc 세 변수는 분포가 매우 유사함

2. Ordinal 변수

v= meta[(meta.level == 'ordinal') & (meta.keep)].index
df_train[v].describe()
indexps_ind_01ps_ind_03ps_ind_14ps_ind_15ps_car_11ps_calc_04ps_calc_05ps_calc_06ps_calc_07ps_calc_08ps_calc_09ps_calc_10ps_calc_11ps_calc_12ps_calc_13ps_calc_14
count595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0
mean1.90037835258697754.4233180782645510.0124510258529733947.29992170856770352.3460716517812142.37208087202542961.88588603724387287.6894451052734153.0058231352862519.225904383648182.3390338232428118.433590048587735.4413822301969721.44191817369273472.87228752108492467.539026430918732
std1.98378911750731082.6999019606950330.127544969552776553.54604210065565660.83254780877789651.11721893607962191.13492706675558691.33431221563495521.4145638683670271.45967194723976411.24694915857757722.9045972852736582.3328712439095641.20296252734517321.69488686360502542.746651636388356
min0.00.00.00.0-1.00.00.00.00.02.00.00.00.00.00.00.0
25%0.02.00.05.02.02.01.07.02.08.01.06.04.01.02.06.0
50%1.04.00.07.03.02.02.08.03.09.02.08.05.01.03.07.0
75%3.06.00.010.03.03.03.09.04.010.03.010.07.02.04.09.0
max7.011.04.013.03.05.06.010.09.012.07.025.019.010.013.023.0
  • 결측값 1개 ; 'ps_car_11'
  • interval 데이터와 비슷하게 범위를 보면 변수들 사이의 차이가 있지만 작음
  • 다른 범위를 가지는 것에 대해 스케일링 적용 가능

3. Binary variables

v = meta[(meta.level == 'binary') & (meta.keep)].index
df_train[v].describe()
indextargetps_ind_06_binps_ind_07_binps_ind_08_binps_ind_09_binps_ind_10_binps_ind_11_binps_ind_12_binps_ind_13_binps_ind_16_binps_ind_17_binps_ind_18_binps_calc_15_binps_calc_16_binps_calc_17_binps_calc_18_binps_calc_19_binps_calc_20_bin
count595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0595212.0
mean0.0364475178591829460.393742061651982830.257032788317439860.163921426315329660.185303723715247670.000372976351283240250.00169183417001001310.009438653790582180.00094756154109796180.66082337049656260.121081228201044330.153446167079964780.122426967198241980.62784016451281220.55418237535533550.287181710046168440.349023541192045870.15331848148222818
std0.18740105470315550.48857921731058690.43699800329779060.37020456853820960.38854408672293860.019309009977720260.041097137427722430.09669323302687190.0307679258106742480.473430269488003650.326221923195020470.360417340219789330.32777856154110420.48338109696055110.49705601825923230.45244747693721670.47666181996196120.36029452231774634
min0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
25%0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
50%0.00.00.00.00.00.00.00.00.01.00.00.00.01.01.00.00.00.0
75%0.01.01.00.00.00.00.00.00.01.00.00.00.01.01.01.01.00.0
max1.01.01.01.01.01.01.01.01.01.01.01.01.01.01.01.01.01.0
  • 결측값 X
  • 값은 무조건 0 또는 1이므로 범위확인 X
  • train 데이터의 1(=true)의 비율은 3.645%로 매우 불균형적
  • 평균을 통해 대부분의 변수값이 0이라는 것을 알 수 있음

🔶불균형 classes 처리

  • 보험 특성상 불균형한 target인 것이 일반적이긴 함
  • target이 1이 레코드 비율이 0인 레코드 비율보다 훨씬 적음 → 보험 청구하지 않는 경우가 더 많음
  • imbalanced data라 정확도는 높지만 실제로는 부가가치가 있는 모델이 될수도 있음
  • 해결 방안:
    1. target이 1인 레코드 oversampling(1인 data를 늘리는 것)
    2. target이 0인 레코드 undersampling(0인 data를 줄이는 것)
      ✔️ 현재 training set가 크기 때문에 undersampling 진행! oversampling할 경우 너무 많은 cost가 들어가게 될듯(시간, 컴퓨팅 파워 등)
desired_apriori = 0.10
# 언더샘플링 비율을 지정해줌

# target 값에 따른 인덱스 지정
idx_0 = df_train[df_train.target == 0].index # 보험 청구
idx_1 = df_train[df_train.target == 1].index # 보험 청구 X

# 지정해준 인덱스로 레코드 수(클래스의 길이) 지정
nb_0 = len(df_train.loc[idx_0])
nb_1 = len(df_train.loc[idx_1])

# undersampling 수행
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))

Rate to undersample records with target=0 : 0.34043569687437886
Number of records with target=0 after undersampling : 195246

✔️ 불균형이 많이 사라졌음(96% → 34%)

# undersampling 비율이 적용된 개수만큼 랜덤으로 샘플 뽑아서 인덱스에 저장
undersampled_idx = shuffle(idx_0, random_state = 37, n_samples = undersampled_nb_0)

# undersampling 인덱스와 target=1인 인덱스를 리스트로 저장
idx_list = list(undersampled_idx) + list(idx_1)

# undersampling된 데이터를 train set으로 반환
train = df_train.loc[idx_list].reset_index(drop=True)

🔶데이터의 quality 확인

결측치 확인

  • 결측치는 -1로 표시
vars_with_missing = []

# 모든 컬럼에 -1 값이 1개 이상 있는 것을 확인하여 출력
for f in df_train.columns:
  missings = df_train[df_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 vales'.format(f, missings, missings_perc))
   # 어느 변수인지, 레코드 개수, 비율을 전부 출력

print('In total, there are {} variables with missing values'.format(len(vars_with_missing)))

Variable ps_ind_02_cat has 216 records (0.10%) with missing vales
Variable ps_ind_04_cat has 83 records (0.04%) with missing vales
Variable ps_ind_05_cat has 5809 records (2.68%) with missing vales
Variable ps_reg_03 has 107772 records (49.68%) with missing vales
Variable ps_car_01_cat has 107 records (0.05%) with missing vales
Variable ps_car_02_cat has 5 records (0.00%) with missing vales
Variable ps_car_03_cat has 411231 records (189.56%) with missing vales
Variable ps_car_05_cat has 266551 records (122.87%) with missing vales
Variable ps_car_07_cat has 11489 records (5.30%) with missing vales
Variable ps_car_09_cat has 569 records (0.26%) with missing vales
Variable ps_car_11 has 5 records (0.00%) with missing vales
Variable ps_car_12 has 1 records (0.00%) with missing vales
Variable ps_car_14 has 42620 records (19.65%) with missing vales
In total, there are 13 variables with missing values

  • ps_car_03_cat과 ps_car_05_cat은 결측치가 굉장히 많음 ➡️ 확실한 대체 방법 X니까 변수 제거
  • 나머지 번주형 cat 변수들은 -1 값 그대로 둠
  • ps_reg_03(연속)은 18%가 결측치 ➡️ 평균으로 대체
  • ps_car_11(순서)는 5개 레코드에만 결측값 존재 ➡️ 최빈값으로 대체
  • ps_car_12(연속)은 1개 레코드에만 결측값 존재 ➡️ 평균으로 대체
  • ps_car_14(연속)은 7%가 결측치 ➡️ 평균으로 대체
# 결측치가 너무 많은 변수들 제거
vars_to_drop = ['ps_car_03_cat', 'ps_car_05_cat']
df_train.drop(vars_to_drop, inplace=True, axis=1)

# 만들어둔 메타데이터에서 버린 변수를 keep = True에서 False로 업데이트
meta.loc[(vars_to_drop),'keep'] = False

# 나머지 결측치를 평균과 최빈값으로 대체
mean_imp = SimpleImputer(missing_values=-1, strategy ='mean')
mode_imp = SimpleImputer(missing_values = -1, strategy = 'most_frequent')
df_train['ps_reg_03'] = mean_imp.fit_transform(df_train[['ps_reg_03']])
df_train['ps_car_12'] = mean_imp.fit_transform(df_train[['ps_car_12']])
df_train['ps_car_14'] = mean_imp.fit_transform(df_train[['ps_car_14']])
df_train['ps_car_11'] = mode_imp.fit_transform(df_train[['ps_car_11']])

범주형 변수의 cardinality 확인

  • cardinality : 변수에 포함된 서로 다른 값의 수
  • 나중에 범주형 변수에서 dummy 변수를 만들것이므로 다른 값을 가진 변수의 개수 확인 필요
  • 다른 값을 가진 변수가 많으면 많은 dummy 변수를 초래하므로 다르게 처리해야함
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))

Variable ps_ind_02_cat has 5 distinct values
Variable ps_ind_04_cat has 3 distinct values
Variable ps_ind_05_cat has 8 distinct values
Variable ps_car_01_cat has 13 distinct values
Variable ps_car_02_cat has 3 distinct values
Variable ps_car_04_cat has 10 distinct values
Variable ps_car_06_cat has 18 distinct values
Variable ps_car_07_cat has 3 distinct values
Variable ps_car_08_cat has 2 distinct values
Variable ps_car_09_cat has 6 distinct values
Variable ps_car_10_cat has 3 distinct values
Variable ps_car_11_cat has 104 distinct values

  • ps_car_11_cat가 많은 고유한 값을 가지지만 여전히 합리적임
  • Smoothing은 Daniele Micci-Barreca에 의해 아래 논문처럼 계산됨
  • parameters
    • trn_series : 범주형 변수를 pd.Series로 train
    • tst_series : 범주형 변수를 pd.Series로 test
    • target : target data를 pd.Series 형으로
    • min_samples_leaf(int) : 범주의 평균을 설명해 줄 수 있는 최소한의 표본
    • smoothing(int) : 범주형 평균과 이전 평균의 균형을 맞추려고 효과를 smoothing
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):
                  
    assert len(trn_series) == len(target)
    assert trn_series.name == tst_series.name
    
    temp = pd.concat([trn_series, target], axis=1)
    
    # agg를 사용해서 평균값을 구해줌
    # agg(): 여러 개의 함수를 여러개의 열에 적용하는 집계 연산
    averages = temp.groupby(by=trn_series.name)[target.name].agg(["mean", "count"])
    
    # 오버피팅 방지를 위한 smoothing
    smoothing = 1 / (1 + np.exp(-(averages["count"] - min_samples_leaf) / smoothing))
    
    prior = target.mean()
    averages[target.name] = prior * (1 - smoothing) + averages["mean"] * smoothing
    averages.drop(["mean", "count"], axis=1, inplace=True)
    
    # train과 test에 적용시켜준다.
    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 
    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)
# 위에서 구현한 함수를 ps_car_11_cat(104개의 유니크 값)에 적용시켜줌
# feature가 바뀌었으므로 메타데이터를 업데이트

# 범주형 변수가 dummy 변수로 encoding
train_encoded, test_encoded = target_encode(df_train["ps_car_11_cat"], 
                             df_test["ps_car_11_cat"], 
                             target=df_train.target, 
                             min_samples_leaf=100,
                             smoothing=10,
                             noise_level=0.01)
    
df_train['ps_car_11_cat_te'] = train_encoded
df_train.drop('ps_car_11_cat', axis=1, inplace=True)
meta.loc['ps_car_11_cat','keep'] = False  
df_test['ps_car_11_cat_te'] = test_encoded
df_test.drop('ps_car_11_cat', axis=1, inplace=True)

🔶Exploratory data visualization

범주형 변수의 시각화

  • target값이 1인 범주형 변수들(고객들)의 특성을 시각화를 통해 비율 파악
Nominal = meta[(meta["level"] == 'nominal') & (meta["keep"])].index


# 변수별로 반복문을 돌려서 barplot을 그린다.
for f in Nominal:
    plt.figure()
    fig, ax = plt.subplots(figsize=(20,10))
    ax.grid(axis = "y", linestyle='--')
    
    # 변수 별 target=1의 비율 계산
    cat_perc = df_train[[f, 'target']].groupby([f],as_index=False).mean()
    cat_perc.sort_values(by='target', ascending=False, inplace=True)
    
    # 위에서 계산해준 비율을 통해 target = 1의 데이터 중 어떤 유니크값의 비율이 높은지 확인할 수 있음
    # target 평균을 내림차순으로 정렬하여 막대그래프 그리기
    sns.barplot(ax=ax, x=f, y='target',palette = "Pastel1", edgecolor='black', linewidth=0.8, data=cat_perc, order=cat_perc[f], )
    plt.ylabel('% target', fontsize=18)
    plt.xlabel(f, fontsize=18)
    plt.tick_params(axis='both', which='major', labelsize=18)
    plt.show();

< Figure size 432x288 with 0 Axes >










  • 결측값은 모드로 대체하는 대신 별도의 범주 값으로 유지하는 것이 좋음
  • 결측값을 가진 고객은 보험금 청구를 요청할 확률이 훨씬 높은(경우에 따라 훨씬 낮은) 것으로 보임
  • 시각화한 barplot만 보면, 단순 비율로만 계산하고 count 값은 고려X이며 결측치가 많은 것은 대체해줬으므로 -1 count는 작은 것 밖에 안남아, 잘못된 인사이트를 얻을 수 있음

cf] 범주형 변수의 countplot이나 barplot을 그릴 때 groupby(hue를 지정)하여 살피면 어떤 feature을 만들 수 있을지 생각해볼 수 있음 → 더 다양한 인사이트 가질 수 있음

  • 범주형 변수에 대한 결과
    1. ps_ind_02_cat: -1(결측치)의 경우 target = 1의 데이터가 40%, 나머지는 10% 정도
      ✴️ 높다고 좋은게 아님!!
      ▪️ target 비율이므로 50%에 가까울수록 애매한 unique 값임
      ▪️ 차라리 10%인 나머지 unique 값들이 보험 청구를 안할 확률이 높다는 뜻으로 오히려 확실한 정보
    2. ps_ind_04_cat: -1(결측치)가 65% 정도로 target = 1의 값을 가짐. 보험 청구할 확률이 높아보임
    3. ps_ind_05_cat: unique 값 마다 차이가 있지만 눈에 띄지 X
    4. ps_car_01_cat: -1(결측치)가 거의 50%에 가까우므로 애매하고 나머지 값은 다 보험을 청구하지 않을 확률이 높아보임
    5. ps_car_02_cat: -1(결측치)가 0%이므로 보험을 절대 청구하지 않을 것으로 보임

Interval 변수의 시각화(연속형)

  • 구간 변수 간의 상관 관계를 확인
  • heatmap는 변수 간의 상관 관계를 시각화하는 좋은 방법
  • Seaborn의 diverging_palette() 클래스를 사용해서 양 끝 값들이 나뉘는 데이터에 대해 커스터마이징한 컬러맵을 만들 수 있음
    • as_cmap=True 를 사용해서 생성한 팔레트를 seaborn이나 matplotlib에서 바로 쓸 수 있는 컬러맵 객체로도 받아오기도 가능
  • corr() : 각 열 간의 상관 계수를 반환하는 메서드
def corr_heatmap(Interval):
    correlations = df_train[Interval].corr()
    # corr() : 각 열 간의 상관 계수를 반환하는 메서드

    # Create color map ranging between two colors
    cmap = sns.diverging_palette(220, 10, as_cmap=True)

    fig, ax = plt.subplots(figsize=(10,10))
    sns.heatmap(correlations, cmap=cmap, vmax=1.0, center=0, fmt='.2f',
                square=True, linewidths=.5, annot=True, cbar_kws={"shrink": .75})
    plt.show();
    
Interval = meta[(meta["role"] == "target") | (meta["level"] == 'interval') & (meta["keep"])].index
corr_heatmap(Interval)

  • 몇개의 변수들은 강한 상관관계를 보임
    • ps_reg_02 & ps_reg_03 (0.7)
    • ps_car_12 & ps_car_13 (0.67)
    • ps_car_12 & ps_car_14 (0.58)
    • ps_car_13 & ps_car_15 (0.67)
  • 살펴본 강한 상관관계를 가지는 변수들에 대해 추가로 시각화를 진행
  • pairplot을 사용하여 변수 사이의 관계 시각화
  • heatmap에는 상관관계가 있는 변수의 수가 제한돼있으므로 각 변수를 개별적으로 살펴보자
    • 프로세스 속도를 높이기 위해 train data sample 채취
s = train.sample(frac=0.1)

✳️ ps_reg_02 & ps_reg_03

sns.lmplot(x='ps_reg_02', y='ps_reg_03', data=df_train, hue='target', palette='Set1', scatter_kws={'alpha':0.3})
plt.show()

  • plot을 보면 두 개의 변수가 선형관계를 이루는 것을 확인할 수 있음
  • 회귀선이 겹치거나 비슷한 모습
  • 색상 매개변수로 인해 target = 0, 1의 회귀선이 동일함을 알 수 있음

✳️ ps_car_12 & ps_car_13

sns.lmplot(x='ps_car_12', y='ps_car_13', data=df_train, hue='target', palette='Set1', scatter_kws={'alpha':0.3})
plt.show()

✳️ ps_car_12 & ps_car_14

sns.lmplot(x='ps_car_12', y='ps_car_14', data=df_train, hue='target', palette='Set1', scatter_kws={'alpha':0.3})
plt.show()

✳️ ps_car_13 & ps_car_15

sns.lmplot(x='ps_car_15', y='ps_car_13', data=df_train, hue='target', palette='Set1', scatter_kws={'alpha':0.3})
plt.show()

  • 변수에 대한 PCA(주성분 분석)을 수행하여 치수를 줄일 수 있음
    • 상관 변수의 수가 적으므로 모델이 heavy-lifting을 하도록 하자

Ordinal 변수의 시각화

Ordinal = meta[(meta["role"] == "target") | (meta["level"] == 'ordinal') & (meta["keep"])].index
corr_heatmap(Ordinal)

  • heatmap으로 상관관계 파악
  • 순서형 변수의 경우 변수간에 큰 상관관계를 보이지 않음
    • target 값을 grouping하면 분포가 어떻게 형성되는지 확인 가능

🔶Feature Engineering

Dummy 변수 생성

  • 범주형 변수는 순서나 크기 나타내지 X
    • category 2 ≠ (category 1) *2
  • 이러한 문제해결을 위해 dummy 변수 활용
    • 원래 변수의 범주에 대해 생성된 다른 dummy 변수에서 파생 가능 -> 첫번째 dummy 변수 삭제
  • unique 값을 갖는 범주형 변수가 아닌 나머지 변수들은 one-hot encoding 으로 dummy화
  • 범주형 변수에 순서도 부여하지 않고, unique 값도 많지 않아 차원이 많이 늘어나지 X
v = meta[(meta["level"] == 'nominal') & (meta["keep"])].index
print('Before dummification we have {} variables in train'.format(df_train.shape[1]))
df_train = pd.get_dummies(df_train, columns=v, drop_first=True)
print('After dummification we have {} variables in train'.format(df_train.shape[1]))

Before dummification we have 57 variables in train
After dummification we have 109 variables in train

  • 52개의 변수 증가

interaction 변수 생성

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(df_train[v]), columns=poly.get_feature_names_out(v))
interactions.drop(v, axis=1, inplace=True)  # interactions 데이터프레임에서 기존 변수 삭제

# 새로 만든 변수들을 기존 데이터에 concat 시켜줌
print('Before creating interactions we have {} variables in train'.format(train.shape[1]))
train = pd.concat([df_train, interactions], axis=1)
print('After creating interactions we have {} variables in train'.format(train.shape[1]))

Before creating interactions we have 59 variables in train
After creating interactions we have 164 variables in train

  • train 데이터에 interaction 변수 추가
  • get_feature_names_out() 메서드로 열 이름을 새 변수에 할당 가능

🔶Feature 선택

분산이 낮거나 0인 feature 제거

  • Variance Threshold(분산임계값) : feature을 제거할 수 있는 sklearn의 방법
    • 분산이 0인 형상 제거
    • 이전 단계에서 zero-variance 변수 없었음
    • 분산이 1% 미만인 feature 제거하면 31개의 변수 제거됨
selector = VarianceThreshold(threshold=.01)
selector.fit(df_train.drop(['id', 'target'], axis=1)) # Fit to train without id and target variables

f = np.vectorize(lambda x : not x) # Function to toggle boolean array elements

v = df_train.drop(['id', 'target'], axis=1).columns[f(selector.get_support())]
print('{} variables have too low variance.'.format(len(v)))
print('These variables are {}'.format(list(v)))

26 variables have too low variance.
These variables are ['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_0', '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']

🔶Feature scaling

scaler = StandardScaler()
scaler.fit_transform(train.drop(['target'], axis=1))
profile
기술블로그

0개의 댓글