EDA (Exploratory Data Analysis) with Python
EDA (Exploratory Data Analysis) 는 수집한 데이터를 다양한 각도에서 관찰하고 이해하는 과정이다. Python 으로 하는 방법을 정리하고자 한다.
EDA 기본 – 데이터 기본정보 확인
데이터를 읽고 데이터를 확인한다.
확인 방법은 .sample, head 의 명령어로 확인 할수 있다. 해당 함수를 이용하여 기본적인 데이터를 확인할 수 있다.
import pandas as pd
import numpy as np
fueleco = pd.read_csv("../data/vehicles.csv.zip")
Code language: Python (python)
- head() 를 이용한 데이터 확인
fueleco.head()
Code language: CSS (css)

- sample() 를 이용한 데이터 확인
fueleco 데이터에서 random 하게 5개의 데이터 추출
fueleco.sample(5,random_state=42)
Code language: Python (python)

데이터 type 확인
info, dtype, select_type 을 이용하여 데이터 type 및 type별로 데이터를 확인할 수 있다.
- info() 를 이용한 데이터 정보 확인
Code language: Python (python)fueleco.info()

- dtype 를 이용한 데이터 정보 확인
Code language: Python (python)fueleco.dtypes

- select_dtypes 을 이용한 특정 type 데이터 정보확인
fueleco.select_dtypes('int64')
Code language: Python (python)

데이터 요약 통계량 확인
std(), mean() 를 사용해서 특정 통계량만 볼 수 있지만 describe() 를 이용하여 요약 통계량 전체를 확인한다.
또한 특정 type 에 대한 요약 통계량도 확인 가능하다. percentiles 요약통계량도 정확한 분위수를 명시할 수 있다.
Code language: Python (python)fueleco.describe()

Code language: Python (python)fueleco.describe().T

fueleco.select_dtypes('int64').describe().T
fueleco.describe(include=[np.int64]).T
Code language: Python (python)

