public bigdata

Tidy evaluation, most common actions(개인적 정리)_비표준 평가 관련2 본문

R programming

Tidy evaluation, most common actions(개인적 정리)_비표준 평가 관련2

public bigdata 2019. 8. 29. 22:25

<해당 링크의 github 글을 보고난 뒤 내 생각데로 정리한 내용이다>

 

<bare to quosure : quo>

library(tidyverse)

bare_to_quo <- function(x, var){
  x %>% select(!!var) %>% head(1)
}
bare_to_quo(mtcars, quo(cyl))

dplyr 동사에 변수명을 전달하는 방법이다. var = cyl이라고 적는다면 var에 할당된 cyl이 무엇인지 평가하게 되는데 cyl은 함수 내부의 select 함수에서 평가될 객체인데 그냥 car = cyl이라고 적는다면 이 단계에서 cyl이 무엇인지 평가하게 되므로 오류가 발생한다. var = quo(cyl)으로 적어서 cyl을 인용하여 평가하지 않도록 한다. 그러면 함수 내부의 select에 도달한 뒤 !!와 quo(cyl)이 만나서 quo가 벗겨지고 cyl이 select 안에 도달하게 된다. 그럼 mtcars의 변수인 cyl을 select 함수를 통해 제대로 호출할 수 있다.

<bare to quosure in function: enquo>

bare_to_quo_in_func <- function(x, var) {
  var_enq <- enquo(var)
  x %>% select(!!var_enq) %>% head(1)
}
bare_to_quo_in_func(mtcars, mpg)

함수 내부에서 인용하는 방법이다. 두 번째 줄 enquo(var)은 한 번 평가하고 인용하라는 의미이다. var이 무엇인지 한 번 평가하게 되고 1번 평가함으로서 var에 할당된 mpg라는 글자를 가져오게 되고 인용하여 var_enq에 의미적으로 quo(mpg)로 저장하게 된다. 위 예시와 마찬가지로 select 함수 안의 !!와 quo(mpg)가 만나서 quo가 벗겨지고 select 함수 안에 mpg가 도달하게 된다. 두 예시를 비교해 본다면 즉, 함수 내부에서 변수명을 인용하고 싶다면 함수 내부에서 enquo를 사용하고 함수 내부에서 인용하기 싫고 함수 바깥에서 미리 인용하고 함수 내부로 보낼려면 인자에서 미리 인용해서 함수 내부로 보내는 것이다.

<quosure to a name: quo_name>

library(tidyverse)
library(rlang)
bare_to_name <- function(x, nm) {
  nm_name <- as_name(nm)
  x %>% mutate(!!nm_name := 42) %>% head(1) %>% 
    select(!!nm)
}
bare_to_name(mtcars, quo(this_is_42))

비표준평가로 변수명 할당하는 방법을 설명한다. 인자 nm에 quo(this_is_42)를 할당한다. quo(this_is_42)가 as_name을 만나서 변수명으로 사용할 수 있는 문자열로 변경된다. as_name(quo(this_is_42) --> "this_is_42" 즉. as_name은 인용된 표현을 풀어서 변수명으로 사용할 문자열로 만들어 준다. mutate 내부의 nm_name을 !! 명령을 통해 평가하도록 하면 nm_name이 무엇인지 평가하게 되고 nm_name <- as_name(quo(this_is_42)) [여기서 as_name(quo(this_is_42))는 "this_is_42"를 반환한다. 결국 !!nm_name은 "this_is_42"로 변경된다. 그리고 "this_is_42" := 42를 통해서 "this_is_42"는 안전히 변수명으로 할당된다. 

함수 내부에서 함수명을 인자로 넘겨주고 싶다면 4 단계를 따라라, 1) 넘겨줄 변수명을 quo로 감싼다 2) as_name을 통해서 quo로 감싼 변수명을 풀어 문자열로 변경해준다. 3) := 를 사용하여 := 왼쪽에 오는 문자열이 안전하게 변수명으로 지정될 수 있도록 한다. 4) !!을 사용하여 := 왼쪽의 객체를 평가하도록 하여 이전의 3단계가 실행되도록 한다. (어렵다....)

<quosure to text: quo_text>

quo_to_text <- function(x, var) {
  var_enq <- enquo(var)
  glue::glue("The following column was selected: {rlang::quo_text(var_enq)}")
}
quo_to_text(mtcars, cyl)

## The following column was selected: cyl

var 인자에 cyl을 넘겨준다. 함수 내부에 enquo(var)이 있기 때문에 cyl을 오류없이 quo(cyl)상태로 var_enq에 할당한다. glue패키지의 glue 함수 안에 rlang::quo_text는 quo(cyl)을 받아서 문자 사이에 cyl이 문자로 들어가게 된다. 그 결과## The following column was selected: cyl 가 반환된다. (어렵다....)

<character to name: sym (edited)>

char_to_quo <- function(x, var) {
  var_enq <- rlang::sym(var)
  x %>% select(!!var_enq) %>% head(1)
}
char_to_quo(mtcars, "vs")

##           vs
## Mazda RX4  0

