Say I have the following
library(data.table)
cars1 = setDT(copy(cars))
cars2 = setDT(copy(cars))
car_list = list(cars1, cars2)
class(car_list) <- "dd"
`[.dd` <- function(x,...) {
code = rlang::enquos(...)
cars1 = x[[1]]
rlang::eval_tidy(quo(cars1[!!!code]))
}
car_list[,.N, by = speed]
so I wished to perform arbitrary operations on cars1
and cars2
by defining the [.dd
function so that whatever I put into ...
get executed by cars1
and cars2
using the [
data.table syntax e.g.
car_list[,.N, by = speed]
should perform the following
cars1[,.N, by = speed]
cars2[,.N, by = speed]
also I want
car_list[,speed*2]
to do
cars1[,speed*2]
cars2[,speed*2]
Basically, ...
in [.dd
has to accept arbitrary code.
somehow I need to capture the ...
so I tried to do code = rlang::enquos(...)
and then rlang::eval_tidy(quo(cars1[!!!code]))
doesn't work and gives error
Error in
[.data.table
(cars1, ~, ~.N, by = ~speed) : argument "i" is missing, with no default
First base R option is substitute(...())
followed by do.call
:
library(data.table)
cars1 = setDT(copy(cars))
cars2 = setDT(copy(cars))
cars2[, speed := sort(speed, decreasing = TRUE)]
car_list = list(cars1, cars2)
class(car_list) <- "dd"
`[.dd` <- function(x,...) {
a <- substitute(...()) #this is an alist
expr <- quote(x[[i]])
expr <- c(expr, a)
res <- list()
for (i in seq_along(x)) {
res[[i]] <- do.call(data.table:::`[.data.table`, expr)
}
res
}
all.equal(
car_list[,.N, by = speed],
list(cars1[,.N, by = speed], cars2[,.N, by = speed])
)
#[1] TRUE
all.equal(
car_list[, speed*2],
list(cars1[, speed*2], cars2[, speed*2])
)
#[1] TRUE
Second base R option is match.call
, modify the call and then evaluate (you find this approach in lm
):
`[.dd` <- function(x,...) {
thecall <- match.call()
thecall[[1]] <- quote(`[`)
thecall[[2]] <- quote(x[[i]])
res <- list()
for (i in seq_along(x)) {
res[[i]] <- eval(thecall)
}
res
}
all.equal(
car_list[,.N, by = speed],
list(cars1[,.N, by = speed], cars2[,.N, by = speed])
)
#[1] TRUE
all.equal(
car_list[, speed*2],
list(cars1[, speed*2], cars2[, speed*2])
)
#[1] TRUE
I haven't tested if these approaches will make a deep copy if you use :=
.