107 votes

Appliquer une fonction à chaque colonne spécifiée dans un tableau de données et mettre à jour par référence.

J'ai un tableau 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, je voudrais multiplier toutes ces colonnes par -1.

Quelques données de 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")

Pour l'instant, je le fais de cette façon, en bouclant sur le vecteur de caractères :

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

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

183voto

Frank Points 51885

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 :

  • Parce qu'il y a des parenthèses dans (cols) := le résultat est affecté aux colonnes spécifiées dans le champ cols plutôt que dans une nouvelle variable nommée "cols".
  • .SDcols indique à l'appel que nous ne regardons que ces colonnes, et nous permet d'utiliser la fonction .SD le S ubrique de l D ata associés à ces colonnes.
  • lapply(.SD, ...) fonctionne sur .SD qui est une liste de colonnes (comme tous les data.frames et data.tables). lapply renvoie une liste, donc à la fin j ressemble à cols := list(...) .

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

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

23voto

hannes101 Points 638

Je voudrais ajouter une réponse, lorsque vous souhaitez également changer le nom des colonnes. Cela s'avère très pratique si vous voulez 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]

13voto

Orhan Celik Points 758

UPDATE : Voici une façon élégante de le faire sans boucle for

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

Il s'agit d'un moyen efficace de faciliter la lecture du code. Mais pour ce qui est de la performance, elle reste derrière la solution de Frank selon les résultats du microbenchmark 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]]

2voto

Jfly Points 232

Aucune des solutions ci-dessus ne semble fonctionner avec le calcul par groupe. La solution suivante est la meilleure que j'ai obtenue :

for(col in cols)
{
    DT[, (col) := scale(.SD[[col]], center = TRUE, scale = TRUE), g]
}

2voto

Arthur Yip Points 931

dplyr les fonctions fonctionnent sur data.table donc voici un dplyr solution qui permet également "d'éviter la boucle for" :)

dt %>% mutate(across(all_of(cols), ~ -1 * .))

Je l'ai testé en utilisant le code d'orhan (en ajoutant des lignes et des colonnes) et vous verrez que dplyr::mutate con across s'exécute généralement plus rapidement que la plupart des autres solutions et plus lentement que la solution data.table utilisant lapply.

library(data.table); library(dplyr)
dt <- data.table(a = 1:100000, b = 1:100000, d = 1:100000) %>% 
  mutate(a2 = a, a3 = a, a4 = a, a5 = a, a6 = a)
cols <- c("a", "b", "a2", "a3", "a4", "a5", "a6")

dt %>% mutate(across(all_of(cols), ~ -1 * .))
#>               a       b      d      a2      a3      a4      a5      a6
#>      1:      -1      -1      1      -1      -1      -1      -1      -1
#>      2:      -2      -2      2      -2      -2      -2      -2      -2
#>      3:      -3      -3      3      -3      -3      -3      -3      -3
#>      4:      -4      -4      4      -4      -4      -4      -4      -4
#>      5:      -5      -5      5      -5      -5      -5      -5      -5
#>     ---                                                               
#>  99996:  -99996  -99996  99996  -99996  -99996  -99996  -99996  -99996
#>  99997:  -99997  -99997  99997  -99997  -99997  -99997  -99997  -99997
#>  99998:  -99998  -99998  99998  -99998  -99998  -99998  -99998  -99998
#>  99999:  -99999  -99999  99999  -99999  -99999  -99999  -99999  -99999
#> 100000: -100000 -100000 100000 -100000 -100000 -100000 -100000 -100000

library(microbenchmark)
mbm = microbenchmark(
  base_with_forloop = for (col in 1:length(cols)) {
    dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
  },
  franks_soln1_w_lapply = dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols],
  franks_soln2_w_forloop =  for (j in cols) set(dt, j = j, value = -dt[[j]]),
  orhans_soln_w_forloop = for (j in cols) dt[,(j):= -1 * dt[,  ..j]],
  orhans_soln2 = dt[,(cols):= - dt[,..cols]],
  dplyr_soln = (dt %>% mutate(across(all_of(cols), ~ -1 * .))),
  times=1000
)

library(ggplot2)
ggplot(mbm) +
  geom_violin(aes(x = expr, y = time)) +
  coord_flip()

Créé le 2020-10-16 par le paquet reprex (v0.3.0)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X