데이터 과학

리스트 자료 원소 추출하기

학습 목표

  • 리스트 자료 내부 원소를 추출한다.
  • 리스트 자료를 데이터프레임으로 변환한다.
  • magrittr과 파이프 연산자를 활용하여 깔끔하게 코드화 한다.

1. map() 함수 소개 - 원소 추출 1

1.1. 환경설정

함수형 프로그래밍의 핵심 팩키지 purrr을 적재한다. 예제 리스트 데이터를 담고 있는 repurrrsive 팩키지를 불러오고, 리스트 데이터를 살펴보는데 listviewer 팩키지를 활용하고, 함수형 프로그래밍 코드를 깔끔하게 만드는데 magrittr 팩키지를 사용한다.

# 0. 환경설정 ----------------------------------------------------
library(purrr)
library(repurrrsive) # devtools::install_github("jennybc/repurrrsive")
library(listviewer) # devtools::install_github('timelyportfolio/reactR')
library(magrittr)

1.2. 벡터화(vectorized) 연산과 리스트화(“list-ized”) 연산

각 원소마다 특정 연산작업을 수행하려면 R에서 벡터화 연산기능을 활용하면 루프를 사용해서 각 벡터에서 원소를 하나씩 꺼내서 연산을 가하고 이를 돌려 저장하는 작업을 코드 한줄로 간단하게 처리할 수 있다.

리스트에서도 동일한 작업을 수행하고자 하는데 과거 lapply() 함수가 작업을 잘 수행했는데, 이를 일반화해서 일관된 문법을 가지고 작업을 수행하고자 한다. 이때 필요한 것이 purrr 팩키지 purrr::map 함수다.

(3:5) ^ 2
#> [1]  9 16 25
sqrt(c(9, 16, 25))
#> [1] 3 4 5

map(c(9, 16, 25), sqrt)
#> [[1]]
#> [1] 3
#> 
#> [[2]]
#> [1] 4
#> 
#> [[3]]
#> [1] 5

1.3. 명칭 혹은 위치로 리스트원소 추출

왕좌의 게임 미드에 등장하는 캐릭터는 누굴까? 라는 질문에 map 함수에 명칭 “name”을 지정해서 추출해보자. 위치를 통해 동일한 결과를 얻을 수 있다.

명칭

map(got_chars[1:4], "name")
#> [[1]]
#> [1] "Theon Greyjoy"
#> 
#> [[2]]
#> [1] "Tyrion Lannister"
#> 
#> [[3]]
#> [1] "Victarion Greyjoy"
#> 
#> [[4]]
#> [1] "Will"

위치

map(got_chars[1:4], 3)
#> [[1]]
#> [1] "Theon Greyjoy"
#> 
#> [[2]]
#> [1] "Tyrion Lannister"
#> 
#> [[3]]
#> [1] "Victarion Greyjoy"
#> 
#> [[4]]
#> [1] "Will"

즉, map() 함수 내부에 “명칭” 텍스트를 넣어 리스트 내부 원소를 추출 - function(x) x[[“TEXT”]] map() 함수 내부에 “위치”지정 숫자를 넣어 리스트 내부 i번째 원소를 추출 - function(x) x[[i]]

파이프 %>% 연산자를 map() 함수와 함께 흔히 사용한다.

got_chars %>% 
  map("name")
got_chars %>% 
  map(3)

1.4. 자료형 고려한 map

map() 함수만 사용하면 리스트가 반환되는데, 흔히 특정 자료형에 맞춘 작업결과를 원하는 경우가 많다. 이런 경우 객체:원자벡터 형태를 원하는 경우가 많다.

리스트를 반환받아 리스트를 해당 원자벡터형태로 변환하는 방식보다 한번에 map_자료형 형태를 반환받는 것이 더 효율적이고, 데이터분석 전문가가 자료를 분석할 때 자료형이 맞지 않는 것도 사전에 확인하여 조치를 취할 수 있는 장점도 있다.

map_chr(got_chars[9:12], "name")
#> [1] "Daenerys Targaryen" "Davos Seaworth"     "Arya Stark"        
#> [4] "Arys Oakheart"
map_chr(got_chars[9:12], 3)
#> [1] "Daenerys Targaryen" "Davos Seaworth"     "Arya Stark"        
#> [4] "Arys Oakheart"

map_자료형 함수는 다음과 같은 네가지 형태가 있다.

  • map_chr()
  • map_lgl()
  • map_int()
  • map_dbl()

1.5. 다수 리스트 원소 추출