var인자에 "vs"값을 입력하고, 해당 값을 rlang::sym이 받아서 name객체로 변경한 뒤에 var_enq에 저장한다. 결과적으로 select 내부에 vs 값이 도달하게 되고 select는 vs를 변수명으로 인식하게 된다.

<multiple bares to quosure: quos>

bare_to_quo_mult <- function(x, ...) {
  grouping <- quos(...)
  x %>% group_by(!!!grouping) %>% summarise(nr = n())
}
bare_to_quo_mult(mtcars, vs, cyl)

## # A tibble: 5 x 3
## # Groups:   vs [?]
##      vs   cyl    nr
##   <dbl> <dbl> <int>
## 1     0     4     1
## 2     0     6     3
## 3     0     8    14
## 4     1     4    10
## 5     1     6     4

 

...을 통해서 함수 내부의 quos에 vs, cyl을 전달한다.(여기서 의문점 왜...은 enquos 를 통해서 인용되지 않은체로 할당되는 인자값 들을 받아오지 않는가? ...은 그냥 그런 과정을 생략하고 함수 내부로 바로 연결되기 때문이라고 생각하자 즉. ...으로 실로 연결되어 있으니 ...은 그대로 함수 내부의 quos에 전달되고 인용되게 된다. 즉. 인용하지 않아도 ...을 사용하면 함수 내부로 곧장 연결되기 때문에 enquo 혹은 인자에 전달할 때 미리 quo로 인용할 필요가 없는 것)

결국 group_by(!!!grouping)은 group_by(vs, cyl)로 변환된다.

<multiple characters to names: syms (edited)>

bare_to_quo_mult_chars <- function(x, ...) {
  grouping <- rlang::syms(...)
  x %>% group_by(!!!grouping) %>% summarise(nr = n())
}
bare_to_quo_mult_chars(mtcars, list("vs", "cyl"))


## # A tibble: 5 x 3
## # Groups:   vs [?]
##      vs   cyl    nr
##   <dbl> <dbl> <int>
## 1     0     4     1
## 2     0     6     3
## 3     0     8    14
## 4     1     4    10
## 5     1     6     4

syms에 list("vs", "cyl")을 보내면 vs, cyl이 리스트 내부의 첫번째 값으로 vs, 두번째 값을 cyl이 할당된다. 아래처럼 그러면 group_by 내부에 vs, cyl이 전달된다. (어려워///./ㅇㄹㄴ아ㅐㅁㄴ;ㅇㅁㄴㅇ)

[[1]]
vs

[[2]]
cyl

<quoting full expression>

컬럼 이름을 인용하는 것이 자주 쓰이지만 유일한 옵션은 아니다. 표현식 전체를 인용할 수 있다.

filter_func <- function(x, filter_exp) {
  filter_exp_enq <- enquo(filter_exp)
  x %>% filter(!!filter_exp_enq)
}
filter_func(mtcars, hp == 93)

##    mpg cyl disp hp drat   wt  qsec vs am gear carb
## 1 22.8   4  108 93 3.85 2.32 18.61  1  1    4    1

filter_exp 인자로 hp==93 이라는 표현식을 작성해 주고 함수 내부에서 enquo를 통해서 표현식을 가져온다. 이를 통해 표현식 전체를 인자로 넘겨줄 수 있다. 정말 유용한 기능일 것 같다. 함수를 만들어야 할 필요가 있는데 그 중에서 표현식만 바꿔줘야 할 경우가 있다면 복잡하게 구현하지 않고 간단하게 구현 가능할 것으로 기대한다.

<quoting full expression in a character: parse_expr>

filter_by_char <- function(x, char) {
  func_call <- rlang::parse_expr(char)
  x %>% filter(!!func_call)
}
filter_by_char(mtcars, "cyl == 6") %>% head(1)


##   mpg cyl disp  hp drat   wt  qsec vs am gear carb
## 1  21   6  160 110  3.9 2.62 16.46  0  1    4    4

바로 위 예제와는 달리 문자열로 표현식을 먼저 전달한 뒤에 해당 문자열을 표현식 형태로 parsing하는 방식이다. 데이터 과학에서 파싱이란 어떤 data를 원하는 형태로 만들어 내는 것을 파싱이라고 한다. 문자열을 받아서 R코드의 표현식으로 인식해야 하므로 문자열을 표현식으로 바꿔준다. 이게 바로 파싱이다.

해당 예제는 이해하는데 어렵지 않을 것이라 생각한다.

<Edit notes>

Edit notes

1) I mistakingly thought that rlang::sym(s) created quosures. However, as pointed out to me by a reader, this creates a name, not a quosure. A name however can also be unquoted. See this github discussion.

just_a_name <- rlang::sym("cyl") class(just_a_name)

## [1] "name"

mtcars %>% select(!!just_a_name) %>% head(1)

## cyl ## Mazda RX4 6

2) Quoting expressions in a character was added roughly a year after the first apperance of this blog.

3) The quo_text example used to be with ggplot2. However, since v.3.0.0 it supports tidy evaluation. Therefore, the example was changed and now it uses glue.

해당 저자의 Edit notes이다 무슨 말인지 잘 모르겠어서 그냥 원문을 복사하여 남긴다.