intro_r/script/non_stand_eval.R

335 lines
8.7 KiB
R
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#' ---
#' title: "Non-standard evaluation in R"
#' date: "2021"
#' author: "Jose https://ajuda.multifarm.top"
#' output:
#' html_document:
#' code_folding: show
#' toc: yes
#' toc_float:
#' smooth_scroll: true
#' df_print: paged
#' highlight: zenburn
#' ---
#' remove objects
rm(list = ls())
#' ## Function calls
#'
#' "the essential computation in R is the evaluation of a call to a function. Function calls in their ordinary form consist of the functions name followed by a parenthesized argument list;...
#' Arguments in function calls can be any expression (20 p Chambers, Programming with R)
## subset() is special because it implements different scoping rules: the expressions a >= 4 or b == c are evaluated in the specified data frame rather than in the current or global environments. This is the essence of non-standard evaluation.
## How does subset() work? Weve already seen how to capture an arguments expression rather than its result, so we just need to figure out how to evaluate that expression in the right context. Specifically, we want x to be interpreted as sample_df$x, not globalenv()$x. To do this, we need eval(). This function takes an expression and evaluates it in the specified environment.
## Before we can explore eval(), we need one more useful function: quote(). It captures an unevaluated expression like substitute(), but doesnt do any of the advanced transformations that can make substitute() confusing. quote() always returns its input as is:
## Structure of expressions
## To compute on the language, we first need to understand the structure of the language. That will require some new vocabulary, some new tools, and some new ways of thinking about R code. The first thing youll need to understand is the distinction between an operation and a result:
x <- 4
y <- x * 10
y
#> [1] 40
#' When an assign-ment takes place in the top-level of the R session, the current environment is whats called the global environment.
#' (Chambers p. 240)
#'
#' assignments during function calls use an environment specially created for that call.
## We want to distinguish the action of multiplying x by 10 and assigning that result to y from the actual result (40). As weve seen in the previous chapter, we can capture the action with quote():
z <- quote(y <- x * 10)
z
#> y <- x * 10
#' ## Quote function
#'
#' quote() returns an expression: an object that represents an action that can be performed by R. (Unfortunately expression() does not return an expression in this sense. Instead, it returns something more like a list of expressions. See parsing and deparsing for more details.)
#' An expression is also called an abstract syntax tree (AST) because it represents the hierarchical tree structure of the code. Well use pryr::ast() to see this more clearly:
quote(1+10)# The output is the expression
eval(1+10)# The output is the result of the expression
quote(x)
quote(x + y^2)
eval(quote(x <- 1))
x <- 1:5
ex <- eval(x)
ex
typeof(ex)
class(ex)
qx <- quote(x)
qx
class(qx)
typeof(qx)
class(x)
class(eqx)
class(qx)
eval(quote(x))
eval(quote(y))
z <- 10
qxz <- quote(x + z^2)
class(qxz)
typeof(qxz)
eqxz <- eval(quote(x + z^2))
eqxz
class(eqxz)
typeof(eqxz)
eval(x + z^2)
#' ## Parse
#'
## eqxz <- parse("./scritp_parse.R")
## class(eqxz)
## deparse(eqxz)
## evaluated_f <- eval(eqxz)
## class(eqxz)
xz <- quote(x + z^2)# this object saves the expression that represents an action that can be performed by R
eval(xz) # The expression can be evaluated and the output is printed
xz2 <- x + z^2 # this object saves the value
eval(xz2)
sample_df <- data.frame(a = 1:5, b = 5:1, c = c(5, 3, 1, 4, 1))
## substitute() makes non-standard evaluation possible. It looks at a function argument and instead of seeing the value, it sees the code used to compute the value:
substitute(subset)
x <- c(1:5)
xs <- substitute(x)
class(xs)
typeof(xs)
sum(xs)
exs <- eval(substitute(x))
class(exs)
exs
sum(exs)
str(x)
?letters
y <- letters[1:5]
dxy <- data.frame(x, y = LETTERS[6:10])
substitute(y)
eval(substitute(y))
substitute(y, dxy)
substitute(d)
subset2 <- function(x, condition) {
condition_call <- substitute(condition)
r <- eval(condition_call, x)
x[r, ]
}
subset2(sample_df, a >= 4)
subset(sample_df, a < 4 & b > 3)
cond1 <- substitute(a >= 4)
cond2 <- quote(a >= 4)
class(cond1)
typeof(cond1)
class(cond2)
typeof(cond2)
subset(sample_df, eval(cond1))
subset(sample_df, eval(cond2))
## Qual é a utilidade de usar o non-standard evaluation em R?
## "Non-standard evaluation" permite chamar e avaliar expressões desde outros
## ambientes diferentes ao ambiente global ou um ambiente específico em que o
## usuário está trabalhando. Isso porque no R, usuários podem manipular as
## expressões da linguagem antes que elas sejam executadas.
## Geralmente quando se trabalha em R, você cria objetos e muitos desses objetos
## são salvos no ambiente de trabalho (".GlobalEnv"). Por exemplo esses
## objetos são criados:
x <- c(1:5)
y <- letters[1:5]
set.seed(45)
z <- rnorm(5)
dxy <- data.frame(x, y = LETTERS[6:10], z)
#' Eles devem aparecer na sessão:
objects()
#' E podem ser removidos
rm(z) # removendo "z"
#' Esses objetos são "avaliados" por padrão ("standard-evaluation") no ambiente de trabalho quando se executa uma operação ou se mostra o conteúdo deles
dxy
## No capítulo do livro http://adv-r.had.co.nz/Computing-on-the-language.html#capturing-expressions quote() e eval() são usados para exemplificar
## a diferença entre expressões ou nomes que são objetos da linguagem e
## o resultado da avaliação de uma expressão ou um "statement". Isso
## facilita entender o que é uma "promessa" no contexto de uma função e a relação com "non-standard evaluation".
## Por exemplo, o seguinte objeto da linguagem é uma expressão que representa uma
## ação que pode ser executada no R:
xz <- quote(x + z^2)
xz
class(xz)
typeof(xz)
xzs <- substitute(x + z^2)
#' Printing abstract sintax tree
#'
pryr::ast(xz)
pryr::call_tree(xz)
pryr::ast(xzs)
pryr::call_tree(xzs)
#' O objeto é um "call" que tem o "código para computar". Para executar o conteúdo desse nome, pode ser usada a função eval. Esse novo objeto já é um vector numérico que resulta de executar a operação contida no nome:
exz <- eval(xz)
class(exz)
typeof(exz)
## A função "substitute" permite elaborar funções que fogem da avaliação
## padrão do R no ambiente de trabalho e substituir o chamado para objetos
## que estão em outros ambientes ou em tabelas ou dataframes. Muitas
## funções de pacotes como ggplot2, ou dplyr usam esse método para
## manipular objetos que estão dentro de tabelas ao invés de
## objetos do ambiente global. Isso permite escrever um código menos verboso.
#' Nesse exemplo do livro de Wickham é apresentada a lógica usando uma função chamada subset2.
#'
subset2 <- function(x, condition) {
condition_call <- substitute(condition)# cria o "parse tree", sustitui variáveis do ambiente
r <- eval(condition_call, x)# a expressão anterior é avaliada no ambiente "x" (uma tabela)
x[r, ]
}
## A função opera sobre uma tabela e permite extrair valores de uma tabela.
## "substitute()" é usada para capturar a chamada que é realizada
## pelo usuário e avalia a expressão dentro da tabela, ao invés de
## chamar um objeto que está no ambiente global:
subset2(dxy, z > 0)
#' A função aplica "non-standard evaluation" sobre o vector z que está na tabela: antes que a expressão seja executada no ambiente global, a chamada é passada para a tabela e executa a seleção de linhas dentro da tabela, ao invés de procurar por um objeto no ambiente.
#' Outras funções que operam de maneira similar como o subset "original" ou with usam o mesmo princípio.
subset(dxy, z > 0)
with(dxy, z >0)
## Onde usar substitute, eval e quotes? (se alguém puder oferecer um exemplo diferente do livro, mais didático, ajudaria a entender...)
#' substitute() deve ser usada quando se quer construir funções com "non-standard evaluation". Usar quote conduz a erros. Se a função "subset" é construída usando quote, a avaliação do código será padrão (standard evaluation):
subset3 <- function(x, condition) {
condition_call <- quote(condition)
r <- eval(condition_call, x)
x[r, ]
}
#' A função não consegue achar dito objeto que está na tabela porque está procurando "z" no ambiente global
subset3(dxy, z >0)
subset2(dxy, z >0)
#' create a new z
#'
z <- c(1:10)
z >0
dxy[z >0]
dxy[dxy$z >0,]
#' ## Referências
#'
#'Essa vignette aprofunda o conceito de "non-standard evaluation" no R
#'
#' https://cran.r-project.org/web/packages/lazyeval/vignettes/lazyeval.html
#'
#' https://cran.r-project.org/doc/manuals/r-release/R-lang.html#Language-objects