fueleco.describe(
include=[np.number],
percentiles=[
0.01,
0.05,
0.10,
0.25,
0.5,
0.75,
0.9,
0.95,
0.99,
],
).T
Code language: Python (python)
pandas 에서 csv 파일 읽을 때 데이터 유추 단계
– 열의 모든 값이 정수처럼 보이면 정수로 변환하고 int64형식으로 열을 지정한다.
– 값들이 부동소수점수처럼 보이면 float64형식으로 지정한다.
– 값들이 수치, 부동소수점수, 정수처럼 보이면서 결측치가 있다면 float64를 지정한다. 결측치에 일반적으로 사용되는 np.nan은 부동소수점수 형식이기 때문이다.
– 값이 flase나 true면 불리언을 지정한다.
– 이외의 경우에는 열을 문자열로 남겨두고 object 형식을 지정한다.(이는 float64 형식의 결측치 일 수 있다.)
데이터 효율화(연속형 데이터) – memory 관리
위 그림 중에 int64 type의 데이터중에 city08, comb08 데이터를 보면 max 값이 150을 넘지 않고 있다.
int8, int16 type가 표현할 수 있는 숫자를 보면 아래와 같다.
np.iinfo(np.int8)
iinfo(min=-128, max=127, dtype=int8)
np.iinfo(np.int16)
iinfo(min=-32768, max=32767, dtype=int16)
Code language: Python (python)
그럼 두 컬럼이 현재 사용하고 있는 memory를 확인 해보면 아래와 같다.( info 함수의 memory_usage 를 사용)
fueleco[['city08','comb08']].info(memory_usage='deep')
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 39101 entries, 0 to 39100
Data columns (total 2 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 city08 39101 non-null int64
1 comb08 39101 non-null int64
dtypes: int64(2)
memory usage: 611.1 KB
Code language: Python (python)
두 컬럼의 데이터 type를 변경하여 메모리를 확인
(
fueleco[['city08','comb08']]
.assign(
city08 = fueleco.city08.astype(np.int16),
comb08 = fueleco.comb08.astype(np.int16),
)
.info(memory_usage='deep')
)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 39101 entries, 0 to 39100
Data columns (total 2 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 city08 39101 non-null int16
1 comb08 39101 non-null int16
dtypes: int16(2)
memory usage: 152.9 KB
Code language: Python (python)
number type의 데이터를 확인하여 min, max 값을 확인 한 후 data type 를 변경하여 memory 공간을 확보 할 수 있다.
데이터 효율화(object 데이터) – memory 관리
object type의 요약 통계량을 확인하면 아래와 같다.ㅣ
fueleco.select_dtypes('object').describe()
Code language: Python (python)

make 컬럼을 확인하면 unique 가 134 개의 통계량 값이 나왔다. 총 39101 데이터의 갯수 중에 134개의 고유한 값이 1% 내외이기 때문에 object 형태의 type를 category로 변경해도 괜찮을 데이터로 보인다.
fueleco.make.nunique()
134
fueleco[['make']].info(memory_usage='deep')
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 39101 entries, 0 to 39100
Data columns (total 1 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 make 39101 non-null object
dtypes: object(1)
memory usage: 2.4 MB
(
fueleco[['make']]
.assign(make=fueleco.make.astype('category'))
.info(memory_usage='deep')
)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 39101 entries, 0 to 39100
Data columns (total 1 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 make 39101 non-null category
dtypes: category(1)
memory usage: 89.5 KB
Code language: Python (python)
범주형 데이터
범주형 데이터는 날짜, 연속 값, 범주형 값으로 광범위하게 분류한다.
범주형 변수를 검사할 때는 고유한 값이 몇 개인지 알 필요가 있다.
고유한 값이 아주 많다면 이 열은 범주형이 아닐 수 있으며 자유로인 형식의 문서이거나 유효하지 않은 숫자가 섞여 pandas가 수치로 어떻게 처리할 지 판단하지 못했을 수도 있다.
- 결측치 확인
object 데이터는 결측치가 있는 확인이 필요하다. 결측치의 비율이 어느정도인지 확인해야하고, 결측치를 어떻게 처리할지 고민해야 한다.
fueleco.select_dtypes('object').columns
Index(['drive', 'eng_dscr', 'fuelType', 'fuelType1', 'make', 'model',
'mpgData', 'trany', 'VClass', 'guzzler', 'trans_dscr', 'tCharger',
'sCharger', 'atvType', 'fuelType2', 'rangeA', 'evMotor', 'mfrCode',
'c240Dscr', 'c240bDscr', 'createdOn', 'modifiedOn', 'startStop'],
dtype='object')
fueleco.drive.nunique()
7
fueleco.drive.sample(5, random_state=42)
4217 4-Wheel or All-Wheel Drive
1736 4-Wheel or All-Wheel Drive
36029 Rear-Wheel Drive
37631 Front-Wheel Drive
1668 Rear-Wheel Drive
Name: drive, dtype: object
fueleco.drive.isna().sum()
1189
fueleco.drive.isna().mean() * 100
3.0408429451932175
fueleco.drive.value_counts()
Front-Wheel Drive 13653
Rear-Wheel Drive 13284
4-Wheel or All-Wheel Drive 6648
All-Wheel Drive 2401
4-Wheel Drive 1221
2-Wheel Drive 507
Part-time 4-Wheel Drive 198
Name: drive, dtype: int64
Code language: Python (python)
- 상위 데이터만 확인
요약 통계량이 너무 많은 값이 있어서 최상단의 6개 값만 살펴보고 나머지 값은 Other 로 하여 데이터를 확인 할 수 있다.
fueleco.make.value_counts()
Chevrolet 3900
Ford 3208
Dodge 2557
GMC 2442
Toyota 1976
...
JBA Motorcars, Inc. 1
ASC Incorporated 1
Aurora Cars Ltd 1
Import Foreign Auto Sales Inc 1
Lambda Control Systems 1
Name: make, Length: 134, dtype: int64
top_n = fueleco.make.value_counts().index[:6]
top_n
(
fueleco.assign(
make=fueleco.make.where(
fueleco.make.isin(top_n), "Other"
)
).make.value_counts()
)
Other 23211
Chevrolet 3900
Ford 3208
Dodge 2557
GMC 2442
Toyota 1976
BMW 1807
Name: make, dtype: int64
Code language: Python (python)
- 그래프 그리기 (plot.bar, seaborn.countplot)
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10,8))
top_n = fueleco.make.value_counts().index[:6]
(
fueleco.assign(
make = fueleco.make.where(
fueleco.make.isin(top_n), 'Other'
)
)
.make.value_counts()
.plot.bar(ax=ax)
)
Code language: Python (python)

import seaborn as sns
fig, ax = plt.subplots(figsize=(10,8))
top_n = fueleco.make.value_counts().index[:6]
sns.countplot(
y = "make",
data=(
fueleco.assign(
make = fueleco.make.where(
fueleco.make.isin(top_n), 'Other'
)
)
),
)
Code language: Python (python)

- object 형식이지만 아닐 수 있는 데이터 처리 방법
object 로 표현되지만 실제로 두 가지 형식이 있는 열이다. 또한 결측치를 포함한 열이 경우이다.
value_count() 함수를 이용하여 데이터를 파악하고, .str.extract() 정규표현식을 사용하여 확인한다.
fueleco.rangeA.value_counts()
290 74
270 56
280 53
310 41
277 38
..
220/260 1
250/370 1
289/430 1
286 1
334 1
Name: rangeA, Length: 216, dtype: int64
(
fueleco.rangeA.str.extract(r"([^0-9.])")
.dropna()
.apply(lambda row: "".join(row), axis=1)
.value_counts()
)
/ 280
- 71
dtype: int64
set(fueleco.rangeA.apply(type))
{float, str}
fueleco.rangeA.isna().sum()
37616
Code language: Python (python)
여기서 rangeA 값은 두번째 연료 형식의 범위를 나타낸다. pandas로 결측치를 0으로 바꾸고 – 를 / 로 변경한 다음 나누고 각 행의 평균값을 취하는 동작을 한다.
(
fueleco.rangeA.fillna('0')
.str.replace('-','/')
.str.split('/', expand=True)
.astype(float)
.mean(axis=1)
)
0 0.0
1 0.0
2 0.0
3 0.0
4 0.0
...
39096 0.0
39097 0.0
39098 0.0
39099 0.0
39100 0.0
Length: 39101, dtype: float64
Code language: Python (python)
또한 데이터를 숫자 열도 서로 묶어서 구간으로 만들어서 범주처럼 취급할 수 있다.(cut, qcut)
(
fueleco.rangeA.fillna('0')
.str.replace("-","/")
.str.split("/",expand=True)
.astype(float)
.mean(axis=1)
.pipe(lambda ser_ : pd.cut(ser_, 10))
.value_counts()
)
(-0.45, 44.95] 37688
(269.7, 314.65] 559
(314.65, 359.6] 352
(359.6, 404.55] 205
(224.75, 269.7] 181
(404.55, 449.5] 82
(89.9, 134.85] 12
(179.8, 224.75] 9
(44.95, 89.9] 8
(134.85, 179.8] 5
dtype: int64
Code language: Python (python)
qcut 분위수 절단은 항목을 동일한 크기를 구간으로 자른다. rangeA 열의 경우에는 심하게 치우쳐 있고 대부분 항목이 0이다. 0을 여러 개의 구간으로 정량화 할 수 없어서 적용할 수 없다.
(
fueleco.rangeA.fillna('0')
.str.replace("-","/")
.str.split("/",expand=True)
.astype(float)
.mean(axis=1)
.pipe(lambda ser_ : pd.qcut(ser_, 10))
.value_counts()
)
ValueError: Bin edges must be unique: array([ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ,
0. , 449.5]).
You can drop duplicate edges by setting the 'duplicates' kwarg
(
fueleco.city08.pipe(
lambda ser: pd.qcut(ser, q=10)
).value_counts()
)
(5.999, 13.0] 5939
(19.0, 21.0] 4477
(14.0, 15.0] 4381
(17.0, 18.0] 3912
(16.0, 17.0] 3881
(15.0, 16.0] 3855
(21.0, 24.0] 3676
(24.0, 150.0] 3235
(13.0, 14.0] 2898
(18.0, 19.0] 2847
Name: city08, dtype: int64
Code language: Python (python)
연속형 데이터
연속형 데이터에 대한 정의는 정수나 부동소수점수 등 수치로 저장된 데이터이다.
범주형 데이터와 연속형 데이터 사이에는 회색지대가 있다.
예를 들어 학년은 숫자로 표시될 수 있다. 이 경우 학년 열은 범주형 혹은 연속형일 수 있다.
연속형 데이터의 요약통계량을 확인하고 그래프로 확인한다.
fueleco.city08.sample(5,random_state=42)
4217 11
1736 21
36029 16
37631 16
1668 17
Name: city08, dtype: int16
fueleco.city08.isna().sum()
0
fueleco.city08.describe()
count 39101.000000
mean 18.077799
std 6.970672
min 6.000000
25% 15.000000
50% 17.000000
75% 20.000000
max 150.000000
Name: city08, dtype: float64
Code language: Python (python)
- 그래프 그리기(hist, sns.distplot, sns.boxplot, sns.violinplot, sns.boxenplot)
fig, ax = plt.subplots(figsize=(10,8))
fueleco.city08.hist(ax=ax)
Code language: Python (python)

fig, ax = plt.subplots(figsize=(10,8))
fueleco.city08.hist(ax=ax, bins=30)
Code language: Python (python)

fig, ax = plt.subplots(figsize=(10,8))
sns.distplot(fueleco.city08, rug=True, ax=ax)
Code language: Python (python)

fig, axs = plt.subplots(nrows=3, figsize=(10,8))
sns.boxplot(fueleco.city08, ax=axs[0])
sns.violinplot(fueleco.city08, ax=axs[1])
sns.boxenplot(fueleco.city08, ax=axs[2])
Code language: Python (python)

- 정규분포 검정( 콜모고로프-스미로프)- Kolmogorov-Smirnov
콜모고로프-스미로프검정은 정규 분포 여부를 평가할 수 있다. p-value 값을 제공하고, 이 값이 유의( < 0.05)하다면 정규분포가 아니다.
from scipy import stats
stats.kstest(fueleco.city08, cdf='norm')
KstestResult(statistic=0.9999999990134123, pvalue=0.0)
Code language: Python (python)
또한 확률을 도식화(stats.probplot) 하면 값이 정규분포인지 확인 할 수 있다. 샘플이 선을 추적한다면 데이터는 정규 분포이다.
fig, ax = plt.subplots(figsize=(10,8))
stats.probplot(fueleco.city08, plot=ax)
((array([-4.1352692 , -3.92687024, -3.81314873, ..., 3.81314873,
3.92687024, 4.1352692 ]),
array([ 6, 6, 6, ..., 137, 138, 150], dtype=int16)),
(5.385946629915967, 18.077798521776934, 0.7725879414597128))
Code language: Python (python)

범주 간의 연속 값 비교
범주데이터인 열과 연속데이터인 열과의 비교를 한다.
mask = fueleco.make.isin(
['Ford','Honda','Tesla','BMW']
)
fueleco[mask].groupby('make').city08.agg(['mean','std'])
Code language: Python (python)

- 데이터 시각화(sns.catplot)
g = sns.catplot(
x = "make", y='city08', data=fueleco[mask], kind='box'
)
Code language: JavaScript (javascript)

상자 그림의 한 가지 단점은 데이터의 산포를 나타내지만 각 제조사의 샘플 개수는 나타내지 않는다.
mask = fueleco.make.isin(
['Ford','Honda','Tesla','BMW']
)
fueleco[mask].groupby('make').city08.count()
make
BMW 1807
Ford 3208
Honda 925
Tesla 46
Name: city08, dtype: int64
Code language: Python (python)
상자 그림위에 스윔(swarm) 도면을 그린다.
g = sns.catplot(
x="make", y="city08", data=fueleco[mask], kind='box'
)
sns.swarmplot(
x='make',
y='city08',
data=fueleco[mask],
color='k',
size=1,
ax=g.ax,
)
Code language: Python (python)

g = sns.catplot(
x = "make",
y = "city08",
data = fueleco[mask],
kind = "box",
col='year',
col_order=[2012,2014,2016,2018],
col_wrap=2
)
Code language: Python (python)

g = sns.catplot(
x = "make",
y = "city08",
data = fueleco[mask],
kind = "box",
hue='year',
hue_order=[2012,2014,2016,2018]
)
Code language: Python (python)

(
fueleco[mask]
.groupby("make")
.city08.agg(["mean","std"])
.style.background_gradient(cmap="RdBu",axis=0)
)
Code language: Python (python)

두개의 연속 열 비교
공분산, 상관관계를 계산한다.
fueleco.city08.cov(fueleco.highway08)
46.333260236736244
fueleco.city08.cov(fueleco.comb08)
47.41994667819078
fueleco.city08.cov(fueleco.cylinders)
-5.931560263764761
fueleco.city08.corr(fueleco.highway08)
0.9324945062284952
fueleco.city08.corr(fueleco.cylinders)
-0.7016548423827886
fueleco.city08.corr(
fueleco.barrels08, method='spearman'
)
-0.9743658763718624
Code language: Python (python)
- 공분산 및 상관관계에 대한 그래프(sns.heatmap, plot.scatter, sns.lmplot, sns.replot)
fig, ax = plt.subplots(figsize=(8,8))
corr = fueleco[
['city08','highway08','cylinders']
].corr()
mask = np.zeros_like(corr, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True
sns.heatmap(
corr,
mask=mask,
fmt=".2f",
annot=True,
ax=ax,
cmap="RdBu",
vmin=-1,
vmax=1,
square=True
)
Code language: Python (python)

fig, ax = plt.subplots(figsize=(8,8))
fueleco.plot.scatter(
x='city08',y='highway08',alpha=0.1, ax=ax
)
Code language: Python (python)

fig, ax = plt.subplots(figsize=(8,8))
(
fueleco.assign(
cylinders = fueleco.cylinders.fillna(0)
).plot.scatter(
x='city08', y='cylinders', alpha=0.1, ax=ax
)
)
Code language: Python (python)

res = sns.lmplot(
x='city08', y='highway08', data=fueleco
)
Code language: Python (python)

res = sns.relplot(
x="city08",
y="highway08",
data=fueleco.assign(
cylinders=fueleco.cylinders.fillna(0)
),
hue='year',
size='barrels08',
alpha=0.5,
height=8
)
Code language: Python (python)

범주값과 범주값 비교
다양한 범주 값을 축소하여 단순화 한다.
def generalize(ser, match_name, default):
seen = None
for match, name in match_name:
mask = ser.str.contains(match)
if seen is None:
seen = mask
else:
seen |= mask
ser = ser.where(~mask, name)
ser = ser.where(seen,default)
return ser
makes = ['Ford','Tesla','BMW','Toyota']
data = fueleco[fueleco.make.isin(makes)].assign(
SClass = lambda df_ : generalize(
df_.VClass,
[
("Seaters", "Car"),
("Car", "Car"),
("Utility", "SUV"),
("Truck", "Truck"),
("Van", "Van"),
("van", "Van"),
("Wagon", "Wagon"),
],
'other',
)
)
data.groupby(['make','SClass']).size().unstack()
Code language: Python (python)

Code language: Python (python)pd.crosstab(data.make, data.SClass)

Code language: Python (python)pd.crosstab([data.year, data.make],[data.SClass, data.VClass])

- 크래머(Cramder) 의 V 척도를 이용하여 상관관계 확인
import scipy.stats as ss
def cramers_v(x,y):
confusion_matrix = pd.crosstab(x,y)
chi2 = ss.chi2_contingency(confusion_matrix)[0]
n = confusion_matrix.sum().sum()
phi2 = chi2/n
r, k = confusion_matrix.shape
phi2corr = max(
0, phi2 - ((k-1)*(r-1))/(n-1)
)
rcorr = r - ((r-1)**2)/(n-1)
kcorr = k - ((k-1)**2)/(n-1)
return np.sqrt(
phi2corr / min((kcorr-1), (rcorr)-1)
)
cramers_v(data.make, data.SClass)
0.2859720982171866
data.make.corr(data.SClass, cramers_v)
0.2859720982171866
Code language: Python (python)
- 그래프 그리기(plot.bar, sns,catplot)
fig, ax = plt.subplots(figsize=(10,8))
(
data.pipe(
lambda df_ : pd.crosstab(df_.make, df_.SClass)
).plot.bar(ax=ax)
)
Code language: Python (python)

fig, ax = plt.subplots(figsize=(10,8))
(
data.pipe(
lambda df_:pd.crosstab(df_.make,df_.SClass)
)
.pipe(lambda df_: df_.div(df_.sum(axis=1), axis=0))
.plot.bar(stacked=True, ax=ax)
)
Code language: Python (python)

res = sns.catplot(
kind='count',
x='make',
hue='SClass',
data=data
)
Code language: Python (python)
