1
votes

Comment enregistrer des plages (ou intervalles de temps) qui ne se chevauchent pas de manière R "data.table"?

Mon objectif était de définir des plages (ou intervalles de temps) uniques sans chevauchement par ID, lorsque chaque ID comportait plusieurs plages d'exposition potentiellement superposées. J'ai trouvé que la fonction "aplatir" du package R "IntervalSurgeon" peut implémenter la tâche. Ma question est: comment implémenter efficacement la même tâche et obtenir le même résultat "tab_out" de manière "data.table"?

library(data.table)
library(IntervalSurgeon)

set.seed(2019)

N <- 3 # number of IDs

IDs <- paste0("ID", 1:N) # unique IDs

K <- 4 # number of exposures per ID

DT <- data.table(IDs = rep(IDs, each = K), 
    starts = sample(1:20, N * K, replace = T))[,
    ends := starts + sample(1:5, N * K, replace = T)]


DT <- DT[order(IDs, starts),]

tab_out <- DT[, as.list(data.table(
    flatten(as.matrix(cbind(starts, ends))))), 
    by = IDs]

DT
    IDs starts ends
 1: ID1      7   11
 2: ID1     13   17
 3: ID1     15   16
 4: ID1     16   18
 5: ID2      1    5
 6: ID2      1    4
 7: ID2      2    3
 8: ID2     17   19
 9: ID3      3    6
10: ID3     13   16
11: ID3     14   15
12: ID3     16   21

tab_out
   IDs V1 V2
1: ID1  7 11
2: ID1 13 18
3: ID2  1  5
4: ID2 17 19
5: ID3  3  6
6: ID3 13 21


0 commentaires

3 Réponses :


0
votes

Voici une solution utilisant le intervals-package..

exemple de données

myfun <- function( y ) {
  data.table::as.data.table( 
    intervals::interval_union(
      intervals::Intervals( as.matrix( y ) ), check_valid = TRUE ) 
    )
}

DT[, myfun( .SD ), by = .(IDs)]

#    IDs V1 V2
# 1: ID1  7 11
# 2: ID1 13 18
# 3: ID2  1  5
# 4: ID2 17 19
# 5: ID3  3  6
# 6: ID3 13 21

code

library( data.table )
library( intervals )

DT <- fread("
IDs starts ends
ID1      7   11
ID1     13   17
ID1     15   16
ID1     16   18
ID2      1    5
ID2      1    4
ID2      2    3
ID2     17   19
ID3      3    6
ID3     13   16
ID3     14   15
ID3     16   21")


2 commentaires

Cela semble inefficace. Je m'attendrais à ce qu'il copie en profondeur les données.


@Roland Je suis ouvert à toute suggestion ;-)




1
votes

Voici les temps de calcul des approches "IntervalSurgeon", "intervalles" et "data.table". Les heures correspondent à l'ensemble de données contenant un million d'identifiants, avec 10 expositions par identifiant, soit 10 millions de lignes. J'ai fait une seule exécution en raison de l'approche «intervalles» qui prenait trop de temps.

La machine était un MacBook Pro (15 pouces, 2018), avec un processeur Intel Core i9 à 2,9 GHz, une mémoire DDR4 de 32 Go à 2400 MHz, fonctionnant sur MacOC Mojave v. 10.14.6, max 12 threads.

L'approche "data.table" était clairement gagnante, la même que l'approche "intervalles" était clairement perdante:

TEMPS DE CALCUL

sessionInfo()
R version 3.5.2 (2018-12-20)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS Mojave 10.14.6

Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRlapack.dylib

locale:
[1] en_AU.UTF-8/en_AU.UTF-8/en_AU.UTF-8/C/en_AU.UTF-8/en_AU.UTF-8

attached base packages:
[1] parallel  stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] intervals_0.15.1    IntervalSurgeon_1.0 fst_0.9.0           data.table_1.12.2  

loaded via a namespace (and not attached):
[1] compiler_3.5.2 Rcpp_1.0.1 

Il est intéressant de noter qu'aucune des approches n'a bénéficié de la définition d'un plus grand nombre de threads data.table via setDTthreads (). Le fractionnement du jeu de données en 100 parties égales et le passage à l'approche "data.table" via parallel :: mclapply (mc.cores = 10) amène le temps de calcul en dessous de 5 secondes (le code est également fourni ci-dessous).

L'approche combinée "data.table + parallel :: mclapply" a également fonctionné avec un ensemble de données beaucoup plus grand de 50 millions d'ID (généré de la même manière que l'ensemble de données d'ID 1M mais avec MM = 50). Cet ensemble de données comprend 500 millions de lignes, divisées en 1000 sous-ensembles pour mclapply. J'ai mis mc.cores = 4 pour ne pas épuiser la RAM. Le temps de calcul d'une seule exécution était de 451,485 s, ce qui n'est pas un mauvais résultat pour un ordinateur portable! Ce serait formidable que ce type d'analyse soit un jour incorporé dans le package data.table, avec d'autres types d'analyses d'intervalle similaires comme dans le IntervalSurgeon package.

GÉNÉRATION DU JEU DE DONNÉES

library(data.table)
library(IntervalSurgeon)
library(intervals)
library(fst)
library(parallel)

rm(list = ls())
gc()

mc_cores <- 10

threads_no <- 2L
setDTthreads(threads_no)
print(getDTthreads())

DT <- read_fst("fst_DT_1Mx3.fst", as.data.table = T)

# split the dataset into G equal parts
##################### 
G <- 100
nn <- nrow(DT)
step_c <- nn/G

# collect the indexes in the list
list_inx <- vector('list', G)

ii_low <- 1
ii_up <- step_c
for (i2 in 1:G) {

    list_inx[[i2]] <- ii_low:ii_up 
    ii_low <- ii_up+1
    ii_up <- step_c*(i2+1)

    }
##################### 

ptm3 <- proc.time()

# approach implementation
#####################
list_out <- mclapply(1:G, function(i) {
            DT_tmp <- DT[list_inx[[i]],]
            return(DT_tmp[, g := c(0L, cumsum(shift(starts, -1L) > cummax(ends))[-.N]), IDs][,
                .(min(starts), max(ends)), .(g, IDs)][,g := NULL])
                },
                mc.cores = mc_cores
                )
#####################

exec_time3 <- proc.time() - ptm3
print("data.table + parallel::mclapply:")
print(exec_time3)


0 commentaires