'scaling data with outliers'에 해당되는 글 1건

  1. 2016.12.15 [Python] 이상치, 특이값이 들어있는 데이터의 표준화 (Scaling data with outliers) 2

지난번 포스팅에서는 zscore(), StandardScaler() 등을 사용해서 척도(scale)가 다른 변수들을 X ~ N(0, 1) 의 표준정규분포로 변환시키는 표준화에 대해서 알아보았습니다. 

 

그런데 표준정규분포로의 표준화 변환 시에는 "이상치, 특이값 (outlier)이 없어야 한다"는 가정사항이 있습니다.  표준정규분포로 변환하는 공식이 z = (x - mean) / std  이며, 평균(mean)은 이상치, 특이값에 엄청 민감하기 때문입니다.

 

그럼, 데이터에 "이상치"가 포함되어 있다면 어떻게 해야할까요?

 

첫번째 방법은 "이상치, 특이값을 찾아서 제거"한 후 표준정규분포로 표준화 변환을 해서 분석, 모델링을 진행하는 방법입니다.  "이상치, 특이값을 찾아서 제거"하는 노~력이 필요합니다. 물론,  회귀분석과 같은 parametric modeling 에서는 이상치 제거 후 모델링이 적합한 방법입니다.

 

두번째 방법은 "이상치, 특이값에 덜 민감한" 중앙값(median)과 IQR(Inter-Quartile Range)을 이용해서 척도를 표준화하는 방법입니다.  K-NN 같은 non-parametric modeling 은 두번째 방법도 써볼만 합니다.

 

이번 포스팅의 주제가 바로 두번째 방법에 대한 것입니다. 

 

이번 포스팅의 주인공은 RobustScaler() 이지만, 왜 필요하고 무엇이 표준정규분포 표준화와 다른지를 이해하기 쉽도록 하기 위해서, 이상치(outlier)를 포함하고 있는 동일한 예제 데이터에 대해서 Python 의 sklearn.preprocessing의

  (1) StandardScaler() method를 이용한 표준정규분포로의 표준화 ((x-mean)/std )와

  (2) RobustScaler() method를 이용한 표준화 ( (x-median)/IQR )를

비교하면서 설명을 해보겠습니다.

 

 

 

먼저, 필요한 모듈을 불러오고, 이상치, 특이값을 포함하고 있는 예제 데이터를 만들어보겠습니다.

 

 

# importing modules

In [1]: import numpy as np


In [2]: from sklearn.preprocessing import StandardScaler, RobustScaler


In [3]: import matplotlib.pyplot as plt


In [4]: import pandas as pd

 

# setting the number of digits of precision for floating point output

In [5]: np.set_printoptions(precision=2)

 

# setting random seed number

In [6]: np.random.seed(10)

 

# making 100 random x ~ N(10, 2)

In [7]: mu, sigma = 10, 2


In [8]: x = mu + sigma*np.random.randn(100)


In [9]: x

Out[9]:

array([ 12.66,  11.43,   6.91,   9.98,  11.24,   8.56,  10.53,  10.22,
        10.01,   9.65,  10.87,  12.41,   8.07,  12.06,  10.46,  10.89,
         7.73,  10.27,  12.97,   7.84,   6.04,   6.51,  10.53,  14.77,
        12.25,  13.35,  10.2 ,  12.8 ,   9.46,  11.23,   9.47,   8.9 ,
        10.27,   9.05,  12.62,  10.39,  10.8 ,   9.32,  12.51,   8.54,
        11.32,   9.3 ,   8.12,   9.02,   8.39,   9.57,   9.32,  10.62,
        11.13,   9.71,   9.95,  10.58,   8.92,  11.42,  11.68,  10.41,
        14.79,  11.83,   9.78,   9.28,   9.54,   9.  ,  12.26,   8.6 ,
         9.84,   8.94,  12.09,   7.16,   9.28,   9.76,  10.64,  10.92,
         9.57,  11.98,  10.63,  14.94,   6.98,  11.24,   7.91,   8.4 ,
        13.97,  13.49,   6.29,   9.55,   9.87,   5.74,   9.9 ,  10.79,
        10.43,   6.01,  12.22,  10.49,   9.88,   8.49,  11.42,  11.84,
         9.04,  10.18,  11.65,   6.09])

 


# plotting histogram

In [10]: plt.hist(x)

Out[10]:

(array([ 6., 3., 8., 15., 22., 20., 11., 9., 3., 3.]),

array([ 5.74, 6.66, 7.58, 8.5 , 9.42, 10.34, 11.26, 12.18,

13.1 , 14.02, 14.94]),

<a list of 10 Patch objects>)

 

# checking mean, std

In [11]: np.mean(x) # 10.15 (about 10)

Out[11]: 10.158833325873747


In [12]: np.std(x) # 1.93 (about 2)

Out[12]: 1.9340789542274115

 


# inserting outliers

In [13]: x[98:100] = 100


In [14]: x

Out[14]:

array([  12.66,   11.43,    6.91,    9.98,   11.24,    8.56,   10.53,
         10.22,   10.01,    9.65,   10.87,   12.41,    8.07,   12.06,
         10.46,   10.89,    7.73,   10.27,   12.97,    7.84,    6.04,
          6.51,   10.53,   14.77,   12.25,   13.35,   10.2 ,   12.8 ,
          9.46,   11.23,    9.47,    8.9 ,   10.27,    9.05,   12.62,
         10.39,   10.8 ,    9.32,   12.51,    8.54,   11.32,    9.3 ,
          8.12,    9.02,    8.39,    9.57,    9.32,   10.62,   11.13,
          9.71,    9.95,   10.58,    8.92,   11.42,   11.68,   10.41,
         14.79,   11.83,    9.78,    9.28,    9.54,    9.  ,   12.26,
          8.6 ,    9.84,    8.94,   12.09,    7.16,    9.28,    9.76,
         10.64,   10.92,    9.57,   11.98,   10.63,   14.94,    6.98,
         11.24,    7.91,    8.4 ,   13.97,   13.49,    6.29,    9.55,
          9.87,    5.74,    9.9 ,   10.79,   10.43,    6.01,   12.22,
         10.49,    9.88,    8.49,   11.42,   11.84,    9.04,   10.18,
        100.  ,  100.  ])

 


# checking change of mean and std

In [15]: np.mean(x) # 11.98

Out[15]: 11.981383595820532


In [16]: np.std(x) # 12.71

Out[16]: 12.714552555982538

 

# plotting histogram to see the change of distribution, especially by outliers

In [17]: plt.hist(x, bins=np.arange(0, 102, 2))

Out[17]:

(array([  0.,   0.,   1.,  10.,  36.,  34.,  14.,   3.,   0.,   0.,   0.,
          0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
          0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
          0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
          0.,   0.,   0.,   0.,   0.,   2.]),
 array([  0,   2,   4,   6,   8,  10,  12,  14,  16,  18,  20,  22,  24,
         26,  28,  30,  32,  34,  36,  38,  40,  42,  44,  46,  48,  50,
         52,  54,  56,  58,  60,  62,  64,  66,  68,  70,  72,  74,  76,
         78,  80,  82,  84,  86,  88,  90,  92,  94,  96,  98, 100]),

<a list of 50 Patch objects>)

 

 

 

 

 

  (1) 이상치가 포함된 데이터의 표준정규분포로의 표준화 : 
       sklearn.preprocessing.
StandardScaler()

 

 

# reshape from 1d arrays to ndarray

In [18]: x = x.reshape(-1, 1)

 

# numpy.ndarray

In [19]: x[0:10]

Out[19]:

array([[ 12.66],
       [ 11.43],
       [  6.91],
       [  9.98],
       [ 11.24],
       [  8.56],
       [ 10.53],
       [ 10.22],
       [ 10.01],
       [  9.65]])

 

# By StandardScaler()

In [20]: x_StandardScaler = StandardScaler().fit_transform(x)


In [21]: x_StandardScaler

Out[21]:
array([[  5.36e-02],
       [ -4.33e-02],
       [ -3.99e-01],
       [ -1.57e-01],
       [ -5.81e-02],
       [ -2.69e-01],
       [ -1.14e-01],
       [ -1.39e-01],
       [ -1.55e-01],
       .....

       ..... (중간 생략)

       .....

       [ -4.38e-02],
       [ -1.14e-02],
       [ -2.32e-01],
       [ -1.42e-01],
       [  6.92e+00],
       [  6.92e+00]])

 


# checking mean and std, z ~ N(0, 1)

In [22]: np.mean(x_StandardScaler) # mean = 0

Out[22]: 5.3290705182007512e-17


In [23]: np.std(x_StandardScaler) # std = 1

Out[23]: 1.0

 


# look at the outliers at the right corner

In [24]: plt.hist(x_StandardScaler)

Out[24]:

(array([ 98., 0., 0., 0., 0., 0., 0., 0., 0., 2.]),

array([-0.49, 0.25, 0.99, 1.73, 2.47, 3.22, 3.96, 4.7 , 5.44,

6.18, 6.92]),

<a list of 10 Patch objects>)

 

 

# zoom in

In [25]: x_StandardScaler_zoonin = x_StandardScaler[x_StandardScaler < 5]


In [26]: plt.hist(x_StandardScaler_zoonin, bins=np.arange(-3.0, 3.0, 0.2))

Out[26]:

(array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,

0., 5., 26., 50., 14., 3., 0., 0., 0., 0., 0.,

0., 0., 0., 0., 0., 0., 0.]),

array([ -3.00e+00, -2.80e+00, -2.60e+00, -2.40e+00, -2.20e+00,

-2.00e+00, -1.80e+00, -1.60e+00, -1.40e+00, -1.20e+00,

-1.00e+00, -8.00e-01, -6.00e-01, -4.00e-01, -2.00e-01,

2.66e-15, 2.00e-01, 4.00e-01, 6.00e-01, 8.00e-01,

1.00e+00, 1.20e+00, 1.40e+00, 1.60e+00, 1.80e+00,

2.00e+00, 2.20e+00, 2.40e+00, 2.60e+00, 2.80e+00]),

