4 votes

Augmenter la vitesse de collage de deux colonnes dans data.table en R (reproductible)

J'ai des données comme ça :

library(data.table)
NN = 10000000
set.seed(32040)
DT <- data.table(
  col = 1:10000000,
  timestamp = 1521872652 + sample(7000001, NN, replace = TRUE)
)

J'essaie d'extraire l'année et la semaine uniques sous forme de code afin de pouvoir trier les doublons (la table de données réelle contient l'ID utilisateur et bien plus encore). I J'ai une solution actuelle qui fonctionne (ci-dessous), mais elle est lente. sur la partie collant les semaines et l'année uniquement à partir de la colonne de date. La création de la date à l'aide de anytime l'emballage et le tirage week y year de lubridate sont toujours très rapides. Quelqu'un peut-il m'aider à accélérer le processus ? Merci.

Mon code lent (fonctionne mais j'aimerais l'accélérer) :

library(anytime)
library(lubridate)
tz<-"Africa/Addis_Ababa"
DT$localtime<-  anytime(DT$timestamp, tz=tz) ###Lightning fast
DT$weekuni <- paste(year(DT$localtime),week(DT$localtime),sep="") ###super slow

Mes tests montrent que c'est le paste qui me tue :

Très rapide anytime la conversion en date :

system.time(DT$localtime<-  anytime(DT$timestamp, tz=tz)) ###Lightning fast
       user  system elapsed 
      0.264   0.417   0.933 

Rapidement lubridate conversion de la semaine et de l'année à partir de la date, mais lente paste :

> system.time(DT$weekuni1 <- week(DT$localtime)) ###super slow
   user  system elapsed 
  1.203   0.188   1.400 
> system.time(DT$weekuni2 <- year(DT$localtime))
   user  system elapsed 
  1.229   0.189   1.427 
> system.time(DT$weekuni <- paste0(DT$weekuni1,dt$weekuni2))
   user  system elapsed 
 14.652   0.344  15.483

5voto

MichaelChirico Points 76

J'ai fait en sorte que votre code s'exécute environ 50% plus vite en utilisant format au lieu de paste .

D'abord, je ne suis pas sûr que l'intérêt de anytime pour votre cas d'utilisation puisqu'on peut simplement jeter l'horodatage dans un fichier POSIXct structure presque instantanément :

DT[ , localtime := .POSIXct(timestamp, tz = tz)]

Ensuite, j'ai fait des recherches sur ?strptime pour les codes de formatage basés sur la semaine ISO à obtenir :

DT[ , weekuni := format(localtime, format = '%G%V')]

Je ne suis pas sûr à 100% que ce sera toujours la même chose que paste(year, week) mais c'était pour vos données de test. est une différence entre eux, vous devriez vous demander si cela a vraiment de l'importance pour vous.

La seule chose à laquelle je pense qui pourrait être plus rapide serait d'utiliser l'arithmétique des entiers sur le timestamp lui-même. C'est nettement plus facile si Africa/Addis_Ababa n'a pas subi d'ajustement de son décalage UTC dans votre échantillon de période (malheureusement, il semble que Africa/Addis_Ababa observe l'heure d'été, de sorte que le décalage UTC varie entre 2 et 3 heures, ce qui rend l'approche arithmétique des nombres entiers nettement plus difficile).


Pour mémoire, l'utilisation de data.table::year y data.table::week est à peu près aussi rapide que l'approche utilisée ici, mais il utilise une définition de l'"année" et de la "semaine" différente de celle de l'UE. lubridate (qui utilise par défaut l'année/la semaine ISO que %G%V fait ci-dessus).

data.table n'a pas encore de isoyear la mise en œuvre, et data.table::isoweek est nettement plus lent que lubridate::week .

3voto

Hugh Points 6858

Si vous êtes prêt à définir une semaine-année basée uniquement sur la date, vous pouvez obtenir une solution 20 fois plus rapide :

library(data.table)
NN = 10000000
# NN = 1e4
set.seed(32040)
DT <- data.table(
  col = seq_len(NN),
  timestamp = 1521872652 + sample(7000001, NN, replace = TRUE)
)
DT1 <- copy(DT)

DT2 <- copy(DT)
tz <- "Africa/Addis_Ababa"

old <- function(DT) {
  DT$localtime<-  anytime::anytime(DT$timestamp, tz=tz) ###Lightning fast
  DT$weekuni <- paste(lubridate::year(DT$localtime), lubridate::week(DT$localtime), sep="")
  DT[, timestamp := NULL]
  DT[, .(col, localtime, weekuni)]
}

new <- function(DT) {
  DT[ , localtime := anytime::anytime(timestamp, tz = tz)]
  DT[, Date := as.Date(localtime)]
  DT[, weekuni := paste0(lubridate::year(.BY[[1L]]), lubridate::week(.BY[[1L]])),
     keyby = "Date"]
  DT[, Date := NULL]
  # DT[, timestamp := NULL]
  DT[order(col), .(col, localtime, weekuni)]
}

bench::mark(old(DT1), new(DT2), check = FALSE, filter_gc = FALSE)
#> # A tibble: 2 x 10
#>   expression     min    mean median    max `itr/sec` mem_alloc  n_gc n_itr
#>   <chr>      <bch:t> <bch:t> <bch:> <bch:>     <dbl> <bch:byt> <dbl> <int>
#> 1 old(DT1)    22.39s  22.39s 22.39s 22.39s    0.0447    2.28GB     5     1
#> 2 new(DT2)     1.13s   1.13s  1.13s  1.13s    0.888   878.12MB     1     1
#> # ... with 1 more variable: total_time <bch:tm>

Créé le 2018-06-23 par le paquet reprex (v0.2.0).

Même si vous ne le faites pas, vous pouvez toujours obtenir une vitesse 10 fois plus rapide en utilisant seulement paste une fois par date :

library(data.table)
NN = 1e7
# NN = 1e4
set.seed(32040)
DT <- data.table(
  col = seq_len(NN),
  timestamp = 1521872652 + sample(7000001, NN, replace = TRUE)
)
DT1 <- copy(DT)

DT2 <- copy(DT)
DT3 <- copy(DT)
tz <- "Africa/Addis_Ababa"

old <- function(DT) {
  DT$localtime<-  anytime::anytime(DT$timestamp, tz=tz) ###Lightning fast
  DT$weekuni <- paste(lubridate::year(DT$localtime), lubridate::week(DT$localtime), sep="")
  DT[, timestamp := NULL]
  DT[, .(col, weekuni)]
}

new <- function(DT) {
  DT[ , Date := anytime::anydate(timestamp, tz = tz)]
  DT[, weekuni := paste0(lubridate::year(.BY[[1L]]), lubridate::week(.BY[[1L]])),
     keyby = "Date"]
  DT[, Date := NULL]
  # DT[, timestamp := NULL]
  setorderv(DT[, .(col, weekuni)], "col")
}

bench::mark(old(DT1), new(DT2), check = TRUE, filter_gc = FALSE)
#> # A tibble: 2 x 10
#>   expression     min    mean median    max `itr/sec` mem_alloc  n_gc n_itr
#>   <chr>      <bch:t> <bch:t> <bch:> <bch:>     <dbl> <bch:byt> <dbl> <int>
#> 1 old(DT1)     22.2s   22.2s  22.2s  22.2s    0.0450    2.21GB     4     1
#> 2 new(DT2)      2.8s    2.8s   2.8s   2.8s    0.357     1.42GB     3     1
#> # ... with 1 more variable: total_time <bch:tm>

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