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.
Short answer: You can't. The result of the expression evaluation is passed to the function rather than the expression itself.
1. Passing Pointer to a Function. A function can also be passed to another function by passing its address to that function; In simple terms, it could be achieved via pointers.
Passing arguments to function is a very important aspect of C++ programming. Arguments refer to values that can be passed to a function. Furthermore, this passing of arguments takes place for the purpose of being used as input information.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With