web-dev-qa-db-fra.com

Comment appliquer la même fonction à chaque colonne spécifiée dans un fichier data.table

J'ai un data.table avec lequel je voudrais effectuer la même opération sur certaines colonnes. Les noms de ces colonnes sont donnés dans un vecteur de caractères. Dans cet exemple particulier, j'aimerais multiplier toutes ces colonnes par -1.

Quelques données sur les jouets et un vecteur spécifiant les colonnes pertinentes: 

library(data.table)
dt <- data.table(a = 1:3, b = 1:3, d = 1:3)
cols <- c("a", "b")

En ce moment, je le fais de cette façon, en boucle sur le vecteur de caractère:

for (col in 1:length(cols)) {
   dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
}

Existe-t-il un moyen de le faire directement sans la boucle for?

61
Dean MacGregor

Cela semble fonctionner:

dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols]

Le résultat est

    a  b d
1: -1 -1 1
2: -2 -2 2
3: -3 -3 3

Il y a quelques astuces ici:

  • Comme il y a des parenthèses dans (cols) :=, le résultat est affecté aux colonnes spécifiées dans cols, au lieu d'une nouvelle variable nommée "cols".
  • .SDcols indique à l'appel que nous examinons uniquement ces colonnes et nous permet d'utiliser .SD, le sous-ensemble S du Data associé à ces colonnes.
  • lapply(.SD, ...) fonctionne sur .SD, qui est une liste de colonnes (comme tous les data.frames et data.tables). lapply retourne une liste, donc à la fin j ressemble à cols := list(...).

EDIT: Voici un autre moyen probablement plus rapide, comme l'a mentionné @Arun:

for (j in cols) set(dt, j = j, value = -dt[[j]])
113
Frank

Je voudrais ajouter une réponse, lorsque vous souhaitez également modifier le nom des colonnes. Cela s'avère très pratique si vous souhaitez calculer le logarithme de plusieurs colonnes, ce qui est souvent le cas dans les travaux empiriques.

cols <- c("a", "b")
out_cols = paste("log", cols, sep = ".")
dt[, c(out_cols) := lapply(.SD, function(x){log(x = x, base = exp(1))}), .SDcols = cols]
10
hannes101

UPDATE: Ce qui suit est une excellente façon de le faire sans boucle

dt[,(cols):= - dt[,..cols]]

C'est un moyen astucieux pour une lisibilité facile du code. Mais en ce qui concerne les performances, il reste derrière la solution de Frank selon le résultat obtenu ci-dessous

mbm = microbenchmark(
  base = for (col in 1:length(cols)) {
    dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
  },
  franks_solution1 = dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols],
  franks_solution2 =  for (j in cols) set(dt, j = j, value = -dt[[j]]),
  hannes_solution = dt[, c(out_cols) := lapply(.SD, function(x){log(x = x, base = exp(1))}), .SDcols = cols],
  orhans_solution = for (j in cols) dt[,(j):= -1 * dt[,  ..j]],
  orhans_solution2 = dt[,(cols):= - dt[,..cols]],
  times=1000
)
mbm

Unit: microseconds
expr                  min        lq      mean    median       uq       max neval
base_solution    3874.048 4184.4070 5205.8782 4452.5090 5127.586 69641.789  1000  
franks_solution1  313.846  349.1285  448.4770  379.8970  447.384  5654.149  1000    
franks_solution2 1500.306 1667.6910 2041.6134 1774.3580 1961.229  9723.070  1000    
hannes_solution   326.154  405.5385  561.8263  495.1795  576.000 12432.400  1000
orhans_solution  3747.690 4008.8175 5029.8333 4299.4840 4933.739 35025.202  1000  
orhans_solution2  752.000  831.5900 1061.6974  897.6405 1026.872  9913.018  1000

comme indiqué dans le tableau ci-dessous

 performance_comparison_chart

Ma réponse précédente: Ce qui suit fonctionne également

for (j in cols)
  dt[,(j):= -1 * dt[,  ..j]]
7
Orhan Celik

Aucune des solutions ci-dessus ne semble fonctionner avec un calcul par groupe. Voici le meilleur que j'ai eu:

for(col in cols)
{
    DT[, (col) := scale(.SD[[col]], center = TRUE, scale = TRUE), g]
}
1
Jfly
library(data.table)
(dt <- data.table(a = 1:3, b = 1:3, d = 1:3))

Hence:

   a b d
1: 1 1 1
2: 2 2 2
3: 3 3 3

Whereas (dt*(-1)) yields:

    a  b  d
1: -1 -1 -1
2: -2 -2 -2
3: -3 -3 -3
1
amonk

Pour ajouter un exemple pour créer de nouvelles colonnes basées sur un vecteur chaîne de colonnes. Basé sur la réponse de Jfly:

dt <- data.table(a = rnorm(1:100), b = rnorm(1:100), c = rnorm(1:100), g = c(rep(1:10, 10)))

col0 <- c("a", "b", "c")
col1 <- paste0("max.", col0)  

for(i in seq_along(col0)) {
  dt[, (col1[i]) := max(get(col0[i])), g]
}

dt[,.N, c("g", col1)]
0
Dorian Grv