데이터과학 유망주의 매일 글쓰기 — 여섯번째 토요일

배우는 자(Learner Of Life)
18 min readOct 17, 2020

--

기본으로 돌아가자(Back to Basics)

# Back to Basics, # EDA

데이터의 곳곳을 최대한 탐험해 보는 것만으로도 중요한 인사이트를 얻을 수 있다.

오늘 한일:

어제 과제에서, EDA(Exploratory Data Analysis)라고 하는 데이터에 대한 “탐험"이 부족했다는 피드백을 받은 나는, 오늘을 이용하여 EDA를 좀 더 공부해보고자 했다. 이 과정을 시작하면서 가장 처음 접한 개념중에 하나이고, 그만큼 기본이라는 것인데, 내가 이를 충분히 수행하지 못했다는 것은 기본에 충실하지 못했다는 의미이다. 조금 부끄럽게 느껴졌다.

그렇지만 동시에 어떻게 해야 충분히 제대로된 “데이터 탐험"을 했다고 할 수 있을까? 도저히 감이 오지 않아 무작정 영어로 “how to EDA data science” 라는 검색어를 구글에 입력했다. 그러자 내가 구독하는 Towards Data Science의 블로그가 하나 보였다. “An Extensive Step by Step Guide to Exploratory Data Analysis”라는 제목의 블로그였다. 이 블로그는 주어진 데이터에 대해 EDA를 수행하는 과정을 친절하게 설명하는 글이였다. 그래 바로 이거다! 나는 곧 이 블로그를 읽어보면서, 저자가 이야기하는 과정에 따라서 글에서 사용한 데이터를 가지고 EDA를 수행해보았다. 그 결과, EDA를 수행하기 위해 전체적으로 3가지의 단계가 필요하다는 것을 알게 되었다. 데이터의 feature들 또는 변수들을 이해하고, 필요할 경우 데이터를 전처리하여, 마지막으로 시각화까지 충분히 하는 과정은 이 분야에서 매우 중요한 기본중에 기본이다.

오늘의 글은 나처럼 EDA에 아직은 충실하지 못한 데이터 과학 유망주들에게 바치는 글이다. 이 글을 읽고 다시는 “EDA가 부족하다"라는 피드백을 받지 않도록, 기본에 충실하여 좋은 데이터과학자로 거듭날 수 있는 작은 계기가 되기를 바란다.

1. 데이터를 이해하기

먼저 데이터가 주어졌다면, 데이터를 충분히 이해하려는 노력이 필요하다. 그렇게 함으로써 분석에 필요한 데이터와 그렇지 않은 데이터를 구분할 수 있기 때문이다. 데이터에 어떠한 이상치(outliers)나 결측치(null)등이 존재하는지 파악하고, 데이터내 변수들 사이의 상관관계, 그리고 이후에 데이터셋의 인사이트를 최대화하고 잠재적인 에러를 최소화하는 과정이 필요하다.

먼저 본 기사에서 사용한 중고차 데이터를 Kaggle에서 다운로드 받고 필요한 라이브러리를 가져오는 것부터 시작해 보기로 했다.

# 필요한 라이브러리 가져오기import numpy as np
import pandas as pd
import matplotlib.pylab as plt
import seaborn as sns#Understanding my variables
# 데이터 셋 불러오기df = pd.read_csv("/Users/seungtaemoon/Downloads/vehicles.csv")
df.columns
데이터 칼럼의 이름들을 한눈에 본다.
# 데이터의 크기와 첫 5개의 head entry를 본다.
print(df.shape)
df.head()
데이터의 크기와 첫 5개의 entry를 본다.
# 유니크한 값들을 보기
df.nunique(axis=0)
각 변수의 유니크한 값들을 한눈에 보기
# 데이터의 전반적인 요약본 보기
df.describe().apply(lambda s: s.apply(lambda x: format(x, 'f')))
.describe() 을 통해 데이터의 전체적인 특징을 볼 수 있다.

