studio seleccionar filas extraer español eliminar elegir datos data conjunto columnas r performance data.table dplyr

seleccionar - subset en r español



Cómo acelerar el subconjunto por grupos (2)

Solía ​​lograr que mis datos se disputaran con dplyr, pero algunos de los cálculos son "lentos". En particular, subgrupo por grupos, leí que dplyr es lento cuando hay muchos grupos y, según este punto de referencia data.table podría ser más rápido, así que comencé a aprender data.table.

Aquí es cómo reproducir algo cercano a mis datos reales con 250k filas y aproximadamente 230k grupos. Me gustaría agrupar por id1, id2 y subconjunto de las filas con el max(datetime) para cada grupo.

Datos

# random datetime generation function by Dirk Eddelbuettel # https://stackoverflow.com/questions/14720983/efficiently-generate-a-random-sample-of-times-and-dates-between-two-dates rand.datetime <- function(N, st = "2012/01/01", et = "2015/08/05") { st <- as.POSIXct(as.Date(st)) et <- as.POSIXct(as.Date(et)) dt <- as.numeric(difftime(et,st,unit="sec")) ev <- sort(runif(N, 0, dt)) rt <- st + ev } set.seed(42) # Creating 230000 ids couples ids <- data.frame(id1 = stringi::stri_rand_strings(23e4, 9, pattern = "[0-9]"), id2 = stringi::stri_rand_strings(23e4, 9, pattern = "[0-9]")) # Repeating randomly the ids[1:2000, ] to create groups ids <- rbind(ids, ids[sample(1:2000, 20000, replace = TRUE), ]) # Adding random datetime variable and dummy variables to reproduce real datas datas <- transform(ids, datetime = rand.datetime(25e4), var1 = sample(LETTERS[1:6], 25e4, rep = TRUE), var2 = sample(c(1:10, NA), 25e4, rep = TRUE), var3 = sample(c(1:10, NA), 25e4, rep = TRUE), var4 = rand.datetime(25e4), var5 = rand.datetime(25e4)) datas.tbl <- tbl_df(datas) datas.dt <- data.table(datas, key = c("id1", "id2"))

No pude encontrar la forma directa de subconjuntos por grupos con data.table, así que hice esta pregunta: Filtrar filas por grupos con data.table

Sugerimos que use .SD:

datas.dt[, .SD[datetime == max(datetime)], by = c("id1", "id2")]

Pero tengo dos problemas, funciona con fecha pero no con POSIXct ("Error en UseMethod (" as.data.table "): ningún método aplicable para ''as.data.table'' aplicado a un objeto de la clase" c ('' POSIXct '','' POSIXt '') ""), y esto es muy lento. Por ejemplo con fechas:

> system.time({ + datas.dt[, .SD[as.Date(datetime) == max(as.Date(datetime))], by = c("id1", "id2")] + }) utilisateur système écoulé 207.03 0.00 207.48

Así que encontré otra forma mucho más rápida de lograr esto (y mantener las fechas y horas) con data.table:

Las funciones

f.dplyr <- function(x) x %>% group_by(id1, id2) %>% filter(datetime == max(datetime)) f.dt.i <- function(x) x[x[, .I[datetime == max(datetime)], by = c("id1", "id2")]$V1] f.dt <- function(x) x[x[, datetime == max(datetime), by = c("id1", "id2")]$V1]

Pero luego pensé que data.table sería mucho más rápido, la diferencia horaria con dplyr no es significativa.

Microbenchmark

mbm <- microbenchmark( dplyr = res1 <- f.dplyr(datas.tbl), data.table.I = res2 <- f.dt.i(datas.dt), data.table = res3 <- f.dt(datas.dt), times = 50L) Unit: seconds expr min lq mean median uq max neval dplyr 31.84249 32.24055 32.59046 32.61311 32.88703 33.54226 50 data.table.I 30.02831 30.94621 31.19660 31.17820 31.42888 32.16521 50 data.table 30.28923 30.84212 31.09749 31.04851 31.40432 31.96351 50

¿Me falta / uso incorrecto de algo con data.table? ¿Tienes ideas para acelerar este cálculo?

Cualquier ayuda sería muy apreciada ! Gracias

Editar: Algunas precisiones sobre el sistema y las versiones de paquetes utilizados para el microbenchmark. (La computadora no es una máquina de guerra, 12Go i5)

Sistema

sessionInfo() R version 3.1.3 (2015-03-09) Platform: x86_64-w64-mingw32/x64 (64-bit) Running under: Windows 7 x64 (build 7601) Service Pack 1 locale: [1] LC_COLLATE=French_France.1252 LC_CTYPE=French_France.1252 [3] LC_MONETARY=French_France.1252 LC_NUMERIC=C [5] LC_TIME=French_France.1252 attached base packages: [1] stats graphics grDevices utils datasets methods base other attached packages: [1] readr_0.1.0 ggplot2_1.0.1 microbenchmark_1.4-2 [4] data.table_1.9.4 dplyr_0.4.1 plyr_1.8.2 loaded via a namespace (and not attached): [1] assertthat_0.1 chron_2.3-45 colorspace_1.2-6 DBI_0.3.1 [5] digest_0.6.8 grid_3.1.3 gtable_0.1.2 lazyeval_0.1.10 [9] magrittr_1.5 MASS_7.3-39 munsell_0.4.2 parallel_3.1.3 [13] proto_0.3-10 Rcpp_0.11.5 reshape2_1.4.1 scales_0.2.4 [17] stringi_0.4-1 stringr_0.6.2 tools_3.1.3 > packageVersion("data.table") [1] ‘1.9.4’ > packageVersion("dplyr") [1] ‘0.4.1’


