Lab_04: 데이터 정돈하기

저자

이상일ㆍ박서우ㆍ김우형(서울대학교 지리교육과)

Modified

2025년 12월 30일

개요

데이터 정돈하기는 지저분한 데이터(messy data)를 정돈된 데이터(tidy data)로 만드는 과정을 의미한다. 정돈된 데이터는 다음의 세 가지 속성을 갖는다.

  • 개별 변수(variable)는 열(컬럼, column) 하나를 차지한다. 즉, 개별 열에는 하나의 변수가 위치한다.

  • 개별 관측개체(observation)는 하나의 행(로, row)을 차지한다. 즉, 개별 행에는 하나의 관측개체가 위치한다.

  • 개별 값(value)은 하나의 셀(cell)을 차지한다. 즉, 개별 셀에는 하나의 값이 위치한다.

이 세가지 속성 중 하나라도 위배하는 데이터는 정돈된 데이터가 아니다. 어떤 데이터이건 그것이 정돈된 데이터이기만 하다면, 표준적인 툴을 통해 해당 데이터를 다른 데이터와 동일한 방식으로 다룰 수 있다. 데이터 정돈하기에 특화된 패키지가 tidyverse 패키지의 핵심 패키지 중의 하나인 tidyr 패키지이다.

데이터 정돈하기는 다음의 세 가지 범주로 나뉜다.

  • 데이터 구조 변형: 가장 중요한 오퍼레이션으로 데이터 늘이기와 데이터 넓히기기가 포함된다.

  • 컬럼의 결합 및 분할: 두 개 이상의 컬럼을 하나의 컬럼으로 결합하거나 한 컬럼을 두 개 이상의 컬럼으로 분할한다.

  • 결측치 처리: 결측치가 포함된 행을 다양한 방식으로 처리한다.

우선 tidyverse 패키지를 불러온다.

1 데이터 구조 변형

여기서는 데이터 늘이기(lengthening data)와 데이터 넓히기(widening data)에 집중한다. 데이터 늘이기는 행을 늘이는 방식으로 데이터 구조를 변형하는 것이고, 데이터 넓히기는 컬럼의 숫자를 늘이는 방식으로 데이터 구조를 변형하는 것이다.

1.1 데이터 늘이기(longer)

실습을 위해 tidyverse 패키지에 포함되어 있는 billboard 데이터를 사용한다. 이 데이터셋에는 2000년 한해 동안 노래의 주별 순위 정보가 포함되어 있다.

billboard
# A tibble: 317 × 79
   artist     track date.entered   wk1   wk2   wk3   wk4   wk5   wk6   wk7   wk8
   <chr>      <chr> <date>       <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1 2 Pac      Baby… 2000-02-26      87    82    72    77    87    94    99    NA
 2 2Ge+her    The … 2000-09-02      91    87    92    NA    NA    NA    NA    NA
 3 3 Doors D… Kryp… 2000-04-08      81    70    68    67    66    57    54    53
 4 3 Doors D… Loser 2000-10-21      76    76    72    69    67    65    55    59
 5 504 Boyz   Wobb… 2000-04-15      57    34    25    17    17    31    36    49
 6 98^0       Give… 2000-08-19      51    39    34    26    26    19     2     2
 7 A*Teens    Danc… 2000-07-08      97    97    96    95   100    NA    NA    NA
 8 Aaliyah    I Do… 2000-01-29      84    62    51    41    38    35    35    38
 9 Aaliyah    Try … 2000-03-18      59    53    38    28    21    18    16    14
10 Adams, Yo… Open… 2000-08-26      76    76    74    69    68    67    61    58
# ℹ 307 more rows
# ℹ 68 more variables: wk9 <dbl>, wk10 <dbl>, wk11 <dbl>, wk12 <dbl>,
#   wk13 <dbl>, wk14 <dbl>, wk15 <dbl>, wk16 <dbl>, wk17 <dbl>, wk18 <dbl>,
#   wk19 <dbl>, wk20 <dbl>, wk21 <dbl>, wk22 <dbl>, wk23 <dbl>, wk24 <dbl>,
#   wk25 <dbl>, wk26 <dbl>, wk27 <dbl>, wk28 <dbl>, wk29 <dbl>, wk30 <dbl>,
#   wk31 <dbl>, wk32 <dbl>, wk33 <dbl>, wk34 <dbl>, wk35 <dbl>, wk36 <dbl>,
#   wk37 <dbl>, wk38 <dbl>, wk39 <dbl>, wk40 <dbl>, wk41 <dbl>, wk42 <dbl>, …