위 .describe() 기능을 통해 데이터의 전체적인 그림을 본 결과, 가장 큰 문제 하나를 확인할 수 있었다. 그것은 바로, price, year, odometer변수들의 최대값이 IQR(Interquartile Range)에서 매우 크게 벗어나 있다는 것이다. 이 것은 세 데이터에서 모두 75% IRQ의 3/2 이상을 매우 크게 벗어나는 이상치(outlier)가 있다는 것을 보여준다.

여기서 확인하지 못한 범주형 데이터 중 자동차의 상태를 나타내는 condition이라는 변수가 있다. 이 변수도 전체 데이터에서 매우 중요한 부분이므로, 어떤 값들로 구성이 되어 있는지 확인해 보자.

# condition 변수의 유니크한 값들을 보기
df.condition.unique()
condition 변수의 모든 유니크한 값들

여기서 확인할 수 있는 것은, 유니크한 값들이 너무 많다는 것이다. 범주가 너무 많으면, 오히려 데이터의 분석을 어렵게 할 수 있다. 그러므로, 비슷해 보이는 범주들은 한데 묶는 것이 좋다. “good”과 “fair”는 둘다 “양호"를 의미하므로, “good”으로 묶는다. “excellent”와 “like new”역시 “매우 좋은”, “새것" 등의 비슷한 의미를 지니므로 “excellent” 하나로 그룹화한다.

# 범주형 데이터의 범주 축소
# "good" & "fair" = "good"로 그룹화
# "excellent" & "like new" = "excellent"로 그룹화
def clean_condition(row):

good = ['good','fair']
excellent = ['excellent','like new']

if row.condition in good:
return 'good'
if row.condition in excellent:
return 'excellent'
return row.condition# Clean dataframe
def clean_df(playlist):
df_cleaned = df.copy()
df_cleaned['condition'] = df_cleaned.apply(lambda row: clean_condition(row), axis=1)
return df_cleaned# Get df with reclassfied 'condition'column
df_cleaned = clean_df(df)
print(df_cleaned.condition.unique())
범주형 데이터의 비슷한 값들은 그룹화하는 것이 좋다.

자 이제 데이터는 어느 정도 정리가 되었다. 그렇다면, 우리의 분석에 있어, 진정으로 필요한 데이터들은 어떤 것들인지 확인해보자. 필요없는 데이터는 전처리가 필요하기 때문이다.

2. 데이터 전처리

데이터를 관찰해본 결과, “url”을 보여주는 변수들은 범주화하기도 어려우며, 분석에 크게 도움이 되지 않는다는 것을 알 수 있었다. 이러한 데이터들은 제거할 필요가 있다.

# url데이터들을 제거한다.
df_cleaned = df_cleaned.copy().drop(['url','image_url','region_url'], axis=1)

이후에 필요한 것은 결측치(null)값이 너무 많은 데이터들을 제거하는 것이다. 통상적으로 변수를 구성하는 값의 절반 이상(40~50% 이상)이 결측치라면, 분석에 있어 큰 영향을 끼치지 못하거나 방해가 될 수 있기 때문에 제거하는 것이 좋다. 그러므로, 결측치가 40% 이상이 되는 변수들은 제거해보겠다.

# 결측치가 40% 이상인 변수들 제거
NA_val = df_cleaned.isna().sum()
def na_filter(na, threshold = .4): #only select variables that passees the threshold
col_pass = []
for i in na.keys():
if na[i]/df_cleaned.shape[0]<threshold:
col_pass.append(i)
return col_pass
df_cleaned = df_cleaned[na_filter(NA_val)]
df_cleaned.columns
결측치가 너무 많은 변수들을 제거하고 남은 데이터들

결측치를 제거했다면, 다음으로 이상치(outlier)들을 제거해야 한다. 평균치에서 너무 크게 벗어난 데이터역시 전체 분석을 방해하는 요인이기 때문이다. 이전에 확인했듯이, 이상치는 price, year, odometer에서 매우 큰 것으로 나타났다. 다만, 어떤 수치를 기준으로 이상치를 제거할지는, 중고자동차 시장에 대한 이해가 필요하므로, 도메인 지식이 없다면 어려울 수 있다. 아마도, 이 부분에 있어서는 중고차 판매업자들을 인터뷰하거나, 중고차 시장에서 평균적으로 수긍할 수 있는 가격대를 조사하는 방법 등을 통해 알 수 있을 것이라는 생각이 든다. 여기서는 가격(price)의 경우 $1,000에서 $100,000 정도의 범위를 잡고, 자동차 출시 연도(year)는 1990년대 이후, 마지막으로 주행거리 또는 마일리지를 나타내는 odometer는 900,000km를 최대로 한다.

