티스토리 뷰

본 커널은 Porto Seguro의 금메달 EDA 커널을 쉽게 이해하도록 작성한 Korean Starter들을 위한 커널입니다.

 

대부분의 내용은 이미 공개되어 있는 커널이며 이해하기 쉽게 한글로 번역, 설명을 추가한 커널입니다 참고 부탁드립니다.

 

본 커널은 비식별 Feature를 사용하는 대회의 EDA를 위한 커널로, 모델링 및 submission은 진행하지 않습니다.

초보자의 입장에서 초보자분들을 위해 커널을 작성하는 만큼, 쉽게 설명하고자 하였습니다.

 

데이터를 이해함에 있어서 큰 도움을 주신 참고 코드 작성자 분께 감사합니다 :)

 

금메달 커널 원본: https://www.kaggle.com/bertcarremans/data-preparation-exploration

 

작성자 커널 원본: https://www.kaggle.com/kongnyooong/porto-eda

 

PORTO SEGURO: EDA for Korean (한글커널)

Explore and run machine learning code with Kaggle Notebooks | Using data from Porto Seguro’s Safe Driver Prediction

www.kaggle.com

< 캐글 커널 원본입니다! 하나의 Upvote가 더 좋은 커널을 만드는 원동력이 됩니다 :) >

Part2는 본격적인 EDA에 대해 다뤄볼 예정입니다. 

 

Part1을 아직 안보고 오신 분들께서는 여기를 먼저 보고 와주시는게 좋을 것 같습니다! 

 

Part2. EDA Part1

Interval 변수

Interval = meta[(meta["level"] == "interval") & (meta["keep"])].index
# describe를 통해 interval 변수들의 통계량을 확인 

df_train[Interval].describe()

Part1에서 만들었던 메타데이터를 활용하여 Interval 변수의 통계량을 확인해봅니다.

 

1) 결측치 확인

  • 말했듯이 Porto 대회의 결측치는 전부 -1로 대체되어 있습니다.
  • 그렇기 때문에 isnull()이 아닌 -1값을 확인해주면 됩니다.
  • 위의 통계량을 보면 min값이 -1인 변수가 몇개 존재합니다.
  • ps_reg_03, ps_car_12, ps_car_14 (calc 변수는 결측치 X)

2) 변수들 사이의 범위 확인

  • 범위를 확인해보면 변수들 간의 차이가 있지만 커보이지는 않습니다.
  • Scaling을 할지 말지 추후에 생각해보도록 합니다. (아마 트리모델을 사용할것이기 때문에 딱히..)

3) 변수들의 숫자 크기 확인

  • 어떤 변수인지 확인할 수는 없지만 변수의 크기가 전부 작은것으로 보입니다.
  • 이 커널을 작성한 사람은 Log를 씌워준게 아닌가 라고 생각했다고 합니다.

Ordinal 변수

Ordinal = meta[(meta["level"] == "ordinal") & (meta["keep"])].index
# describe를 통해 Ordinal 변수들의 통계량을 확인 

df_train[Ordinal].describe()

역시 메타데이터를 활용하여 Ordinal 변수의 통계량을 확인해봅니다.

 

1) 결측치 확인

  • 위의 통계량을 보면 min값이 -1인 변수가 한개 존재합니다.
  • ps_car_11 (나머지는 결측치 X)

2) 변수들 사이의 범위 확인

  • Ordinal 데이터 또한 범위를 확인해보면 변수들 간의 차이가 있지만 커보이지는 않습니다.

Binary 변수

Binary = meta[(meta["level"] == 'binary') & (meta["keep"])].index
# describe를 통해 Ordinal 변수들의 통계량을 확인 

df_train[Binary].describe()

한번 만들어놓은 메타데이터를 유용하게 사용합니다.

이번엔 Binary 변수의 통계량을 확인해보도록 합니다.

 

1) 결측치 확인

  • 결측치 X

2) 변수들 사이의 범위 확인

  • Binary 데이터이기 때문에 범위를 확인할 필요는 없습니다. (0 or 1)