관측개체는 개별 노래이며 앞의 세 변수(artist, track, data.entered)는 노래 관련 속성이고, 나머지 76개 변수(wk1~wk76)는 76주간 개별 노래의 순위를 나타낸다. ’몇 번째 주인가’는 변수일 수 없으므로 week라는 변수를 생성하여 개별주가 변수값이 되도록 데이터를 변형할 필요가 있다.

billboard |> 
  pivot_longer(
    cols = starts_with("wk"), 
    names_to = "week", 
    values_to = "rank"
  )
# A tibble: 24,092 × 5
   artist track                   date.entered week   rank
   <chr>  <chr>                   <date>       <chr> <dbl>
 1 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk1      87
 2 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk2      82
 3 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk3      72
 4 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk4      77
 5 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk5      87
 6 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk6      94
 7 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk7      99
 8 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk8      NA
 9 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk9      NA
10 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk10     NA
# ℹ 24,082 more rows

cols는 새로 생성될 변수의 변수값이 되어야 할 현 데이터셋의 변수들을 지정한다. names_tocols를 통해 지정된 변수명이 변수값으로 들어갈 새로 운 변수의 이름을 지정한다. values_tocols 변수들의 변수값들이 들어갈 새로운 변수의 이름을 지정한다.

그런데, 좀 더 복잡한 데이터 늘이기의 상황이 있을 수 있다. tidyverse 패키지에 포함되어 있는 who2 데이터를 사용한다. 이것은 WHO(world Health Organization, 세계보건기구)에서 제공한 데이터로서 1980~2013년 전세계 209개국의 결핵 환자수에 대한 데이터이다.

glimpse(who2)
Rows: 7,240
Columns: 58
$ country    <chr> "Afghanistan", "Afghanistan", "Afghanistan", "Afghanistan",…
$ year       <dbl> 1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989,…
$ sp_m_014   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sp_m_1524  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sp_m_2534  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sp_m_3544  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sp_m_4554  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sp_m_5564  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sp_m_65    <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sp_f_014   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sp_f_1524  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sp_f_2534  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sp_f_3544  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sp_f_4554  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sp_f_5564  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sp_f_65    <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_m_014   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_m_1524  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_m_2534  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_m_3544  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_m_4554  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_m_5564  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_m_65    <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_f_014   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_f_1524  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_f_2534  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_f_3544  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_f_4554  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_f_5564  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ sn_f_65    <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_m_014   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_m_1524  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_m_2534  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_m_3544  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_m_4554  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_m_5564  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_m_65    <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_f_014   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_f_1524  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_f_2534  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_f_3544  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_f_4554  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_f_5564  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ep_f_65    <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_m_014  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_m_1524 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_m_2534 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_m_3544 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_m_4554 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_m_5564 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_m_65   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_f_014  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_f_1524 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_f_2534 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_f_3544 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_f_4554 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_f_5564 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ rel_f_65   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…

countryyear은 분명한 변수이지만 나머지 56개 변수는 무엇인지 알 수 없다. 그런데 잘 살펴보면, 변수명에 어떤 패턴이 있다는 것을 알 수 있다. 모두 세 부분으로 나뉘어져 있는데, 첫 번째 부분은 진단법(sp, rel, ep)과 관련되어 있고, 두 번째 부분은 성(m, f)과 관련되어 있고, 세 번째 부분은 연령(014, 1524, 2534, 3544, 4554, 5564, 65)과 관련되어 있다. 56개 변수의 셀 값은 모두 케이스(환자수)를 의미한다. 이 지저분한 데이터를 pivot_longer() 함수를 이용해 정돈된 데이터로 만들어 본다.

who2 |> 
  pivot_longer(
    cols = !c(country, year),
    names_to = c("diagnosis", "gender", "age"), 
    names_sep = "_",
    values_to = "count"
  )
# A tibble: 405,440 × 6
   country      year diagnosis gender age   count
   <chr>       <dbl> <chr>     <chr>  <chr> <dbl>
 1 Afghanistan  1980 sp        m      014      NA
 2 Afghanistan  1980 sp        m      1524     NA
 3 Afghanistan  1980 sp        m      2534     NA
 4 Afghanistan  1980 sp        m      3544     NA
 5 Afghanistan  1980 sp        m      4554     NA
 6 Afghanistan  1980 sp        m      5564     NA
 7 Afghanistan  1980 sp        m      65       NA
 8 Afghanistan  1980 sp        f      014      NA
 9 Afghanistan  1980 sp        f      1524     NA
