1

E.g. I want to transform the code

mean(x)

to

fn(x)

everytime I see mean in the code.

replace_mean <- function(code) {
  substitute(code, list(mean = fn)) # doesn't work
  substitute(substitute(code), list(mean = fn)) # doesn't work
}

the above two approaches don't work. E.g.

replace_mean(list(mean(y), mean(x)))

What's the best way to do function replacement using NSE in R?

Base R Solutions preferred.

Update example output

replace(mean(x)) # fn(x)

replace(list(a = mean(x), mean(ok))) # list(a=fn(x), fn(ok)))
xiaodai
  • 14,889
  • 18
  • 76
  • 140

2 Answers2

7

The following function, when passed mean(x) and some fn such as sqrt as its two arguments returns the call object fn(x), i.e. sqrt(x), replacing occurrences of mean with fn.

replace_mean <- function(code, fn) {
  do.call("substitute", list(substitute(code), list(mean = substitute(fn))))
}

Examples

1) Basic example

e <- replace_mean(mean(x), sqrt)
e
## sqrt(x)

x <- 4
eval(e)
## [1] 2

2) more complex expression

ee <- replace_mean(mean(x) + mean(x*x), sqrt)
ee
## sqrt(x) + sqrt(x * x)

x <- 4
eval(ee)
## [1] 6

3) apply replace_mean to body of f creating g

f <- function(x) mean(x) + mean(x*x)
g <- f
body(g) <- do.call("replace_mean", list(body(f), quote(sqrt)))

g
## function (x) 
## sqrt(x) + sqrt(x * x)

x <- 4
g(x)
## [1] 6
G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
  • Great answer! What would be awesome if the "why"s can be slightly explored. I know there is Hadley's Advance R (https://adv-r.hadley.nz/metaprogramming.html) but after reading it I still couldn't get this right so this answer is pretty awesome! – xiaodai Dec 09 '19 at 06:28
  • 1
    The two inner `substitute` calls grab the two arguments using nonstandard evaluation. We need to use `do.call` with the outer `substitute` to force its first argument to be evaluated; otherwise, it would just return literally `substitute(code)` – G. Grothendieck Dec 09 '19 at 14:02
0

One way is much more ugly and relies on string manipulation to generate the code you want to run and then evaluating it.

replace_mean <- function(code) {
  code_subbed = substitute(code)

  # constructu the code I want
  code_subbed_subbed = sprintf("substitute(%s, list(mean=quote(fn)))", deparse(code_subbed))

  eval(parse(text = code_subbed_subbed))
}

replace_mean(list(mean(x), a=  mean(ok)))
xiaodai
  • 14,889
  • 18
  • 76
  • 140