ROIpad ← Back to Search
stackoverflow › answer

Answer to: Calculate multilple stats for two or more columns in R data.table

Score: 2
Answered: Apr 10, 2026
User Rep: 1,184
By total coincidence I was working today on a helper function calls_across that does exactly what you want. It pairs with the env programming mechanism in recent versions of {data.table}. [Edit: as promised, now allowing aliasing the functions on the fly via a named character vector (which preserves GForce)] library(data.table) set.seed(1) # please use a seed for reproducibility!!! dt <- data.table(class = rep(letters[1:3], 4), val1=sample(12), val2=sample(12)*10) query <- calls_across( c(maximo="max", minimo="min"), c("val1", "val2"), na.rm=TRUE, order="col_fun" ) dt[, j, by=class, env=list(j=query)] #> class val1_maximo val1_minimo val2_maximo val2_minimo #> <char> <int> <int> <num> <num> #> 1: a 11 1 120 40 #> 2: b 12 2 110 10 #> 3: c 10 5 90 30 Output with verbose=TRUE: dt[, j, by=class, env=list(j=query), verbose=TRUE] #> Argument 'by' after substitute: class #> Argument 'j' after substitute: list(val1_maximo = max(val1, na.rm = TRUE), val1_minimo = min(val1, na.rm = TRUE), val2_maximo = max(val2, na.rm = TRUE), val2_minimo = min(val2, na.rm = TRUE)) #> [...] #> GForce optimized j to 'list(gmax(val1, na.rm = TRUE), gmin(val1, na.rm = TRUE), gmax(val2, na.rm = TRUE), gmin(val2, na.rm = TRUE))' (see ?GForce) #> Making each group and running j (GForce TRUE) ... #> [...] Note: {data.table}'s "GForce" optimisations of common stats functions avoid (slow) split-apply-combine when there is a by, so it's important to take advantage of them. I will change the code to allow such aliasing in the funs argument (using a named character vector). (Let me do that later and edit at some point.)[done] Here is the source code, still a bit WIP [but now revised to allow optional aliases]: calls_across <- function( funs, cols, ..., order = c("fun_col","col_fun"), named = TRUE, naming = order, sep = "_" ){ order <- match.arg(order) stopifnot(naming %in% c("fun_col","col_fun")) .make_call <- function(fun, cols, ...) { dots <- as.list(substitute(list(...)))[-1] cols <- if (is.list(cols)) { lapply(cols, \(x) if (is.character(x)) as.name(x) else x) } else { if (is.character(cols)) lapply(cols, as.name) else cols } as.call(c(list(as.name(fun)), cols, dots)) } if (isTRUE(named)) fun_nms <- if (!is.null(names(funs))) names(funs) else funs if (match.arg(order)=="fun_col") { ans <- lapply(funs, \(f) lapply(cols, \(c) .make_call(f,c,...))) if (isTRUE(named)) { .f <- rep(fun_nms, each=length(cols)) .c <- rep(cols,times=length(funs)) } } else { ans <- lapply(cols, \(c) lapply(funs, \(f) .make_call(f,c,...))) if (isTRUE(named)) { .f <- rep(fun_nms, times=length(cols)) .c <- rep(cols, each=length(funs)) } } ans <- do.call(c, ans) if (isTRUE(named)) names(ans) <- if (naming=="fun_col") paste(.f,.c,sep=sep) else paste(.c,.f,sep=sep) ans } It allows separate flexibility over i) the order and ii) the names of the output columns (fun-then-col or col-then-fun). Please notice from the examples that it also permits additional arguments, such as na.rm=TRUE.
r data.table
View Question ↗
Question
Parent Entity
Score: 2 • Views: 62
Site: stackoverflow