Verifying Outlier Values

Page content

이상값의 정의

  • 다소 주관적이며(Somewhat Subjective), 특정 분포의 중심경향성, 퍼진 정도와 형태에 따라 밀접한 관련이 있다.
    • 평균에서 표준편차보다 몇 배 더 떨어져 있다거나, 즉, 정규분포를 이루고 있지 않을 때
    • 왜도 또는 첨도가 발생할 때
  • 균등분포(Uniform Distribution)는, 발생할 확률이 모두 같다.
    • 만약, 확진자수가 최소 1부터 최대 10,000,000까지 균등하게 분포한다면, 어떤 값도 이상값으로 고려하지 않는다.
  • 이상값을 파악하려면, 반드시, 각 변수의 분포를 먼저 이해해야 한다.

라이브러리 및 데이터 불러오기

  • 실습을 위한 데이터를 불러온다.
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import statsmodels.api as sm
import scipy.stats as scistat

covidtotals = pd.read_csv("data/covidtotals.csv")
covidtotals.set_index("iso_code", inplace = True)

case_vars = ["location", "total_cases", "total_deaths", "total_cases_pm", "total_deaths_pm"]
demo_vars = ["population", "pop_density", "median_age", "gdp_per_capita", "hosp_beds"]

print(covidtotals.head())
            lastdate     location  total_cases  total_deaths  total_cases_pm  \
iso_code                                                                       
AFG       2020-06-01  Afghanistan        15205           257         390.589   
ALB       2020-06-01      Albania         1137            33         395.093   
DZA       2020-06-01      Algeria         9394           653         214.225   
AND       2020-06-01      Andorra          764            51        9888.048   
AGO       2020-06-01       Angola           86             4           2.617   

          total_deaths_pm  population  pop_density  median_age  \
iso_code                                                         
AFG                 6.602  38928341.0       54.422        18.6   
ALB                11.467   2877800.0      104.871        38.0   
DZA                14.891  43851043.0       17.348        29.1   
AND               660.066     77265.0      163.755         NaN   
AGO                 0.122  32866268.0       23.890        16.8   

          gdp_per_capita  hosp_beds  
iso_code                             
AFG             1803.987       0.50  
ALB            11803.431       2.89  
DZA            13913.839       1.90  
AND                  NaN        NaN  
AGO             5819.495        NaN  
  • describe() 함수를 통해 수치 데이터의 분포를 확인하도록 한다.
covid_case_df = covidtotals.loc[:, case_vars]
print(covid_case_df.describe())
        total_cases   total_deaths  total_cases_pm  total_deaths_pm
count  2.100000e+02     210.000000      210.000000       210.000000
mean   2.921614e+04    1770.714286     1355.357943        55.659129
std    1.363978e+05    8705.565857     2625.277497       144.785816
min    0.000000e+00       0.000000        0.000000         0.000000
25%    1.757500e+02       4.000000       92.541500         0.884750
50%    1.242500e+03      25.500000      280.928500         6.154000
75%    1.011700e+04     241.250000     1801.394750        31.777250
max    1.790191e+06  104383.000000    19771.348000      1237.551000
  • 백분위수(quantile)로 데이터를 표시한다.
print(covid_case_df.quantile(np.arange(0.0, 1.1, 0.1)))
     total_cases  total_deaths  total_cases_pm  total_deaths_pm
0.0          0.0           0.0          0.0000           0.0000
0.1         22.9           0.0         17.9986           0.0000
0.2        105.2           2.0         56.2910           0.3752
0.3        302.0           6.7        115.4341           1.7183
0.4        762.0          12.0        213.9734           3.9566
0.5       1242.5          25.5        280.9285           6.1540
0.6       2514.6          54.6        543.9562          12.2452
0.7       6959.8         137.2       1071.2442          25.9459
0.8      16847.2         323.2       2206.2982          49.9658
0.9      46513.1        1616.9       3765.1363         138.9045
1.0    1790191.0      104383.0      19771.3480        1237.5510
  • 왜도는 분포가 얼마나 대칭적인지를 나타냄
  • 왜도와 첨도는 어떻게 대칭적인지를 설명하며, 분포의 꼬리가 각각 얼마나 두꺼운지 나타냄.
