0

Question: Is there a better way to indirectly reference a data variable in a function when using base R?

Example Setup

I have a dataframe that I want to loop through some column (will vary in name) and build a string. This is simplified, but illustrates the approach:

library(tidyverse)

df_original <- data.frame(id = 1:3, string = c("dog", "cat", "mouse"))
df_original

> df_original
  id string
1  1    dog
2  2    cat
3  3  mouse

# Easily can build string using base R with loop like this:
for (i in 1:nrow(df_original)){
  print(paste("I love my pet", df_original[i, "string"]))
}

[1] "I love my pet dog"
[1] "I love my pet cat"
[1] "I love my pet mouse"

Problem:

I want to put this in a function with the ability to map different column names to the data-variable. I was looking at "indirection", which I've used when writing function in DPLYR, but this doesn't work (and I expected that):

# Want to apply same idea inside function where df's column names may change,
# so I need to be able to quote them, but I know this is wrong.
myfun <- function(df, var){
  for (i in 1:nrow(df)){
    print(paste("I love my pet", df[i, {{var}}]))
  }
}

# This fails:
myfun(df_original, string)

Error: object 'string' not found

Works, but would like to do better:

I can rename() (DPLYR verb) the dataframe at the start of the function -- as seen below -- and get where I want, but this is a fuzzy area of R for me, and I'm hoping someone can explain how I might achieve that result in base R. My actual use case (not this simplified example) has me building URL GET requests using lat/longs for origin/destination combinations, and the four columns are never consistently named in the source dataframes.

# Can rename the variable locally in the function, then use, but 
# this seems clunky.
myfun2 <- function(df, var){
  df <- rename(df, var = {{var}})
  for (i in 1:nrow(df)){
    print(paste("I love my pet", df[i, "var"]))
  }
}

# But it does work:
myfun2(df_original, string)

> myfun2(df_original, string)
[1] "I love my pet dog"
[1] "I love my pet cat"
[1] "I love my pet mouse"

I appreciate any suggestions. Thank you.

ScottyJ
  • 945
  • 11
  • 16
  • 1
    First you do not need any for-loop. directly build the string `with(df_original, paste('I love my pet', var))` – Onyambu Jul 11 '23 at 19:57
  • 1
    If you use `myfun <- function(df, var){for (i in 1:nrow(df)){print(paste("I love my pet", df[i, var]))}}`, just pass your column name in as a string `myfun(df_original, "string")`. Does your function really need to use unquoted column names? Note that `{{}}` is a tidyverse/rlang specific syntax and is not recognized by= base R function. – MrFlick Jul 11 '23 at 20:07
  • @MrFlick -- Actually, no it doesn't. I just didn't think of that solution and that would just fine for my purpose. – ScottyJ Jul 11 '23 at 20:21

2 Answers2

2

As @MrFlick says in comments above, if your workflow can get you the string "string" rather than the symbol string, this is easy in base R:

myfun <- function(df, var){
  for (i in 1:nrow(df)){
    print(paste("I love my pet", df[i, var]))
  }
}
myfun(df_original, "string")

If you have the symbol, then @Onyambu's deparse(substitute(...)) trick is what you need (although the argument should be var, not string

myfun <- function(df, var){
   var <- deparse(substitute(var))
   for (i in 1:nrow(df)){
     print(paste("I love my pet", df[i, var]))
   }
 }
myfun(df_original, string)

As also commented, the {{}} stuff — while powerful — is tidyverse-specific; for more detail on how it works, see Hadley Wickham's Advanced R book.

Ben Bolker
  • 211,554
  • 25
  • 370
  • 453
1
myfun <- function(df, var){
     paste("I love my pet", eval(substitute(var), df))
}
myfun(df_original, string)
[1] "I love my pet dog"   "I love my pet cat"   "I love my pet mouse"

myfun <- function(df, var){
     paste("I love my pet", df[,deparse(substitute(var))])
}
myfun(df_original, string)
[1] "I love my pet dog"   "I love my pet cat"   "I love my pet mouse"

Edit:

 myfun <- function(df, var){
   var <- deparse(substitute(string))
   for (i in 1:nrow(df)){
     print(paste("I love my pet", df[i, var]))
   }
 }
 myfun(df_original, string)
[1] "I love my pet dog"
[1] "I love my pet cat"
[1] "I love my pet mouse"
Onyambu
  • 67,392
  • 3
  • 24
  • 53
  • I appreciate the fact that this can be done above without a loop, but my real function has more things going on inside the loop than this simple example -- so in my case I still need/prefer the loop. I didn't know how to do this technique you're showing, however, and I am very appreciative for this answer. – ScottyJ Jul 11 '23 at 20:24
  • @wackojacko1997 even with the forloop, you still need to use `substitute(var)`. Check updated: – Onyambu Jul 11 '23 at 21:01