목차
데이터 관련하여 포스팅하면서 가장 어려운 부분이 대상 데이터를 만드는 것이다. 실제 데이터는 회사 내에서 추출이 안되니 내가 경험했던 것과 유사한 데이터를 임의로 만들야 하기 때문이다. 그래도 데이터를 생성하는 것도 하나의 재미를 주기 때문에 그리 나쁘지만은 않다. 이상치 제거를 하기 위해 이상치가 포함된 데이터를 우선 만들어 보기로 한다.
1. 정규분포를 갖는 일반 데이터 생성
자연계의 데이터들은 일반적으로 정규분포를 갖는다라는 이론이 있다. 실제 데이터 처리 및 분석 시 정규분포의 평균과 표준편차를 많이 이용한다. 데이터를 생성하기 위해 정규분포를 이용했다.
python에서 사용할 라이브러리는 다음과 같이 import 시킨다.
import numpy as np
import pandas as pd
import random
import matplotlib.pyplot as plt
import scipy.stats as stats
불러온 라이브러리의 목적을 간단하게 기술한다.
- numpy : 정규분포 데이터 생성 (np.random.normal)
- random : 평균, 표준편차 랜덤 생성
- scipy.stats : 정규분포 그래프 생성 (stats.norm.pdf)
이제 임의의 평균과 표준편차를 이용하여 정규분포를 이루는 3000개의 데이터 셋을 생성해 보자
row_no = 3000
mean = random.random()
std = random.random()
data = list(np.random.normal(mean, std, row_no))
어렵지 않게 일반데이터를 만들었다. 물론 해당 데이터를 정규분포 생성함수(np.random.normal)를 이용하여 만들긴 했지만 확인을 위해 정규분포를 그려본다. 정규분포를 만들기 위해 scipy의 stats 라이브러리를 사용한다.
data_mean = np.mean(data)
data_std = np.std(data)
pdf = stats.norm.pdf(np.sort(data), data_mean, data_std)
plt.figure()
plt.plot(np.sort(data), pdf)
데이터의 평균과 표준편차를 구해서 pdf(Probability Density Function, 확률밀도함수) 함수에 넣으면 된다. 이때 sort를 사용하는 이유는 x축 값에 대한 y축 값을 매칭하는 정도로 이해하면 될 듯하다.
2. 10개의 이상치를 갖는 데이터 셋 만들기
일반 데이터에 이상치 10개를 삽입해 본다. 초기에 데이터를 list type으로 만든 이유는 데이터 삽입 함수인 list.insert를 사용하기 위해서였다. 데이터를 outlier_data로 복사를 하고 리스트 내 임의의 위치에 값을 3배 증폭시켜 삽입한다.
ol_data = data.copy()
ol_no = 10
for i in range(ol_no):
rand_no = random.randint(0, len(data))
ol_data.insert(rand_no, ol_data[rand_no]*3)
3. 이상치 제거하기
이상치 제거하는 방법에는 여러 가지가 있지만, 본 포스팅에서는 박스차트의 개념을 도입하였다. 내가 실제 데이터의 이상치를 제거할 때 편히 쓰는 방식으로 이상치 제거 민감도가 쉽게 조절이 되고 python에서 pandas의 특성을 잘 이용할 수 있기 때문이다.
이상치 제거 민감도는 박스차트를 구해주는 공식에서 IQR에 곱해주는 1.5의 값이다. 1.5의 값은 박스차트의 Whisker 값을 만들어 주는 인자로 정규분포로 말하자면 2.698σ(시그마)와 동일하다. 필자는 일반적으로 현장데이터에 한해 3을 적용한다.
s_ol_data = pd.Series(ol_data)
level_1q = s_ol_data.quantile(0.25)
level_3q = s_ol_data.quantile(0.75)
IQR = level_3q - level_1q
rev_range = 3 # 제거 범위 조절 변수
dff = s_ol_data[(s_ol_data <= level_3q + (rev_range * IQR)) & (s_ol_data >= level_1q - (rev_range * IQR))]
dff = dff.reset_index(drop=True)
위의 코드는 조금의 설명이 필요하겠다. 첫 줄에서 리스트를 시리즈로 변환하는 이유는 dff를 만들 때 pandas의 조건문을 사용하기 위해서다. pandas내의 조건문을 이용하게 되면 for문을 쓰는 것보다 속도가 빨라지기 때문에 데이터 처리에서 유리하다.
지금은 한 종류의 데이터 셋에 대해서 설명하지만 실제 100개 이상의 종류의 데이터에 몇십만 개의 데이터 셋을 처리하다 보면 일반 노트북에서는 잘 동작되지 않는다. 이상치 제거 후의 그래프는 아래와 같다.
처음에 만들었던 일반 데이터 셋과 같음을 확인할 수 있다. 필자가 데이터 생성 코드까지 같이 공유하는 이유는 데이터 분석 공부에서 가장 어려운 게 데이터를 구하는 것이기 때문이다. 향후의 포스팅 시에도 가능하면 데이터를 생성할 수 있는 방법을 고민해서 같이 올리려 한다.
4. 이상치 제거 함수
이상치 제거 방법을 코드로 확인했으나, 여러 종류의 데이터에서 적용하려면 함수로 만드는 것이 편하다. 3번에서 설명한 코드를 DataFrame type을 다룰 수 있도록 함수로 구현해 봤다.
def remove_out(dataframe, remove_col):
dff = dataframe
for k in remove_col:
level_1q = dff[k].quantile(0.25)
level_3q = dff[k].quantile(0.75)
IQR = level_3q - level_1q
rev_range = 3 # 제거 범위 조절 변수
dff = dff[(dff[k] <= level_3q + (rev_range * IQR)) & (dff[k] >= level_1q - (rev_range * IQR))]
dff = dff.reset_index(drop=True)
return dff
함수의 입력 변수에서 dataframe은 데이터 셋을 의미하고, remove_col은 이상치를 제거하고 싶은 데이터 종류를 말한다. 추가적으로 rev_range를 입력변수로 받아서 제거 범위 조절도 함수 호출 시 별도로 할 수 있다.
5. 마치며 - 이상치에 대한 고찰
이번 포스팅에서 이상치 제거를 위해 여러 방법 중에 박스차트 공식을 사용하였다. 그중에서 이상치 제거 민감도를 어떻게 결정할 것인지에 대해서 고민을 할 필요가 있다. 데이터를 확인하다 보면 제거를 해야 하는 이상치인지 제거를 하면 안 되는 불량치인지를 구분해야 한다.
사실 이상치 제거 민감도 값을 결정하기 앞서 데이터를 그래프로 그려 확인하는 분석을 먼저 수행해야 한다. 수집되는 데이터에서 불량값으로 판단되는 데이터는 이상치 제거에서 삭제되면 안 된다. 이상값과 불량값을 단순하게 아래와 같이 예로 정리했다.(레이저 변위 센서로 제품의 길이를 측정한다고 가정)
제품의 정상값 : 30cm ± 5mm
불량값 : 30.6cm, 30.9cm (실제 제품이 양품 기준을 벗어나 제작된 상태)
이상값 : 0cm, 99cm
(센서 미동작으로 값이 읽히지 않은 상태 또는 측정 위치에 이물이 묻어 레이저가 반사된 이상값이 측정된 상태)
이상치 제거 민감도를 1.5로 한다면 2.7σ 수준으로 불량값을 제거할 가능성이 높다. 제거 민감도 값을 결정할 때는 사전에 꼭 데이터를 확인해서 이상값이 발생하는 수준을 판단해야 한다. 필자가 3으로 설정한 이유는 현장의 데이터, 즉 양품데이터와 불량데이터는 그대로 사용하고 그 범주에 속하지 않는 이상상태에서 입력된 데이터를 잘라내기 위해 5σ 수준으로 설정한 것이다. 사실 그 정도면 양품/불량품 데이터가 다치치 않았다.