10 Afghanistan  1980 sp        f      2534     NA
# ℹ 405,430 more rows

각 인수의 역할에 대해 이해하는 것이 중요하다. 그리고 정돈된 데이터를 만들기 위해 왜 데이터 ’늘이기’를 해야하는지 생각해 본다.

이것보다 좀 더 복잡한 데이터 늘이기의 상황이 있을 수 있다. 간단한 예시인 household 데이터를 살펴보자.

household
# A tibble: 5 × 5
  family dob_child1 dob_child2 name_child1 name_child2
   <int> <date>     <date>     <chr>       <chr>      
1      1 1998-11-26 2000-01-29 Susan       Jose       
2      2 1996-06-22 NA         Mark        <NA>       
3      3 2002-07-11 2004-04-05 Sam         Seth       
4      4 2004-10-10 2009-08-27 Craig       Khai       
5      5 2000-12-05 2005-02-28 Parker      Gracie     

자세히 살펴보면 변수명에 두 개의 변수(dob, name)와 또 다른 변수(child)의 두 숫자(1, 2)가 포함되어 있다. 각 family별로 최대 두 명까지의 자녀가 있고 각 자녀별로 생년월일과 이름에 대한 정보가 포함되어 있다. 첫 행은 첫 번째 가정의 첫 번째 자녀의 생년월일은 1998년 11월 26일이고 이름은 Susan이며, 두 번째 자녀의 생년월일은 2000년 1월 29일이고 이름은 Jose이다. 분리자인 “_”이 존재하므로 dobname은 두 변수로 분리하고, 첫번째 자녀인지 두번째 자녀인지를 알려주는 또 다른 변수를 생성해야 한다. 이를 위해 .value라고 하는 특별한 것을 사용한다.

household |> 
  pivot_longer(
    cols = !family, 
    names_to = c(".value", "child"), 
    names_sep = "_", 
    values_drop_na = TRUE
  )
# A tibble: 9 × 4
  family child  dob        name  
   <int> <chr>  <date>     <chr> 
1      1 child1 1998-11-26 Susan 
2      1 child2 2000-01-29 Jose  
3      2 child1 1996-06-22 Mark  
4      3 child1 2002-07-11 Sam   
5      3 child2 2004-04-05 Seth  
6      4 child1 2004-10-10 Craig 
7      4 child2 2009-08-27 Khai  
8      5 child1 2000-12-05 Parker
9      5 child2 2005-02-28 Gracie

names_to = c(".value", "child")names_sep = "_"는 네 개의 변수(dob_child1, dob_child2, name_child1, name_child2)의 이름을 크게 두 부분으로 분할하여 앞 부분(dobname)을 새로운 변수로 생성하고, 뒷 부분(child1, child2)는 child라는 변수를 새로 생성하여 그것은 변수값으로 전환한다. 결국 변수명의 일부는 새로운 변수명이 되고, 또 다른 일부는 변수값이 되는 것이다. 이렇게 되면 values_to 아규먼트가 필요없게 된다. 마지막에 있는 values_drop_na 아규먼트도 중요한 역할을 하는데, 두 번째 가정은 한 자녀만을 가지고 있기 때문에 결측값이 포함되어 있는데 그것을 최종 결과에 포함시키지 않는 일을 한다.

1.2 데이터 넓히기(wider)

실습을 위해 tidyverse 패키지에 포함되어 있는 cms_patient_experience 데이터를 사용한다. 이것은 미국의 Centers of Medicare and Meicaid Services가 제공한 데이터이다.

