Atribua várias colunas usando: = em data.table, por grupo

130

Qual é a melhor maneira de atribuir a várias colunas usando data.table? Por exemplo:

f <- function(x) {c("hi", "hello")}
x <- data.table(id = 1:10)

Gostaria de fazer algo assim (é claro que esta sintaxe está incorreta):

x[ , (col1, col2) := f(), by = "id"]

E para estender isso, eu posso ter muitas colunas com nomes armazenados em uma variável (por exemplo col_names) e gostaria de fazer:

x[ , col_names := another_f(), by = "id", with = FALSE]

Qual é a maneira correta de fazer algo assim?

Alex
fonte
1
Isto olha como ele foi respondida: stackoverflow.com/questions/11308754/...
Alex
Alex, essa resposta está próxima, mas parece não funcionar em combinação com bycomo é correto dizer @Christoph_J. Link para sua pergunta adicionada à FR # 2120 "Solte a necessidade com = FALSE for LHS of: =", para que não se esqueça de revisitar.
quer
Para ser claro, f()é uma função que retorna vários valores, um para cada uma de suas colunas.
smci

Respostas:

161

Agora isso funciona na v1.8.3 no R-Forge. Obrigado por destacá-lo!

x <- data.table(a = 1:3, b = 1:6) 
f <- function(x) {list("hi", "hello")} 
x[ , c("col1", "col2") := f(), by = a][]
#    a b col1  col2
# 1: 1 1   hi hello
# 2: 2 2   hi hello
# 3: 3 3   hi hello
# 4: 1 4   hi hello
# 5: 2 5   hi hello
# 6: 3 6   hi hello

x[ , c("mean", "sum") := list(mean(b), sum(b)), by = a][]
#    a b col1  col2 mean sum
# 1: 1 1   hi hello  2.5   5
# 2: 2 2   hi hello  3.5   7
# 3: 3 3   hi hello  4.5   9
# 4: 1 4   hi hello  2.5   5
# 5: 2 5   hi hello  3.5   7
# 6: 3 6   hi hello  4.5   9 

mynames = c("Name1", "Longer%")
x[ , (mynames) := list(mean(b) * 4, sum(b) * 3), by = a]
#     a b col1  col2 mean sum Name1 Longer%
# 1: 1 1   hi hello  2.5   5    10      15
# 2: 2 2   hi hello  3.5   7    14      21
# 3: 3 3   hi hello  4.5   9    18      27
# 4: 1 4   hi hello  2.5   5    10      15
# 5: 2 5   hi hello  3.5   7    14      21
# 6: 3 6   hi hello  4.5   9    18      27


x[ , get("mynames") := list(mean(b) * 4, sum(b) * 3), by = a][]  # same
#    a b col1  col2 mean sum Name1 Longer%
# 1: 1 1   hi hello  2.5   5    10      15
# 2: 2 2   hi hello  3.5   7    14      21
# 3: 3 3   hi hello  4.5   9    18      27
# 4: 1 4   hi hello  2.5   5    10      15
# 5: 2 5   hi hello  3.5   7    14      21
# 6: 3 6   hi hello  4.5   9    18      27

x[ , eval(mynames) := list(mean(b) * 4, sum(b) * 3), by = a][]   # same
#    a b col1  col2 mean sum Name1 Longer%
# 1: 1 1   hi hello  2.5   5    10      15
# 2: 2 2   hi hello  3.5   7    14      21
# 3: 3 3   hi hello  4.5   9    18      27
# 4: 1 4   hi hello  2.5   5    10      15
# 5: 2 5   hi hello  3.5   7    14      21
# 6: 3 6   hi hello  4.5   9    18      27

Versão mais antiga usando o withargumento (nós desencorajamos esse argumento quando possível):

x[ , mynames := list(mean(b) * 4, sum(b) * 3), by = a, with = FALSE][] # same
#    a b col1  col2 mean sum Name1 Longer%
# 1: 1 1   hi hello  2.5   5    10      15
# 2: 2 2   hi hello  3.5   7    14      21
# 3: 3 3   hi hello  4.5   9    18      27
# 4: 1 4   hi hello  2.5   5    10      15
# 5: 2 5   hi hello  3.5   7    14      21
# 6: 3 6   hi hello  4.5   9    18      27
Matt Dowle
fonte
Obrigado por esta resposta e pelos exemplos. Como devo modificar a linha a seguir para obter duas colunas para cada objectName da saída não ofuscante, em vez de uma coluna com duas linhas? data.table(objectName=ls())[,c("rows","cols"):=dim(get(objectName)),by=objectName](Estou usando data.table1.8.11)
dnlbrky
@dnlbrky dimretorna um vetor tão convertido que para digitar listdeve girá-lo; por exemplo [,c("rows","cols"):=as.list(dim(get(objectName))),by=objectNa‌​me]. O problema é que as.listtem sobrecarga de chamada e também copia o vetor pequeno. Se a eficiência for um problema à medida que o número de grupos aumentar, informe-nos.
Matt Dowle
1
Oi Matt. O primeiro exemplo no seu segundo bloco de código (ou seja x[,mynames:=list(mean(b)*4,sum(b)*3),by=a,with=FALSE][]) agora lança um aviso, então talvez o remova? Em uma nota relacionada, alguém sugeriu que, com options(datatable.WhenJisSymbolThenCallingScope=TRUE)uma tarefa como x[,mynames:=list(mean(b)*4,sum(b)*3),by=a]deveria de fato funcionar? Parece que isso seria consistente com as outras alterações, embora eu acho que possa quebrar muito código de usuário existente (?).
21416 Josh O'Brien
1
@ PanFrancisco Sem by=aele funcionará, mas retorne uma resposta diferente. Os agregados mean(a)e sum(a)estão sendo reciclados dentro de cada grupo quando by=a. Sem by=aele, basta colar o meane sumpara a coluna inteira em cada célula (ou seja, números diferentes).
precisa
1
@MattDowle, e se minha função já retornar lista nomeada, existe alguma maneira de adicionar as colunas à dt sem precisar nomeá-las novamente? por exemplo, f <- função (x) {list ("c" = "oi", "d" = "olá")} imprimirá resultados com colunas nomeadas com x [, f (), por = a] []. Não sei como anexar o resultado ao dt.
Jfly
48

A seguinte notação abreviada pode ser útil. Todo o crédito é para Andrew Brooks, especificamente este artigo .

dt[,`:=`(avg=mean(mpg), med=median(mpg), min=min(mpg)), by=cyl]
Gerry
fonte