M.Bergen
M.Bergen

Reputation: 174

R: Lexical Scoping issue when creating a function with ellipsis argument

I make a lot of frequency tables in R and was working on writing my own quick frequency table (qft) function when I ran into what I believe is a lexical scoping issue. Here is the version of my function that does not work

library("tidyverse")

data(mtcars)

qft_bad<-function(data,...){
  ft<-data.frame(with(data, table(...)))
  ft<-ft[ft$Freq!=0,]
  return(ft)
}

tst_bad<-qft_bad(mtcars,cyl,gear)

You will notice that if you try to run qft_bad() that the Error "Error in table(...) : object 'cyl' not found" is produced. As a work around I wrote the following function which does produce the desired result but requires slightly different inputs.

qft_good<-function(data,...){
      nmes<-c(...)
      vars<-dplyr::select(data,...)
      ft<-data.frame(table(vars))
      ft<-ft[ft$Freq!=0,]
      colnames(ft)[1:length(nmes)]<-nmes
      return(ft)
}

tst_good<-qft_good(mtcars,"cyl","gear")

I'm guessing qft_bad() does not work because R tries to evaluate the arguments outside of the input dataset but am a little unclear as to the specifics of this problem (is there some issue with the with() function?).

As qft_good() works well enough for my purposes I am mainly asking this question for my own R Enlightenment. Can anyone provide a better explanation what's going on in my qft_bad() function, or create a version of the qft function that does not require you to list the variable names in quotes (as you must in qft_good())?

Upvotes: 0

Views: 52

Answers (1)

dave-edison
dave-edison

Reputation: 3726

You can use quosures from rlang to capture the arguments in ... then unquote splice them into a select call:

library(tidyverse)
library(rlang)

qft <- function(data, ...){
  args <- enquos(...)
  vars <- select(data, !!!args)
  ft <- data.frame(table(vars))
  ft[ft$Freq != 0, ]
}

qft(mtcars, cyl, gear)
#  cyl gear Freq
#1   4    3    1
#2   6    3    2
#3   8    3   12
#4   4    4    8
#5   6    4    4
#7   4    5    2
#8   6    5    1
#9   8    5    2

Upvotes: 1

Related Questions