cms_patient_experience
# A tibble: 500 × 5
   org_pac_id org_nm                           measure_cd measure_title prf_rate
   <chr>      <chr>                            <chr>      <chr>            <dbl>
 1 0446157747 USC CARE MEDICAL GROUP INC       CAHPS_GRP… CAHPS for MI…       63
 2 0446157747 USC CARE MEDICAL GROUP INC       CAHPS_GRP… CAHPS for MI…       87
 3 0446157747 USC CARE MEDICAL GROUP INC       CAHPS_GRP… CAHPS for MI…       86
 4 0446157747 USC CARE MEDICAL GROUP INC       CAHPS_GRP… CAHPS for MI…       57
 5 0446157747 USC CARE MEDICAL GROUP INC       CAHPS_GRP… CAHPS for MI…       85
 6 0446157747 USC CARE MEDICAL GROUP INC       CAHPS_GRP… CAHPS for MI…       24
 7 0446162697 ASSOCIATION OF UNIVERSITY PHYSI… CAHPS_GRP… CAHPS for MI…       59
 8 0446162697 ASSOCIATION OF UNIVERSITY PHYSI… CAHPS_GRP… CAHPS for MI…       85
 9 0446162697 ASSOCIATION OF UNIVERSITY PHYSI… CAHPS_GRP… CAHPS for MI…       83
10 0446162697 ASSOCIATION OF UNIVERSITY PHYSI… CAHPS_GRP… CAHPS for MI…       63
# ℹ 490 more rows

이 데이터도 정돈된 데이터가 아니다. 자세히 살펴보면 다음과 같은 사실을 알 수 있다.

  • org_pac_idorg_nm 변수는 의료조직의 식별자와 이름이다.

  • 의료조직별로 6개씩의 열을 차지하고 있는데, 6개의 열은 measure_cdmeasure_title에 나타나 있는 것과 같은 6개의 서로 다른 조사 항목을 나타낸다. (ex. CAHPS_GRP_1은 환자의 경험 지표중 진료 예약 정보 제공 점수)

  • 마지막의 prf_rate는 조사 항목별 점수이다.

pivot_wider() 함수를 이용하여, 행에는 개별 의료조직이, 열에는 개별 조사 항목이 나타나는 정돈된 데이터를 만들어 본다.

cms_patient_experience |> 
  pivot_wider(
    id_cols = starts_with("org"),
    names_from = measure_cd,
    values_from = prf_rate
  )
# A tibble: 95 × 8
   org_pac_id org_nm CAHPS_GRP_1 CAHPS_GRP_2 CAHPS_GRP_3 CAHPS_GRP_5 CAHPS_GRP_8
   <chr>      <chr>        <dbl>       <dbl>       <dbl>       <dbl>       <dbl>
 1 0446157747 USC C…          63          87          86          57          85
 2 0446162697 ASSOC…          59          85          83          63          88
 3 0547164295 BEAVE…          49          NA          75          44          73
 4 0749333730 CAPE …          67          84          85          65          82
 5 0840104360 ALLIA…          66          87          87          64          87
 6 0840109864 REX H…          73          87          84          67          91
 7 0840513552 SCL H…          58          83          76          58          78
 8 0941545784 GRITM…          46          86          81          54          NA
 9 1052612785 COMMU…          65          84          80          58          87
10 1254237779 OUR L…          61          NA          NA          65          NA
# ℹ 85 more rows
# ℹ 1 more variable: CAHPS_GRP_12 <dbl>

이제 어떤 의료조직이 어떤 항목에서 얼마의 점수를 받았는지를 일목요연하게 알아 볼 수 있다. id_cols는 각 행의 완전한 고유성을 위해 필요한 모든 변수를 지정한다. 사례의 경우 org가 들어가는 org_pac_idorg_nm이 그 역할을 하는데, 사실 둘은 판별자로서 동일한 것이기 때문에 둘 중 하나만 사용해도 된다. 그러나 그러면 최종 산출물에서 지정되지 않은 컬럼은 나타나지 않기 때문에 동일한 기능을 하더라도 모두 지정하는 것이 좋다. 여러개의 변수가 집합적으로 판별자 역할을 하는 경우가 많이 있으며, 그럴 경우 해당 변수를 모두 지정해 주어야 한다. 하나라도 누락되면 에러가 발생하거나 원치 않는 결과를 얻게 된다. names_from은 새로이 생성될 변수들의 이름을 변수값으로 가지고 있는 변수명을 지정한다. values_from은 새로 생성될 변수들의 변수값이 될 값이 어떤 변수로부터 오는지를 지정한다. 정돈된 데이터를 만들기 위해 이번에는 왜 데이터 ’넓히기’를 해야했는지 생각해 본다.

노트단원 마무리