¿Qué tal resumir data.table y join datos originales?

system.time({ datas1 <- datas.dt[, list(datetime=max(datetime)), by = c("id1", "id2")] #summarize the data setkey(datas1, id1, id2, datetime) setkey(datas.dt, id1, id2, datetime) datas2 <- datas.dt[datas1] }) # user system elapsed # 0.083 0.000 0.084

que filtra correctamente los datos

system.time(dat1 <- datas.dt[datas.dt[, .I[datetime == max(datetime)], by = c("id1", "id2")]$V1]) # user system elapsed # 23.226 0.000 23.256 all.equal(dat1, datas2) # [1] TRUE

Apéndice

setkey argumento setkey es superfluo si está utilizando la versión de desarrollo de data.table (Gracias a @akrun por el puntero)

system.time({ datas1 <- datas.dt[, list(datetime=max(datetime)), by = c("id1", "id2")] #summarize the data datas2 <- datas.dt[datas1, on=c(''id1'', ''id2'', ''datetime'')] })


Gran pregunta!

Asumiré que df y dt son los nombres de los objetos para una escritura fácil / rápida.

df = datas.tbl dt = datas.dt

Comparación a nivel de optimización -O3 :

Primero, aquí está el tiempo en mi sistema en la versión CRAN actual de dplyr y la versión de desarrollo de data.table . La versión de desarrollo de dplyr parece sufrir regresiones de rendimiento (y está siendo dplyr por Romain).

system.time(df %>% group_by(id1, id2) %>% filter(datetime == max(datetime))) # 25.291 0.128 25.610 system.time(dt[dt[, .I[datetime == max(datetime)], by = c("id1", "id2")]$V1]) # 17.191 0.075 17.349

Ejecuté esto varias veces, y parece que no cambia. Sin embargo, compilo todos los paquetes con el indicador de optimización -O3 (estableciendo ~/.R/Makevars adecuadamente). Y he observado que el rendimiento de data.table mejora mucho más que otros paquetes que lo he comparado con -O3 .

Comparación de velocidad de agrupamiento

En segundo lugar, es importante entender la razón de tal lentitud. Primero, comparemos el tiempo para agrupar .

system.time(group_by(df, id1, id2)) # 0.303 0.007 0.311 system.time(data.table:::forderv(dt, by = c("id1", "id2"), retGrp = TRUE)) # 0.002 0.000 0.002

Aunque hay un total de 250,000 filas, su tamaño de datos es de aproximadamente ~ 38MB. Con este tamaño, es poco probable que vea una diferencia notable en la velocidad de agrupación.

La agrupación de data.table es >100x más rápida aquí, claramente no es la razón de tal lentitud ...

¿Por qué es lento?

Entonces, ¿cuál es la razón? datatable.verbose opción datatable.verbose y verifiquemos nuevamente:

options(datatable.verbose = TRUE) dt[dt[, .I[datetime == max(datetime)], by = c("id1", "id2")]$V1] # Detected that j uses these columns: datetime # Finding groups (bysameorder=TRUE) ... done in 0.002secs. bysameorder=TRUE and o__ is length 0 # lapply optimization is on, j unchanged as ''.I[datetime == max(datetime)]'' # GForce is on, left j unchanged # Old mean optimization is on, left j unchanged. # Starting dogroups ... # memcpy contiguous groups took 0.097s for 230000 groups # eval(j) took 17.129s for 230000 calls # done dogroups in 17.597 secs

¡Así que eval(j) solo tomó ~ 97% del tiempo! La expresión que proporcionamos en j se evalúa para cada grupo . Como tiene 230,000 grupos y hay una penalización en la llamada eval() , eso se suma.

Evitar la penalización eval()

Como somos conscientes de esta penalización, seguimos adelante y comenzamos a implementar versiones internas de algunas funciones de uso común: sum , mean , min , max . Esto / debería expandirse a tantas otras funciones como sea posible (cuando encontremos el tiempo).

Entonces, intentemos calcular el tiempo solo para obtener max(datetime) primero:

dt.agg = dt[, .(datetime = max(datetime)), by = .(id1, id2)] # Detected that j uses these columns: datetime # Finding groups (bysameorder=TRUE) ... done in 0.002secs. bysameorder=TRUE and o__ is length 0 # lapply optimization is on, j unchanged as ''list(max(datetime))'' # GForce optimized j to ''list(gmax(datetime))''

Y es instantáneo. ¿Por qué? Debido a que max() se optimiza internamente a gmax() y no hay una llamada eval() para cada uno de los 230K grupos.

Entonces, ¿por qué datetime == max(datetime) instantáneo? Porque es más complicado analizar tales expresiones y optimizarlas internamente, y aún no lo hemos logrado.

Solución alternativa

Entonces, ahora que conocemos el problema y una forma de solucionarlo, usémoslo.

dt.agg = dt[, .(datetime = max(datetime)), by = .(id1, id2)] dt[dt.agg, on = c("id1", "id2", "datetime")] # v1.9.5+

Esto toma ~ 0.14 segundos en mi Mac.

Tenga en cuenta que esto solo es rápido porque la expresión se optimiza a gmax() . Compáralo con:

dt[, .(datetime = base::max(datetime)), by = .(id1, id2)]

Estoy de acuerdo en que optimizar las expresiones más complicadas para evitar la penalización eval() sería la solución ideal, pero aún no hemos llegado.