r data.table

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