Selecione / atribua a data.table quando os nomes das variáveis ​​forem armazenados em um vetor de caracteres


93

Como você se refere a variáveis ​​em a data.tablese os nomes das variáveis ​​são armazenados em um vetor de caracteres? Por exemplo, isso funciona para data.frame:

df <- data.frame(col1 = 1:3)
colname <- "col1"
df[colname] <- 4:6
df
#   col1
# 1    4
# 2    5
# 3    6

Como posso realizar esta mesma operação para uma data.table, com ou sem :=notação? A coisa óbvia de dt[ , list(colname)]não funciona (nem eu esperava que funcionasse).

Respostas:


134

Duas maneiras de selecionar variável (s) de forma programática :

  1. with = FALSE:

     DT = data.table(col1 = 1:3)
     colname = "col1"
     DT[, colname, with = FALSE] 
     #    col1
     # 1:    1
     # 2:    2
     # 3:    3
    
  2. 'ponto ponto' ( ..) prefixo:

     DT[, ..colname]    
     #    col1
     # 1:    1
     # 2:    2
     # 3:    3
    

Para obter uma descrição mais detalhada da ..notação 'ponto ponto' ( ), consulte Novos recursos em 1.10.2 (atualmente não está descrito no texto de ajuda).

Para atribuir a variável (s), coloque o LHS de :=entre parênteses:

DT[, (colname) := 4:6]    
#    col1
# 1:    4
# 2:    5
# 3:    6

O último é conhecido como plonk de coluna , porque você substitui todo o vetor de coluna por referência. Se um subconjunto iestivesse presente, ele seria subatribuído por referência. Os parênteses ao redor (colname)são uma abreviação introduzida na versão v1.9.4 no CRAN de outubro de 2014. Aqui está o item de notícias :

Usar with = FALSEcom :=agora está obsoleto em todos os casos, uma vez que :=há algum tempo é preferível envolver o LHS de entre parênteses.

colVar = "col1"
DT[, (colVar) := 1]                             # please change to this
DT[, c("col1", "col2") := 1]                    # no change
DT[, 2:4 := 1]                                  # no change
DT[, c("col1","col2") := list(sum(a), mean(b))]  # no change
DT[, `:=`(...), by = ...]                       # no change

Consulte também a seção Detalhes em ?`:=`:

DT[i, (colnamevector) := value]
# [...] The parens are enough to stop the LHS being a symbol

E para responder a outras perguntas no comentário, aqui está uma maneira (como de costume, há muitas maneiras):

DT[, colname := cumsum(get(colname)), with = FALSE]
#    col1
# 1:    4
# 2:    9
# 3:   15 

ou, você pode achar mais fácil ler, escrever e depurar apenas em evalum paste, semelhante a construir uma instrução SQL dinâmica para enviar a um servidor:

expr = paste0("DT[,",colname,":=cumsum(",colname,")]")
expr
# [1] "DT[,col1:=cumsum(col1)]"

eval(parse(text=expr))
#    col1
# 1:    4
# 2:   13
# 3:   28

Se você faz muito isso, pode definir uma função auxiliar EVAL:

EVAL = function(...)eval(parse(text=paste0(...)),envir=parent.frame(2))

EVAL("DT[,",colname,":=cumsum(",colname,")]")
#    col1
# 1:    4
# 2:   17
# 3:   45

Agora que data.table1.8.2 otimiza automaticamente jpara eficiência, pode ser preferível usar o evalmétodo. O get()in jimpede algumas otimizações, por exemplo.

Ou, existe set(). Uma baixa sobrecarga, forma funcional de :=, o que seria ótimo aqui. Veja ?set.

set(DT, j = colname, value = cumsum(DT[[colname]]))
DT
#    col1
# 1:    4
# 2:   21
# 3:   66

1
Obrigado pela resposta Matthew. O with = FALSE definitivamente resolve parte do meu problema. Na realidade, porém, quero substituir a coluna pelo cumsum da coluna. Posso fazer referência ao nome da coluna por variável no lado direito da atribuição de alguma forma?
frankc

Agudamente, acabei de colocar o cumsum externamente com um nome diferente que não existe dentro do dt e que funciona bem.
frankc

1
Mas isso seria uma linha extra inteira! Não é muito elegante :) Mas ok às vezes é útil. Nesses casos, é melhor começar o nome da variável com ., ou ..evitar qualquer mascaramento potencial, se DTalguma vez contiver esse símbolo como um nome de coluna no futuro (e seguir a convenção de que nomes de coluna não começam com .). Existem algumas solicitações de recursos para torná-lo mais robusto para questões de escopo como essa, como adicionar .()e ..().
Matt Dowle