3) Target 변수 확인

  • Binary에는 Target 변수까지 포함되어 있습니다.
  • 그렇기 때문에 Target 변수에 대한 통계량을 확실하게 짚고 가야합니다
  • Target 데이터의 평균을 살펴보면 이 대회의 핵심을 알 수 있습니다.
  • 데이터는 0 or 1 이기 때문에 균형이 맞기 위해선 평균이 0.5가 되어야 합니다.
  • 하지만 Target 데이터의 평균은 0.0364로 보입니다. (굉장히 Imbalanced하다. 0이 훨씬 많아보입니다)
  • 대회의 Metric으로 Normalized Gini Coefficient를 사용하는 이유라고 할 수 있습니다.

다음으로 불균형 데이터를 어떻게 처리할 지 생각해보도록 하겠습니다.

Imbalanced Class 처리

  • 보험이라는 도메인 특성상 불균형적인 타겟값은 사실 일반적입니다.
  • 보험을 청구하는 경우 (1) 보다 하지 않는 경우 (0)이 굉장히 많은 것을 위에서 확인하였습니다.
  • Imbalanced한 데이터는 일반적으로 Undersampling 혹은 Oversampling으로 처리합니다.
  • UnderSampling: 0이 1보다 훨씬 많으므로 0인 데이터를 줄여 균형을 맞춰줍니다.
  • OverSampling: 0이 1보다 훨씬 많으므로 1인 데이터를 늘려 균형을 맞춰줍니다.
  • 선택방법: 본인의 결정이지만 보통 데이터셋의 크기를 기준으로 선택합니다.
    데이터가 너무 많으면, 오버샘플링 시 너무 많은 Cost가 들어가게 됩니다(시간, 컴퓨팅파워)
    이 커널은 데이터가 많은편이라고 판단하여 언더샘플링을 수행하였습니다.

Inbalanced Class에 대해 구글링하며 찾아본 아주 좋은 자료입니다. 

참고:  https://datascienceschool.net/view-notebook/c1a8dad913f74811ae8eef5d3bedc0c3/

 

Data Science School

Data Science School is an open space!

datascienceschool.net

f, ax = plt.subplots(figsize = (8,8))

df_train['target'].value_counts().plot.pie(explode = [0, 0.1], autopct = '%1.1f%%', 
                                               shadow = True, colors = ['lightcoral', 'lightskyblue'],
                                              textprops={'fontsize': 18})
plt.title("Target PiePlot", size = 20)

# 불균형이 굉장히 심하다.

파이플랏을 그려 불균형의 정도를 확인합니다.

 

class가 1인 데이터가 3.6%로 굉장히 작습니다. 

# 언더샘플링 비율을 지정해주기 위함 
desired_apriori=0.10

# target 변수의 클래스에 따른 인덱스 지정 
idx_0 = df_train[df_train["target"] == 0].index
idx_1 = df_train[df_train["target"] == 1].index

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

# 언더샘플링 수행
undersampling_rate = ((1-desired_apriori)*nb_1)/(nb_0*desired_apriori)
undersampled_nb_0 = int(undersampling_rate*nb_0)
print('target=0에 대한 언더샘플링 비율: {}'.format(undersampling_rate))
print('언더샘플링 전 target=0 레코드의 개수: {}'.format(nb_0))
print('언더샘플링 후 target=0 레코드의 개수: {}'.format(undersampled_nb_0))

# 언더샘플링 비율이 적용된 개수 만큼 랜덤하게 샘플을 뽑아서 그 인덱스를 저장
undersampled_idx = shuffle(idx_0, random_state=37, n_samples=undersampled_nb_0)

# 언더샘플링 인덱스와 클래스 1의 인덱스를 리스트로 저장
idx_list = list(undersampled_idx) + list(idx_1)

# 저장한 인덱스로 train set 인덱싱
df_train = df_train.loc[idx_list].reset_index(drop=True)

위 커널에서 Imbalanced class를 undersampling 해주기 위해 사용한 코드입니다. 

 

뭔가 복잡해보이지만 간단하게 샘플링할 비율을 정해주고 비율에 맞게 언더샘플링을 수행한 코드입니다.

 

위 방법이 복잡하다면 아래와 같은 라이브러리를 사용하셔도 좋습니다.

# 실습을 위해 불균형 데이터 랜덤으로 생성 

import scipy as sp