# 이상치 제거
df_cleaned = df_cleaned[df_cleaned['price'].between(999.99, 99999.00)]
df_cleaned = df_cleaned[df_cleaned['year'] > 1990]
df_cleaned = df_cleaned[df_cleaned['odometer'] < 899999.00]
df_cleaned.describe().apply(lambda s: s.apply(lambda x: format(x, 'f')))
이상치를 제거하고나니, 훨씬 더 최대값들이 자연스러워 보인다.

결측치를 각 변수인 열(column)에서는 제거했으나, 아직 각 행(row)에서의 결측치는 제거하지 않았다. 결국 결측치는 반환하지 않으면, 데이터 분석에 하나도 도움될 것이 없으므로, 결측치가 하나라도 있는 행(row)은 제거한다.

# 결측치가 하나라도 있는 행은 제거한다.
df_cleaned = df_cleaned.dropna(axis=0)
df_cleaned.shape
결측치를 제거한 후의 데이터의 크기

3. 변수들의 관계 분석

자 이제 본격적으로 데이터를 분석할 수 있는 여건이 마련되었다. 사실 데이터의 분석에서 전처리가 80% 이상을 차지한다는 것이 괜히하는 이야기가 아니다. 데이터의 전처리가 그만큼 시간이 많이 걸린다는 의미이다. 그렇다면, 데이터의 분석은 그리 많은 시간이 걸리지 않는다. 좋은 재료를 사용할수록, 요리가 수월해지는 듯한 느낌과 비유할 수 있다.

데이터내 모든 변수들의 상관관계를 확인하는 것에 correlation matrix를 그리는 것만큼 좋은 방법은 없을 것이다.

# Correlation matrix를 그려 데이터 내 변수들의 상관관계 보기
corr = df_cleaned.corr()# plot the heatmap
sns.heatmap(corr, xticklabels=corr.columns, yticklabels=corr.columns, annot=True, cmap=sns.diverging_palette(220, 20, as_cmap=True))
변수들의 상관관계를 보여주는 correlation matrix

위 데이터를 통해 관찰한 결과 아래와 같은 사실들을 알 수 있었다.

  • price와 year가 서로 비례한다는 것을 알 수 있었다. 물건들이 상대적으로 새 것 일수록 더 비싼 경우가 많은 것을 생각한다면 충분히 이해할 수 있는 결과이다.
  • price와 odometer는 서로 반비례한다. 주행거리가 더 길수록, 차가 저렴해 진다는 의미이다.
  • year와 odometer 역시 서로 반비례한다. 중고차가 상대적으로 새 것 일수록, 주행거리가 더 적을 확률이 높다는 뜻이다.

데이터내 변수들의 전체적인 상관관계를 보았다면, 중요한 변수들끼리의 상관관계를 좀 더 자세히 들여다보는 과정도 필요할 것이다. 이를 위해서 scatterplot을 생성할 수 있다. 이를 이용하여 price와 odometer의 관계를 보자.

# price와 odometer의 상관관계를 보여줄 scatterplot 그리기 
df_cleaned.plot(kind='scatter', x='odometer', y='price')
scatterplot을 통해본 price와 odometer의 관계

관찰결과, 아래와 같은 사실들을 확인할 수 있다.

  • 주행거리는 가격과 반비례한다.
  • 중고차가 새것일수록, 주행거리가 쌓일 때마다 가격이 내려가는 정도가, 상대적으로 좀 더 오래된 중고차보다 상당히 더 크다. 이는 위 그래프에서 급격하게 내려가는 하향곡선을 통해 알 수 있다.

괜히 사람들이 “새차를 사는게 미래를 위한 좋은 투자는 아니다.”라고 이야기하는 것이 아닌 것 같다. 새로운 차량 일수록, 운전거리가 쌓이면서 가격이 급락하는 경향이 훨씬 더 크기 때문이다.