지금까지 리스트에서 한 원소만 추출하였는데, 다수 원소를 추출하려면 어떻게 하면 될까? 전총적인 방식은 [[ 연산자로 해당 리스트 원소를 뽑아내고, 명칭을 벡터로 넣어 추출하는 방식이다. 아무래도 구문이 직관적이 않고 괄호가 많아 복잡하다는 느낌이 강하다.

got_chars[[3]][c("name", "culture", "gender", "born")]
#> $name
#> [1] "Victarion Greyjoy"
#> 
#> $culture
#> [1] "Ironborn"
#> 
#> $gender
#> [1] "Male"
#> 
#> $born
#> [1] "In 268 AC or before, at Pyke"

위와 동일한 기능을 map 함수를 활용해 코드를 작성하면 다음과 같다.

map(.x, .f, ...) 구문을 사용하는데 [이 함수위치에 들어갔다. 하지만 놀랄 것도 없는 것이… John Chambers가 “everything that happens in R is a function call” 명언을 남겼다. 즉, 존재하는 모든 것은 객체고, 모든 사건은 함수호출을 통해 발생한다.

x <- map(got_chars, `[`, c("name", "culture", "gender", "born"))
str(x[16:17])
#> List of 2
#>  $ :List of 4
#>   ..$ name   : chr "Brandon Stark"
#>   ..$ culture: chr "Northmen"
#>   ..$ gender : chr "Male"
#>   ..$ born   : chr "In 290 AC, at Winterfell"
#>  $ :List of 4
#>   ..$ name   : chr "Brienne of Tarth"
#>   ..$ culture: chr ""
#>   ..$ gender : chr "Female"
#>   ..$ born   : chr "In 280 AC"

[ 처리방법이 이상하다고 생각하시는 분은 magrittr 팩키지 extract 함수를 사용하면 동일한 결과를 얻을 수 있다.

x <- map(got_chars, extract, c("name", "culture", "gender", "born"))
str(x[16:17])
#> List of 2
#>  $ :List of 4
#>   ..$ name   : chr "Brandon Stark"
#>   ..$ culture: chr "Northmen"
#>   ..$ gender : chr "Male"
#>   ..$ born   : chr "In 290 AC, at Winterfell"
#>  $ :List of 4
#>   ..$ name   : chr "Brienne of Tarth"
#>   ..$ culture: chr ""
#>   ..$ gender : chr "Female"
#>   ..$ born   : chr "In 280 AC"

1.6. 데이터프레임 출력

지금까지 map() 함수에 [ 연산을 활용하여 원하는 리스트 내부 원소를 뽑아내는 것을 살펴봤는데, 리스트 내부 원소를 뽑아내는 구문을 간결하게 작성하는 방법은 터특했지만, 여전히 반환결과는 리스트다. 반환된 리스트를 추가작업을 통해 데이터프레임으로 변환하는 것도 가능하지만, 한방에 다음 데이터분석작업까지 일관되게 처리하는 방법은 무엇일까?

map_df()를 사용하면 이 문제가 일거에 해결된다.

map_df(got_chars, extract, c("name", "culture", "gender", "id", "born", "alive"))
#> # A tibble: 29 × 6
#>                  name  culture gender    id
#>                 <chr>    <chr>  <chr> <int>
#> 1       Theon Greyjoy Ironborn   Male  1022
#> 2    Tyrion Lannister            Male  1052
#> 3   Victarion Greyjoy Ironborn   Male  1074
#> 4                Will            Male  1109
#> 5          Areo Hotah Norvoshi   Male  1166
#> 6               Chett            Male  1267
#> 7             Cressen            Male  1295
#> 8     Arianne Martell  Dornish Female   130
#> 9  Daenerys Targaryen Valyrian Female  1303
#> 10     Davos Seaworth Westeros   Male  1319
#> # ... with 19 more rows, and 2 more variables: born <chr>, alive <lgl>

데이터프레임으로 불러와서 작업을 할 때 항상 자료형과 변수명에 대해서 사전에 충분한 작업을 수행하는 것이 필요하다. 기억할 점은 . 은 파이프 연산에 앞선 입력값으로 got_chars가 되고, { 괄호는 tibble 자료형에 got_chars 객체가 데이터프레임 첫번째 변수로 list-column 형태로 저장되는 것을 방지한다.

library(tibble)
got_chars %>% {
  tibble(
       name = map_chr(., "name"),
    culture = map_chr(., "culture"),
     gender = map_chr(., "gender"),       
         id = map_int(., "id"),
       born = map_chr(., "born"),
      alive = map_lgl(., "alive")
  )
} 
#> # A tibble: 29 × 6
#>                  name  culture gender    id
#>                 <chr>    <chr>  <chr> <int>
#> 1       Theon Greyjoy Ironborn   Male  1022
#> 2    Tyrion Lannister            Male  1052
#> 3   Victarion Greyjoy Ironborn   Male  1074
#> 4                Will            Male  1109
#> 5          Areo Hotah Norvoshi   Male  1166
#> 6               Chett            Male  1267
#> 7             Cressen            Male  1295
#> 8     Arianne Martell  Dornish Female   130
#> 9  Daenerys Targaryen Valyrian Female  1303
#> 10     Davos Seaworth Westeros   Male  1319
#> # ... with 19 more rows, and 2 more variables: born <chr>, alive <lgl>