-
탐색적 데이터분석 : EDA(Explatory Data Analysis)데이터 2022. 8. 21. 20:24
탐색적 데이터분석은
우리가 존재한다고 믿는 것들은 물론이고
존재하지 않는다고 믿는 것들을
발견하려는 태도, 유연성 그리고 자발성이다.
by 존 듀키탐색적 데이터분석 : EDA(Explatory Data Analysis)
탐색적 분석이란 것이 말 그대로 데이터를 탐색해보는 것이기 때문에 다양한 시각에서 데이터를 들여다 보는 작업이다. 데이터의 종류와 성격도 다양하고 분석하는 목적도 다양하기 때문에 탐색적 분석을 수행하는 범위나 절차 등이 정형화 되어 있지는 않다. 여기서는 EDA 과정에서 수행하는 일반적이고 필수적인 작업 위주로 탐색적 데이터 분석을 설명한다.
예제로 사용하는 데이터는 ADP 21회 실기문제에서 사용했던 데이터와 유사한 데이터를 사용한다.
시험은 이 데이터로 grade을 예측하는 문제였다. 데이터의 개략적 명세는 다음과 같다.- 데이터명세
- school - 학교 유형 (binary: “GP” or “MS”)
- sex - 학생들의 성별 (binary: “F” - female or “M” - male)
- paid - 과목(Math or Portuguese)에 대한 추가 유료 수업 수강 여부 (binary: yes or no)
- famrel - 가족관계 (numeric: from 1 - very bad to 5 - excellent)
- freetime - 방과 후 자유시간 (numeric: from 1 - very low to 5 - very high)
- goout - 친구들과의 외출 빈도 (numeric: from 1 - very low to 5 - very high)
- Dalc - 주중 알코올 소비량 (numeric: from 1 - very low to 5 - very high)
- Walc - 주말 알코올 소비량 (numeric: from 1 - very low to 5 - very high)
- health - 현재 건강 상태 (numeric: from 1 - very bad to 5 - very good)
- absences - 결석 횟수 (numeric: from 0 to 93)
- grade - 성적 등급 (numeric: from 0 to 11, output target)
setwd("c:/BigdataClass") file_path <- "./data_from_dondon/data/ex_data" files <- list.files(file_path) data_original <- fread(file.path(file_path, "adp1.csv")) dat <- data_original
데이터 구조 및 요약통계량 확인
데이터를 읽고 나면 제일 먼저 데이터의 구조와 변수들의 속성을 살펴보고 종속변수와 설명변수들이 어떤 것들이 있는지 확인한다. 또 변수별 요약통계량을 통해서 결측치의 존재여부와 숫치형 변수의 값의 범위 등의 확인이 가능하다.
데이터는 366개의 관측치와 11개의 변수로 구성되며 문자형 변수 3개와 정수형변수 8개로 되어있다. R 베이스 패키지의 summary()함수를 활용하여 기본적인 통계량을 확인 할 수 있다.
dat %>% summary()
school sex paid famrel Length:366 Length:366 Length:366 Min. :1.000 Class :character Class :character Class :character 1st Qu.:4.000 Mode :character Mode :character Mode :character Median :4.000 Mean :3.943 3rd Qu.:5.000 Max. :5.000 freetime goout Dalc Walc health Min. :1.000 Min. :1.000 Min. :1.00 Min. :1.000 Min. :1.000 1st Qu.:3.000 1st Qu.:2.000 1st Qu.:1.00 1st Qu.:1.000 1st Qu.:3.000 Median :3.000 Median :3.000 Median :1.00 Median :2.000 Median :4.000 Mean :3.208 Mean :3.098 Mean :1.47 Mean :2.279 Mean :3.577 3rd Qu.:4.000 3rd Qu.:4.000 3rd Qu.:2.00 3rd Qu.:3.000 3rd Qu.:5.000 Max. :5.000 Max. :5.000 Max. :5.00 Max. :5.000 Max. :5.000 NA's :10 absences grade Min. : 0.000 Min. : 0 1st Qu.: 0.000 1st Qu.: 3 Median : 4.000 Median : 5 Mean : 5.587 Mean : 5 3rd Qu.: 8.000 3rd Qu.: 7 Max. :75.000 Max. :11
요약 통계량을 통해 goout에 결측치 10개가 존재하는 것을 알았고, absences 변수의 최대값이 75로 이상치 임을 예상할 수 있다.
요약 통계량을 확인 할 수 있는 패키지 중 skimr이란 패키지의 skim()함수는 summary()함수를 대체 할 수 있는 함수 이며. summary() 함수보다 더 다양한 통계량을 제공해준다.dat %>% skim()
Data summary Name Piped data Number of rows 366 Number of columns 11 Key NULL _______________________ Column type frequency: character 3 numeric 8 ________________________ Group variables None Variable type: character
skim_variable n_missing complete_rate min max empty n_unique whitespace school 0 1 2 2 0 2 0 sex 0 1 1 1 0 2 0 paid 0 1 2 3 0 2 0 Variable type: numeric
skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist famrel 0 1.00 3.94 0.89 1 4 4 5 5 ▁▁▃▇▅ freetime 0 1.00 3.21 0.99 1 3 3 4 5 ▁▃▇▆▂ goout 10 0.97 3.10 1.11 1 2 3 4 5 ▂▇▇▆▃ Dalc 0 1.00 1.47 0.88 1 1 1 2 5 ▇▂▁▁▁ Walc 0 1.00 2.28 1.28 1 1 2 3 5 ▇▅▅▂▂ health 0 1.00 3.58 1.38 1 3 4 5 5 ▂▂▅▃▇ absences 0 1.00 5.59 8.11 0 0 4 8 75 ▇▁▁▁▁ grade 0 1.00 5.00 3.05 0 3 5 7 11 ▇▇▇▆▆ skim()함수의 결과로부터 변수 속성 별로 다양한 정보를 확인 할 수 있다. 팩터형 변수는 결측치, 결측률, 팩터형 변수의 레벨 수 와 불균형 정도까지 다양한 정보를 확인 할 수 있고 숫치형 변수도 결측치 뿐만 아니라, 평균, 표준편차 등 통계랑과 분위수, 히스토그램을 통한 변수의 개략 분포까지 확인 할 수 있다.
across() 함수를 이용하면 컬럼별로 통계량이나 요약값을 구할 수 도 있다. 다음은 across() 함수를 이용하여 컬럼별 결측치 갯수를 구하는 코드이다.
dat %>% summarise(across(everything(), ~sum(is.na(.x))))
school sex paid famrel freetime goout Dalc Walc health absences grade 1 0 0 0 0 0 10 0 0 0 0 0
변수 속성 변환
구조와 요약 통계량을 통해 반응변수(종속변수)와 설명변수(독립변수_에 대한 개략적인 파악이 끝나고 나면 분석 목적이나 원하는 가시화에 유리하도록 변수의 속성을 변환하는 작업이 필요하다. 변수를 변환하거나, 파생변수를 쉽게 만들수 있는 함수가 mutate()함수이다.
mutate 함수는 mutate_if( ), mutate_at(), mutate_all() 등 몇 가지 종류가 있다. (참고: 이런 함수를 동사의 기능 범위를 한정해서 적용한다는 의미에서 scoped verbs 라고 표현하기도 하며 select, filter, summary 등 dplyr 패키지 다른 함수도 동일하다)- mutate : 지정된 변수 낱개에 대해 적용하는 함수
- mutate_at : 이름으로 지정된 변수들에 대해 적용하는 함수
- mutate_if : 조건에 해당하는 변수만 적용하는 함수
- mutate_all : 모든 변수를 조작하는 함수
또한 위의 함수를 사용할 때 변수 선택을 쉽게 하도록 도와주는 보조 함수들도 있다. 이를 활용하면 원하는 조건의 변수들을 쉽게 찾아 변환할 수 있다.
- vars() : 함수를 적용할 변수를 지정할 때 사용
- starts_with(“f”) : f로 시작하는 변수를 선택
- ends_with(“lc”) : lc로 끝나는 변수
- contains(“lc” ) : lc를 포함하고 있는 변수
- everything() : 모든 변수
mutate_at
mutate_at() 함수는 변수를 지정하여 지정한 변수들을 조작하는 함수이다.
dat %>% mutate_at(vars("school", "sex", "paid"), factor) %>% # 세 변수를 팩터로 변환 str()
Classes 'data.table' and 'data.frame': 366 obs. of 11 variables: $ school : Factor w/ 2 levels "GP","MS": 1 1 1 1 1 1 1 1 1 1 ... $ sex : Factor w/ 2 levels "F","M": 1 1 1 1 1 2 2 1 2 1 ... $ paid : Factor w/ 2 levels "no","yes": 1 1 2 2 2 2 1 1 2 2 ... $ famrel : int 4 5 4 3 4 5 4 4 5 3 ... $ freetime: int 3 3 3 2 3 4 4 1 5 3 ... $ goout : int 4 3 2 2 2 2 4 4 1 3 ... $ Dalc : int 1 1 2 1 1 1 1 1 1 1 ... $ Walc : int 1 1 3 1 2 2 1 1 1 2 ... $ health : int 3 3 3 5 5 5 3 1 5 2 ... $ absences: int 6 4 10 2 4 10 0 6 0 0 ... $ grade : int 1 1 4 9 4 9 5 1 9 3 ... - attr(*, ".internal.selfref")=<externalptr>
dat %>% mutate_at(vars('freetime', 'goout'), scale) %>% # 두 변수를 표준화 str()
Classes 'data.table' and 'data.frame': 366 obs. of 11 variables: $ school : chr "GP" "GP" "GP" "GP" ... $ sex : chr "F" "F" "F" "F" ... $ paid : chr "no" "no" "yes" "yes" ... $ famrel : int 4 5 4 3 4 5 4 4 5 3 ... $ freetime: num [1:366, 1] -0.21 -0.21 -0.21 -1.22 -0.21 ... ..- attr(*, "scaled:center")= num 3.21 ..- attr(*, "scaled:scale")= num 0.991 $ goout : num [1:366, 1] 0.816 -0.089 -0.994 -0.994 -0.994 ... ..- attr(*, "scaled:center")= num 3.1 ..- attr(*, "scaled:scale")= num 1.11 $ Dalc : int 1 1 2 1 1 1 1 1 1 1 ... $ Walc : int 1 1 3 1 2 2 1 1 1 2 ... $ health : int 3 3 3 5 5 5 3 1 5 2 ... $ absences: int 6 4 10 2 4 10 0 6 0 0 ... $ grade : int 1 1 4 9 4 9 5 1 9 3 ... - attr(*, ".internal.selfref")=<externalptr>
iris %>% mutate_at(vars(ends_with("Length")), scale) %>% # Length로끝나는 변수 표준화 str()
'data.frame': 150 obs. of 5 variables: $ Sepal.Length: num [1:150, 1] -0.898 -1.139 -1.381 -1.501 -1.018 ... ..- attr(*, "scaled:center")= num 5.84 ..- attr(*, "scaled:scale")= num 0.828 $ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ... $ Petal.Length: num [1:150, 1] -1.34 -1.34 -1.39 -1.28 -1.34 ... ..- attr(*, "scaled:center")= num 3.76 ..- attr(*, "scaled:scale")= num 1.77 $ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ... $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
mutate_if
변환하고 싶은 변수들 중 조건에 맞는 변수를 선택하여 변환할 때 사용한다.
mutate_if는 mutate_at과 다르게 변수 속성을 인식하는 고유의 함수를 사용하여 변환에 적용할 변수를 선택할 수 있다.- is.integer : int형 변수 선택
- is.character : chr형 변수 선택
- is.numeric : numeric형 변수 선택(integer 포함)
dat %>% mutate_if(is.integer, as.factor) %>% # 정수형 변수를 팩터로 변환 str()
Classes 'data.table' and 'data.frame': 366 obs. of 11 variables: $ school : chr "GP" "GP" "GP" "GP" ... $ sex : chr "F" "F" "F" "F" ... $ paid : chr "no" "no" "yes" "yes" ... $ famrel : Factor w/ 5 levels "1","2","3","4",..: 4 5 4 3 4 5 4 4 5 3 ... $ freetime: Factor w/ 5 levels "1","2","3","4",..: 3 3 3 2 3 4 4 1 5 3 ... $ goout : Factor w/ 5 levels "1","2","3","4",..: 4 3 2 2 2 2 4 4 1 3 ... $ Dalc : Factor w/ 5 levels "1","2","3","4",..: 1 1 2 1 1 1 1 1 1 1 ... $ Walc : Factor w/ 5 levels "1","2","3","4",..: 1 1 3 1 2 2 1 1 1 2 ... $ health : Factor w/ 5 levels "1","2","3","4",..: 3 3 3 5 5 5 3 1 5 2 ... $ absences: Factor w/ 34 levels "0","1","2","3",..: 7 5 11 3 5 11 1 7 1 1 ... $ grade : Factor w/ 12 levels "0","1","2","3",..: 2 2 5 10 5 10 6 2 10 4 ... - attr(*, ".internal.selfref")=<externalptr>
dat %>% mutate_if(is.character, as.factor) %>% # 문자형 변수를 팩터형으로 str()
Classes 'data.table' and 'data.frame': 366 obs. of 11 variables: $ school : Factor w/ 2 levels "GP","MS": 1 1 1 1 1 1 1 1 1 1 ... $ sex : Factor w/ 2 levels "F","M": 1 1 1 1 1 2 2 1 2 1 ... $ paid : Factor w/ 2 levels "no","yes": 1 1 2 2 2 2 1 1 2 2 ... $ famrel : int 4 5 4 3 4 5 4 4 5 3 ... $ freetime: int 3 3 3 2 3 4 4 1 5 3 ... $ goout : int 4 3 2 2 2 2 4 4 1 3 ... $ Dalc : int 1 1 2 1 1 1 1 1 1 1 ... $ Walc : int 1 1 3 1 2 2 1 1 1 2 ... $ health : int 3 3 3 5 5 5 3 1 5 2 ... $ absences: int 6 4 10 2 4 10 0 6 0 0 ... $ grade : int 1 1 4 9 4 9 5 1 9 3 ... - attr(*, ".internal.selfref")=<externalptr>
across() 함수를 mutate 함수와 같이 사용하면 mutate_if(), mutate_at()과 동일한 효과를 낼 수 있다. across()는 mutate() 외에 summarise 함수와 사용하여 컬럼별 요약값을 확인하는데 사용할 수 도 있다.
dat %>% mutate(across(c(school:paid), as.factor)) # school ~ paid 까지 팩터로 dat %>% mutate(across(starts_with('s'), as.factor)) # s로 시작하는 변수만 dat <- dat %>% mutate_if(is.character, as.factor) # 변환한 데이터로 치환
EDA와 데이터 시각화
탐색적 데이터 분석인 EDA 과정은 아이들이 장난감을 만져보고 두드려보고 하듯이 데이터를 이렇게 저렇게 만져보고 반복적으로 탐색하는 작업과정이다. EDA 과정에서는 다음과 같은 것을 주로 확인 하고 파악한다.
- 반응변수와 종속변수, 분포확인
- 연속형변수 : 히스토그램/산점도 활용, 팩터형변수 : boxplot 등 활용
- 연속형 변수 치우침, 팩터형 변수의 불균형 여부 확인
- NZV(Near Zero Variance) 변수 확인
- 변수들간의 상관관계확인 : GGally 혹은 Corrplot 활용
- 반응변수와 설명변수간의 상관관계
- 설명변수들 간의 상관관계
EDA 수행의 결과는 통상 시각화된 그래프를 통해 확인하고 설명한다. 여기에서는 EDA 과정에서 많이 수행하는 시각화 문제로 한정해서 소개하기로 한다. EDA 과정에서 수행하는 가시화는 통상 다음과 같은 것을 포함한다.
변수별 분포 확인
- 팩터형 변수 하나에 대한 레벨별 빈도를 막대 그래프 확인
예) 학교 유형별 빈도
펭귄 종류별 빈도
g1 <- dat %>% ggplot(aes(school)) + # 학교 변수 하나에 대한 빈도 geom_bar() g2 <- penguins %>% na.omit() %>% ggplot(aes(species, fill = sex)) + geom_bar(position = "dodge") + theme(legend.position = "none") grid.arrange(g1, g2, nrow = 1)
- 팩터형 변수 하나에 대한 수치형 변수의 분포를 boxplot로 확인
예)학교(팩터형) 유형별 등급(수치형)의 분포
펭귄(팩터형) 유형별 체중(수치형)의 분포
g3 <- dat %>% ggplot(aes(x = school, y= grade)) + geom_boxplot() g4<- penguins %>% ggplot(aes(x = species, y = body_mass_g)) + geom_boxplot() grid.arrange(g3, g4, nrow = 1)
- 팩터형 변수 여러개에 대한 빈도 막대그래프
예) paid별, schoo별 성별 빈도를 하나의 막대그래프로 출력하기
ggplot의 facet_wrap, facet_grid 함수를 이용해서 그리기 위해 데이터를 longform 형식으로 만들어야 한다. 데이터를 longform 형식으로 만는 함수는 gather()와 pivot_longer()함수이다.
먼저 gather()함수를 이용해 롱폼으로 변환해보자.
형식: gather(data, key = “key”, value = “value”, …)
- key : 새로 생성할 컬럼명, default는 ‘key’
- value : 정리되는 값이 들어가는 컬럼명, default는 ‘value’
팩터형 변수 school, sex, paid 세 변수를 gather()를 이용해서 long form으로 변환해보자.dat %>% select_if(is.factor) %>% # factor 변수 모두 선택 gather() %>% # default인 key와 value로 생성되겠지.. glimpse()
Rows: 0 Columns: 0
팩터형 변수들을 뽑아 long form으로 쌓은 데이터는 변수명이 key로 들어가 있고 각 팩터 변수의 레벨이 value로 들어있다.(디폴트값)
이제 롱폼 데이터를 ggplot에 넣어서 레벨(value)별 빈도를 막대그래프까지 그려보자.dat %>% select_if(is.factor) %>% # factor 변수 모두 선택 gather() %>% # long form으로 ggplot(aes(x=value,fill=key)) + # x = 팩터의 레벨, 구분을 key로 geom_bar() + facet_wrap(~ key, scales = "free") + # key 별로 scale은 free로 지정 theme(legend.position = 'none') # legend는 불필요
생성된 그래프는 팩터형 변수 paid, school, sex에 대한 빈도를 동시에 출력 했다.
pivot_longer를 이용해서 long form으로 데이터를 만들어 동일한 그래프를 그릴 수도 있다.
- 형식: pivot_longer(data, cols = c(), names_to = “name”, values_to = “value”, …)
- cols : 구겨 넣을 컬럼들을 지정
- names_to : 변수들이 데이터로 들어가게 되는 컬럼명, default는 ‘name’
- values_to = 변수의 값이 들어갈 이름, default는 ‘value’
dat %>% select_if(is.factor) %>% # 팩터형 변수만, pivot_longer(cols = everything()) %>% # col 만 지정 나머진 default ggplot() + geom_bar(aes(x=value,fill=name)) + # x는 level, fill은 변수명 facet_wrap(~ name, scales = "free") + # name(변수명) 별로 theme(legend.position = 'none')
pivot_longer(cols = everything())에서 everything()은 모든 변수를 선택할 때 사용하는 옵션이다. 칼럼명을 제외하고 gather와 결과는 동일한 결과를 산출한다. 팩터형 변수들의 레벨을 확인해 보면, school 변수에서 GP와 MS의 빈도 차이가 큼을 알 수 있다.
- 수치형 변수의 분포(histogram, density)
- 하나의 변수에 대한 분포
g5 <- dat %>% ggplot() + geom_histogram(aes(grade)) # 히스토그램으로 g6<- dat %>% ggplot() + geom_density(aes(grade)) # density로 grid.arrange(g5, g6, nrow = 1)
- 여러 수치형 변수의 분포(histogram, density)를 한꺼번에
여러 수치형 변수의 분포를 한꺼번에 출력해서 보는 이유는 각각의 분포를 한눈에 확인하고 분포가 정규분포형태를 따르는 지를 확인해서 Box_Cox 변환등 변환이 필요한 지를 판단해 보기 위해서이다.
dat %>% select_if(is.numeric) %>% pivot_longer(col = everything()) %>% group_by(name) %>% ggplot() + geom_histogram(aes(x=value, fill = name )) + # 히스토그램으로 facet_wrap( ~ name, scale = "free") + theme(legend.position = 'none')
dat %>% select_if(is.numeric) %>% pivot_longer(col = everything()) %>% group_by(name) %>% ggplot() + geom_density(aes(x=value, fill = name )) + facet_wrap(~ name, scale = "free") + theme(legend.position = 'none')
변수별 상관관계 확인
두 수치형 변수들간의 상관관계는 산점도로 확인이 가능하다. 수치형 변수의 상관관계를 확인하는 이유는 회귀분석간 다중 공선성 등의 문제를 야기하는 변수들이 존재하는지 확인해야 하기 때문이다. 하지만 수치형 변수의 수가 많아지면, 두변수를 조합하는 경우의 수가 많아져서 각각의 산점도를 그리기 곤란하다. 이럴때, GGally 패키지의 ggpairs()를 이용하면 변수별 상관계수 및 변수별 분포, 각 변수별 관계를 확인해볼 수 있다. 다만 변수가 많아질 경우 그래프가 잘리는 현상이 있으므로 주의가 필요하다.
dat %>% select_if(is.numeric) %>% ggpairs(title = " 수치형 변수의 상관관계 도표")
penguins %>% select_if(is.numeric) %>% select(-year) %>% ggpairs(title = "Palmer 펭귄 데이터 수치형변수 상관관계")
상관계수만 따로 보고 싶은 경우 corrplot 패키지의 corrplot(method = “number”)을 이용할 수 있다.
library(corrplot) dat %>% select_if(is.integer) %>% cor(use = "complete.obs") %>% #use = “complete.obs” : 결측치가 있는 경우 제외 corrplot(method = "number")
상자그림(boxplot)
boxplot은 연속형 변수의 값을 IQR 형식으로 표현하는 그래프이다. 범주형 변수의 레벨별로 boxplot을 그리면 범주별 데이터의 분포를 알아보기 쉽다. 특히 범주형 설명변수와 연속형 종속변수 간의 관계를 파악하기 위해 boxplot을 통해 각 범주에 대한 종속변수의 경향성을 파악할 수 있다.
g7 <- dat %>% ggplot(aes(x = school, y = grade, fill = school)) + geom_boxplot() g8 <- dat %>% ggplot(aes(x = sex, y = grade, fill = sex)) + geom_boxplot() g9 <- dat %>% ggplot(aes(x = paid, y = grade, fill = school)) + geom_boxplot() g10 <- dat %>% ggplot(aes(x = paid, y = grade, fill = sex)) + geom_boxplot() grid.arrange(g7, g8, g9, g10, nrow = 2)
정리
이번 글은 데이터를 만져보고 파악해보는 과정인 탐색적 데어터 분석(EDA) 과정에 대한 내용을 다루었다. “탐색”한다고 하는 말이 함의 하듯이 탐색적 데이터 분석은 데이터를 들여다 보면서 그 안에 들어있는 스토리를 찾아내고 데이터의 특성에 대해서 알아가는 과정이기 때문에 매우 다양한 시각에서 분석을 할 수 있다. 이 글은 데이터 분석과정의 일반화된 탐색적분석에 대한 포괄적인 내용이라기 보다, 정해진 시간내에 탐색적 분석을 끝내고 전처리를 한 후 머신러닝 모델을 돌려하는 시험 같은 환경에서 빠른 시간내에 필요한 정보만 확인하고 처리하기 위한 방법과 사례들을 다루어 보았다.
'데이터' 카테고리의 다른 글
Imbalnced Data Classification (0) 2022.10.02 CARET을 활용한 회귀 모형 (0) 2022.09.04 Handling Factor Variable using forcats package (0) 2022.08.07 Tidymodels를 활용한 ML Process (0) 2022.07.23 ggplot을 이용한 데이터가시화 II (0) 2022.07.08 - 데이터명세