ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • kaggle 데이터로 EDA 이해하기
    잡다R 2021. 1. 19. 22:20

     

    데이터 분석 기초 역량을 확인하는 방법으로 EDA에 대해 말이 많습니다. 이번 글에서는 EDA란 무엇이고 어떻게 진행하는지 알아보도록 하겠습니다.

    EDA란 탐색적 자료분석의 약자로 데이터를 처음 받았을 때 그 특성을 파악하기 위해 이루어집니다. 데이터가 어떻게 생겼는지, 오류는 없는지 등 기본적인 정보를 파악하는 목적이 되고 분석 주제를 잡거나 문제 해결을 위한 척도가 되기도 합니다. 목적이 광범위한 느낌이 듭니다. 그래서 제가 배웠던 내용 중에 EDA를 이해하기에 가장 쉽고 깔끔한 내용을 소개해드리면 다음과 같습니다.

    데이터를 사과라고 했을 때, 사과를 살펴보는 방법을 크게 2가지로 나누고 사과의 특징을 다음과 같이 정리할 수 있습니다.

    1. 시각적인 정보로만 사과를 살펴본다.
      예) 빨간색이다, 모양이 동그랗다, 크기가 주먹만하다
    2. 사과에 물리적인 힘을 가하는 등 눈으로 보여지는 정보 이외의 특징을 찾는다.
      예) 쪼개보니 속 알맹이는 노르스름하다, 먹어보니 맛있다, 딱딱하지만 물에 가라앉지 않고 뜬다

     

    여러 정보를 정리하고 나면 이어지는 질문을 뽑아낼 수 있겠습니다.

    왜 빨간색인가?
    왜 물에 뜨는가?

     

    그러면 경우에 따라 이어지는 정답들을 끄집어낼 수 있습니다.

    사과 껍질에는 안토시아닌이라는 색소가 있어 햇빛을 받으면 빨간색을 띈다.
    사과는 알맹이에 공기가 많고 껍질 안으로 물이 침투하지 않아 물에 잠기지 않는다.

     

    이렇게 새로운 지식 혹은 인사이트를 얻기 위해 사과를 살펴보고 이해하고 질문하는 과정을 EDA라고 보시면 됩니다.

    그러면 사과를 살펴보는 방법을 어떻게 데이터에 응용할 수 있을까요? 이미 우리가 이미 알고 있는 R 내장 함수와 라이브러리로 살펴볼 수 있겠습니다. 앞서 시각정보로만 사과를 살펴보는 것은 데이터의 기본 정보를 파악하기 위한 방법으로 str(), head()와 같은 기본 함수를 이용하면 될 것입니다. 그리고 사과에 손을 대고 이리저리 굴려보는 방법은 dplyr로 쪼개보거나, ggplot2을 이용해 그래프를 그리거나 통계값으로 볼 수 있겠습니다.

    그럼 데이터를 가지고 함께 EDA를 진행해볼가요? 데이터는 kaggle에 있는 House Price 연습용 대회 데이터를 가지고 진행하겠습니다. kaggle 회원가입 후 아래 링크에서 Join Competiton을 클릭하시면 데이터를 받으실 수 있습니다.

    https://www.kaggle.com/c/home-data-for-ml-course/data

    데이터는 미국의 부동산 관련 데이터입니다. 면적, 건축자재 등 주어진 정보를 토대로 부동산의 가격을 예측하는 모델링을 진행하는 것이 대회의 목표입니다. 필요하다면 데이터 설명서 description을 살펴보며 어떤 변수들과 어떤 값이 있는지 살펴보시기 바랍니다.

    필요한 라이브러리를 불러온 뒤 데이터를 읽어들이겠습니다.

    library(dplyr)
    library(tidyr)
    library(ggplot2)
    train <- read.csv('train.csv')

    이제 데이터를 하나씩 살펴보도록 하겠습니다. dim( )은 데이터의 행과 열을 보여줍니다. nrow(train)와 ncol(train) 함수를 동시에 보여준다고 생각하시면 됩니다.

    dim(train)
    ## [1] 1460   81

    전체 column의 class 빈도를 확인하는 함수는 이렇게 적어주시면 됩니다.

    table(sapply(train, class))
    ## 
    ## character   integer 
    ##        43        38

    써보시면 43개의 범주형 변수와 38개의 수치형 변수를 가지고 있는 것을 확인할 수 있습니다.

    str(train)
    ## 'data.frame':    1460 obs. of  81 variables:
    ##  $ Id           : int  1 2 3 4 5 6 7 8 9 10 ...
    ##  $ MSSubClass   : int  60 20 60 70 60 50 20 60 50 190 ...
    ##  $ MSZoning     : chr  "RL" "RL" "RL" "RL" ...
    ##  $ LotFrontage  : int  65 80 68 60 84 85 75 NA 51 50 ...
    ##  $ LotArea      : int  8450 9600 11250 9550 14260 14115 10084 10382 6120 7420 ...
    ##  $ Street       : chr  "Pave" "Pave" "Pave" "Pave" ...
    ##  $ Alley        : chr  NA NA NA NA ...
    ##   ( 중략 )
    ##  $ SalePrice    : int  208500 181500 223500 140000 250000 143000 307000 200000 129900 118000 ...

    str()은 변수의 class type과 전체값에 대한 head 값을 보여줍니다. 무슨 말인지 모르겠다면 3번째 변수인 MSZoning을 보시기 바랍니다. 값이 “RL” “RL” “RL” “RL” …  로 반복해서 나타나는 것을 확인할 수 있는데, 이건 첫번째에서 네번째 값이 모두 “RL” 이라는 뜻입니다. 한편 MSZoning에 값이 “RL”만 있는 건 아닙니다. 범주형 변수들이 어떤 값들을 가지고 있는지 확인하고 싶다면 lapply()와 unique()를 합친 str()을 써주면 됩니다.

    str(lapply(train, unique))
    ## List of 81
    ##  $ Id           : int [1:1460] 1 2 3 4 5 6 7 8 9 10 ...
    ##  $ MSSubClass   : int [1:15] 60 20 70 50 190 45 90 120 30 85 ...
    ##  $ MSZoning     : chr [1:5] "RL" "RM" "C (all)" "FV" ...
    ##  $ LotFrontage  : int [1:111] 65 80 68 60 84 85 75 NA 51 50 ...
    ##  $ LotArea      : int [1:1073] 8450 9600 11250 9550 14260 14115 10084 10382 6120 7420 ...
    ##  $ Street       : chr [1:2] "Pave" "Grvl"
    ##  $ Alley        : chr [1:3] NA "Grvl" "Pave"
    ##   ( 중략 )
    ##  $ SalePrice    : int [1:663] 208500 181500 223500 140000 250000 143000 307000 200000 129900 118000 ...

    일반적인 str()을 썼을 때와 조금 다르지만 적어도 변주형 변수만큼은 깔끔하게 보여주는 느낌입니다. MSZoning의 카테고리가 5개이고 “RL” “RM” “C (all)” “FV” …  로 나타나는 것을 확인할 수가 있습니다. 기호에 맞게 주어진 함수를 사용하면서 살펴보시면 됩니다.

    숫자형 변수의 경우 4분위와 중앙값을 보여주는 요약 통계량은 summary()를 사용합니다.

    summary(train)
    ##        Id           MSSubClass      MSZoning          LotFrontage    
    ##  Min.   :   1.0   Min.   : 20.0   Length:1460        Min.   : 21.00  
    ##  1st Qu.: 365.8   1st Qu.: 20.0   Class :character   1st Qu.: 59.00  
    ##  Median : 730.5   Median : 50.0   Mode  :character   Median : 69.00  
    ##  Mean   : 730.5   Mean   : 56.9                      Mean   : 70.05  
    ##  3rd Qu.:1095.2   3rd Qu.: 70.0                      3rd Qu.: 80.00  
    ##  Max.   :1460.0   Max.   :190.0                      Max.   :313.00  
    ##                                                      NA's   :259     
    ##     LotArea          Street             Alley             LotShape        
    ##  Min.   :  1300   Length:1460        Length:1460        Length:1460       
    ##  1st Qu.:  7554   Class :character   Class :character   Class :character  
    ##  Median :  9478   Mode  :character   Mode  :character   Mode  :character  
    ##  Mean   : 10517                                                           
    ##  3rd Qu.: 11602                                                           
    ##  Max.   :215245                                                           
    ##
    ##   ( 이하 생략 )                                                                                                     
    ##
    ##    SalePrice     
    ##  Min.   : 34900  
    ##  1st Qu.:129975  
    ##  Median :163000  
    ##  Mean   :180921  
    ##  3rd Qu.:214000  
    ##  Max.   :755000  
    ## 

    데이터를 조금 떼어서 보는 건 잘 알고 있듯이 head()와 tail()이 있습니다. 숫자와 함께 쓰면 row값을 조절해서 사용할 수 있습니다. dplyr의 파이프 연산자와 함께 사용할 경우도 마찬가지 입니다.

    head(train, 10)
    ##    Id MSSubClass MSZoning LotFrontage LotArea Street Alley LotShape LandContour
    ## 1   1         60       RL          65    8450   Pave  <NA>      Reg         Lvl
    ## 2   2         20       RL          80    9600   Pave  <NA>      Reg         Lvl
    ## 3   3         60       RL          68   11250   Pave  <NA>      IR1         Lvl
    ## 4   4         70       RL          60    9550   Pave  <NA>      IR1         Lvl
    ## 5   5         60       RL          84   14260   Pave  <NA>      IR1         Lvl
    ## 6   6         50       RL          85   14115   Pave  <NA>      IR1         Lvl
    ## 7   7         20       RL          75   10084   Pave  <NA>      Reg         Lvl
    ## 8   8         60       RL          NA   10382   Pave  <NA>      IR1         Lvl
    ## 9   9         50       RM          51    6120   Pave  <NA>      Reg         Lvl
    ## 10 10        190       RL          50    7420   Pave  <NA>      Reg         Lvl
    ##    Utilities LotConfig LandSlope Neighborhood Condition1 Condition2 BldgType
    ## 1     AllPub    Inside       Gtl      CollgCr       Norm       Norm     1Fam
    ## 2     AllPub       FR2       Gtl      Veenker      Feedr       Norm     1Fam
    ## 3     AllPub    Inside       Gtl      CollgCr       Norm       Norm     1Fam
    ## 4     AllPub    Corner       Gtl      Crawfor       Norm       Norm     1Fam
    ## 5     AllPub       FR2       Gtl      NoRidge       Norm       Norm     1Fam
    ## 6     AllPub    Inside       Gtl      Mitchel       Norm       Norm     1Fam
    ## 7     AllPub    Inside       Gtl      Somerst       Norm       Norm     1Fam
    ## 8     AllPub    Corner       Gtl       NWAmes       PosN       Norm     1Fam
    ## 9     AllPub    Inside       Gtl      OldTown     Artery       Norm     1Fam
    ## 10    AllPub    Corner       Gtl      BrkSide     Artery     Artery   2fmCon
    ##  ( 이하 생략 )
    # ==
    # train %>% 
    #   head(10)

     

    이제 dplyr과 ggplot2을 통해 시각화를 다루며 사과를 쪼개는 느낌의 EDA입니다. 먼저 가볍게 데이터의 종속변수가 되는 SalePrice의 분포가 어떻게 되는지 그려봅니다.

    train %>%
      ggplot(aes(x = SalePrice,
                 y = ..density..)) +
      geom_histogram(fill = 'steelblue',
                     alpha = 0.8) +
      stat_density(geom = 'line',
                   size = 0.5) +
      theme_minimal()

    부동산 연식에 따른 부동산 가격의 분포는 어떻게 되는지 그려봅니다.

    train %>% 
      ggplot(aes(x = YearBuilt,
                 y = SalePrice)) +
      geom_point() + 
      geom_smooth(method = 'lm',formula = y ~ poly(x,2))

    그런데 생각해보니 그려볼 수 있는 경우의 수가 너무 많습니다. 81개의 변수를 일일이 또는 조합해서 그려본다는 건 분명 한계입니다. 그래서 중요한 게 질문을 통해 데이터를 살펴보는 것입니다. 데이터 설명서를 읽어보면서 주제에 맞거나 원하는 질문을 뽑아낼 수 있겠습니다. 그러고 나면 숨겨진 의미를 찾아보는 거죠.

    이렇게 한번 해보겠습니다. 데이터에 지역 정보를 담고 있는 Neighborhood 변수가 있습니다. 지역에 따른 부동산 가격의 차이는 어떻게 발생하는지 궁금하니 박스플롯을 그려보도록 합니다.

    train %>% 
      ggplot(aes(x = reorder(Neighborhood, -SalePrice),
                 y = SalePrice,
                 group = Neighborhood,
                 fill = Neighborhood)) +
      geom_boxplot(size = 0.5) + 
      theme(axis.text.x = element_text(angle = 60, 
                                       hjust = 1),
            legend.position = 'none') +
      xlab('Neighborhood')

    왜 이런 차이가 나타날까요? 이유를 찾기 위해 구글맵을 통해 지역별 차이를 살펴보았습니다. 그 결과 평균 부동산 가격 상위 3개 지역인 노스리지, 노스리지 헤이츠, 스톤브릿지는 모두 사립학교 인접 지역이라는 사실을 확인할 수 있었습니다. 사립학교 인접지역이라는 요인이 부동산 가격에 영향을 미칠 수 있는 것이죠.

    새로운 사실을 알았습니다. 그러면 이제 어떻게 할까요? 대회 점수가 목적이라면 Feature Engineer ing 단계에서 사립학교 인접지역 여부를 묻는 ArroundPrivateSchool 변수를 추가해서 모델링의 정확도를 높일 수 있습니다. 지역에 방문판매를 나가는게 목적이라면 아이들이 좋아할만한 레고 같은 장난감을 사은품으로 준비하는 것이 고객의 만족도를 높이일 수 있는 방법이 될 수도 있습니다. 인사이트에 대한 대응 방식이 기업들의 비즈니스 모델에 따라 달라지겠네요.

    지금까지 데이터를 살펴보고 질문하고 답을 찾는 과정이었습니다. EDA를 진행하며 인사이트를 찾는다는 건 이러한 과정의 반복이라고 생각하시면 되겠습니다. 체계적으로 구분하지 않고 큰 흐름에서의 EDA를 진행했으니, 부족한 부분이 있더라도 쉽게 설명하기 위해 노력했다는 점을 이해해주시면 감사하겠읍니다.

    :)

     

Designed by Tistory.