Passing expression through functions
Asked Answered
C

2

6

I'm using data.table package and trying to write a function (shown below):

require(data.table)
# Function definition
f = function(path, key) {
  table = data.table(read.delim(path, header=TRUE))
  e = substitute(key)
  setkey(table, e) # <- Error in setkeyv(x, cols, verbose = verbose) : some columns are not in the data.table: e
  return(table)
}

# Usage
f("table.csv", ID)

Here I try to pass an expression to the function. Why this code doesn't work?

I've already tried different combinations of substitute(), quote() and eval(). So, it'd be great if you could also explain how to get this to work.

Cung answered 11/6, 2013 at 5:1 Comment(6)
Can you explain what you're trying to do? Also your code currently won't even get to that step; data.table is not a function.Ellersick
Why not to simply use setkeyv here and give the ID as a character?Garnet
@Manetheran, probably the OP should've mentioned that he's using the data.table package. Just made the edit and added the tag.Hilariahilario
Ah, I am not familiar with that package, that'd be why the question made no sense to me!Ellersick
@Hilariahilario Thanks I've forgotten to mention that.Cung
@Garnet the aim is not to just do the trick but to understand how this kind of things works.Cung
H
10

First, let's look at how the setkey function does things from the data.table package:

# setkey function
function (x, ..., verbose = getOption("datatable.verbose")) 
{
    if (is.character(x)) 
        stop("x may no longer be the character name of the data.table. The possibility was undocumented and has been removed.")
    cols = getdots()
    if (!length(cols)) 
        cols = colnames(x)
    else if (identical(cols, "NULL")) 
        cols = NULL
    setkeyv(x, cols, verbose = verbose)
}

So, when you do:

require(data.table)
dt <- data.table(ID=c(1,1,2,2,3), y = 1:5)
setkey(dt, ID)

It calls the function getdots which is internal to data.table (that is, it's not exported). Let's have a look at that function:

# data.table:::getdots
function () 
{
    as.character(match.call(sys.function(-1), call = sys.call(-1), 
        expand.dots = FALSE)$...)
}

So, what does this do? It takes the parameter you entered in setkey and it uses match.call to extract the arguments separately. That is, the match.call argument for this example case would be:

setkey(x = dt, ... = list(ID))

and since it's a list, you can access the ... parameter with $... to get a list of 1 element with its value ID and converting to this list to a character with as.character results in "ID" (a character vector). And then setkey passes this to setkeyv internally to set the keys.


Now why doesn't this work when you write setkey(table, key) inside your function?

This is precisely because of the way setkey/getdots is. The setkey function is designed to take any argument after the first argument (which is a data.table) and then return the ... argument as a character.

That is, if you give setkey(dt, key) then it'll return cols <- "key". If you give setkey(dt, e), it'll give back cols <- "e". It doesn't look for if "key" is an existing variable and then if so substitute the value of the variable. All it does is convert the value you provide (whether it be a symbol or character) back to a character.

Of course this won't work in your case because you want the value in key = ID to be provided in setkey. At least I can't think of a way to do this.


How to get around this?

As @agstudy already mentions, the best/easiest way is to pass "ID" and use setkeyv. But, if you really insist on using f("table.csv", ID) then, this is what you could do:

f <- function(path, key) {
    table = data.table(read.delim(path, header=TRUE))
    e = as.character(match.call(f)$key)
    setkeyv(table, e)
    return(table)
}

Here, you first use match.call to get the value corresponding to argument key and then convert it to a character and then pass that to setkeyv.

In short, setkey internally uses setkeyv. And imho, setkey is a convenient function to be used when you already know the column name of the data.table for which you need to set the key. Hope this helps.

Hilariahilario answered 11/6, 2013 at 6:44 Comment(3)
Arun can I claim that setkey is actually a handy function/ shortcut to setkeyv? I mean it is intended to be used at the console level for example? +1Garnet
@agstudy, precisely. That's what the last paragraph from the post says as well :).Hilariahilario
Thank you, now it's clear. I've watched through setkey() implementation but I should go deeper and take a look at getdots() as well.Cung
E
3

I can't tell from your code what you're trying to achieve, so I'll answer the question the title asks instead; "How to pass an expression through a function?"

If you want to do this (this should be avoided where possible), you can do the following:

f <- function(expression) {
  return(eval(parse(text=expression)))
}

For example:

f("a <- c(1,2,3); sum(a)")
# [1] 6
Ellersick answered 11/6, 2013 at 6:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.