탐색적 데이터 분석
Contents
2.8. 탐색적 데이터 분석#
EDA(Exploratory Data Analysis)라고도 부르는 탐색적 데이터 분석은 데이터 각각에 대한 특징을 찾아내는 작업이다. 탐색적 데이터 분석에서 찾아내야 할 데이터의 특징은 여러가지가 있지만 가장 중요한 것은 해당 데이터가 어떤 확률분포를 따르며 확률분포함수의 모수가 어떤 값을 가지는지를 추정하는 것이다.
탐색적 데이터 분석에서 수행해야 할 일은 다음과 같다.
데이터의 개수
데이터 중 누락 혹은 잘못된 데이터의 개수
데이터가 수치형 데이터인지 범주형 데이터인지 유형 판단
데이터의 대표값 (수치형 데이터의 경우에는 평균이나 중간값, 범주형 데이터의 경우에는 최빈값)
데이터의 최대/최소값 혹은 분산
데이터의 제한조건 (수치형 데이터의 경우에는 제한조건, 범주형 데이터의 경우에는 범주값의 종류)
데이터의 확률분포 및 모수
2.8.1. 탐색적 데이터 분석의 예 : 팁 데이터#
지금까지 많이 살펴보았던 팁 데이터에 대해 탐색적 데이터 분석을 하면 다음과 같은 결과를 얻을 수 있다.
import seaborn as sns
tips = sns.load_dataset("tips")
tips
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 |
1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 |
2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 |
3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 |
4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 |
... | ... | ... | ... | ... | ... | ... | ... |
239 | 29.03 | 5.92 | Male | No | Sat | Dinner | 3 |
240 | 27.18 | 2.00 | Female | Yes | Sat | Dinner | 2 |
241 | 22.67 | 2.00 | Male | Yes | Sat | Dinner | 2 |
242 | 17.82 | 1.75 | Male | No | Sat | Dinner | 2 |
243 | 18.78 | 3.00 | Female | No | Thur | Dinner | 2 |
244 rows × 7 columns
2.8.1.1. 데이터의 개수#
데이터프레임에 포함된 데이터의 개수는 len
함수로 구한다.
len(tips)
244
열 별로 각각 데이터의 개수를 셀 때는 count
메서드를 사용한다.
tips.count()
total_bill 244
tip 244
sex 244
smoker 244
day 244
time 244
size 244
dtype: int64
info
메서드를 사용할 수도 있다.
tips.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 244 entries, 0 to 243
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 total_bill 244 non-null float64
1 tip 244 non-null float64
2 sex 244 non-null category
3 smoker 244 non-null category
4 day 244 non-null category
5 time 244 non-null category
6 size 244 non-null int64
dtypes: category(4), float64(2), int64(1)
memory usage: 7.4 KB
2.8.1.2. 데이터 유형#
데이터의 유형은 일차적으로 데이터프레임의 dtypes
속성으로 파악할 수 있다. float 타입이면 수치형이고 category 타입이면 범주형이다. 다만 숫자임에도 불구하고 문자열로 되어 category 타입으로 나타내는 경우도 있기 때문에 주의를 요한다. int 타입일 경우에는 일반적으로 수치형으로 보지만 경우에 따라서는 범주형으로 취급할 수도 있다.
tips.dtypes
total_bill float64
tip float64
sex category
smoker category
day category
time category
size int64
dtype: object
위 결과에서 total_bill, tip, size는 수치형 데이터이고 sex, smoker, day, time은 범주형 데이터임을 알 수 있다.
2.8.1.3. 수치형 데이터의 통계#
수치형 데이터의 개수나 평균, 최대/최소값 등은 pandas 데이터프레임의 describe
메서드로 얻을 수 있다.
tips.describe()
total_bill | tip | size | |
---|---|---|---|
count | 244.000000 | 244.000000 | 244.000000 |
mean | 19.785943 | 2.998279 | 2.569672 |
std | 8.902412 | 1.383638 | 0.951100 |
min | 3.070000 | 1.000000 | 1.000000 |
25% | 13.347500 | 2.000000 | 2.000000 |
50% | 17.795000 | 2.900000 | 2.000000 |
75% | 24.127500 | 3.562500 | 3.000000 |
max | 50.810000 | 10.000000 | 6.000000 |
2.8.1.4. 범주형 데이터의 범위#
범주형 데이터가 가질 수 있는 값의 종류와 범위를 구하려면 해당 열의 describe
및 unique
메서드를 사용한다.
tips.sex.describe()
count 244
unique 2
top Male
freq 157
Name: sex, dtype: object
tips.sex.unique()
['Female', 'Male']
Categories (2, object): ['Male', 'Female']
2.8.1.5. 데이터 누락#
데이터의 누락을 알아보기 위해서는 missingno 패키지를 사용한다. 만약 누락된 데이터가 있다면 bar
함수를 사용했을 때 해당 개수가 표시된다.
import missingno as msno
msno.bar(tips)
plt.show()