<a list of 29 Patch objects>)

 

 

 

 

위의 '표준정규분포로의 표준화' 예시의 제일 마지막에 이상치(outlier)를 무시하고 표준화 이후 값 범위 (-3 ~ 3) 사이로 그린 히스토그램을 아래의 RobustScaler()로 표준화를 한 값과 비교해보시기 바랍니다. 

StandardScaler() 에 의한 표준화가 이상치에 영향을 더 심하게 받아서 이상치가 아닌 값들이 조밀하게, 촘촘하게 서로 붙어있음을 알 수 있습니다.

 

 

 

  (2) 이상치가 포함된 데이터의 중앙값과 IQR 를 이용한 표준화

      : sklearn.preprocessing.RobustScaler()

 

 

In [27]: np.median(x) # 10.2

Out[27]: 10.207697741550213


In [28]: Q1 = np.percentile(x, 25, axis=0)


In [29]: Q1 # 9.04

Out[29]: array([ 9.04])


In [30]: Q3 = np.percentile(x, 75, axis=0) # 5.700


In [31]: Q3 # 11.4

Out[31]: array([ 11.42])


In [32]: IQR = Q3 - Q1


In [33]: IQR # 2.37

Out[33]: array([ 2.37])


In [34]: x_RobustScaler = RobustScaler().fit_transform(x)

 

# list up 10 elememts from backward

In [35]: x_RobustScaler[-10:]

Out[35]:

array([[  8.46e-01],
       [  1.19e-01],
       [ -1.40e-01],
       [ -7.23e-01],
       [  5.12e-01],
       [  6.86e-01],
       [ -4.94e-01],
       [ -1.20e-02],
       [  3.78e+01],
       [  3.78e+01]])

 

# RobustScaler() removes the median and scales the data according to IQR(Inerquartile Range)

In [36]: np.median(x_RobustScaler)

 

Out[36]: 0.0


In [37]: np.mean(x_RobustScaler)

Out[37]: 0.74729363822182793


In [38]: np.std(x_RobustScaler)

Out[38]: 5.3569262082386482

 

# checking the distribution by histogram after RobustScaler

In [39]: plt.hist(x_RobustScaler)

Out[39]:

(array([ 98.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   2.]),
 array([ -1.88,   2.09,   6.06,  10.03,  14.  ,  17.97,  21.95,  25.92,
         29.89,  33.86,  37.83]),
 <a list of 10 Patch objects>)

 

 

# zoom in

In [40]: x_RobustScaler_zoonin = x_RobustScaler[x_RobustScaler < 5]


In [41]: plt.hist(x_RobustScaler_zoonin, bins=np.arange(-3, 3, 0.2))

Out[41]:

(array([  0.,   0.,   0.,   0.,   0.,   1.,   3.,   1.,   3.,   1.,   4.,
          6.,   7.,  13.,  11.,  14.,   6.,   7.,   6.,   5.,   4.,   2.,
          1.,   0.,   3.,   0.,   0.,   0.,   0.]),
 array([ -3.00e+00,  -2.80e+00,  -2.60e+00,  -2.40e+00,  -2.20e+00,
         -2.00e+00,  -1.80e+00,  -1.60e+00,  -1.40e+00,  -1.20e+00,
         -1.00e+00,  -8.00e-01,  -6.00e-01,  -4.00e-01,  -2.00e-01,
          2.66e-15,   2.00e-01,   4.00e-01,   6.00e-01,   8.00e-01,
          1.00e+00,   1.20e+00,   1.40e+00,   1.60e+00,   1.80e+00,
          2.00e+00,   2.20e+00,   2.40e+00,   2.60e+00,   2.80e+00]),
 <a list of 29 Patch objects>)

 

 

 

아래의 두 개의 히스토그램은 이상치, 특이값(outlier)이 포함되어 있는 데이터를 표준화하는 경우에 (1) 평균과 표준편차를 이용한 표준정규분포 표준화 결과 (outlier 미포함한 범위의 zoom in)와, (2) 중앙값과 IQR(Interquartile Range)를 이용한 이상치에 견고한 표준화 (outlier 미포함한 범위의 zoom in) 결과의 분포를 나타내고 있습니다.

 

왼쪽의 StandardScaler()에 의한 표준화보다 오른쪽의 RobustScaler()에 의한 표준화가 동일한 값을 더 넓게 분포시키고 있음을 알 수 있습니다.  즉, 목표변수 y값을 분류나 예측하는데 있어 산포가 더 크기 때문에 설명변수 x변수로서 더 유용할 수 있다고 추정할 수 있습니다.

 

 

(1) StandardScaler() zoon in

(2) RobustScaler() zoon in 

 

 

 

 

 

 

다음번 포스팅에서는 최소.최대 [0~1] 범위 변환에 대해서 소개하도록 하겠습니다.

 

많은 도움 되었기를 바랍니다.

 

 

 

728x90
반응형
Posted by Rfriend
,