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


92

Comment faites-vous référence aux variables dans a data.tablesi 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 data.table, avec ou sans :=notation? La chose évidente de dt[ , list(colname)]ne fonctionne pas (et je ne m'y attendais pas).

Réponses:


133

Deux façons de sélectionner par programme des variables:

  1. with = FALSE:

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

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

Pour une description plus détaillée de la ..notation «point point» ( ), voir Nouvelles fonctionnalités de la 1.10.2 (elle n'est actuellement pas décrite dans le texte d'aide).

Pour attribuer une ou plusieurs variables, placez la LHS de :=entre parenthèses:

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

Ce dernier est connu sous le nom de plonk de colonne , car vous remplacez le vecteur de colonne entier par référence. Si un sous-ensemble iétait présent, il se sous-attribuerait par référence. Le parens autour (colname)est un raccourci introduit dans la version v1.9.4 sur CRAN octobre 2014. Voici la nouvelle :

L'utilisation de with = FALSEwith :=est désormais déconseillée dans tous les cas, étant donné que l'encapsulation de la LHS de :=avec des parenthèses est préférée depuis un certain temps.

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

Voir également 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 de lire, d'écrire et de déboguer juste à evalun 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 la data.table1.8.2 optimise automatiquement l' jefficacité, il peut être préférable d'utiliser la evalméthode. Le get()in jempêche certaines optimisations, par exemple.

Ou, il y en a set(). Une forme fonctionnelle de faible frais généraux, :=qui conviendrait ici. Voir ?set.

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

1
Merci pour la réponse Matthew. Le with = FALSE résout définitivement une partie de mon problème. En réalité cependant, je veux remplacer la colonne par le sperme de la colonne. Puis-je référencer le nom de la colonne par variable sur le côté droit de l'affectation d'une manière ou d'une autre?
frankc

En fait, je viens de storded le sperme à l'extérieur avec un nom différent qui n'existe pas à l'intérieur du dt et qui fonctionne bien.
frankc

1
Mais ce serait toute une ligne supplémentaire! Pas très élégant :) Mais ok parfois c'est utile. Dans ces cas, il est préférable de commencer le nom de la variable par ., ou ..d'éviter tout masquage potentiel si DTjamais il contenait ce symbole comme nom de colonne à l'avenir (et respectez la convention par laquelle les noms de colonne ne commencent pas .). Il y a quelques demandes de fonctionnalités pour le rendre plus robuste aux problèmes de portée comme celui-ci, tels que l'ajout .()et ..().
Matt Dowle

J'ai répondu avant de remarquer que vous avez modifié votre réponse. Ma première pensée avait été eval (parse ()) mais pour une raison quelconque, j'avais du mal à le faire fonctionner, quand il m'est venu à l'esprit de simplement le faire en externe. C'est une excellente réponse avec beaucoup de choses auxquelles je n'ai pas pensé. Merci pour data.table en général, c'est un excellent package.
frankc

2
Notez que vous pouvez utiliser l'interpolation de chaîne de type quasi-perl de fn$partir du package gsubfn pour améliorer la lisibilité de la solution EVAL: library(gsubfn); fn$EVAL( "DT[,$colname:=cumsum($colname)]" ).
G. Grothendieck

8

* Ce n'est pas vraiment une réponse, mais je n'ai pas assez de street cred pour poster 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 prudent 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 sum () mais je n'arrive pas à le faire attribuer dans la même étape. BTW, la raison pour laquelle je dois le faire est que colname sera basé sur l'entrée de l'utilisateur dans une application Shiny.


+1 pour juste travailler: je suis d'accord que ce ne doit pas être "le moyen" de le faire, mais après avoir passé environ 45 minutes à se consacrer à chaque article SO sur ce sujet, c'est la seule solution à laquelle j'ai pu arriver travail - merci d'avoir pris le temps de le signaler!
neuropsych

Heureux d'avoir pu aider! Malheureusement, je n'ai jamais trouvé de solution plus élégante directement en utilisant data.tables, bien que ce 3 lignes ne soit pas terrible. Dans mon scénario, j'ai réalisé qu'une alternative plus simple aurait été d'utiliser tidyr pour simplement rendre mes données "longues" au lieu de "larges", car en fonction de l'entrée de l'utilisateur, je pourrais toujours filtrer sur une seule colonne plutôt que de sélectionner dans un ensemble de colonnes.
efh0888

2
Il n'est pas sûr de supposer que V1c'est le nouveau nom. Par exemple, si vous lisez csv avec freadet qu'il y a une colonne sans nom, elle aura un V1nom (et read.csvdonnera X). Il est donc possible que votre table ait déjà un fichier V1. Peut-être juste obtenir le nom parnames(DT)[length(names(DT))]
dracodoc

2

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

Lors de la mise à jour des valeurs d'une fonction, le RHS doit être un objet de liste, donc utiliser une boucle sur .SDavec lapplyfera 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

Récupérez plusieurs colonnes de data.table via une variable ou une fonction:

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)]

qui tous rapportent

   that whatever
1:    1        1
2:    2        2

Je trouve la .SDcolsvoie la plus élégante.


1

Tu pourrais essayer ça

colname <- as.name ("COL_NAME")

DT2 <- DT [, list (COL_SUM = sum (eval (colname, .SD))), by = c (group)]


1
Il est toujours recommandé d'ajouter une explication avec votre code au lieu de simplement poster du code.
MBorg
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.