0

Is there any way to pass generic column names to functions like xtabs in R?

Typically, I'm trying to do something like:

xtabs(weight ~ col, data=dframe)

with col and weight two columns of my data.frame, weight being a column containing weights. It works, but if I want to wrap xtabs in a function to which I pass the column names as argument, it fails. So, if I do:

xtabs.wrapper <- function(dframe, colname, weightname) {
    return(xtabs(weightname ~ colname, data=dframe))
}

it fails. Is there a simple way to do something similar? Perhaps I'm missing something with R logic, but it seems to me quite annoying not to be able to pass generic variables to such functions since I'm not particularly fond of copy/paste.

Any help or comments appreciated!

Edit: as mentioned in comments, I was suggested to use eval and I came with this solution:

xtabs.wrapper <- function(dframe, wname, cname) {
    xt <- eval(parse(text=paste("xtabs(", wname, "~", cname, ", data=",
                         deparse(substitute(dframe)), ")")))
    return(xt)
}

As I said, I seems to me to be an ugly trick, but I'm probably missing something about the language logic.

MBR
  • 794
  • 13
  • 34
  • Someone suggested me to use `eval`, but it seems to me to be an ugly trick to do the job... – MBR Aug 13 '15 at 15:21
  • I don't know if it is worth the effort or if this exactly what you want, but you could look at using non-standard evaluation (http://adv-r.had.co.nz/Computing-on-the-language.html) in your functions ... but as I read further this might be the same suggestion that you have already received. – Carl Frederick Aug 13 '15 at 16:28
  • I edited my question to add the thing I tried based on this suggestion, which I find quite unsatisfying... But thanks for the comment! – MBR Aug 14 '15 at 15:02

3 Answers3

2

Not sure if this is any prettier, but here is a way to define a function without using eval ... it involves accessing the correct columns of dframe via []:

    xtabs.wrapper <- function(dframe, wname, cname) {
      tmp.wt <- dframe[,wname]
      tmp.col <- dframe[,cname]
      xt <- xtabs(tmp.wt~tmp.col)
      return(xt)
    }

Or you can shorten the guts of the function to:

    xtabs.wrapper2 <- function(dframe, wname, cname) {
      xt <- xtabs(dframe[,wname]~dframe[,cname])
      return(xt)
    }

To show they are equivalent here with an example from the mtcars data:

    data(mtcars)
    xtabs(wt~cyl, mtcars)
    xtabs.wrapper(mtcars, "wt", "cyl")
    xtabs.wrapper2(mtcars, "wt", "cyl")
  • That's exactly what I was looking for, using `dframe` with `[]`. I couldn't find the proper syntax, but that's it, thanks! – MBR Aug 14 '15 at 16:07
  • This is much cleaner than using the eval solution. Note however it won't work with data.table. See solutions for data.table here: https://stackoverflow.com/questions/12391950/select-assign-to-data-table-when-variable-names-are-stored-in-a-character-vect – JamesR Jan 21 '19 at 09:33
0

I did this once:

    creatextab<-function(factorsToUse, data)
    {      
            newform<-as.formula(paste("Freq ~", paste(factorsToUse, collapse="+"), sep=""))
           xtabs(formula= newform, drop.unused.levels = TRUE, data=data)  
}

Obviously this is a different form because of the Freq, but basically .. you can generate the forumula as a string and then you are just using xtabs() directly.

Elin
  • 6,507
  • 3
  • 25
  • 47
0

If you want an n-way crosstab and cname contains a string of variable names, then you'll want the following:

xtabs.wrapper3 <- function(dframe, wname, cname) {
  eval(cname)
  formula <- paste0(wname, " ~ ", paste0(cname, collapse=" + ") )
  xt <- xtabs(formula, data = dframe)
  return(xt)
}

xtabs.wrapper3(mtcars, "wt", c("cyl", "vs"))
Logit
  • 83
  • 1
  • 5