Eu respondi antes de perceber que você editou sua resposta. Meu primeiro pensamento foi eval (parse ()), mas por algum motivo eu estava tendo problemas para fazer funcionar, quando me ocorreu fazer apenas externamente. Esta é uma ótima resposta com muitas coisas nas quais não pensei. Obrigado por data.table em geral, é um ótimo pacote.
frankc

2
Note que você pode usar a interpolação tipo string quasi-perl de fn$do pacote gsubfn para melhorar a legibilidade da solução EVAL: library(gsubfn); fn$EVAL( "DT[,$colname:=cumsum($colname)]" ).
G. Grothendieck

8

* Esta não é uma resposta, mas não tenho credibilidade suficiente para postar comentários: /

De qualquer forma, para qualquer um que esteja procurando realmente criar uma nova coluna em uma tabela de dados com um nome armazenado em uma variável, tenho o seguinte para funcionar. Não tenho ideia de seu desempenho. Alguma sugestão de melhoria? É seguro presumir que uma nova coluna sem nome sempre receberá o nome V1?

colname <- as.name("users")
# Google Analytics query is run with chosen metric and resulting data is assigned to DT
DT2 <- DT[, sum(eval(colname, .SD)), by = country]
setnames(DT2, "V1", as.character(colname))

Observe que posso referenciá-lo bem no sum (), mas não consigo fazer com que seja atribuído na mesma etapa. BTW, o motivo pelo qual preciso fazer isso é colname será baseado na entrada do usuário em um aplicativo Shiny.


+1 por apenas trabalhar: concordo que não deve ser "o jeito" de fazer isso, mas depois de passar cerca de 45 minutos lendo todos os posts sobre este assunto, esta é a única solução que eu realmente consegui encontrar trabalho - obrigado por dedicar seu tempo para apontá-lo!
neuropsych

Que bom que pude ajudar! Infelizmente, nunca encontrei uma solução mais elegante diretamente usando data.tables, embora este 3 liner não seja terrível. No meu cenário, percebi que uma alternativa mais simples seria usar tidyr para apenas tornar meus dados "longos" em vez de "largos", pois, com base na entrada do usuário, eu sempre poderia filtrar em uma única coluna em vez de selecionar em um conjunto de colunas.
efh0888

2
Não é seguro presumir que V1é o novo nome. Por exemplo, se você ler csv comfread e houver uma coluna sem nome, ela terá V1nome (e read.csvfornecerá X). Portanto, é possível que sua mesa já tenha um V1. Talvez apenas receba o nome pornames(DT)[length(names(DT))]
dracodoc

3

Recupere várias colunas de data.table por meio de variável ou função:

library(data.table)

x <- data.table(this=1:2,that=1:2,whatever=1:2)

# === explicit call
x[, .(that, whatever)]
x[, c('that', 'whatever')]

# === indirect via  variable
# ... direct assignment
mycols <- c('that','whatever')
# ... same as result of a function call
mycols <- grep('a', colnames(x), value=TRUE)

x[, ..mycols]
x[, .SD, .SDcols=mycols]

# === direct 1-liner usage
x[, .SD, .SDcols=c('that','whatever')]
x[, .SD, .SDcols=grep('a', colnames(x), value=TRUE)]

que todos rendem

   that whatever
1:    1        1
2:    2        2

Acho a .SDcolsforma mais elegante.


2

Para várias colunas e uma função aplicada aos valores da coluna.

Ao atualizar os valores de uma função, o RHS deve ser um objeto de lista, portanto, usando um loop em .SD com lapplyfará o truque.

O exemplo abaixo converte colunas inteiras em colunas numéricas

a1 <- data.table(a=1:5, b=6:10, c1=letters[1:5])
sapply(a1, class)  # show classes of columns
#         a           b          c1 
# "integer"   "integer" "character" 

# column name character vector
nm <- c("a", "b")

# Convert columns a and b to numeric type
a1[, j = (nm) := lapply(.SD, as.numeric ), .SDcols = nm ]

sapply(a1, class)
#         a           b          c1 
# "numeric"   "numeric" "character" 

1

Você poderia tentar isso

colname <- as.name ("COL_NAME")

DT2 <- DT [, lista (COL_SUM = sum (eval (colname, .SD))), por = c (grupo)]


1
É sempre recomendável adicionar uma explicação com o seu código em vez de apenas postar o código.
MBorg
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.