데이터구조 변경의 마지막으로 한 가지 퀴즈를 내보고자 한다. 퀴즈에서 사용할 데이터는 world_bank_poptidyr에 포함된 데이터셋 중 하나이다. 이 데이터셋에는 연도별 인구수가 들어가 있다. 우선, 우리는 오늘 총인구 데이터를 정돈할 것이기 때문에 indicator에서 SP.POP.TOTL만을 선택하고자 한다.

world_pop <- world_bank_pop |> 
  filter(indicator == "SP.POP.TOTL")

world_pop의 구조를 확인하고, 아래의 분석을 수행할 수 있도록 데이터를 정돈하라.

  • 한 나라(예: “KOR”)의 연도별 인구 추이 그래프를 그리려고 한다.

  • x축에는 연도를 y축에는 인구수의 데이터가 필요하다.

  • 연도가 추가 되도라도 새로운 열 변수를 추가하지 않고도 작업할 수 있어야 한다.

wb_long <- world_pop |> 
  pivot_longer(
    cols = !c(country, indicator),
    names_to = "year",
    values_to = "pop"
  )
wb_long
# A tibble: 4,788 × 4
   country indicator   year    pop
   <chr>   <chr>       <chr> <dbl>
 1 ABW     SP.POP.TOTL 2000  89101
 2 ABW     SP.POP.TOTL 2001  90691
 3 ABW     SP.POP.TOTL 2002  91781
 4 ABW     SP.POP.TOTL 2003  92701
 5 ABW     SP.POP.TOTL 2004  93540
 6 ABW     SP.POP.TOTL 2005  94483
 7 ABW     SP.POP.TOTL 2006  95606
 8 ABW     SP.POP.TOTL 2007  96787
 9 ABW     SP.POP.TOTL 2008  97996
10 ABW     SP.POP.TOTL 2009  99212
# ℹ 4,778 more rows

여러나라의 인구를 한 눈에 비교할 수 있는 표가 필요하다. wb_long의 구조를 확인하고, 이를 위한 데이터를 정돈하라.

  • 결과는 표에서 연도 간 값을 가로로 나란히 비교하기 쉽도록 구성되어 있어야 한다.
wb_long |>
  pivot_wider(
    id_cols = c(country),
    names_from = year,
    values_from = pop)
# A tibble: 266 × 19
   country `2000` `2001` `2002` `2003` `2004` `2005` `2006` `2007` `2008` `2009`
   <chr>    <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
 1 ABW     8.91e4 9.07e4 9.18e4 9.27e4 9.35e4 9.45e4 9.56e4 9.68e4 9.80e4 9.92e4
 2 AFE     4.02e8 4.12e8 4.23e8 4.34e8 4.45e8 4.57e8 4.70e8 4.82e8 4.96e8 5.09e8
 3 AFG     1.95e7 1.97e7 2.10e7 2.26e7 2.36e7 2.44e7 2.54e7 2.59e7 2.64e7 2.74e7
 4 AFW     2.70e8 2.77e8 2.85e8 2.93e8 3.01e8 3.10e8 3.19e8 3.28e8 3.37e8 3.46e8
 5 AGO     1.64e7 1.69e7 1.75e7 1.81e7 1.88e7 1.95e7 2.02e7 2.09e7 2.17e7 2.25e7
 6 ALB     3.09e6 3.06e6 3.05e6 3.04e6 3.03e6 3.01e6 2.99e6 2.97e6 2.95e6 2.93e6
 7 AND     6.61e4 6.78e4 7.08e4 7.39e4 7.69e4 7.98e4 8.02e4 7.82e4 7.61e4 7.39e4
 8 ARB     2.87e8 2.94e8 3.00e8 3.07e8 3.13e8 3.21e8 3.29e8 3.37e8 3.47e8 3.56e8
 9 ARE     3.28e6 3.45e6 3.63e6 3.81e6 3.99e6 4.28e6 4.90e6 5.87e6 6.99e6 7.99e6
10 ARG     3.71e7 3.75e7 3.79e7 3.83e7 3.87e7 3.91e7 3.95e7 3.99e7 4.03e7 4.07e7
# ℹ 256 more rows
# ℹ 8 more variables: `2010` <dbl>, `2011` <dbl>, `2012` <dbl>, `2013` <dbl>,
#   `2014` <dbl>, `2015` <dbl>, `2016` <dbl>, `2017` <dbl>

2 컬럼의 결합 및 분할

2.1 컬럼 결합하기

두 개 이상의 컬럼을 연합하여 하나의 새로운 컬럼을 생성한다. 우선 간단한 예제 티블 객체를 생성한다.

