ScottyJ
ScottyJ

Reputation: 1087

Is it possible to use indirection with base R?

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.

Upvotes: 0

Views: 112

Answers (2)

Ben Bolker
Ben Bolker

Reputation: 226771

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.

Upvotes: 3

Onyambu
Onyambu

Reputation: 79338

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"

Upvotes: 1

Related Questions