library(tidyverse)
## -- Attaching packages ---------------------------------- tidyverse 1.2.1 --
## √ ggplot2 3.1.0     √ purrr   0.2.5
## √ tibble  1.4.2     √ dplyr   0.7.8
## √ tidyr   0.8.2     √ stringr 1.3.1
## √ readr   1.1.1     √ forcats 0.3.0
## -- Conflicts ------------------------------------- tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
library(haven)
library(readxl)

변수관리-1

2-1 변수변환:결측값 처리 및 리코딩

누구나 한번쯤은 이런말을 많이 들어보았을 것입니다. 데이터 분석을 할때 데이터 전처리에 80%이상 시간을 소요하고 분석은 20%정도의 시간을 소요하게 된다. 프로젝트를 경험해본 사람이라면 이 말에 동의를 할 것입니다. 지금까지 데이터 관리 라는 목적으로 공부를 했습니다. 숲을 공부했다면 지금 부터는 나무를 공부할 것입니다. 변수를 어떻게 관리하고 바꾸고 이런 일련의 과정들을 해볼것입니다. 가장 중요한 부분입니다. 타이디버스 라이브러리 접근에서 데이터 내의 변수를 변환하거나 데이터 내에 새로운 변수를 추가하려면 ’mutate()’함수를 사용합니다. 두 파트로 구성이 되어 있는데 첫번째는 변수에서 결측값을 확인하고 처리하는 방법을 다룰 것입니다. 두번째는 분석자가 원하는 방식으로 변수를 리코딩하는 것입니다.

먼저 결측치를 확인하고 처리하는 방법에 대해 알아보겠습니다.

앞에서도 언급한적이 있습니다. 변수값 중 결측값을 판정하는 함수는 is.na()함수 입니다. 변수의 결측값이 있다면 T를 없다면 F를 반환합니다. 우선 mutate()함수를 이용해 변수값이 실측값인지 결측값인지를 확인할 수 있는 새로운 파생변수를 만든 후에 빈도표를 만들겠습니다.

small_gss<-read_dta("data_gss_panel06.dta") %>% select(starts_with("affrmact_"))
small_gss %>% print(n=3)
## # A tibble: 2,000 x 3
##   affrmact_1 affrmact_2 affrmact_3
##   <dbl+lbl>  <dbl+lbl>  <dbl+lbl> 
## 1 NA         NA         NA        
## 2 " 1"       " 3"       " 4"      
## 3 NA         NA         NA        
## # ... with 1,997 more rows

결측값이 상당히 많이 있습니다. 우선 affrmact_1변수에서 결측값 여부를 나타내는 새로운 이름의 affrmact_NA_1변수를 만든후에 빈도표를 만들겠습니다.

small_gss2=small_gss %>% mutate(affrmact_NA_1=is.na(affrmact_1)) %>% print(n=10)
## Warning: package 'bindrcpp' was built under R version 3.4.4
## # A tibble: 2,000 x 4
##    affrmact_1 affrmact_2 affrmact_3 affrmact_NA_1
##    <dbl+lbl>  <dbl+lbl>  <dbl+lbl>  <lgl>        
##  1 NA         NA         NA         TRUE         
##  2 " 1"       " 3"       " 4"       FALSE        
##  3 NA         NA         NA         TRUE         
##  4 " 1"       " 3"       " 4"       FALSE        
##  5 NA         NA         NA         TRUE         
##  6 " 3"       " 3"       " 1"       FALSE        
##  7 " 4"       NA         " 3"       FALSE        
##  8 " 3"       NA         NA         FALSE        
##  9 " 3"       " 3"       " 3"       FALSE        
## 10 NA         NA         NA         TRUE         
## # ... with 1,990 more rows
small_gss2 %>% count(affrmact_NA_1)
## # A tibble: 2 x 2
##   affrmact_NA_1     n
##   <lgl>         <int>
## 1 FALSE          1251
## 2 TRUE            749

이번에는 세 변수들(affrmact_)에서 발견된 결측값의 총 합을 구해보겠습니다.

small_gss2=small_gss %>% mutate(n.NA=is.na(affrmact_1)+is.na(affrmact_2)+is.na(affrmact_3))
small_gss2 %>% count(n.NA)
## # A tibble: 4 x 2
##    n.NA     n
##   <int> <int>
## 1     0   752
## 2     1   214
## 3     2   330
## 4     3   704

만약 변수가 10개라면 간단하게 rowSums()함수를 이용하면 됩니다. 참고로 is.na(.)여기서 .의 경우 앞의 데이터를 그대로 사용한다는 뜻입니다.