이번에는 자동차 출시 연도(year)와 가격(price)의 상관관계를 보자.

# year와 price의 상관관계 보기
df_cleaned.plot(kind='scatter', x='year', y='price')
Year와 Price의 상관관계

위 그래프를 통해 볼 수 있는 사실은 출시 연도가 더 빠를 수록 중고차의 가격도 높을 확률이 높다는 것이다, 그러나, 이전의 price와 odometer의 관계만큼 극적이지는 않다. 전체적으로 출시 연도에 따른 가격 비율이 상대적으로 훨씬 더 고르게 나타나기 때문이다.

Seaborn에서 pairplot을 통해, 전체적인 변수들의 상관관계를 한눈에 볼 수 있다. Correlation Matrix를 scatterplot으로 한눈에 본다고 생각하면 쉽다.

# 상관관계를 한눈에 볼 수 있는 pairplot 그리기
sns.pairplot(df_cleaned)
전체 데이터의 상관관계를 한눈에 볼 수 있는 pairplot

그렇다면 변수 하나의 특징을 좀 더 자세하게 보고 싶다면 어떻게 해야할까? Histogram을 그려볼 수 있다.

# "odometer" 변수를 한눈에 보기
df_cleaned['odometer'].plot(kind='hist', bins=50, figsize=(12,6), facecolor='grey',edgecolor='black')
odometer 변수를 한눈에 보기

왜도(skewness)가 오른쪽으로 기울어진 것을 볼 수 있다. 주행거리는 거의 대부분 0에서 200,000km를 넘지 않는다는 의미이다.

다음으로 차량의 출시 연도에 대한 정보를 보자.

# year 변수 한눈에 보기
df_cleaned['year'].plot(kind='hist', bins=20, figsize=(12,6), facecolor='grey',edgecolor='black')
year변수를 표현한 histogram. 왜도가 이번에는 왼쪽으로 기울어진 것을 볼 수 있다.

year변수는 odometer와 반대로 왼쪽으로 기울어진 왜도를 보였다. 2000년 이후 2020까지의 차량이 대부분인 것을 알 수 있다.

Boxplot역시 변수의 분포를 볼 수 있게 해주는 좋은 도구이다. price변수의 가격정보는 boxplot으로 보는 것이 더 직관적이라고 파악된다.

# price 변수에 대한 boxplot그리기df_cleaned.boxplot('price')
price변수의 전체 분포도를 보여주는 boxplot.

상당수의 데이터가 0~40,000 달러의 범위를 벗어나 있지만, 대부분의 데이터는 이 IQR범위에 있는 것을 알 수 있다. 혹시라도 boxplot 을 읽기 힘들어하는 이들을 위해 아래의 설명을 참조한다.

Boxplot 읽기:

  • 이상치(outliers): upper quartile의 3/2를 초과하는 범위에 있는 데이터
  • 최대값(maximum): 이상치를 제외한 최대값
  • Upper quartile: 25%의 데이터가 기준값보다 더 크다는 의미
  • 중간값(median): 50%의 데이터가 기준값보다 더 크다 는 의미— 데이터 셋의 중간치
  • Lower quartile: 25%의 데이터가 기준값보다 더 작다는 의미
  • 최소값(Min): 이상치(outliers)를 제외한 최소값
  • 이상치(Outlier): lower quartile의 3/2 미만인 범위에 있는 데이터

이 정도 파악이 되었다면, EDA를 충분히 수행했다고 볼 수 있다. 물론, 각 상황과 데이터에 따라 여기서 더 할 수도 있고 덜할 수도 있다. 일단 전체적으로 지금까지 어떠한 과정으로 EDA를 수행했는지 요약하자면 아래와 같다.

요약:

1. 데이터의 전체적인 그림 그리기(데이터 전체적으로 이해하기)

2. 데이터를 전처리하여 분석에 필요한 데이터만을 남기기

3. 데이터 시각화를 통해 변수들의 상관관계를 파악하기

목적:

결국 위 3가지 단계를 밟는 이유는, 2가지의 큰 목적이 있다 그것은,

  1. 머신러닝을 위해, 더 쉽게 모델과 모델의 “속성들(attributes)”을 선택하기 위해
  2. 데이터의 전처리를 더 용이하게 하기 위해

여기서 다룬 시각화 기법이외에 더 유용할 수 있는 시각화 기법들도 많다. 대표적으로 더 예를 들어보자면,

  • Stacked bar graph
  • Area plot
  • Violin plot
  • Geospatial plot

등이 있을 수 있다. 궁금하다면 찾아보는 것도 좋을 것 같다.

앞으로 할일:

다시 한번 생각해보니, EDA를 충분히 수행하지 못했다는 것은 기본에 충실하지 못했다라는 의미이기 때문에, 상대적으로 머신 러닝 같은 좀 더 심화된 개념을 이해하지 못했다는 피드백 보다 더 부끄럽게 느껴졌다. 아무리 어려운 것을 하고 배우더라도, 그 개념의 기초가되는 원리를 확실하게 습득하지 못한다면, 나의 실력은 곧 바닥을 드러내고, 무엇을 새로 배우던 쉽게 무너져 내릴 것이다. 건물도 기반을 찬찬히 다진 후에 기둥을 세우듯, 배움 역시 기초가 탄탄해야 그 어떤 어려운 문제도 해결해 낼 수 있다는 것을 다시 한번 깨닫는다.

한편으로는, 내가 무엇을 더 연마해야 하는지 알게 해준 선생님께 감사하고, 이번 주말에 시간을 들여 그 것을 채우려고 한 나 자신의 의지와 행동력에 감사한다. 모르는 것은 부끄럽지 않지만, 모르는 것이 무엇인지 알고도 행동하지 않으려하는 것은 부끄럽다. 앞으로도 내가 모르는 것에 대해서는, 시간을 들여 조금 이라도 더 정확하게 이해하려고 노력하는 습관을 계속 유지해야겠다고 다짐한다. 탄탄한 기초를 쌓아 앞으로 무엇을 배우더라도 겁내거나 걱정하지 않고, 차분한 마음으로 침착하게 내게 주어진 과제나 문제들을 해결할 수 있는 내공을 다지고 싶다. 그것이 데이터 뿐만 아니라 삶에 있어서도 조금이라도 더 깊은 인사이트를 얻게 하는 원동력이 된다고 믿는다.

나와 나의 수강생, 그리고 이 글을 읽는 많은 분들은 아직 초보 데이터 과학자들이다. 초보라는 사실에는 부끄러워할 필요가 전혀없다. 하지만, 데이터라는 무수한 자원이 있는 동굴을 충분히 탐험하지 않고 포기하려는 태도가 있다면 빨리 반성하여 털어버리고, 내가 무엇을 할 수 있는지 생각한 후에 행동에 옮기는데 집중하자. 다시 강조하지만, 모르는 것은 죄가 아니다. 하지만, 그것을 채우고, 더 알려고 하는 태도는 반드시 기르자. 수 개월이 지났을 때, 내 글을 읽는 모두와, 전 세계의 동료 데이터과학자 유망주들이 기초가 탄탄한 중급 데이터 사이언티스트로 다시 태어나기를 기대하고 간절히 바란다.

무엇보다도, 내게 필요한 것을 채워준 “An Extensive Step by Step Guide to Exploratory Data Analysis”의 저자인 Terence Shin에게 큰 감사를 표한다.

참조:

An Extensive Step by Step Guide to Exploratory Data Analysis, Shin. T, Jan/2020: https://towardsdatascience.com/an-extensive-guide-to-exploratory-data-analysis-ddd99a03199e

--

--

배우는 자(Learner Of Life)
배우는 자(Learner Of Life)

Written by 배우는 자(Learner Of Life)

배움은 죽을 때까지 끝이 없다. 어쩌면 그게 우리가 살아있다는 증거일지도 모른다. 배움을 멈추는 순간, 혹은 배움의 기회가 더 이상 존재하지 않는 순간, 우리의 삶은 어쩌면 거기서 끝나는 것은 아닐까? 나는 배운다 그러므로 나는 존재한다. 배울 수 있음에, 그래서 살아 있음에 감사한다.

No responses yet