n0 = 200; n1 = 20
rv1 = sp.stats.multivariate_normal([-1, 0], [[1, 0], [0, 1]])
rv2 = sp.stats.multivariate_normal([+1, 0], [[1, 0], [0, 1]])
X0 = rv1.rvs(n0, random_state=0)
X1 = rv2.rvs(n1, random_state=0)
X_imb = np.vstack([X0, X1])
y_imb = np.hstack([np.zeros(n0), np.ones(n1)])
X_train = pd.DataFrame(data = X_imb, columns = ["X0", "X1"])
y_train = pd.DataFrame(data = y_imb, columns = ["target"])

#-------------------------------------------------------------------------------------------------------------
# 1) RandomUnderSampler

from imblearn.under_sampling import RandomUnderSampler
Undersampled_train, Undersampled_target = RandomUnderSampler(random_state=0).fit_sample(X_train, y_train)

nb_0 = len(y_train[y_train["target"] == 0.0].index)
undersampled_nb_0 = len(Undersampled_target[Undersampled_target["target"] == 0.0].index)

print('RandomUnderSampler 전 target=0 레코드의 개수: {}'.format(nb_0))
print('RandomUnderSampler 후 target=0 레코드의 개수: {}'.format(undersampled_nb_0))

#-------------------------------------------------------------------------------------------------------------
# 2) TomekLinks

from imblearn.under_sampling import TomekLinks
Undersampled_train, Undersampled_target = TomekLinks().fit_sample(X_train, y_train)

* 예를 들기위해 불균형 데이터를 만든것임으로 Porto의 데이터를 사용한 코드가 아닙니다 *

 

이 코드와 같이 RandomUnderSampler라는 라이브러리를 사용하면 훨씬 간편하게 언더샘플링 수행이 가능합니다.

또한 TomekLinks라는 방법도 있습니다.

 

자세한 내용은 위의 참고링크를 들어가시면 확인하실 수 있습니다.

 

결측치 확인

  • 위에서 간단하게 어느 feature에 결측치가 있는지 확인하였습니다.
  • 얼마나 있는지도 확인해봅니다.
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/df_train.shape[0]
        
        print('Variable {}\t has {:>10} records\t ({:.2%})\t with missing values'.format(f, missings, missings_perc))
print()        
print('In total, there are {} variables with missing values'.format(len(vars_with_missing)))

중요한것은 Null Value를 처리할 때 항상 Null값의 의미를 파악해야합니다.

진짜 결측된 값인지,
결측된 값 때문에 target에 변화를 줄 수 있는지,
0으로 표현되었을 경우 Null Value인지 혹은 진짜 0으로 관측된 값인지,
이 대회처럼 -1로 표현되었을 경우 실제 -1인 값을 가질수도 있는지,
등등

커널의 경우 어떤 기준인지는 나와있지 않고 어떻게 처리했는지만 나옵니다.

  • ps_car_03_cat와 ps_car_05_cat는 결측치가 굉장히 많은 것으로 보입니다.
  • 이러한 변수는 확실한 대체방법이 있지 않는 이상 제거하는것이 좋습니다.
  • 이 커널은 ps_car_03_cat와 ps_car_05_cat를 제외한 나머지 cat 변수들은 -1값을 그대로 두었습니다.
  • ps_reg_03의 경우 평균으로 대체해줍니다.
  • ps_car_11의 경우 결측치가 딱 한개 있다. 최빈값으로 대체해줍니다. (순서형 변수이므로)
  • 결론적으로 이 커널에서는 연속형 변수는 mean, 범주형 변수는 mode로 채워주었습니다.
# 결측치가 너무 많았던 변수들 제거 
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  

# 그 외의 결측치를 평균과 최빈값으로 대체
# SimpleImputer를 사용 (커널에서는 그냥 Imputer를 사용하는데 업데이트 후 이름이 바뀐듯)
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']])

Part. 2는 여기까지 다뤄보도록 하겠습니다.

 

다음 파트에서는 EDA를 이어나가 범주형 변수 인코딩과 countplot 시각화를 해보려고 합니다. 

 

그럼 다음 포스팅에서 뵙겠습니다.

 

감사합니다 :) 

반응형
댓글