b_tibble <- tibble(
  country = c("A", "A", "B", "B"),
  century = c("19", "20", "19", "20"),
  year = c("99", "00", "99", "00")
)
b_tibble
# A tibble: 4 × 3
  country century year 
  <chr>   <chr>   <chr>
1 A       19      99   
2 A       20      00   
3 B       19      99   
4 B       20      00   

centuryyear 컬럼을 결합하여 새로운 컬럼을 생성한다.

b_tibble |> unite(century, year, col = "year", sep = "")
# A tibble: 4 × 2
  country year 
  <chr>   <chr>
1 A       1999 
2 A       2000 
3 B       1999 
4 B       2000 

2.2 컬럼 분할하기

하나의 컬럼을 두 개 이상의 컬럼으로 분할한다. 우선 간단한 예제 티블 객체를 생성한다.

c_tibble <- tibble(
  country = c("A", "A", "B", "B"),
  year = c("1999", "2000", "1999", "2000"),
  rate = c("0.7K/19M", "2K/20M", "37K/172M", "80K/174M")
)
c_tibble
# A tibble: 4 × 3
  country year  rate    
  <chr>   <chr> <chr>   
1 A       1999  0.7K/19M
2 A       2000  2K/20M  
3 B       1999  37K/172M
4 B       2000  80K/174M

rate 컬럼을 분할하여 새로운 두 개의 컬럼을 생성한다. 컬럼이 늘어 나는 것이므로 separate_wider_delim() 함수를 통해 수행한다.

c_tibble |> separate_wider_delim(
  rate, delim = "/", names = c("cases", "pop") 
)
# A tibble: 4 × 4
  country year  cases pop  
  <chr>   <chr> <chr> <chr>
1 A       1999  0.7K  19M  
2 A       2000  2K    20M  
3 B       1999  37K   172M 
4 B       2000  80K   174M 

유사한 기능을 하는 파생 함수에 separate_wider_position()separate_wider_regex() 함수도 있다.

하나의 셀 속의 값을 분할하는 것은 위와 동일하지만, 분할된 값을 새로운 컬럼으로 옮기는 방식이 아니라 동일한 행의 새로운 열로 옮기는 방식 역시 가능한다. 행을 늘이기 때문에 separate_longer_delim() 함수를 사용한다.

c_tibble |> separate_longer_delim(
  rate, delim = "/"
)
# A tibble: 8 × 3
  country year  rate 
  <chr>   <chr> <chr>
1 A       1999  0.7K 
2 A       1999  19M  
3 A       2000  2K   
4 A       2000  20M  
5 B       1999  37K  
6 B       1999  172M 
7 B       2000  80K  
8 B       2000  174M 

유사한 기능을 하는 파생 함수에 separate_longer_position() 함수도 있다.

3 결측치(NA) 처리

해당 셀에 데이터가 존재하지 않는 경우 그것을 결측치(missing value)라 하고, R에서는 보통 NA(Not Available)이라고 한다. tidyr 패키지는 이러한 결측치를 다루기 위한 몇 가지 유용한 함수를 제공한다. 우선 간단한 예제 티블 객체를 생성한다.

d_tibble <- tibble(
  x1 = c("A", "B", "C", "D", "E"), 
  x2 = c(1, NA, NA, 3, 4),
  x3 = c(NA, TRUE, FALSE, NA, FALSE)
)
d_tibble
# A tibble: 5 × 3
  x1       x2 x3   
  <chr> <dbl> <lgl>
1 A         1 NA   
2 B        NA TRUE 
3 C        NA FALSE
4 D         3 NA   
5 E         4 FALSE

3.1 결측치를 포함한 행 삭제하기

drop_na() 함수를 통해 결측치가 포함된 행을 삭제할 수 있다. 우선 어떤 컬럼에서라도 NA를 가지는 모든 행을 삭제한다.

d_tibble |> drop_na()
# A tibble: 1 × 3
  x1       x2 x3   
  <chr> <dbl> <lgl>
1 E         4 FALSE

특정한 컬럼에 NA를 가지는 행만 삭제할 수 있다.

d_tibble |> drop_na(x2)
# A tibble: 3 × 3
  x1       x2 x3   
  <chr> <dbl> <lgl>
1 A         1 NA   
2 D         3 NA   
3 E         4 FALSE
d_tibble |> drop_na(x3)
# A tibble: 3 × 3
  x1       x2 x3   
  <chr> <dbl> <lgl>