small_gss2=small_gss %>% mutate(t.NA=rowSums(is.na(.))) %>% print(n=10)
## # A tibble: 2,000 x 4
##    affrmact_1 affrmact_2 affrmact_3  t.NA
##    <dbl+lbl>  <dbl+lbl>  <dbl+lbl>  <dbl>
##  1 NA         NA         NA             3
##  2 " 1"       " 3"       " 4"           0
##  3 NA         NA         NA             3
##  4 " 1"       " 3"       " 4"           0
##  5 NA         NA         NA             3
##  6 " 3"       " 3"       " 1"           0
##  7 " 4"       NA         " 3"           1
##  8 " 3"       NA         NA             2
##  9 " 3"       " 3"       " 3"           0
## 10 NA         NA         NA             3
## # ... with 1,990 more rows
small_gss2 %>% count(t.NA) #위의 결과와 같음을 볼 수 있습니다.
## # A tibble: 4 x 2
##    t.NA     n
##   <dbl> <int>
## 1     0   752
## 2     1   214
## 3     2   330
## 4     3   704

지금 본 small_gss데이터의 경우 결측값에 대해 NA가 써있습니다. 만약 실측값을 결측값으로 변환해야하는 경우가 생깁니다. 그럴 경우 어떻게 코딩을 할지 생각해봅시다. “data_TESS3_131.sav” 데이터에서 Q2변수를 보겠습니다.

data_131=read_spss("data_TESS3_131.sav")
data_131 %>% count(Q2)
## # A tibble: 8 x 2
##   Q2            n
##   <dbl+lbl> <int>
## 1 -1            5
## 2 " 1"        143
## 3 " 2"         71
## 4 " 3"         82
## 5 " 4"        107
## 6 " 5"         80
## 7 " 6"         40
## 8 " 7"         65
print_labels(data_131$Q2)
## 
## Labels:
##  value                      label
##     -1                    Refused
##      1            Strongly oppose
##      2          Moderately oppose
##      3            Somewhat oppose
##      4 Neither oppose nor support
##      5           Somewhat support
##      6         Moderately support
##      7           Strongly support

-1의 경우 응답을 거절했습니다. 이럴 경우에 결측값으로 바꿔서 분석을하는 것이 용이합니다. ifelse()함수문을 이용해 바꾸어 보겠습니다.

data_131 %>% mutate(Q2r=ifelse(Q2==-1,NA,Q2)) %>% count(Q2r)
## # A tibble: 8 x 2
##     Q2r     n
##   <dbl> <int>
## 1     1   143
## 2     2    71
## 3     3    82
## 4     4   107
## 5     5    80
## 6     6    40
## 7     7    65
## 8    NA     5

만약에 -1 혹은 4를 결측값으로 지정하고 싶다면 다음과 같이 표현하면 됩니다.

data_131 %>% mutate(Q2r=ifelse(Q2==-1|Q2==4,NA,Q2)) %>% count(Q2r)
## # A tibble: 7 x 2
##     Q2r     n
##   <dbl> <int>
## 1     1   143
## 2     2    71
## 3     3    82
## 4     5    80
## 5     6    40
## 6     7    65
## 7    NA   112

이번엔 숫자를 결측치로 바꾸는 것이 아닌 ’-’로 입력된 것을 결측치로 바꾸는 작업을 해보겠습니다.

seoul_library=read_xls("data_library.xls")
seoul_library %>% print(n=10)
## # A tibble: 182 x 7
##    기간  자치구      계 국립도서관 공공도서관 대학도서관 전문도서관
##    <chr> <chr>    <dbl> <chr>           <dbl> <chr>      <chr>     
##  1 2010  합계       464 3                 101 85         275       
##  2 2010  종로구      50 -                   4 9          37        
##  3 2010  중구        57 -                   2 2          53        
##  4 2010  용산구      18 -                   3 2          13        
##  5 2010  성동구       6 -                   4 2          -         
##  6 2010  광진구       9 -                   3 3          3         
##  7 2010  동대문구    17 -                   2 5          10        
##  8 2010  중랑구       4 -                   2 1          1         
##  9 2010  성북구      16 -                   3 8          5         
## 10 2010  강북구      10 -                   5 3          2         
## # ... with 172 more rows

다음과 같이 ’-’표시는 결측치로 인식을 못합니다. 따라서 인식을 시켜줘야합니다.

seoul_library2=seoul_library %>% mutate(계=ifelse(계=='-',NA,계),
                                         국립도서관=ifelse(국립도서관=='-',NA,국립도서관),
                                         공공도서관=ifelse(공공도서관=='-',NA,공공도서관),
                                         대학도서관=ifelse(대학도서관=='-',NA,대학도서관),
                                         전문도서관=ifelse(전문도서관=='-',NA,전문도서관)) %>% print(n=10)
