일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- 최저 시급
- 산입범위
- 통계 오류
- 최저시급 개정안
- 핵개발
- 인터스텔라
- 비행기 추락
- 통계오류
- 성악설
- 동전 던지기
- 티모시페리스
- 비선형성
- 멘탈관리
- 핵 개발
- 산입 범위
- t-test
- t검정
- 비율
- R 기초
- 유닛테스트
- 선형성
- 이기적 유전자
- 아인슈타인
- 조던피더슨
- 큰수의 법칙
- 수학적 사고
- 자기관리
- 찬물샤워
- R4DS
- R 프로그래밍
- Today
- Total
public bigdata
advanced R 자료_비표준 평가 관련3 본문
advanced R 교재를 보고 비표준평가에 대한 내용을 정리한 것이다. 개인적으로 이해하여 정리하고, 이해되지 않는 부분은 날려버렸다.
- 비표준평가 : 대부분의 프로그래밍 언어에서는 단지 함수의 인자로 값만 부여할 수 있지만 R은 인자에 표현식을 할당하여 함수의 인자로 지정할 수 있다. 인자에 할당할 당시에는 평가(계산 및 실행)하지 않고 함수 내부의 원하는 곳에서 실행하도록 하는 것이다. 이를 비표준평가라고 한다.
13.1 표현식 파악
substitute는 비표준평가를 가능하게 한다. 값이 아니라 해당 값을 계산하기 위한 코드를 찾아준다.
substitute는 lexical scoping을 사용한다. 즉. substitute(x)이면 x가 무엇인지 참조한 뒤에 x에 들어있는 표현식을 인용한다. quote는 참조 없이 바로 인용한다.
> f <- function(x){
+ substitute(x)
+ }
> f(1:10)
1:10
>
> x <- 10
> f(x)
x
>
> y <- 13
> f(x + y^2)
x + y^2
substitute가 반환하는 것을 표현식이라고 한다. substitute는 프로미스라는 특별한 유형의 개체를 반환하는데, 프로미스는 값이 계산되기 위해 필요한 표현과 계산하기 위한 환경을 파악한 것이다.
substitute는 종종 deparse와 함께 쓰인다. 이 함수는 substitute의 결과인 표현식을 취해 문자형 벡터로 변환한다. 즉. 프로미스 객체를 문자열로 변환해 주는 것으로 보인다.
> g <- function(x) deparse(substitute(x))
> g(1:10)
[1] "1:10"
>
> g(x)
[1] "x"
>
> g(x + y ^ 2)
[1] "x + y^2"
아래처럼 인용부호를 피하기 위해서, data.frame은 그것을 계산하기 위해 사용된 표현식으로 변수의 이름을 붙인다. 무슨 말인지 이해가 안된다.
## 예제1##
> library(ggplot2)
> library("ggplot2")
## 예제2##
> x <- 1:4
> y <- letters[1:4]
> names(data.frame(x, y))
[1] "x" "y"
13.1 연습문제
1. deparse의 중요한 특성은 입력이 지나치게 긴 경우 다중 문자열을 반환할 수도 있다. 왜 이런 일이 일어나는가 ?deparse의 문서를 주의 깊게 읽어보라. 항상 하나의 문자열을 반환하도록 deparse의 래퍼를 작성할 수 있는가?
> deparse(substitute(a+b+c+d+e+f+g+h+i+j+k+l+m+n+o+p+q+r+s+t+u+v+w+x+y+z))
[1] "a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + "
[2] " q + r + s + t + u + v + w + x + y + z"
deparse함수에는 width.cutoff라는 20~500사이의 값을 정해주는 인자가 존재해 특정 길이를 넘어가면 줄을 바꿔서 출력해준다. 아래와 같은 2가지 방법으로 해결할 수 있다.
> deparse_without_cutoff <- function(x){
+ paste0(deparse(x), collapse = "")
+ }
> deparse_without_cutoff(substitute(a + b + c + d + e + f + g + h + i + j + k + l + m +
+ n + o + p + q + r + s + t + u + v + w + x + y + z))
[1] "a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z"
>
> gsub("\\s+", " ", paste0(deparse(substitute(a + b + c + d + e + f + g + h + i + j + k + l + m +
+ n + o + p + q + r + s + t + u + v + w + x + y + z)), collapse = ""))
[1] "a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z"
첫 번째 deparse_without_cutoff를 사용하면 여러 줄로 나눠지는 결과를 합쳐주긴 하지만 공백이 추가가 된다. gsub를 활용하여 여러 공백들을 하나의 공백으로 변경한 뒤에 합쳐줄 수 있다.(정규표현식을 몰라서 두번째 예시가 자세히 어떻게 동작하는지는 모르겠다)
13.2 연습문제
as.Date.default 함수는 왜 substitute, deparse를 사용하는가?, 왜 pairwise.t.test는 그것들을 사용하는가? 소스코드를 읽어 보라.
> as.Date.default
function (x, ...)
{
if (inherits(x, "Date"))
x
else if (is.logical(x) && all(is.na(x)))
.Date(as.numeric(x))
else stop(gettextf("do not know how to convert '%s' to class %s",
deparse(substitute(x)), dQuote("Date")), domain = NA)
}
<bytecode: 0x000001e9ff770ec8>
<environment: namespace:base>
as.Date.default 는이를 사용하여 예기치 않은 입력 표현식 (날짜 나 NA가 아님)을 문자열로 반환하기 위해서 사용한다.
pairwise.t.test ()는이를 사용하여 데이터 입력 이름 (응답 벡터 x 및 그룹화 요소 g)을 문자열로 변환하여 원하는 출력의 일부로 형식화하기 위해서 사용합니다.
> pairwise.t.test
function (x, g, p.adjust.method = p.adjust.methods, pool.sd = !paired,
paired = FALSE, alternative = c("two.sided", "less",
"greater"), ...)
{
if (paired & pool.sd)
stop("pooling of SD is incompatible with paired tests")
DNAME <- paste(deparse(substitute(x)), "and", deparse(substitute(g)))
g <- factor(g)
p.adjust.method <- match.arg(p.adjust.method)
alternative <- match.arg(alternative)
if (pool.sd) {
METHOD <- "t tests with pooled SD"
xbar <- tapply(x, g, mean, na.rm = TRUE)
s <- tapply(x, g, sd, na.rm = TRUE)
n <- tapply(!is.na(x), g, sum)
degf <- n - 1
total.degf <- sum(degf)
pooled.sd <- sqrt(sum(s^2 * degf)/total.degf)
compare.levels <- function(i, j) {
dif <- xbar[i] - xbar[j]
se.dif <- pooled.sd * sqrt(1/n[i] + 1/n[j])
t.val <- dif/se.dif
if (alternative == "two.sided")
2 * pt(-abs(t.val), total.degf)
else pt(t.val, total.degf, lower.tail = (alternative ==
"less"))
}
}
else {
METHOD <- if (paired)
"paired t tests"
else "t tests with non-pooled SD"
compare.levels <- function(i, j) {
xi <- x[as.integer(g) == i]
xj <- x[as.integer(g) == j]
t.test(xi, xj, paired = paired, alternative = alternative,
...)$p.value
}
}
PVAL <- pairwise.table(compare.levels, levels(g), p.adjust.method)
ans <- list(method = METHOD, data.name = DNAME, p.value = PVAL,
p.adjust.method = p.adjust.method)
class(ans) <- "pairwise.htest"
ans
}
<bytecode: 0x000001e9fe9b0b50>
<environment: namespace:stats>
연습문제 13.3
pairwise.t.test는 deparse가 항상 길이가 1인 문자형 벡터를 반환한다고 가정한다. 이런 기대를 위반하는 입력을 만들 수 있는가? 무슨 일이 일어나는가?
결론부터 말하자면 deparse에 들어가는 표현식을 길게 작성하면 deparse는 길이가 1인 문자열로 반환하지 않고 문자형 벡터를 n개로 나눠서 반환하기에 pairwise.t.test 실행 시 출력되는 문구는 길이 n에 맞춰서 여러개 출력된다.
d=1
pairwise.t.test(2, d+d+d+d+d+d+d+d+d+d+d+d+d+d+d+d+d)
#>
#> Pairwise comparisons using t tests with pooled SD
#>
#> data: 2 and d + d + d + d + d + d + d + d + d + d + d + d + d + d + d + d + 2 and d
#>
#> <0 x 0 matrix>
#>
#> P value adjustment method: holm
pairwise.t.test의 두번째 인자 "d+d+d+d+d+d+d+d+d+d+d+d+d+d+d+d+d"가 너무 길어 길이가 2인 문자형 벡터를 반환한다. 따라서 pairwise.t.test 실행 시 출력되는 메세지는 paste를 통해서 합쳐진 후에 출력하게 되는데 길이가 가장 긴 2에 맞추어 2번 출력되게 된다.
연습문제 13.4
아래에서 정의한 f는 단지 subsitute를 호출한다. 왜 그것을 g를 정의하는데 사용하지 못하는가? 다른 말로 하면 다음 코드는 무엇을 반환하는가?
f <- function(x) substitute(x)
g <- function(x) deparse(f(x))
g(1:10) # -> x
g(x) # -> x
g(x + y ^ 2 / z + exp(a * sin(b))) # -> x
위 코드에 주석으로 결과값도 함께 작성되어 있다. 모두 x를 출력한다. g 함수의 deparse(f(x))에서 f(x)는 표현식이 아니기 때문에 정상적으로 실행되고 프로미스(표현식?)x를 출력한다. deparse 는 표현식 x를 받아서 문자열 "x"로 출력한다.
g(1:10)을 실행하여 f(x = 1:10)이라고 알려주어도 f(x)는 프로미스 x를 출력한다. 따라서 g를 어떻게 실행하여도 "x"가 출력된다.
13.2 서브세트에서 비표준적평가
R에서는 평가되지 않은 코드를 이용하여 보다 많은 일을 할 수 있다. subset을 살펴보자
> sample_df <- data.frame(a = 1:5, b = 5:1, c = c(5, 3, 1, 4, 1))
> subset(sample_df, a >= 4) # sample_df[sample_df$a>=4, ] 와 같음
a b c
4 4 2 4
5 5 1 1
> subset(sample_df, b ==c) # sample_df[sample_df$b == sample_df$c, ]와 같음
a b c
1 1 5 5
5 5 1 1
subset을 활용하면 데이터프레임의 이름을 적게 사용하여 타이핑 양을 많이 줄일 수 있다. subset은 a>=4, b ==c라는 표현식을 현재의 전역환경이 아니라 특정한 데이터프레임에서 평가된다. 이것이 비표준적 평가의 예이다.
subset에서 비표준적 평가를 하기 위해서는 globalenv()$x가 아니라 sample_df$x가 필요하다. 이것을 위해서 eval함수가 필요하고 이 함수는 표현식을 취해 지정된 환경에서 평가한다.
eval을 살펴보기 전에 quote라는 함수가 필요한데 이는 substitute처럼 평가되지 않은 표현식을 파악하지만, 다른 변환을 가하지 않고 항상 다음과 같이 그 입력을 반환한다.
> quote(1:10)
1:10
> quote(x)
x
> quote(x + y^2)
x + y^2
eval을 실험하기 위해서는 quote가 필요하다. eval의 첫번째 인자가 표현식이기 때문이다. 하나의 인자만 제공하면 eval은 현재 환경에서 해당 표현식을 평가한다.
> eval(quote(x <- 1))
> eval(quote(x))
[1] 1
> eval(quote(y))
Error in eval(quote(y)) : object 'y' not found
quote와 eval은 반대다, 아래의 사례에서 eval은 quote의 각 층을 벗겨낸다.
> quote(2 + 2)
2 + 2
> eval(quote(2 + 2))
[1] 4
> quote(quote(2 + 2))
quote(2 + 2)
> eval(quote(quote(2 + 2)))
2 + 2
> eval(eval(quote(quote(2 + 2))))
[1] 4
eval의 두번째 이나는 코드가 실행될 환경을 지정한다.
> x <- 10
> eval(quote(x))
[1] 10
>
> e <- new.env()
> e$x <- 20
> eval(quote(x), e)
[1] 20
eval은 환경에 제약될 필요는 없으며, 리스트나 데이터프레임이 될 수도 있다.
> eval(quote(x), list(x = 30))
[1] 30
> eval(quote(x), data.frame(x = 40))
[1] 40
위 예시가 아래에서 어떻게 활용되는지 볼 수 있다. sample_df(데이터프레임)환경에서 해당 표현식을 평가하기 때문에 a를 호출하면 sample_df 환경 내의 a 를 호출하게 된다.
> eval(quote(a >= 4), sample_df)
[1] FALSE FALSE FALSE TRUE TRUE
> eval(quote(b == c), sample_df)
[1] TRUE FALSE FALSE FALSE TRUE
eval을 사용하면서 공통적인 실수는 첫번째 인자를 인용하는 것을 잊는 것이다. 인용한다는 것은 코드를 동작하지 않고 표현식 그대로 간직하는 것으로 이해하면 좋을 것 같다. 아래에서 첫번째 인자를 인용하는 것을 잊게 되면 전역환경에서 표현식이 실행되게 된다.
그래서 evalq를 통해서 eval(quote(expr)), )을 eval(expr, )로 사용할 수 있도록 한다. 예시는 연습문제 13.2.6을 보자
> a <- 10
> eval(quote(a), sample_df)
[1] 1 2 3 4 5
>
> eval(a, sample_df)
[1] 10
> eval(quote(b), sample_df)
[1] 5 4 3 2 1
> eval(b, sample_df)
Error in eval(b, sample_df) : object 'b' not found
eval, substitute를 활용하여 subset 함수를 만들 수 있다. 표현식을 인용하고, 해당 표현식을 데이터프레임 환경에서 평가하고 해당 결과를 데이터프레임의 인덱스로 활용하는 것이다. 아래를 보자.
> subset2 <- function(x, condition){
+ condition_call <- substitute(condition)
+ r <- eval(condition_call, x)
+ x[r, ]
+ }
> subset2(sample_df, a>=4)
a b c
4 4 2 4
5 5 1 1
위 예에서 quote 대신 substitute를 사용하였는데, 사실 substitute의 기능에 대해서 잘 이해가 안된다. 전역환경에서 quote와는 기능이 같은 것으로 보이는데 두 개의 차이점이 뭘까, 일단 substitute가 함수 내에서 실행되게 되면 substitute(condition)은 condition을 한 번 평가하고 condition안에 들어있는 표현식을 캡쳐한다. 따라서 condition 인자에 표현식을 작성한 것을 원하는데로 가져올 수 있다.
몰론 quote로도 동일하게 작성이 가능하다. quote는 substitite처럼 환경에 따른 변환? 을 하지 않고 있는 그대로 인용하기 때문에 아래와 같이 인자에 표현식을 미리 인용해서 보내줘야 한다.
> subset3 <- function(x, condition){
+ condition_call <- condition
+ r <- eval(condition_call, x)
+ x[r, ]
+ }
> subset3(sample_df, quote(a>=4))
a b c
4 4 2 4
5 5 1 1
사실 substitue, quote, enquote등의 기능에 대해서는 help 페이지를 자세히 살펴볼 필요가 있다. 하지만 위 예제에서 쓰이는 정도만 알아도 함수를 작성하는데 큰 도움이 될 것 이다. 아래는 마지막으로 함수 내부에서 quote, substitute가 어떻게 동작하는지 비교한 코드이다.
> f <- function(argx){
+ list(quote(argx), substitute(argx), argx)
+ }
> suppliedargx <- 100
> f(argx = suppliedargx)
[[1]]
argx
[[2]]
suppliedargx
[[3]]
[1] 100
연습문제 13.2.1
다음 코드의 결과를 예측해 보라
> eval(quote(eval(quote(eval(quote(2+2))))))
[1] 4
> eval(eval(quote(eval(quote(eval(quote(2+2)))))))
[1] 4
> quote(eval(quote(eval(quote(eval(quote(2+2)))))))
eval(quote(eval(quote(eval(quote(2 + 2))))))
괄호를 바깥쪽 부터 풀어가기 때문에 1번, 2번은 eval이 quote를 한꺼풀씩 벗겨내면서 2+2가 평가되고 4가 출력된다. 3번은 quote가 제일 바깥쪽에 있기 때문에 quote 안쪽의 모든 표현식이 인용된다.
연습문제 13.2.2
subset2는 단일 열 데이터프레임과 함께 사용하는 데 버그가 있다. 다음 코드는 무엇을 반환해야 하는가? 어떻게 subset2를 수정하여 올바른 객체 유형을 반환하게 할 수 있는가?
subset2 <- function(x, condition){
condition_call <- substitute(condition)
r <- eval(condition_call, x)
x[r, ,drop = F]
}
sample_df2 <- data.frame(x = 1:10)
subset2(sample_df2, x > 8)
subset2 함수에서 x[r, ] 부분을 x[r, , drop=FALSE)로 변경해주면 된다. data.frame을 인덱싱 할 때 단일 열을 추출하면 벡터로 반환하기 때문에 drop=F를 주어서 항상 data.frame으로 반환하도록 할 수 있다.
연습문제 13.2.3
실제 subset함수 subset.data.frame는 조건에서 결측값을 제거한다. 문제의 행을 제외하도록 subset2를 수정하라.
> subset2 <- function(x, condition){
+ condition_call <- substitute(condition)
+ r <- eval(condition_call, x)
+ x <- x[which(r), ,drop = F]
+ x
+ }
>
> sample_df2 <- data.frame(x = c(1:10, NA))
> subset2(sample_df2, x > 8)
x
9 9
10 10
위와 같이 subset2를 수정하면 NA값은 제외하고 산출한다. 다른 방법으로는 x[!is.na(r) & r, , drop = FALSE]로 사용하거나, r[is.na(r)] <- FALSE.를 NA를 FALSE로 변환하여 TRUE만 호출되도록 할 수 있다.
연습문제 13.2.4
subset2 내부에 substitute 대신 quote를 사용하면 어떻게 되는가?
substitute 대신 quote를 사용하면 condition을 인용하여 eval(condition, x)가 되고 x 객체에서 condition이라는 값을 찾을 수가 없으므로 오류가 생성된다. 자세한 내용은 여기 4번 문제에 대한 해설에서 찾아보도록 하자
연습문제 13.2.5
subset의 두번째 인자는 변수를 선택할 수 있게 해준다. 그 인자는 변수 이름을 마치 그 위치처럼 다룬다. 이것은 cylinder 변수를 제외하기 위해 subset(mtcars, , -cyl)처럼 하거나 disp와 drat사이의 모든 변수를 선택하기 위해 subset(mtcars, , disp:drat)처럼 사용하는 것을 가능하게 해 준다. 어떻게 이렇게 동작하는가? 이해하기 쉽도록 함수를 하나 만들었다.
select <- function(df, vars){
vars <- substitute(vars) # 변수를 표현식으로 인용
var_pos <- setNames(as.list(seq_along(df)), # 데이터프레임 길이만큼 seq_along으로 배열을 만들고 리스트로 변환한다.
names(df)) # setNames 함수로 각 리스트 값에 이름을 부여한다.
pos <- eval(vars, var_pos) # 인용된 -cyl은 먼저 cyl을 var_pos(setNames로 생성된 리스트)에서 찾아오는데
df[, pos, drop=FALSE] # 해당 값은 해당 변수의 위치를 나타낸다. -와 만나서
} # df[, -2, pos, drop = FALSE]를 통해 cyl이 제거된다.
select(mtcars, -cyl)
연습문제 13.2.6
evalq는 어떤 일을 하는가? eval, quote를 모두 사용하는 위의 사례에 대해 타이핑 양을 줄이기 위해 이 함수를 사용해 보라. 아래에 예시가 있다.
identical(eval(quote(x)), evalq(x)) # -> TRUE
eval(quote(eval(quote(eval(quote(2 + 2)))))) #->
evalq(evalq(evalq(2 + 2)))
eval(eval(quote(eval(quote(eval(quote(2 + 2))))))) #->
eval(evalq(evalq(evalq(2 + 2))))
quote(eval(quote(eval(quote(eval(quote(2 + 2))))))) #->
quote(evalq(evalq(evalq(2 + 2))))
간단하게 evalq는 eval(quote(표현식))을 대신한다. quote(표현식)부분을 표현식 만 작성할 수 있도록 한다.
13.3 이슈 스코핑
연습문제 13.2.3에서 subset2 함수를 작성하였는데, 이는 아래와 같이 표현식에 사용된 객체가 함수 내부에 정의된 경우 원하지 않는 결과가 발생한다.
x <- 4
sample_df <- data.frame(a = 1:5, b = 5:1, c = c(5, 3, 1, 4, 1))
subset2 <- function(x, condition){
condition_call <- substitute(condition)
r <- eval(condition_call, x)
x <- x[which(r), ,drop = F]
x
}
subset2(sample_df, a == x)
eval이 데이터프레임에서 변수를 찾지 못하면 subset2의 환경을 찾는다. 이것은 원하는 동작이 아니므로 데이터 프레임에서 변수를 찾지 못할 경우 어디에서 찾아야 할지를 eval에 알려줘야 한다. 아래와 같이 작성한다.
subset2 <- function(x, condition){
condition_call <- substitute(condition)
r <- eval(condition_call, x, enclos = parent.frame())
x[r, ]
}
enclos인자에 parent.frame()을 작성하면 데이터프레임에서 변수를 찾은 뒤에 없으면 subset2 함수가 정의된 환경에서 변수를 찾는다. enclos가 아닌 다른 방법은 list2env를 사용하는 것이다.
subset2 <- function(x, condition){
condition_call <- substitute(condition)
env <- list2env(x, parent = parent.frame())
r <- eval(condition_call, env)
x[r, ]
}
즉. 데이터 프레임을 데이터 프레임에서 변수를 찾지 못할 경우에 어디서 찾으라는 명령을 함께 합쳐서 환경을 생성하여 알려주는 것이다.
연습문제 13.3.1
plyr::arrange()는 subset()과 유사하게 동작하지만, 행을 선택하는 대신 그것을 다시 정렬한다. 어떻게 그렇게 동작하는가? substitute(order(...))은 무엇을 하는가?
arrange_indices <- function (df, ...){
stopifnot(is.data.frame(df))
ord <- eval(substitute(order(...)), df, parent.frame())
ord
}
identical(arrange_indices(iris, Species, Sepal.Length),
order(iris$Species, iris$Sepal.Length))
substitute(order(...))는 quote(order(...))과 달리 ...이 무엇인지 참조한 뒤에 order(참조한 변수들)을 인용한다. 그 다음 eval은 인용된 표현을 df에서 평가한다. 이 때 df안에서 order내부의 함수들이 참조되지 않으면 parant.frame()이 enclos에 적용되어 있으므로 arrange_indices가 선언된 환경에서 다시 찾는다.
이 예에서는 order 내부의 변수들이 df내에서 모두 참조가 되고, 참조된 변수들의 값으로 order가 작동하여 정렬된 인덱스를 반환한다. 인덱스가 제대로 반환되었는지 identical 함수를 통해 order(order(iris$Species, iris$Sepal.Length)와 일치하는지 확인하였다.
연습문제 13.3.2
transform은 무엇을 반환하는가? 문서를 읽어보라. substitute는 무엇을 하는가
※ 먼저 기억할 점(개인적) 1) transform의 ...에 들어가는 표현식 형태는 variable1 = a 이런식으로 들어간다. 이렇게 들어가면 ...을 list로 받았을 때 list의 이름이 생성된다. 2) c()함수는 벡터를 이어주기도 하지만 리스트를 이어주기도 한다.
# Setting "..." as function argument allows the user to specify any kind of extra
# argument to the function. In this case we can expect arguments of the form
# new_col1 = foo(col_in_data_argument), new_col2 = foo(col_in_data_argument),...
> transform.data.frame
function (`_data`, ...)
{
# subsitute(list(...)) takes the dots into a list and just returns the expression
# `list(...)`. Nothing is evaluated until now (which is important).
# Evaluation of the expression happens with the `eval()` function.
# This means: all the names of the arguments in `...` like new_col1, new_col2,...
# become names of the list `e`.
# All functions/variables like foo(column_in_data_argument) are evaluated within
# the context (environment) of the `_data` argument supplied to the `transform()`
# function (this is specified by the second argument of the eval() function).
e <- eval(substitute(list(...)), `_data`, parent.frame())
# Everything that happens from now on is just about formatting and
# returning the correct columns:
# We save the names of the list (the names of the added columns)
tags <- names(e)
# We create a numeric vector and check if the tags (names of the added columns)
# appear in the names of the supplied `_data` argument. If yes, we save the
# column number, if not we save an NA.
inx <- match(tags, names(`_data`))
# We create a logical vector, which is telling us if a column_name is already in the
# data.frame (TRUE) or really new (FALSE)
matched <- !is.na(inx)
# If any new column is corresponding to an old column name,
# the correspong old columns will be overwritten
if (any(matched)) {
`_data`[inx[matched]] <- e[matched]
`_data` <- data.frame(`_data`)
}
# If there is at least one new column name, all of these new columns will be bound
# on the old data.frame (which might have changed a bit during the first if). Then the
# transformed `data_` is returned
if (!all(matched))
do.call("data.frame", c(list(`_data`), e[!matched]))
# Also in case of no new column names the transformed `data_` is returned
else `_data`
}
코드는 처음 봤을 때 엄청 복잡하다고 생각했다. 실제로도 이해하는데 오래 걸렸다.
간단히 설명하면 e라는 list를 생성한다. 여기에는 `_data`에 존재하는 변수들과 아닌 변수들이 함께 있는데 `_data`에 a, b 라는 변수가 있을 때 a = a *100, b = a*b/100이런식으로 '_data`에 이미 존재하는 변수명을 사용하여 덮어쓰게 되는 값들과 c = a + b처럼 c라는 새로운 변수명의 값도 함께 존재한다.
함수는 `_data`에 존재하는 즉. 덮어쓰기 형식으로 생성된 값들을 먼저 `_data`에 저장하고, 그 다음 _data`에 없었던 새로운 변수들을 마지막에 추가하여 `_data` 를 반환한다. 이렇게 하는 이유는 아마 새롭게 생성되는 변수명 들은 데이터프레임의 마지막에 추가되도록 하여 기존 데이터 프레임이 무엇이었는지 헷갈리지 않게 하기 위함인가?
아무튼 transform의 동작은 위와 같고 mutate 함수와의 차이점은 mutate는 transform과 달리 방금 인자에서 선언한 열을 바로 참조할 수 있게 순차적으로? 동작한다. 이는 다음 연습문제에서 알아본다.
연습문제 13.3.3
plyr::mutate는 transform과 유사하지만, 방금 생성된 열을 참조할 수 있게 하기 위해 순차적으로 변형에 적용된다.
df <- data.frame(x = 1:5)
transform(df, x2 = x*x, x3 = x2*x)
plyr::mutate(df, x2 = x*x, x3 = x2*x)
어떻게 동작을 변화시키는가? mutate와, transform간의 핵심적인 차이는 무엇인가?
> mutate
function (.data, ...)
{
stopifnot(is.data.frame(.data) || is.list(.data) || is.environment(.data))
# we catch everything supplied in `...`. But this time we save this in a list of expressions.
# However, again the added column names will be the names of this list.
cols <- as.list(substitute(list(...))[-1])
cols <- cols[names(cols) != ""] # all unnamed arguments in `...` will be thrown away, in
# contrast to `transform()` above, which just creates new columnnames.
# Now a for loop evaluates all added columns iteratively in the context (environment)
# of the data.
# We start with the first added column:.
# If the column name is already in the data, the old column will be overritten.
# If the column name is new, it will be created
# Since the underlying data (the environment for the evaluation) gets automatically
# "updated" in every iteration of the for loop, it will be possible to use the new columns
# directly in the next iteration (which relates to the next added column)
for (col in names(cols)) {
.data[[col]] <- eval(cols[[col]], .data, parent.frame())
}
# Afterwards the data gets returned
.data
}
미리 알고 넘어가야 할 점은 as.list는 표현식을 받으면 해당 표현식을 분해해서 list로 저장한다 as.list(list(...)에서 list(...)이 표현식인 경우에 [[1]] 요소로 표현식 list를 저장하고 ...은 변수 선언 형태일테니 ...이 a=x형태라면 리스트의 2번째 요소($a)에는 표현식 x가 저장된다. 그리고 substitute(list(...))은 쉬운 예를 들면 substitute(list(a=x))일 때는 (내생각) x가 무엇인지 lexical scope하여 찾지만 값이 존재하지 않기 때문에 그대로 list(a=x)를 표현식으로 만드는 듯 하다.
as.list 부분에서 [[-1]]을 통해서 위에서 설명한 표현식 list(...)은 필요없기 때문에 제거하고 각 변수들 이름에 저장된 표현식들만 남긴다.
cols[names(cols) != ""] 부분을 통해서 이름이 없는 list는 제거한다. 즉 이름이 없다는 것은 표현식을 a=x 와 같은 형태로 함수의 인자로 넘겨줘야 하지만, 실수로 a와 같이 그냥 변수명만 적어준 경우에는 제거 되는데 그 이유는 우리가 만들려고 하는 mutate 함수는 먼저 새로운 변수들을 생성하는 표현식만 취한 뒤에 .data에 추가해 주기 위함이다. a와 같은 그냥 변수명만 적어준 경우는 만약 .data 데이터에 존재하는 변수라면 무시해도 되기 때문이다. mutate는 새로운 변수를 생성하여 데이터 프레임 끝에 붙여주는 함수이지 기존 데이터를 중간에 삽입해서 순서를 바꾸지는 않는다.
연습문제 13.3.4
with는 무엇을 하는가? with.default에 대한 소스 코드를 읽어 보라. within은 무엇을 하는가? 그것은 어떻게 동작하는가? within.data.frame에 대한 소스 코드를 읽어 보라. 그 코드는 왜 with보다 훨씬 더 복잡한가
function (data, expr, ...)
{
parent <- parent.frame()
e <- evalq(environment(), data, parent)
eval(substitute(expr), e)
l <- as.list(e, all.names = TRUE)
l <- l[!vapply(l, is.null, NA, USE.NAMES = FALSE)]
del <- setdiff(names(data), (nl <- names(l)))
data[nl] <- l
data[del] <- NULL
data
}
우선 알고 갈 내용은 이번엔 as.list는 환경을 받으면 해당 환경에 존재하는 객체들의 이름과 값들을 list의 이름과 값 형태로 반환한다. all.names의 기능은 help함수를 통해 개인적으로 알아보자.
e <- evalq(environment(), data, parent) 이 코드는 enviroment라는 함수를 (data, parent.frame)환경에서 실행하여 data의 환경을 e변수에 저장한다. 여기서 (data, parent.frame)환경이라고 하면 표현식을 평가할 때 data에서 찾고 없으면 그의 parent.frame (함수가 선언되는 환경)에서 찾을 수 있도록 하는 환경이다.
결론적으로 within.data.frame은 표현식을 주어진 데이터 프레임 환경에서 평가한 뒤에 transform함수처럼 새로운 변수를 데이터 프레임에 추가하여 반환하는 기능을 한다.
원래의 설명은 "In contrast to with(), which returns the value of the evaluated expression, within() returns the modified object. So within() can be used as an alternative to base::transform(). within() first creates an environment with data as parent and within()’s calling environment as grandparent. This environment becomes changed, since afterwards the expression is evaluated inside of it. The rest of the code converts this environment into a list and ensures that new variables are not overriden by the former ones."과 같은데 아직까지는 parent, grandparent 에 대해서 잘 모르고 있어서 간단히 설명하고 넘어가도록 하자.
13.4 다른 함수에서 호출
전형적으로 언어에서의 컴퓨팅은 함수가 사용자에 의해 직접적으로 호출될 때 가장 유용하고 다른 함수에 의해 호출될 때 덜 유용하다. subset이 타이핑을 절약할 수 있지만, 실제로 인터랙티브하지 않게 사용하는 것은 어렵다. 예를 들어 데이터의 행 서브세트를 무작위적으로 다시 정렬하는 함수를 생성하려고 한다고 하자. 그렇게 하는 좋은 방법 중 하나는 재정렬하는 함수와 선택하는 함수를 합성하는 것이다. 시도해 보자
> sample_df <- data.frame(a=1:5, b=2:6, c=3:7)
> subset2 <- function(x, condition){
+ condition_call <- substitute(condition)
+ r <- eval(condition_call, x, parent.frame())
+ x[r, ]
+ }
>
> scramble <- function(x){x[sample(nrow(x)), ]}
> subscramble <- function(x, condition){
+ scramble(subset2(x, condition))
+ }
>
> subscramble(sample_df, a >= 4)
Error in eval(condition_call, x, parent.frame()) : object 'a' not found
... 왜 안되는지 이해가 안된다. 어쨋든 이렇게 사용하면 동작하지 않는다.
무엇이 잘못되었는가? 이유를 찾아내기 위해 subset2를 debug로 코드 줄 단위로 살펴보자.
debugonce(subset2)
subscramble(sample_df, a >= 4)
#> debugging in: subset2(x, condition)
#> debug at #1: {
#> condition_call <- substitute(condition)
#> r <- eval(condition_call, x, parent.frame())
#> x[r, ]
#> }
n
#> debug at #2: condition_call <- substitute(condition)
n
#> debug at #3: r <- eval(condition_call, x, parent.frame())
r <- eval(condition_call, x, parent.frame())
#> Error in eval(expr, envir, enclos) : object 'a' not found
condition_call
#> condition
eval(condition_call, x)
#> Error in eval(expr, envir, enclos) : object 'a' not found
Q
무엇이 문제인지 알 수 있는가? condition_call은 표현식 조건을 포함한다. 그러므로 condition_call은 평가할 때 조건도 평가하여 a>=4의 값을 가진다. 그러나 이것은 계산될 수 없는데, a라고 불리는 객체가 그 부모 환경에 없기 때문이다. 그러나 a가 전역환경에 설정되어 있다면 보다 혼란스러운 일들이 생길 수 있다. ????무슨 말이지....
> a <- 4
> subscramble(sample_df, a==4)
a b c
3 3 4 5
5 5 6 7
4 4 5 6
2 2 3 4
1 1 2 3
>
> a <- c(1, 1, 4, 4 ,4, 4)
> subscramble(sample_df, a>=4)
a b c
3 3 4 5
NA NA NA NA
4 4 5 6
5 5 6 7
왜 이런지 이해가 안된다.
어쨋든 substitute를 사용하는 비표준 평가는 타이핑을 줄일 수 있지만, 다른 함수에서 호출하기에는 어려울 수 있다.
자신이 개발자라면 항상 이스케이프 해치, 즉 표준적 평가를 사용하는 함수의 대안적인 버전을 제공해야 한다. 이 겨웅에서는 이미 인용된 표현식을 취하는 subset2버전을 작성할 수 있다.
> subset2_q <- function(x, condition){
+ r <- eval(condition, x, parent.frame())
+ x[r, ]
+ }
이제 subset2_q를 사용하기 위해 subset2와 subscramble 둘 모두를 다시 작성할 수 있다.
> subset2 <- function(x, condition){
+ subset2_q(x, substitute(condition))
+ }
> subscramble <- function(x, condition){
+ condition <- substitute(condition)
+ scramble(subset2_q(x, condition))
+ }
> subscramble(sample_df, a>=3)
a b c
3 3 4 5
4 4 5 6
5 5 6 7
> subscramble(sample_df, a>=3)
a b c
5 5 6 7
4 4 5 6
3 3 4 5
베이스 R 함수는 다른 종류의 이스케이프 해치를 사용하는 경향이 있다. 그 함수는 종종 NSE를 종료하는 인자를 갖고 있다. 예를 들어 require는 character.only=TRUE를 갖고 있다. 다른 인자의 행위를 바꾸는데 어떤 인자를 사용하는 것은 함수 호출을 이해하기 더 어렵게 하기 때문에 좋은 생각이 아니다. 무슨 말이냐 ..
연습문제 13.4.1
아래의 R 함수는 모두 NSE를 사용한다. 각각에 대해 어떻게 NSE를 사용하는지 설명하고, 그것의 이스케이프 해치를 판단하기 위해 문서를 읽어 보라.
- rm()
- library() and require()
- substitute()
- data()
- data.frame()
<rm>
For NSE in rm(), we just look at its first two arguments: ... and list = character(). If we supply expressions to ... (which can also be character vectors) , these will be caught by match.call() and become an unevaluated call (in this case a pairlist). However, rm() copies and converts the expressions into a character representation and concatenates these with the character vector supplied to the list argument. Then the removing starts… The escape hatch is to supply the objects to be removed as a character vector to rm()’s list argument. (무슨 말인지 모르겠다.)
<library, require>
library, require은 charater.only = FALSE에 의해서 패키지명 들은 as.cahracter(substitute(package))에 의해서 변환되어 ggplot2를 주던, "ggplot2"를 주던 동일한 결과를 수행한다. 이 기능을 사용하지 않고 "ggplot2"의 형태로만 불러오고 싶다면 character.only = TRUE를 사용하면 된다.
<substitute>
substitute, eval, quote는 비표준적 평가를 위한 기본 함수이다. 그것이 어떻게 동작하는지 알려면, 구문 트리 그리고 C코드 내부에서 봐야한다. substitute의 문제가 되는 행위는 꽤 명확하다. 그것은 예측가능하게 하는 몇가지 통찰력이 있을 수 있지만, substitute는 expr, env 두가지 인수만을 가지고 비표준적 평가를 위해 쓰여졌기 때문에, 이스케이프 해치가 존재하지 않는 것 같다?
<data>
data는 첫번째 인자로 ..., 두번째 list = character()를 가진다. ...에 인용되지 않은 것, 그리고 인용된 것을 전달한다. 이것들은 as.charater(substitute(list(...))[-1l)에 의해서 포착될 것이고, 리스트의 문자 입력과 연결된다. 이스케이프 해치는 rm과 유사하다. 명시적으로 list 인수를 사용하라. 이해가 잘 안된다 어쨋든 list가 인자들을 캐치하고, 캐치된 인자들은 결국 인용된 표현이든, 인용되지 않은 표현이든, 문자든 "인자1", "인자2", "인자3", ... 이렇게 출력이 되기 때문에 문자인지 아닌지 구분하지 않고 넣어도 된다.
Like rm() data() has the first arguments ... and list = character(). Again you can supply unquoted or quoted names to .... These will be caught, converted to character via as.character(substitute(list(...))[-1L]) and concatenated with the character input of the list argument. The escape hatch is similar to rm(): use explicitly the list argument.
>> 무슨 말인지 잘 모르겠네...
<data.frame>
... 이것도 무슨 말인지 이해가 안된다... 언젠가 이해할 수 있을까?
data.frame()’s first argument, ..., gets caught once via object <- as.list(substitute(list(...)))[-1L] and once x <- list(...). First one is used among others to create rownames. This can be suppressed via the setting of the argument row.names, which lets you supply a vector or specifing a column of the data.frame for the explicit naming of rows. x will be deparsed later and is then used to create the columnnames. Since this process underlies several complex rules in cases of “special namings”, data.frame() provides the check.names argument. One can set check.names = FALSE, to ensure that columns will be named however they are supplied to data.frame().
연습문제 13.4.2
베이스 함수인 match.fun, page, 그리고 ls는 모두 자동적으로 표준적 평가나 비표준적 평가를 결정하려고 한다. 각각은 다른 접근법을 사용한다. 각 접근법의 유사점과 차이점을 찾아보라
<match.fun>
match.fun은 길이가 하나 인 문자 나 기호 이외의 것을 전달하면 NSE를 사용하고 그렇지 않으면 NSE를 사용하지 않습니다.
> match.fun("which")
function (x, arr.ind = FALSE, useNames = TRUE)
{
wh <- .Internal(which(x))
if (arr.ind && !is.null(d <- dim(x)))
arrayInd(wh, d, dimnames(x), useNames = useNames)
else wh
}
<bytecode: 0x000001d879bca490>
<environment: namespace:base>
길이가 1인 문자를 넣지 않으면 nse를 사용한다.... 그렇다고 한다....
<page>
한 문자 이외의 문자를 전달하면 page에서 NSE를 사용합니다. 기호는 여전히 NSE를 트리거합니다.
... 그렇다고 한다.
<is>
ls는 변수로 전달 된 디렉토리를 평가할 수없는 경우 NSE 대체를 트리거하고 결과가 문자가 아닌 경우 NSE deparse를 트리거합니다.
... 그렇다고 한다.
연습문제 13.4.3
pryr::mutate를 두 개의 함수로 분해하고 이스케이프 해치를 추가하라. 함수 하나는 평가되지 않은 입력을 파악해야 한다. 다른 함수는 데이터 프레임과 리스트로 된 표현식을 위해 계산을 수행해야 한다.
get_cols <- function(...) {
ll <- as.list(substitute(list(...)))
ll[names(ll) != ""]
}
mutate_cols <- function(df, cols) {
for (col in names(cols)) {
df[[col]] <- eval(cols[[col]], df, parent.frame())
}
df
}
mutate2 <- function(df, ...) {
mutate_cols(df, get_cols(df, ...))
}
# a little test
df <- data.frame(x = 1:5)
identical(
plyr::mutate(df, x2 = x * x, x3 = x2 * x),
mutate2(df, x2 = x * x, x3 = x2 * x)
)
#> [1] TRUE
뭐라고 설명이 되어 있는데, 무슨 말인지 이해가 안된다. 링크
연습문제 13.4.4
ggplot2::aes의 이스케이프 해치는 무엇인가? plyr::()의 경우에는 무엇인가? 공통적으로 가지는 것은 무엇인가? 그 차이의 장점과 단점은 무엇인가?
- One can call rename_aes directly.
- plyr::. lets you specify an env in which to evaluate ....
Both evaluate ... using match.call() and create a structure out of them.
plyr::. probably requires less knowledge about internals, but is also less customizable.
뭔소리 하는지 모르겠다.
연습문제 13.4.5
소개한 버전의 subset2_q는 실제 코드를 단순화한 것이다. 왜 다음의 버전이 더 좋은가?
subset_q <- function(x, cond, env = parent.frame()){
r <- eval(cond, x, env)
x[r, ]
}
뭐라고 설명이 되어 있는데, 무슨 말인지 이해가 안된다. 링크
13.5 대체
비표준적 평가를 사용하는 대부분의 함수는 이스케이프 해치를 제공한다. 그러나 이것을 가지지 않는 함수를 호출하기를 원한다면 무슨 일이 발생하는가? 예를 들어, 두 변수의 이름이 주어진 상황에서 lattice 패키지를 이용한 플롯을 생성하고자 한다고 상상해 보라.
> library(lattice)
> xyplot(mpg ~ disp, data = mtcars)
> x <- quote(mpg)
> y <- quote(disp)
> xyplot(x ~ y, data = mtcars)
Error in tmp[subset] : object of type 'symbol' is not subsettable
이스케이프 해치란 비표준적 평가를 사용하는 함수에서도 이미 인용된 표현식을 취하여 사용할 수 있도록 표준적 평가를 사용할 수 있도록 하는 것이다?
pryr 패키지의 subs 함수는 substitute와 달리 전역환경에서도 대체를 허용한다. 그 점을 제외하고는 substitute와 기능이 동일하다.
> a <- 1
> b <- 2
> pryr::subs(a + b)
1 + 2
subs와 substitute는 아래와 같이 리스트를 통한 대안 환경을 사용할 수도 있다.
> library(pryr)
> subs(a + b, list(a = "y"))
"y" + b
> subs(a + b, list(a = quote(y)))
y + b
> subs(a + b, list(a = quote(y())))
y() + b
R에서 모든 행동은 함수 호출이므로 +를 다른 함수로 대체할 수도 있다
> subs(a + b, list(`+` = quote(f)))
f(a, b)
> subs(a + b, list(`+` = quote(`*`)))
a * b
다음과 같은 이상한 코드를 만들 수도 있다.
> subs(y <- y + 1, list(y = 1))
1 <- 1 + 1
대체는 표현식에 있는 모든 이름을 시험하면서 대체가 가능한 경우 대체한다. 대체의 유형은 다음과 같다
- 원시 변수(ordinary variable)이면 변수의 값으로 대체된다.
- (함수 인자인)프로미스이면 그 프로미스와 연관된 표현식으로 대체된다.(무슨말이냐)
- ...이면 ...의 내용으로 대체된다.
위와 같은 경우가 아니라면 현재 그대로의 상태로 남아 있게 된다.
이것을 xyplot을 위한 올바른 호출을 생성하기 위해 사용할 수 있다.
> x <- quote(mpg)
> y <- quote(disp)
> subs(xyplot(x ~ y, data = mtcars))
xyplot(mpg ~ disp, data = mtcars)
함수 내에서는 보다 단순하다. x와 y를 인용할 필요가 없기 때문이다.(위의 두번째 규칙)
> xyplot2 <- function(x, y, data = data){
+ substitute(xyplot(x ~ y, data = data))
+ }
> xyplot2(mpg, disp, data = mtcars)
xyplot(mpg ~ disp, data = mtcars)
대체하기 위한 호출에 ...을 포함한다면 그 호출에 추가적인 인자를 더할 수 있다.
xyplot3 <- function(x, y, ...){
substitute(xyplot(x ~ y, ...))
}
xyplot3(mpg, disp, data = mtcars, col = "red", aspect = "xy")
플롯을 생성하기 위해서는 이 호출을 eval로 평가하면 된다.
13.5.1 대체를 위한 이스케이프 해치를 추가
substitute는 비표준적 평가를 사용하면서, 이스케이프 해치를 가지지 않는 함수다. 이것은 이미 변수에 저장된 표현식이 있는 경우 substitute를 사용할 수 없다는 것을 의미한다.
> library(pryr)
> x <- quote(a + b)
> substitute(x, list(a = 1, b = 2))
x
> subs(x, list(a = 1, b = 2))
x
substitute가 내장된 이스케이프 해치를 가지지 않아도, 이에 해당하는 것을 생성하기 위해 그 함수 자체를 사용할 수 있다.
> substitute_q <- function(x, env){
+ call <- substitute(substitute(y, env), list(y = x))
+ eval(call)
+ }
>
> x <- quote(a + b)
> substitute_q(x, list(a = 1, b = 2))
1 + 2
이렇게 하는 이유는 substitute의 인자로 인용된 표현식을 넣고 싶은데 substitute의 경우 글로벌 환경에서 대체를 수행하지 않고 바로 인용해 버리기 때문이다????. 위에서 작성한 substitute_q를 사용하면 글로벌 환경에서, 인자로 인용된 표현식을 받을 수 있다. x의 인자로 quote(a + b), env의 인자로 list(a = 1, b = 2)가 들어가면 함수 내부의 call에는 substitute(substitute(quote(a + b), list(a = 1, b = 2)))가 실행 되어 quote(1 + 2)가 저장이 되고 call(quote(1 + 2)은 eval을 만나서 평가되어 1 + 2라는 표현식을 반환한다...
어떻게 흘러가는지는 이해가 되나... 왜 이렇게 하는지는 아직 이해가 되질 않는다. 아직 완전히 이해하지 못했기 때문이겠지?
위 설명이 약간? 틀린 것 같다. 책 설명에 보면 먼저 substitute(substitute(y, env), list(y = x)가 평가 되면서 안쪽의 "substitute(y, env)"는 substitute(a + b, env)가 되어 substitute(a + b, env)라는 표현식을 반환하여 call에 저장한다. 그 다음 eval을 만나 subsitute(a + b, list(a = 1, b = 2)가 실행되어 1 + 2라는 표현식을 반환한다.
> 매우 복잡하다. 아직도 잘 이해가 안된다.
13.5.2 평가되지 않은 ...을 파악
다른 유용한 기법으로 ...에서 모든 평가되지 않은 표현식을 파악하는 것이 있다. 베이스 R 함수는 여러 가지 방법으로 이렇게 하지만, 다양한 상황에 걸쳐 잘 동작하는 기법이 한 가지 있다.
> dots <- function(...){
+ eval(substitute(alist(...)))
+ }
> y <- 2
> str(dots(x = 1, y, z = ))
List of 3
$ x: num 1
$ : symbol y
$ z: symbol
dots라고 정의한 함수는 alist함수를 통해서 단순히 모든 인자를 파악한다. 함수 내부를 보면 eval이라는 함수는 substitute(alist(...))이라는 인자를 받는다. 인자는 인용되지 않았으니 곧 바로 평가되어 alist(...)이라는 표현식이 반환되고, 해당 표현식은 eval의 환경에서 평가된다. 여기서 eval의 디폴트 환경은 parent.frame인데 함수가 정의된 환경에서 실행된다는 말이다. 즉. 전역환경에서 alist(...)은 평가된다.
하.. 무슨 말이야!!!!!!!!!!!!!!
연습문제 13.5.1
subs를 이용해 다음 각 쌍을 왼쪽에서 오른쪽으로 변환하라.
a + b + c -> a * b * c
f(g(a, b), c) -> (a + b) * c
f(a < b, c, d) -> if (a < b) c else d
subs(a + b + c, list("+" = quote(`*`))) # -> `a * b * c`
subs(f(g(a, b), c), list(g = quote(`+`),
f = quote(`*`))) # -> `(a + b) * c`
subs(f(a < b, c, d), list(f = quote(`if`))) # -> `if (a < b) c else d`
대략적으로 이해는 되지만 quote함수가 어떤 역할을 하는지는 이해가 되지 않는다...
연습문제 13.5.2
subs를 통해서 아래와 같은 변환이 안되는 이유를 설명하라.
a + b + c -> a + b * c
f(a, b) -> f(a, b, c)
f(a, b, c) -> f(a, b)
subs를 통해서 하나의 +만을 변경할 수는 없다. 일괄 변환만 가능하다. 함수의 인자를 추가하는 것과 제거하는 것도 되지 않는다. 오직 일괄적으로 변환하는 것만 가능하다.
연습문제 13.5.3
어떻게 pryr::named_dots가 동작하는가? 소스코드를 읽어 보라.
> dots(x = 1, y, z = )
$x
[1] 1
[[2]]
y
$z
> named_dots(x=1, y, z=)
$x
[1] 1
$y
y
$z
dots, named_dots의 차이는 named_dots의 경우 인자의 이름이 지정되지 않은 경우에도 값에 기반한 이름을 자동적으로 생성한다. dots는 인자의 이름이 지정되지 않은 경우에는 인자의 이름이 지정되지 않는다. ("")
> alist(x=1, y, z=)
$x
[1] 1
[[2]]
y
$z
dots의 기능과 base 함수인 alist의 기능은 같은 것으로 보인다.
> list(x=1, y, z=)
Error: object 'y' not found
쉽게 이해하자면 alist, dots, named_dots 등은 ...인자를 받아서 리스트 형태로 인용하여 저장한다고 생각하도록 하자.
13.6 비표준적 평가의 단점
NSE의 가장 큰 단점은 그것을 사용하는 함수가 더 이상 참조에 투명하지 않다는 것이다. 함수 f()가 참조에 투명하고, x ,y 모두 10이라면 f(x), f(y)는 모두 같은 결과를 반환할 것이다.
다음과 같은 코드는 추론하기가 어렵다
a <- 1
b <- 2
if ((b <- a + 1) > (a <- b - 1)) {
b <- b + 2
}
NSE를 사용하는 것은 함수가 참조에 투명하게 되는 것을 방해한다. 그러므로 그것은 단지 의미 있는 이득이 있을 때에만 사용할 만한 것이다. 예를 들어 library, require는 인용 부호 유무에 관계 없이 호출될 수 있는데, 내부적으로 deparse(substitute(x))에 다른 트릭을 추가하여 사용하기 때문이다.????????????
변수가 값에 연관되어 있다면 복잡해지기 시작한다. 어떤 패키지가 로드 될 것인가
ggplot2 <- "plyr"
library(ggplot2)
ggplot2가 로드 된다. 왜 그렇게 되는지 잘 모르겠다
비표준 평가가 가치가 있는 한 가지 상황은 data.frame이다. 명시적으로 제공되지 않을 때 자동으로 출력 변수의 이름을 짓기 위해 입력을 사용한다.
비표준 평가는 극단적으로 강력한 함수를 작성할 수 있도록 한다. 그러나 이런 함수는 하기도 어렵고 프로그래밍 하기도 어렵다. 이스케이프 해치를 제공하는 것 뿐만 아니라 새로운 영역에서 NSE를 사용하기 전에 그 비용과 편익을 주의 깊게 고려하라.
연습문제 13.6.1
library(pryr)
nl <- function(...){
dots <- named_dots(...)
lapply(dots, eval, parent.frame())
}
nl(1, 2 + 2, mean(c(3, 5)))
named_dots가 ...을 받아서 각각의 인자들의 이름과 표현식으로 이루어진 list를 생성하고, 생성된 리스트는 eval(parent.frame())에 의해서 전역 환경에서 평가된다.
연습문제 13.6.2
프로미스에 의존하는 대신 표현식과 그 환경을 명백하게 파악하도록 ~로 생성된 포물라를 사용할 수 있다. 인용을 명시적으로 하는 것의 장점과 단점은 무엇인가? 이것은 참조 투명성에 어떤 영향을 미치는가?
이런 식으로 수식을 사용하면 참조 투명성이 허용되지만 NSE 작업이 훨씬 더 장황하게 된다. NSE를 사용할 가치가 있는 이와 같은 수식을 사용하지 않아도 된다.
연습문제 13.6.3
링크에 있는 표준 비표준적 평가 규칙을 읽어 보라.
'R programming' 카테고리의 다른 글
ggplot tip (0) | 2019.11.07 |
---|---|
mutiple plot function 코드 뜯어 보기 (0) | 2019.09.28 |
%>% (R4DS 일부, help("%>%) 일부) 정리 (0) | 2019.09.02 |
apply 계열 함수 정리하기 (0) | 2019.09.01 |
tidyverse other verbs, tips (0) | 2019.09.01 |