1 B        NA TRUE 
2 C        NA FALSE
3 E         4 FALSE

3.2 결측치를 주변 값을 이용해 채워넣기

fill() 함수는 위나 아래의 값을 이용해 NA를 대체하는 것이다. 방향의 “down”은 기본값으로 위의 값을 기준으로 아래 방향으로 채움이 이루어진다는 것이다.

d_tibble |> fill(x2, .direction = "down")
# A tibble: 5 × 3
  x1       x2 x3   
  <chr> <dbl> <lgl>
1 A         1 NA   
2 B         1 TRUE 
3 C         1 FALSE
4 D         3 NA   
5 E         4 FALSE

방향을 다르게 지정할 수도 있다. “up”은 아래의 값을 기준으로 위 방향으로 채움이 이루어진다는 것이다.

d_tibble |> fill(x2, .direction = "up")
# A tibble: 5 × 3
  x1       x2 x3   
  <chr> <dbl> <lgl>
1 A         1 NA   
2 B         3 TRUE 
3 C         3 FALSE
4 D         3 NA   
5 E         4 FALSE

이렇게 하면 아래에 참조할 것이 없는 다섯 번째 행의 값은 그대로 NA로 남게 된다. 이를 해소하기 위해 “updown”으로 설정할 수 있는데, 먼저 위 방향으로 채움이 이루어지고 NA가 여전히 존재하는 경우 아래 방향의 채움이 이루어진다.

d_tibble |> fill(x2, .direction = "updown")
# A tibble: 5 × 3
  x1       x2 x3   
  <chr> <dbl> <lgl>
1 A         1 NA   
2 B         3 TRUE 
3 C         3 FALSE
4 D         3 NA   
5 E         4 FALSE

3.3 결측치를 특정 값으로 채워넣기

raplace_na() 함수를 통해 특정 값을 지정해 NA를 대체할 수 있다.

d_tibble |> replace_na(
  list(x2 = 0, x3 = TRUE)
)
# A tibble: 5 × 3
  x1       x2 x3   
  <chr> <dbl> <lgl>
1 A         1 TRUE 
2 B         0 TRUE 
3 C         0 FALSE
4 D         3 TRUE 
5 E         4 FALSE

3.4 암묵적 결측치 처리

암묵적(implicit) 결측치란 데이터셋에 아예 존재하지 않아 기록되지 않은 결측치를 말하는데, 행의 부재 형태로 나타난다. 예시를 위해 간단한 티블 객체를 생성한다.

e_tibble <- tibble(
  x1 = c("A", "B", "B"), 
  x2 = c("X", "X", "Y"),
  x3 = c(3, 4, 5)
)
e_tibble
# A tibble: 3 × 3
  x1    x2       x3
  <chr> <chr> <dbl>
1 A     X         3
2 B     X         4
3 B     Y         5

여기서 x1과 x2는 범주형 변수이고, 모두 4가지(A-X, A-Y, B-X, B-Y)이 가능하다. 하지만 현재 데이터셋에는 A-Y가 존재하지 않는데, 이 때 이것을 암묵적 결측치라고 부른다. 암묵적 결측치를 명시적 결측치로 만드는 것이 필요한 상황이 있을 수 있는데, complete() 함수를 통해 암묵적 결측치를 확인할 수 있다.

e_tibble |> complete(x1, x2)
# A tibble: 4 × 3
  x1    x2       x3
  <chr> <chr> <dbl>
1 A     X         3
2 A     Y        NA
3 B     X         4
4 B     Y         5

A-Y에 대한 x3의 셀의 NA로 나타나는 것을 확인할 수 있다. 앞의 fill() 함수와 유사한 방식으로 해당 결측치를 채울수도 있다.

e_tibble |> complete(x1, x2, fill = list(x3 = 0))
# A tibble: 4 × 3
  x1    x2       x3
  <chr> <chr> <dbl>
1 A     X         3
2 A     Y         0
3 B     X         4
4 B     Y         5

암묵적 결측치의 확인이 목적이 아니라 모든 카테고리의 조합 상황을 확인하고 싶을 때는 expand() 함수를 사용한다.

e_tibble |> expand(x1, x2)
# A tibble: 4 × 2
  x1    x2   
  <chr> <chr>
1 A     X    
2 A     Y    
3 B     X    
4 B     Y