0

I'm trying to write a recursive function that builds a nested ifelse call. I do realize there are much better approaches than nested ifelse, e.g., dplyr::case_when and data.table::fcase, but I'm trying to learn how to approach such problems with metaprogramming.

The following code builds out the nested ifelse, but I'm struggling to substitute data with the actual supplied value, in this case my_df.

If I replace quote(data) with substitute(data), it only works for the first ifelse, but after entering the next iteration, it turns into data.

I think something like pryr::modify_lang could solve this after the fact, but I think there's probably a base R solution someone knows.

my_df <- data.frame(group = letters[1:3],
                    value = 1:3)

build_ifelse <- function(data, by, values, iter=1){
  
  x <- call("ifelse",
            call("==",
                 call("[[", quote(data), by),
                 values[iter]),
            1,
            if(iter != length(values)) build_ifelse(data, by, values, iter = iter + 1) else NA)
  return(x)
}

build_ifelse(data = my_df, by = "group", values = letters[1:3])
# ifelse(data[["group"]] == "a", 1, ifelse(data[["group"]] == "b", 
#        1, ifelse(data[["group"]] == "c", 1, NA)))

Thanks for any input!

Edit:

I found this question/answer: https://stackoverflow.com/a/59242109/9244371

Based on that, I found a solution that seems to work pretty well:

build_ifelse <- function(data, by, values, iter=1){
  
  x <- call("ifelse",
            call("==",
                 call("[[", quote(data), by),
                 values[iter]),
            1,
            if(iter != length(values)) build_ifelse(data, by, values, iter = iter + 1) else NA)
  
  x <- do.call(what = "substitute",
               args = list(x, 
                           list(data = substitute(data))))
  return(x)
}

build_ifelse(data = my_df, by = "group", values = letters[1:3])
# ifelse(my_df[["group"]] == "a", 1, ifelse(my_df[["group"]] == 
#     "b", 1, ifelse(my_df[["group"]] == "c", 1, NA)))

eval(build_ifelse(data = my_df, by = "group", values = letters[1:3]))
# [1] 1 1 1
Hutch3232
  • 408
  • 4
  • 11
  • You are creating a call, You might consider using an iteration rather than recursion – Onyambu Aug 30 '22 at 01:29
  • What are you trying to come up with? – Onyambu Aug 30 '22 at 01:35
  • I really want to build a nested `ifelse` for use with the `h2o` package. In that case `h2o.ifelse`. `h2o` doesn't have a case when so i was trying to build my own. Left out these details in my original post thinking it would simplify my question to exclude. – Hutch3232 Aug 30 '22 at 02:21

1 Answers1

0

There is a base function, switch, that can deliver sequential testing and results similar to dplyr::case_when, at least when used with a loop wrapper. It's not well documented. It is really two different functions, one that expects a numeric input for it classification variable and another that expects character values. I can never remember it's name, and so typically I need to remind myself that it is referenced in the ?Control page. Since you're using character values, here goes. (I changed the outputs so you can see that some degree of substitution is occurring and that there is an "otherwise" option

sapply( my_df$group, switch,  a=4, b=5, d=6, NA) 
 a  b  c 
 4  5 NA 
IRTFM
  • 258,963
  • 21
  • 364
  • 487
  • Thanks for this answer. The combination of `sapply` and `switch` does seem quite handy. In my case though, I'd like to be able to build the call dynamically. I won't know things in advance like the name of the `data.frame`, what column they're testing values of (`by`), or what they want to submit in the `yes` case. – Hutch3232 Aug 30 '22 at 12:47