## # A tibble: 182 x 7
##    기간  자치구      계 국립도서관 공공도서관 대학도서관 전문도서관
##    <chr> <chr>    <dbl> <chr>           <dbl> <chr>      <chr>     
##  1 2010  합계       464 3                 101 85         275       
##  2 2010  종로구      50 <NA>                4 9          37        
##  3 2010  중구        57 <NA>                2 2          53        
##  4 2010  용산구      18 <NA>                3 2          13        
##  5 2010  성동구       6 <NA>                4 2          <NA>      
##  6 2010  광진구       9 <NA>                3 3          3         
##  7 2010  동대문구    17 <NA>                2 5          10        
##  8 2010  중랑구       4 <NA>                2 1          1         
##  9 2010  성북구      16 <NA>                3 8          5         
## 10 2010  강북구      10 <NA>                5 3          2         
## # ... with 172 more rows
#이번엔 제대로 결측치가 나온 모습을 볼 수 있습니다.
colSums(is.na(seoul_library2))
##       기간     자치구         계 국립도서관 공공도서관 대학도서관 
##          0          0          0        161          0          7 
## 전문도서관 
##         14

만약에 변수가 10개가 있다면 이와 같은 반복작업을 해야할까요?? 여러변수들에 대해 반복되는 작업을 해야할 경우 mutate_all()함수를 사용하면 편리합니다.

seoul_library %>% mutate_all(funs(ifelse(.=='-',NA,.))) %>% print(n=10)
## # A tibble: 182 x 7
##    기간  자치구      계 국립도서관 공공도서관 대학도서관 전문도서관
##    <chr> <chr>    <dbl> <chr>           <dbl> <chr>      <chr>     
##  1 2010  합계       464 3                 101 85         275       
##  2 2010  종로구      50 <NA>                4 9          37        
##  3 2010  중구        57 <NA>                2 2          53        
##  4 2010  용산구      18 <NA>                3 2          13        
##  5 2010  성동구       6 <NA>                4 2          <NA>      
##  6 2010  광진구       9 <NA>                3 3          3         
##  7 2010  동대문구    17 <NA>                2 5          10        
##  8 2010  중랑구       4 <NA>                2 1          1         
##  9 2010  성북구      16 <NA>                3 8          5         
## 10 2010  강북구      10 <NA>                5 3          2         
## # ... with 172 more rows

만약 데이터에서 원하는 변수만 선정하고 싶다면 mutate_at()함수를 이용하면 됩니다.

seoul_library %>% mutate_at(3:7,funs(ifelse(.=='-',NA,.))) %>% print(n=10)
## # A tibble: 182 x 7
##    기간  자치구      계 국립도서관 공공도서관 대학도서관 전문도서관
##    <chr> <chr>    <dbl> <chr>           <dbl> <chr>      <chr>     
##  1 2010  합계       464 3                 101 85         275       
##  2 2010  종로구      50 <NA>                4 9          37        
##  3 2010  중구        57 <NA>                2 2          53        
##  4 2010  용산구      18 <NA>                3 2          13        
##  5 2010  성동구       6 <NA>                4 2          <NA>      
##  6 2010  광진구       9 <NA>                3 3          3         
##  7 2010  동대문구    17 <NA>                2 5          10        
##  8 2010  중랑구       4 <NA>                2 1          1         
##  9 2010  성북구      16 <NA>                3 8          5         
## 10 2010  강북구      10 <NA>                5 3          2         
## # ... with 172 more rows

또한 mutate_at()함수 내부에는 변수의 이름들을 명시할 수 있습니다. 다음과 같이 vars()함수 내부에 나열하면 됩니다.

seoul_library %>% mutate_at(vars(계,ends_with("도서관")),funs(ifelse(.=="-",NA,.))) %>% print(n=3)
## # A tibble: 182 x 7
##   기간  자치구    계 국립도서관 공공도서관 대학도서관 전문도서관
##   <chr> <chr>  <dbl> <chr>           <dbl> <chr>      <chr>     
## 1 2010  합계     464 3                 101 85         275       
## 2 2010  종로구    50 <NA>                4 9          37        
## 3 2010  중구      57 <NA>                2 2          53        
## # ... with 179 more rows

다음 mutate_if()함수는 변수의 성격이 특정한 조건에 부합할 경우 주어진 함수에 맞게 변수를 변환하는 함수입니다. 위의 ’계’변수와 ’도서관’변수는 다 변수로 되어있는데 수치형 변수로 바꾸는 것이 합리적으로 보입니다.

seoul_library %>% mutate(국립도서관=ifelse(국립도서관=="-",NA,국립도서관),
                              국립도서관=as.integer(국립도서관)) %>% print(n=3)
## # A tibble: 182 x 7
##   기간  자치구    계 국립도서관 공공도서관 대학도서관 전문도서관
##   <chr> <chr>  <dbl>      <int>      <dbl> <chr>      <chr>     
## 1 2010  합계     464          3        101 85         275       
## 2 2010  종로구    50         NA          4 9          37        
## 3 2010  중구      57         NA          2 2          53        
## # ... with 179 more rows

