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() 를 이용한 데이터 정보 확인
fueleco.info()
Code language: Python (python)
  • dtype 를 이용한 데이터 정보 확인
fueleco.dtypes
Code language: Python (python)
  • select_dtypes 을 이용한 특정 type 데이터 정보확인
fueleco.select_dtypes('int64')
Code language: Python (python)

데이터 요약 통계량 확인

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

fueleco.describe()
Code language: Python (python)
fueleco.describe().T
Code language: Python (python)
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)
pd.crosstab(data.make, data.SClass)
Code language: Python (python)
pd.crosstab([data.year, data.make],[data.SClass, data.VClass])
Code language: Python (python)
  • 크래머(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)

Related Posts

답글 남기기

이메일 주소는 공개되지 않습니다.