Home Non Standard Evaluation in R
Post
Cancel

Non Standard Evaluation in R

Prologue 

R은 기본적으로 ‘연산’을 위한 도구다보니, 계산을 통해서 어떤 ‘값’을 구하고 그 ‘값’을 처리하는 것을 위주로 만들어졌다. 하지만 이게 어찌어찌하다보니 이제는 나름 일종의 ‘프로그래밍 언어’로 등극하게 되고,파이썬 등과 비교당하는 위치에까지 올라서게 된다. 우와. (물론 나는 이런 비교는 그다지 좋아하지는 않는다만.) 어쨌든 나도 최근 이걸로 팔자에 없는 ‘사용자 패턴 검증 시뮬레이터’를 만드는 일종의 미친 짓(?)도 하다보니, R의 프로그래밍 언어로서의 불편함을 다시 한 번 주위에 강력하게 주장하게도 되었다. R로 프로그래밍을 하려다보니, 아무래도 언어적 성격 상 ‘값’ 계산이 아닌 ‘수식’ 및 ‘변수’를 동적으로 호출하고 변경하기가 (R의 계산 편의성에 비교하면 극단적으로) 불편하기 때문이었다. 하지만 그렇다고 방법이 없는 건 아니고,우리 R의 신같은 존재인 해들리 위컴 옹께서 제창하신 plyr이라는 패키지를 통한, 일명 Non-Standard Evaluation(NSE) 방식으로 어느 정도를 가능하게 했다. 물론 좀 귀찮기는 했지만;; 나도 이게 뭐야…라면서 꾸역꾸역 쓰다 보니 꽤 쓸 만했다. 아무래도 이름만큼이나 코드의 깔끔함은 떨어지지만, 이를 통해 R의 활용도가 꽤 넓어진다는 면에 있어서는 긍정적으로 볼 수도 있을 것 같다. 거기다 이런 내용이 발표된 게 아직 1달도 되지 않았으니, 나같이 활용하고 이런 면을 긍정적으로 보는 사람이 늘어나면, 이런 방법도 보다 간결하게 정리되어 자리잡지 않을까?

동적 표현식 사용

R 함수에 수식을 외부에서 만들어서 적용하고 싶을 때, 이를 사용하느라 애먹는 게 가장 크다. 수식을 string으로 만들어서 넣어주면 string 자체로 인식을 해버리고, 변수에 string을 넣어서 사용하면 변수명을 string으로 인식해서 오류를 내기도 한다. 그래서 이런 string이나 변수의 값을 표현식으로 인식해서 함수에서 실행 가능하게 해주는 함수가 등장했으니, 바로 substitute()다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
f <- function(x) {
  substitute(x)
}
f(1:10)
#> 1:10

x <- 10
f(x)
#> x

y <- 13
f(x + y^2)
#> x + y^2

이 함수는 위와 같이 해당 내용의 값이 아닌 표현식 그대로를 반환한다. 그리고 이렇게 반환된 표현식을 string으로 가져올 때는 deparse()를 사용한다.

1
2
3
4
5
6
g <- function(x) deparse(substitute(x))
g(1:10)
#> [1] "1:10"
g(x)
#> [1] "x"

이를 활용해서 문자열로 식을 만들고 이를 적절한 형태로 함수에 던져준 후 적절히 풀어서 사용할 수 있다.

하지만 substitute()의 경우 일반적인 string이 아닌 promise라는 객체 형식을 사용하는데, 물론 대부분의 경우 그냥 문자열인 양 사용해도 상관없지만 아무래도 약간 껄끄러운 것은 감출 수 없고 문자열과 혼합해서 사용해야 할 경우 조금 귀찮아진다. 그래서 비슷하게 substitute-deparse combo처럼 쓸 수 있는 다른 조합을 찾아봤더니, 역시나 있다.

quote()의 경우 말 그대로 ‘문자열’을 반환한다. 그리고 quote()에서 사용한 것이나 일반 문자열을 그대로 사용할 수 있는 eval()이라는 함수가 있다. 나의 경우 이 두 함수의 콤비 플레이를 훨씬 많이 활용했다.

1
2
3
4
5
6
7
8
9
10
11
12
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

함수 호출 

이런 게 사용하기 가장 좋은 데가 사용자 함수를 생성한다든가 다른 함수에서 호출해서 사용할 때다. 나도 사용자 함수를 뱅만가지 만들면서 이 기능들을 유용하게 써먹었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
#> 4 4 2 4
#> 5 5 1 1
#> 3 3 3 1
subscramble(sample_df, a >= 3)
#>   a b c
#> 5 5 1 1
#> 3 3 3 1
#> 4 4 2 4

주의 사항 

  • 앞에서도 언급했듯이 substitute()의 경우 string type이 아니다. 그래서 사용할 때 약간 신경을 써야 하는데, 특히 변수명을 넣는 게 아니라 수식을 만들어서 (예: lm(x+y~z) 의 ‘x+y~z’ 등) 넣고 싶다면 quote()-eval() 콤보를 더 추천한다.
  • 참조의 투명성: R도 나름 프로그래밍 언어라(…) referentially transparent(참조 투명성)을 보장하고 있지만, NSE의 경우 이에 위배된다.
  • 이 함수들을 사용할 때는 따옴표(‘, “)를 아주 명확히 사용해 주어야 한다.

Epilogue

비표준 연산(NSE)은 나온 지도 얼마 안 되었고, 사실 아직 좀 애매한 부분도 많다. (나도 매뉴얼과 stackoverflow와 한동안 정말 친하게 지냈다. (…)) 거기다 이게 있다고 뭐 R이 서비스 프로그래밍용 언어로 거듭날 거냐면 그건 아직 아닌 것 같다. 여전히 무언가를 구현하는 데는 이걸로 부족한 것도 너무 많고 답답하다. 하지만 이번에 R을 좀 하드코어하게 쓰면서, NSE + dplyr의 조합이 꽤 강력하고 괜찮다는 것을 깨달았다. 이 조합을 잘 활용하고 기능도 좀 더 발달한다면 간단한 알고리즘 프로토타이핑 용으로 쓰기에 괜찮은 언어가 될 것이라고 생각한다.

Reference

    This post is licensed under CC BY 4.0 by the author.

    Data Hackerthon-단기 데이터 분석에 대한 소고

    베이즈 이론이 푸리에 정리를 만났을 때