web-dev-qa-db-fra.com

Sélectionner / affecter à data.table lorsque les noms de variables sont stockés dans un vecteur de caractères

Comment faites-vous référence aux variables dans un data.table Si les noms de variables sont stockés dans un vecteur de caractères? Par exemple, cela fonctionne pour un data.frame:

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

Comment puis-je effectuer cette même opération pour une table de données, avec ou sans notation :=? La chose évidente de dt[ , list(colname)] ne fonctionne pas (je ne m'y attendais pas non plus).

74
frankc

Deux façons de programmer sélectionnez variable (s):

  1. with = FALSE:

    DT = data.table(col1 = 1:3)
    colname = "col1"
    DT[, colname, with = FALSE] 
    #    col1
    # 1:    1
    # 2:    2
    # 3:    3
    
  2. 'dot dot' (..) préfixe:

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

Pour plus de détails sur la notation 'dot dot' (..), Voir Nouvelles fonctionnalités de 1.10.2 (elle n'est actuellement pas décrite dans le texte d'aide).

Pour assigner à la (aux) variable (s), enveloppez le LHS de := Entre parenthèses:

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

Cette dernière est connue sous le nom de colonne plonk, car vous remplacez le vecteur de colonne entier par référence. Si un sous-ensemble i était présent, il serait sous-assigné par référence. Le parens autour de (colname) Est un raccourci introduit dans la version v1.9.4 sur CRAN octobre 2014. Voici l'actualité :

L'utilisation de with = FALSE Avec := Est désormais déconseillée dans tous les cas, étant donné que le remplacement du LHS de := Par des parenthèses est préféré depuis un certain temps.

colVar = "col1"
DT[, colVar := 1, with = FALSE]                 # deprecated, still works silently
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

Voir aussi la section Détails dans ?`:=`:

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

Et pour répondre à une autre question en commentaire, voici une façon (comme d'habitude, il y a plusieurs façons):

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

ou, vous pourriez trouver plus facile à lire, à écrire et à déboguer juste vers eval a paste, similaire à la construction d'une instruction SQL dynamique à envoyer à un serveur:

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

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

Si vous faites beaucoup cela, vous pouvez définir une fonction d'assistance EVAL:

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

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

Maintenant que data.table 1.8.2 optimise automatiquement j pour plus d'efficacité, il peut être préférable d'utiliser la méthode eval. La get() dans j empêche certaines optimisations, par exemple.

Ou, il y a set(). Une forme fonctionnelle basse de :=, Ce qui serait bien ici. Voir ?set.

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

* Ce n'est pas vraiment une réponse, mais je n'ai pas assez de crédit pour publier des commentaires: /

Quoi qu'il en soit, pour tous ceux qui cherchent à créer une nouvelle colonne dans une table de données avec un nom stocké dans une variable, j'ai ce qui suit pour fonctionner. Je n'ai aucune idée de ses performances. Des suggestions d'amélioration? Est-il sûr de supposer qu'une nouvelle colonne sans nom recevra toujours le nom 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))

Remarquez que je peux le référencer très bien dans la somme () mais ne semble pas pouvoir l'attribuer dans la même étape. BTW, la raison pour laquelle je dois le faire est que colname sera basé sur l'entrée utilisateur dans une application Shiny.

7
efh0888

Pour plusieurs colonnes et une fonction appliquée sur les valeurs des colonnes.

Lors de la mise à jour des valeurs d'une fonction, le RHS doit être un objet liste, donc utiliser une boucle sur .SD avec lapply fera l'affaire.

L'exemple ci-dessous convertit les colonnes entières en colonnes numériques

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" 
2
Sathish