위의 방식대로 10개의 변수를 한다면 어떨까요? 마찬가지로 mutate_at변수를 이용해 간소화 시켜보겠습니다.

seoul_library %>% mutate_at(vars(3:7),funs(as.integer(ifelse(.=="-",NA,.)))) %>% print(n=3)
## # A tibble: 182 x 7
##   기간  자치구    계 국립도서관 공공도서관 대학도서관 전문도서관
##   <chr> <chr>  <int>      <int>      <int>      <int>      <int>
## 1 2010  합계     464          3        101         85        275
## 2 2010  종로구    50         NA          4          9         37
## 3 2010  중구      57         NA          2          2         53
## # ... with 179 more rows

지금까지 배운것을 종합해서 적용시켜보겠습니다. 1. “data_library.xls”데이터를 불러온 후, 자치구 변수의 값이 ‘합계’인 경우는 제외 2.’-’기호가 들어가면 결측치 !! 3. 도서관 종류에 해당되는 변수는 모두 수치형으로 전환(double로 전환) 4. 연도별 25개 서울시 구의 모든 도서관의 총계 변화를 나타내는 통계치를 나타내기

read_xls("data_library.xls") %>% filter(자치구!="합계") %>% 
  mutate_at(vars(3:7),funs(as.double(ifelse(.=="-",NA,.)))) %>% 
  group_by(기간) %>% summarize_if(is.double,funs(sum(.,na.rm=T)))
## # A tibble: 7 x 6
##   기간     계 국립도서관 공공도서관 대학도서관 전문도서관
##   <chr> <dbl>      <dbl>      <dbl>      <dbl>      <dbl>
## 1 2010    464          3        101         85        275
## 2 2011    472          3        109         85        275
## 3 2012    479          3        116         85        275
## 4 2013    489          3        123         88        275
## 5 2014    491          3        132         88        268
## 6 2015    500          3        146         89        262
## 7 2016    502          3        147         88        264

1.상상해보기

“data_population.xls”를 열면 특정 연령대의 통계수치가 문자형로 입력되어 있는것을 알 수 있습니다. 문자형으로 입력된 통계수치의 경우 하이픈(-)이 입력되어있습니다. 해당 변수의 경우 하이픈(-)를 결측치(NA)로 바꾸세요

data=read_xls("data_population.xls") 

data %>% mutate_at(vars(22:25),funs(as.double(ifelse(.=="-",NA,.))))
## # A tibble: 312 x 25
##    기간  구분  구분__1 계    `0~4세` `5~9세` `10~14세` `15~19세` `20~24세`
##    <chr> <chr> <chr>   <chr>   <dbl>   <dbl>     <dbl>     <dbl>     <dbl>
##  1 2014  합계  계      10,3~  411132  393451    459224    594242    727181
##  2 2014  합계  한국인  10,1~  404369  389623    456867    587646    701179
##  3 2014  합계  등록외국인~ 266,~    6763    3828      2357      6596     26002
##  4 2014  종로구~ 계      165,~    4609    4991      6704      9372     12979
##  5 2014  종로구~ 한국인  156,~    4445    4886      6622      9004     11384
##  6 2014  종로구~ 등록외국인~ 8,351     164     105        82       368      1595
##  7 2014  중구  계      136,~    4806    4036      4268      6026      9379
##  8 2014  중구  한국인  128,~    4582    3874      4148      5754      8497
##  9 2014  중구  등록외국인~ 8,162     224     162       120       272       882
## 10 2014  용산구~ 계      249,~    9715    9094      9744     12054     15322
## # ... with 302 more rows, and 16 more variables: `25~29세` <dbl>,
## #   `30~34세` <dbl>, `35~39세` <dbl>, `40~44세` <dbl>, `45~49세` <dbl>,
## #   `50~54세` <dbl>, `55~59세` <dbl>, `60~64세` <dbl>, `65~69세` <dbl>,
## #   `70~74세` <dbl>, `75~79세` <dbl>, `80~84세` <dbl>, `85~89세` <dbl>,
## #   `90~94세` <dbl>, `95~99세` <dbl>, `100세 이상+` <dbl>

2.상상해보기

“data_country.xlsx”데이터를보면 어떤 국가의 경우 인구가 0으로 입력되어 있습니다. 0에는 NA로 부여해주세요

data2<-read_xlsx("data_country.xlsx") %>% mutate(POPULATION=ifelse(POPULATION==0,NA,POPULATION))

colSums(is.na(data2)) #단 하나 존재했었네요
##      COUNTRY COUNTRY CODE    ISO CODES   POPULATION     AREA KM2 
##            0            0            0            1            0 
##     GDP $USD 
##           19