팁 데이터의 경우에는 누락된 데이터가 없다는 것을 알 수 있다. 만약 누락 데이터가 존재한다면 해당 열이나 행을 삭제하여 사용하지 않거나 추정값으로 대체(imputation)하는 방법이다. 누락된 데이터를 추정값으로 대체하는 방법에 대해서는 별도로 자세히 다루기로 한다.
2.8.1.6. 데이터 제한조건#
다음으로 데이터가 가진 제한조건을 파악한다. 이 부분은 프로그램으로는 알아내기 어렵고 데이터에 대한 도메인 지식을 갖춘 사람이 스스로 생각해 낼 수 밖에 없다.
total_bill은 금액이므로 음수가 될 수 없다. 따라서 0 또는 양수이어야 한다.
tip도 금액이므로 음수가 될 수 없다. 따라서 0 또는 양수이어야 한다.
size는 사람의 수이므로 자연수이어야 한다.
2.8.1.7. 데이터의 확률분포 및 모수추정#
가장 중요한 작업은 각 데이터의 확률분포와 모수를 추정하는 것이다. distfit 패키지를 사용하면 여러가지 후보군 중에서 가장 유사한 확률분포를 자동으로 찾아준다. 사용방법은 다음과 같다. 여기에서는 예로 total_bill 데이터만 살펴본다.
from distfit import distfit
dist = distfit(distr='popular')
result = dist.fit_transform(tips.total_bill)
result["model"]
[distfit] >fit..
[distfit] >transform..
[distfit] >[norm ] [0.00 sec] [RSS: 0.00974729] [loc=19.786 scale=8.884]
[distfit] >[expon ] [0.00 sec] [RSS: 0.0242953] [loc=3.070 scale=16.716]
[distfit] >[pareto ] [0.02 sec] [RSS: 0.0637558] [loc=-0.002 scale=3.072]
[distfit] >[dweibull ] [0.01 sec] [RSS: 0.0077273] [loc=18.090 scale=6.866]
[distfit] >[t ] [0.04 sec] [RSS: 0.0077672] [loc=18.416 scale=6.880]
[distfit] >[genextreme] [0.08 sec] [RSS: 0.00510926] [loc=15.620 scale=6.435]
[distfit] >[gamma ] [0.01 sec] [RSS: 0.00568935] [loc=1.647 scale=4.060]
[distfit] >[lognorm ] [0.05 sec] [RSS: 0.00522484] [loc=-1.597 scale=19.743]
[distfit] >[beta ] [0.09 sec] [RSS: 0.00569416] [loc=1.654 scale=9567.297]
[distfit] >[uniform ] [0.00 sec] [RSS: 0.023091] [loc=3.070 scale=47.740]
[distfit] >[loggamma ] [0.05 sec] [RSS: 0.0100567] [loc=-2934.138 scale=391.595]
[distfit] >Compute confidence interval [parametric]
{'distr': <scipy.stats._continuous_distns.genextreme_gen at 0x12cd73550>,
'stats': 'RSS',
'params': (-0.06487665152494221, 15.620296989853873, 6.434860032529421),
'name': 'genextreme',
'model': <scipy.stats._distn_infrastructure.rv_frozen at 0x131905900>,
'score': 0.00510925584681514,
'loc': 15.620296989853873,
'scale': 6.434860032529421,
'arg': (-0.06487665152494221,),
'CII_min_alpha': 8.80546496271512,
'CII_max_alpha': 36.69877720209486}
이 결과에서 total_bill 데이터는 genextreme 이라는 분포를 따르는 것으로 나타났다. 이 분포는 “일반화 극단값 분포”라는 확률분포다. summary
속성값에는 각 후보 확률분포에 대한 추정 오차값(score)과 모수값이 들어있다. 오차값이 적을수록 해당 확률분포일 가능성이 높다. 다음 결과에서 로그정규분포는 일반화 극단값 분포과 비슷한 오차를 가지고 있는 것을 알 수 있다. 따라서 total_bill은 로그정규분포라고 볼 수도 있다.
print(dist.summary)
distr score LLE loc scale \
0 genextreme 0.005109 NaN 15.620297 6.43486
1 lognorm 0.005225 NaN -1.596509 19.7426
2 gamma 0.005689 NaN 1.647296 4.060375
3 beta 0.005694 NaN 1.653744 9567.296558
4 dweibull 0.007727 NaN 18.089662 6.86644
5 t 0.007767 NaN 18.416264 6.880142
6 norm 0.009747 NaN 19.785943 8.884151
7 loggamma 0.010057 NaN -2934.13787 391.594895
8 uniform 0.023091 NaN 3.07 47.74
9 expon 0.024295 NaN 3.07 16.715943
10 pareto 0.063756 NaN -0.002397 3.072397
arg
0 (-0.06487665152494221,)
1 (0.39848779123634986,)
2 (4.467235739845137,)
3 (4.45582554677317, 2346.2257696617125)
4 (1.0929626891064381,)
5 (4.563942365370458,)
6 ()
7 (1889.099136595919,)
8 ()
9 ()
10 (0.565767676661274,)
plot_summary
메서드를 사용하면 후보 확률분포들의 오차를 플롯으로 그려준다.
dist.plot_summary()
plt.show()
[distfit] >plot summary..

seaborn 패키지의 distplot
함수를 이용하여 로그정규분포로 추정한 결과를 비교하면 다음과 같다.
from scipy.stats import lognorm
sns.distplot(tips.total_bill, fit=lognorm, kde=False)
plt.show()

2.8.2. pandas_profiling 패키지#
pandas_profiling 패키지를 사용하면 확률분포 추정을 제외한 대부분을 작업을 자동으로 해준다. 사용법은 다음과 같다.
from pandas_profiling import ProfileReport
ProfileReport(tips, progress_bar=False, minimal=True, html={"navbar_show": False})