covid_case_df.skew(axis=0, numeric_only = True)
total_cases        10.804275
total_deaths        8.929816
total_cases_pm      4.396091
total_deaths_pm     4.674417
dtype: float64
covid_case_df.kurtosis(axis=0, numeric_only = True)
total_cases        134.979577
total_deaths        95.737841
total_cases_pm      25.242790
total_deaths_pm     27.238232
dtype: float64
  • 정규성 검정을 테스트 한다.
  • p값 0.05미만에서 95% 수준에서 정규분포의 귀무가설을 기각하고, 대립가설을 채택한다.
    • 귀무가설: 표본의 모집단이 정규분포를 이루고 있다.
    • 대립가설: 표본의 모집단이 정규분포를 이루고 있지 않다.
scistat.shapiro(covid_case_df['total_cases'])
ShapiroResult(statistic=0.19379639625549316, pvalue=3.753789128593843e-29)
scistat.shapiro(covid_case_df['total_deaths'])
ShapiroResult(statistic=0.19832086563110352, pvalue=4.3427896631016077e-29)
scistat.shapiro(covid_case_df['total_cases_pm'])
ShapiroResult(statistic=0.5220695734024048, pvalue=1.3972683006509067e-23)
scistat.shapiro(covid_case_df['total_deaths_pm'])
ShapiroResult(statistic=0.41877639293670654, pvalue=1.361060423265974e-25)
  • 위 4개의 feature 모두 정규분포를 이루고 있지 않음을 확인할 수 있다.
  • 이번에는 qqplot을 그린다.
sm.qqplot(covid_case_df[["total_cases"]].sort_values(["total_cases"]), line = "s")
plt.title("QQ Plot of Total Cases")
Text(0.5, 1.0, 'QQ Plot of Total Cases')

png

  • 이번에는 정규분포를 이루는 샘플 데이터의 shapiro 검정 테스트와 qqplot을 그려본다.
random_normal_datapoints = pd.Series(np.random.normal(0, 0.1, 200))
print(scistat.shapiro(random_normal_datapoints))
ShapiroResult(statistic=0.9939591884613037, pvalue=0.5945821404457092)
sm.qqplot(random_normal_datapoints, line = "s")
plt.title("QQ Plot of Normal Datapoints")
Text(0.5, 1.0, 'QQ Plot of Normal Datapoints')

png

이상값 범위 나타내기

  • 정규성 검정을 통해서 각 데이터가 이상값이 있다는 것을 파악했다면, 이번에는 이상값 범위를 정의한다.
  • 연속형 변수의 이상값 범위는 1사분위-3사분위의 거리를 재는 것이다. 그 거리가 사분위범위의 1.5배를 넘으면 그 값을 이상값으로 간주한다.
thirdq = covid_case_df["total_cases"].quantile(0.75)
firstq = covid_case_df["total_cases"].quantile(0.25)
interquantile_range = 1.5 * (thirdq - firstq)
outlier_high = interquantile_range + thirdq
outlier_low = firstq - interquantile_range

print(outlier_low, outlier_high, sep = " <---> ")
-14736.125 <---> 25028.875
  • 이상치를 제거한 데이터를 가져온다.
remove_out_lier_df = covid_case_df.loc[~(covid_case_df["total_cases"] > outlier_high) | (covid_case_df["total_cases"] < outlier_low)]
print(remove_out_lier_df.info())
<class 'pandas.core.frame.DataFrame'>
Index: 177 entries, AFG to ZWE
Data columns (total 5 columns):
 #   Column           Non-Null Count  Dtype  
---  ------           --------------  -----  
 0   location         177 non-null    object 
 1   total_cases      177 non-null    int64  
 2   total_deaths     177 non-null    int64  
 3   total_cases_pm   177 non-null    float64
 4   total_deaths_pm  177 non-null    float64
dtypes: float64(2), int64(2), object(1)
memory usage: 8.3+ KB
None
  • 이제 두 데이터의 히스토그램을 비교해본다.
fig, ax = plt.subplots(figsize = (16, 6), ncols = 2)
ax[0].hist(covid_case_df["total_cases"]/1000, bins = 7)
ax[0].set_title("Total Covid Cases (thousands) for all")
ax[0].set_xlabel("Cases")
ax[0].set_ylabel("Number of Countries")
ax[1].hist(remove_out_lier_df["total_cases"]/1000, bins = 7)
ax[1].set_title("Total Covid Cases (thousands) for removed outlier")
ax[1].set_xlabel("Cases")
ax[1].set_ylabel("Number of Countries")
plt.show()

png

References

Walker, M. (2020). Python Data Cleaning Cookbook: Modern techniques and Python tools to detect and remove dirty data and extract key insights. Packt Publishing.