purrr
팩키지는 함수 반복용John Chambers: “To understand computations in R, two slogans are helpful: Everything that exists is an object. Everything that happens is a function call.”
purrr
팩키지는 함수(funciton)를 데이터 객체에 반복하기 위해서 다시 만들어진 것으로 데이터 분석하는 분석가의 삶의 질을 획기적으로 높여줄 것이다. 먼저, 함수를 데이터 input x
와 인자(...
)를 넣으면 이를 변환시키는 자그마한 공장(small factory)으로 최종 완성품(finished goods)을 만들어 내는 과정이다. 경우에 따라서 최종 제품 대신에 다음 그림에서 연기로 표현되어 있듯이 재사용이 불가능한 형태로 나타내는 것도 필요한데 이와 같이 공정을 바꿔 연기로 빼내는 것과 유사한 과정이 부작용(side-effect
)이라고 정의한다.
공장에 입력 데이터로 하나만 집어 넣었는데 회귀식을 고려하면 \(x\), \(y\) 두가지가 필요한데 이렇게 두가지 입력을 넣게 되면 map2()
함수를 사용하고, p
개 입력을 넣게 되면 pmap()
함수를 사용해서 최종 완성품을 만들어 낸다. 공장에서 정상적인 과정은 항상 이상적인 상황이고 언제나 고장이 날 수 있는데 이와 같이 불량품을 알고도 만들어내는 과정에 safely()
함수를 사용한다.
purrr
팩키지는 함수 반복용 - for앞서 mean_lst
를 정의하고 나서 for
반복루프를 돌려 5개 길이가 다르고, NA
결측값이 들어있는 벡터의 평균을 산출한다.
# 0. 환경설정 -----
library(tidyverse)
library(purrr)
library(repurrrsive)
# 1. 함수를 반복 - `for` -----
dat_lst <- list(c(33,NA,2,15,7,4,5),
c(22,1,3,NA,11,2),
c(1,5),
c(3),
c(12,5,9))
mean_lst <- vector("list", length=length(dat_lst))
for (i in seq_along(dat_lst)){
mean_lst[[i]] = mean(dat_lst[[i]], na.rm=TRUE)
}
mean_lst %>% unlist
[1] 11.000000 7.800000 3.000000 3.000000 8.666667
purrr
팩키지는 함수 반복용 - mapmap()
함수 내부에 mean
함수를 인자(na.rm = TRUE
)와 함께 넣어 dat_lst
데이터 5개 벡터에 대한 평균을 산출한다.
# 2. 함수를 반복 - `purrr`:map -----
map(dat_lst, mean, na.rm=TRUE) %>% unlist
[1] 11.000000 7.800000 3.000000 3.000000 8.666667
purrr
팩키지는 함수 반복용 - 부작용map()
계열 함수를 통해 나온 출력물은 다시 활용할 수 있도록 특수 제작된 최종제품인데, 경우에 따라서는 화면에 한번 출력하고 공장연기처럼 버려버리는 경우도 있는데 이런 경우 print_mean()
함수와 같이 함수로 제작하고 나서 walk()
함수로 처리하면 된다.
# 3. 함수를 반복 - `purrr`: side-effect -----
print_mean <- function(x){
print(paste("평균값은 ... : ",
mean(x, na.rm=TRUE)))
return(NULL)
}
walk(dat_lst, print_mean)
[1] "평균값은 ... : 11"
[1] "평균값은 ... : 7.8"
[1] "평균값은 ... : 3"
[1] "평균값은 ... : 3"
[1] "평균값은 ... : 8.66666666666667"
purrr
팩키지는 함수 반복용 - 입력값 2개map2_chr(c('축구','야구','배구','수영', '컬링'), c('국대팀'), paste)
[1] "축구 국대팀" "야구 국대팀" "배구 국대팀" "수영 국대팀" "컬링 국대팀"
변동 계수(coefficient of variation, C.V.)는 표준 편차를 산술 평균으로 나눈 것으로 상대표준편차(relative standard deviation, RSD)라고도 하는데, 측정단위가 서로 다른 자료를 비교하고자 할 때 쓰인다.
\[CV=\frac{\sigma}{\overline x}\]
데이터분석을 할 때 데이터프레임, 변수를 많이 만들게 되면 나중에 관리하기도 번잡하고 디버깅하기도 쉽지 않는 경우가 많다. 함수명도 마찬가지로 가능하면 함수명을 만들지 않고 필요한 경우 즉석해서 처리하는 것이 권장된다. 일반적인 함수로 작성해서 코드를 작성하는 경우와 무명함수(anonymous function)를 적극 활용한다.
두가지 기호 ~
, .
이 도입되는데 수행하는 역할은 다음과 같다.
~
: 함수 사용을 표기.
: 데이터프레임을 표기## 2.3. 함수: 변동계수 -----
### 일반함수
coef_variation <- function(x){
sd(x) / mean(x)
}
map(mtcars, coef_variation)
$mpg
[1] 0.2999881
$cyl
[1] 0.2886338
$disp
[1] 0.5371779
$hp
[1] 0.4674077
$drat
[1] 0.1486638
$wt
[1] 0.3041285
$qsec
[1] 0.1001159
$vs
[1] 1.152037
$am
[1] 1.228285
$gear
[1] 0.2000825
$carb
[1] 0.5742933
### 무명함수
map(mtcars, ~ sd(.) / mean(.))
$mpg
[1] 0.2999881
$cyl
[1] 0.2886338
$disp
[1] 0.5371779
$hp
[1] 0.4674077
$drat
[1] 0.1486638
$wt
[1] 0.3041285
$qsec
[1] 0.1001159
$vs
[1] 1.152037
$am
[1] 1.228285
$gear
[1] 0.2000825
$carb
[1] 0.5742933
repurrrsive
팩키지에 포함되어 있는 왕좌의 게임 got_chars
리스트 데이터를 가지고 필요한 데이터 분석과정을 진행해 보자. 가장 먼저, listviewer::jsonedit()
함수를 통해 데이터 구조에 친숙해 진다.
# 2. 데이터 가져오기 -----
## 2.1. repurrrsive 팩키지 내부 데이터
listviewer::jsonedit(got_chars)
got_chars
리스트 데이터에 set_names()
함수를 통해 이름을 붙이는 것부터 시작한다.
# 3. list 데이터 다루기 -----
## 3.1. 이름붙이기
got_chars %>%
set_names(map_chr(., 'name')) %>%
listviewer::jsonedit()
동맹을 가문 3곳 이상 맺은 등장인물을 추출하는 것은 전형적인 필터링(filtering) 문제로… keep()
함수를 사용한다. ~ length(.$allegiances) >= 3
무명함수를 만들어서 조건을 만족시키는 것만 추출시킨다.
## 3.2. 동맹을 가문 3곳 이상 맺은 등장인물
got_chars %>%
set_names(map_chr(.,'name')) %>%
map(`[`,c('name','allegiances')) %>%
keep(~ length(.$allegiances) >= 3) %>%
listviewer::jsonedit()
동맹을 맺은 가문중에서 Stark
가문과 동맹이 되어 있는 등장인물을 추출한다.
## 3.3. 스타크(Stark) 가문과 동맹을 맺은 등장인물
got_chars %>%
set_names(map_chr(.,'name')) %>%
map(`[`,c('name','allegiances')) %>%
keep(~ifelse(length(.$allegiances) > 0, str_detect(.$allegiances, 'Stark'),FALSE)) %>%
listviewer::jsonedit()
중첩리스트를 갖지 않는 단순 리스트의 경우 [
을 통해 리스트를 바로 map_df()
함수를 통해 데이터프레임으로 만들어 낼 수 있다.
# 4. 리스트 --> 데이터 프레임
## 4.1. 단순 리스트 --> 데이터프레임
got_char_df <- map_df(got_chars, `[`, c("id", "name", "culture", "gender", "born", "alive"))
enframe()
함수를 통해 리스트를 리스트 칼럼 데이터프레임으로 바로 만들어 낼 수 있다. 이 경우, name
과 달리 allegiances
는 자체로 리스트로 일반적인 데이터프레임에는 담을 수 없는 자료구조다.
## 4.2. 중첩 리스트 --> 리스트 칼럼 데이터프레임
got_chars %>%
set_names(map_chr(.,'name')) %>%
map(`[`,c('name','allegiances')) %>%
enframe()
# A tibble: 30 x 2
name value
<chr> <list>
1 Theon Greyjoy <list [2]>
2 Tyrion Lannister <list [2]>
3 Victarion Greyjoy <list [2]>
4 Will <list [2]>
5 Areo Hotah <list [2]>
6 Chett <list [2]>
7 Cressen <list [2]>
8 Arianne Martell <list [2]>
9 Daenerys Targaryen <list [2]>
10 Davos Seaworth <list [2]>
# ... with 20 more rows
일반적인 패턴은 리스트 데이터를 바탕으로 해당 변수를 다양한 변수 자료형에 맞춰 추출한 후에 tibble
자료구조로 데이터를 뽑아내는 것이 흔한 작업패턴이다.
## 4.3. 리스트를 데이터프레임 변수로 추출
got_df <- tibble(
id = got_chars %>% map_int("id"),
name = got_chars %>% map_chr("name"),
aliases = got_chars %>% map("aliases"),
allegiances = got_chars %>% map("allegiances"),
alive = got_chars %>% map_lgl("alive")
)
라니스터 가문과 스타크 가문으로 시즌 6, 7로 넘어가면서 명확해지고 있다. 과연 라니스터 가문과 스타크 가문에 속한 등장인물을 뽑아내어 왕좌의 게임의 등장인물 구도로 두 가문의 8번째 시즌의 결과를 유추해보자.
## 4.4. 라니스터 vs. 스타크
got_df %>%
filter(str_detect(allegiances, "Lannister|Stark")) %>%
select(-allegiances, -alive) %>%
filter(lengths(aliases) > 0) %>%
unnest() %>%
print(n=Inf)
# A tibble: 57 x 3
id name aliases
<int> <chr> <chr>
1 1052 Tyrion Lannister The Imp
2 1052 Tyrion Lannister Halfman
3 1052 Tyrion Lannister The boyman
4 1052 Tyrion Lannister Giant of Lannister
5 1052 Tyrion Lannister Lord Tywin's Doom
6 1052 Tyrion Lannister Lord Tywin's Bane
7 1052 Tyrion Lannister Yollo
8 1052 Tyrion Lannister Hugor Hill
9 1052 Tyrion Lannister No-Nose
10 1052 Tyrion Lannister Freak
11 1052 Tyrion Lannister Dwarf
12 148 Arya Stark Arya Horseface
13 148 Arya Stark Arya Underfoot
14 148 Arya Stark Arry
15 148 Arya Stark Lumpyface
16 148 Arya Stark Lumpyhead
17 148 Arya Stark Stickboy
18 148 Arya Stark Weasel
19 148 Arya Stark Nymeria
20 148 Arya Stark Squan
21 148 Arya Stark Saltb
22 148 Arya Stark Cat of the Canaly
23 148 Arya Stark Bets
24 148 Arya Stark The Blind Girh
25 148 Arya Stark The Ugly Little Girl
26 148 Arya Stark Mercedenl
27 148 Arya Stark Mercye
28 208 Brandon Stark Bran
29 208 Brandon Stark Bran the Broken
30 208 Brandon Stark The Winged Wolf
31 216 Brienne of Tarth The Maid of Tarth
32 216 Brienne of Tarth Brienne the Beauty
33 216 Brienne of Tarth Brienne the Blue
34 232 Catelyn Stark Catelyn Tully
35 232 Catelyn Stark Lady Stoneheart
36 232 Catelyn Stark The Silent Sistet
37 232 Catelyn Stark Mother Mercilesr
38 232 Catelyn Stark The Hangwomans
39 339 Eddard Stark Ned
40 339 Eddard Stark The Ned
41 339 Eddard Stark The Quiet Wolf
42 529 Jaime Lannister The Kingslayer
43 529 Jaime Lannister The Lion of Lannister
44 529 Jaime Lannister The Young Lion
45 529 Jaime Lannister Cripple
46 583 Jon Snow Lord Snow
47 583 Jon Snow Ned Stark's Bastard
48 583 Jon Snow The Snow of Winterfell
49 583 Jon Snow The Crow-Come-Over
50 583 Jon Snow The 998th Lord Commander of the Night's Watch
51 583 Jon Snow The Bastard of Winterfell
52 583 Jon Snow The Black Bastard of the Wall
53 583 Jon Snow Lord Crow
54 605 Kevan Lannister ""
55 957 Sansa Stark Little bird
56 957 Sansa Stark Alayne Stone
57 957 Sansa Stark Jonquil