Pase el nombre de la variable como argumento dentro de data.table
(5)
Estoy tratando de crear una función que modifique una tabla data.table y quería usar alguna evaluación no estándar, pero me di cuenta de que realmente no sé cómo trabajar con ella dentro de data.tables. Mi función es básicamente algo como esto:
do_stuff <- function(dt, col) {
copy(dt)[, new_col := some_fun(col)][]
}
y quiero llamarlo así:
do_stuff(data, column)
Donde "columna" es el nombre de la columna que existe dentro de "datos". Si ejecuto esa función me sale un error:
#> Error in some_fun(col) : object ''column'' not found
Lo que me dice que data.table aparentemente está pasando el nombre correcto a la función ("columna") pero por alguna razón no lo está encontrando. Aquí hay un ejemplo mínimo reproducible
library(data.table)
data <- data.table(x = 1:10, y = rnorm(10))
plus <- function(x, y) {
x + y
}
add_one <- function(data, col) {
copy(data)[, z := plus(col, 1)][]
}
add_one(data, y)
#> Error in plus(col, 1): object ''y'' not found
El uso de
deparse(substitute(col))
no parece funcionar, desafortunadamente :(
add_one <- function(data, col) {
copy(data)[, z := plus(deparse(substitute(col)), 1)][]
}
add_one(data, y)
#> Error in x + y: non-numeric argument to binary operator
Aunque es potencialmente más propenso a errores, puede confiar en
...
argumentos.
data <- data.table(x = 1:10, y = rnorm(10))
plus <- function(x, y) {
x + y
}
add_one <- function(data, ...) {
copy(data)[, z:= plus(data[, ...], 1)][]
}
add_one(data, y)
#or
library(dplyr)
data.table(x = 1:10, y = rnorm(10))%>%
add_one(y)
x y z
1: 1 -1.29851891 -0.2985189
2: 2 -1.36494928 -0.3649493
3: 3 0.38282492 1.3828249
4: 4 1.24578886 2.2457889
5: 5 1.12897695 2.1289770
6: 6 -0.80122005 0.1987800
7: 7 1.89093661 2.8909366
8: 8 -0.34525212 0.6547479
9: 9 -0.07070159 0.9292984
10: 10 -1.94145962 -0.9414596
Desafortunadamente, expandir esto a múltiples variables conduciría al fracaso.
Aún así, puede usar el
...
para su ventaja.
add_one2 <- function(data, ...){
copy(data)[...][]
}
add_one2(data, , z:=plus(y, 1))
x y z
1: 1 -0.1565010 0.8434990
2: 2 0.6516824 1.6516824
3: 3 0.5355833 1.5355833
4: 4 0.1941661 1.1941661
5: 5 0.2994167 1.2994167
6: 6 -2.5681215 -1.5681215
7: 7 -1.4587147 -0.4587147
8: 8 0.9375132 1.9375132
9: 9 1.3984343 2.3984343
10: 10 -0.6498709 0.3501291
En general, presupuesto y evaluación funcionarán:
library(data.table)
plus <- function(x, y) {
x + y
}
add_one <- function(data, col) {
expr0 = quote(copy(data)[, z := plus(col, 1)][])
expr = do.call(substitute, list(expr0, list(col = substitute(col))))
cat("Evaluated expression:/n"); print(expr); cat("/n")
eval(expr)
}
set.seed(1)
library(magrittr)
data.table(x = 1:10, y = rnorm(10)) %>%
add_one(y)
lo que da
Evaluated expression:
copy(data)[, `:=`(z, plus(y, 1))][]
x y z
1: 1 -0.6264538 0.3735462
2: 2 0.1836433 1.1836433
3: 3 -0.8356286 0.1643714
4: 4 1.5952808 2.5952808
5: 5 0.3295078 1.3295078
6: 6 -0.8204684 0.1795316
7: 7 0.4874291 1.4874291
8: 8 0.7383247 1.7383247
9: 9 0.5757814 1.5757814
10: 10 -0.3053884 0.6946116
Otra opción, citando el nombre de la columna y usando
get
:
add_one <- function(data, col) {
copy(data)[, z := plus(get(col), 1)][]
}
add_one(data, "y")
Un extracto de mis Rnotebooks ... para la serie / análisis de datos de series de tiempo. Utilizo este patrón para mi propio paquete para envolver paquetes data.table y de series temporales (es decir, xts).
# Non-standard evaluation & wrapper for data.table
data <- data.table(a = 1:2, b = 3:4)
## Non-in-place update - - - - - - - - -
do_something <- function(data, col) {
col <- eval( substitute(col), data )
data[ , col + 123]
}
data %>% do_something(a)
## In-place update without copies (fast, memory efficient) - - - - - - - - -
# Minimalistic example
do_something <- function(data, col) {
col <- eval( substitute(col), data )
data[ , new_col := col + 123]
}
data %>% do_something(a) # print `data` to see results
# More example
# optional multi-assignment %<-% operator from {zeallot} for cleaner syntax
my_func <- function(x, y) x + y
do_something <- function(data, col_1, col_2, col_name) {
c(col_1, col_2) %<-% map(
c( substitute(col_1), substitute(col_2) ), ~ eval( ., data )
)
data[ , (col_name) := my_func(col_1, col_2)]
}
data %>% do_something(a, b, ''new_col_name'')
# Advanced example
# ...
Una opción sería extraer el argumento sin comillas como una cadena con
deparse(substitute
y especificar eso en los
.SDcols
add_one <- function(data, col) {
copy(data)[, z := plus(.SD[[1]], 1), .SDcols = deparse(substitute(col))][]
}
add_one(data, y)
# x y z
# 1: 1 0.50269855 1.5026986
# 2: 2 -0.33022414 0.6697759
# 3: 3 0.57517246 1.5751725
# 4: 4 1.09928586 2.0992859
# 5: 5 0.84683311 1.8468331
# 6: 6 -1.42023443 -0.4202344
# 7: 7 0.04539331 1.0453933
# 8: 8 0.11870596 1.1187060
# 9: 9 -1.11735007 -0.1173501
#10: 10 -1.94834136 -0.9483414
o usando
get
add_one <- function(data, col) {
copy(data)[, z := plus(get(deparse(substitute(col)))][]
}
O usando
tidyverse
library(tidyverse)
add_one <- function(data, col, col2) {
data %>%
dplyr::mutate(z =plus({{col}}, {{col2}}))
}
add_one(data, x, y)
# x y z
#1 1 -0.53389875 0.4661013
#2 2 1.28743777 3.2874378
#3 3 -1.26674091 1.7332591
#4 4 0.95017120 4.9501712
#5 5 0.06741833 5.0674183
#6 6 -0.70212949 5.2978705
#7 7 -0.38003803 6.6199620
#8 8 -0.50941072 7.4905893
#9 9 0.54055720 9.5405572
#10 10 -0.87486953 9.1251305