ventajas tecnicas tablas tabla las implementacion hashing estructura ejemplos direccionamiento desventajas datos colision busqueda binaria algoritmo abierto r data.table

r - tablas - tecnicas de hashing



Subconjunto de una tabla de datos por rango haciendo uso de búsqueda binaria (2)

¿Cómo se hace para subordinar una tabla de datos por un rango numérico, con la intención de usar la búsqueda binaria?

Por ejemplo:

require(data.table) set.seed(1) x<-runif(10000000,min=0,max=10) y<-runif(10000000,min=0,max=10) DF<-data.frame(x,y) DT<-data.table(x,y) system.time(DFsub<-DF[DF$x>5 & DF$y<7,]) # user system elapsed # 1.529 0.250 1.821 #subset DT system.time(DTsub<-DT[x>5 & y<7]) # user system elapsed #0.716 0.119 0.841

Lo anterior no usa una tecla (escaneo vectorial), y la aceleración no es tan dramática. ¿Cuál es la sintaxis para subcontratar un rango numérico de una tabla de datos, haciendo uso de la búsqueda binaria? No puedo encontrar un buen ejemplo en la documentación; sería útil si alguien pudiera proporcionar un ejemplo utilizando la tabla de datos de juguetes anterior.

EDITAR: Esta pregunta es similar, pero aún no muestra cómo subconjuntar por un rango: data.table: vector scan v búsqueda binaria con columnas numéricas - tecla de configuración muy lenta


Interesante pregunta. Primero veamos los datos de ejemplo:

> print(DT) x y 1: 2.607703e-07 5.748127 2: 8.894131e-07 5.233994 3: 1.098961e-06 9.834267 4: 1.548324e-06 2.016585 5: 1.569279e-06 7.957730 --- 9999996: 9.999996e+00 9.977782 9999997: 9.999998e+00 2.666575 9999998: 9.999999e+00 6.869967 9999999: 9.999999e+00 1.953145 10000000: 1.000000e+01 4.001616 > length(DT$x) [1] 10000000 > length(unique(DT$x)) [1] 9988478 > length(DT$y) [1] 10000000 > length(unique(DT$y)) [1] 9988225 > DT[,.N,by=x][,table(N)] N 1 2 3 9976965 11504 9 > DT[,.N,by="x,y"][,table(N)] N 1 10000000 >

Así que hay casi 10 millones de valores de punto flotante únicos en la primera columna: algunos grupos de tamaño 2 y 3 filas, pero en su mayoría grupos de 1 fila. Una vez que se incluye la segunda columna, hay 10 millones de grupos únicos de tamaño 1 fila. Este es un problema bastante difícil, ya que data.table está diseñado más para datos agrupados en mente; por ejemplo, (id, fecha), (id1, id2, fecha, hora) etc.

Sin embargo, data.table y setkey admiten datos de punto flotante en las claves, así que vamos a setkey .

En mi netbook lento:

> system.time(setkey(DT,x,y)) user system elapsed 7.097 0.520 7.650 > system.time(DT[x>5 & y<7]) user system elapsed 2.820 0.292 3.122

Por lo tanto, el método de escaneo vectorial es más rápido que configurar la clave (y aún no hemos usado la clave). Dado que los datos son de punto flotante y casi únicos, esto no es demasiado sorprendente, pero creo que es un momento bastante rápido para que setkey 10 millones de dobles totalmente aleatorios y casi únicos.

Compara con la base, por ejemplo, simplemente clasificando x ni siquiera y también:

> system.time(base::order(x)) user system elapsed 72.445 0.292 73.072

Asumiendo que estos datos son representativos de sus datos reales, y no desea hacer esto solo una vez sino varias veces, por lo que están dispuestos a pagar el precio de setkey , el primer paso es bastante claro:

system.time(w <- DT[.(5),which=TRUE,roll=TRUE]) user system elapsed 0.004 0.000 0.003 > w [1] 4999902

Pero aquí estamos atrapados. Un siguiente paso como DT[(w+1):nrow(DT)] es feo y se copia. No puedo pensar en una forma decente de usar la tecla desde aquí para hacer la parte y<7 también. En otros datos de ejemplo, hacemos algo como DT[.(unique(x), 7), which=TRUE, roll=TRUE] pero en este caso los datos son tan únicos y de punto flotante que van a ser lentos.

Idealmente, esta tarea necesita una combinación de rangos (FR # 203) para su implementación. La sintaxis en este ejemplo podría ser:

DT[.( c(5,Inf), c(-Inf,7) )]

o para hacerlo más fácil, DT[x>5 & y<7] podría optimizarse para hacerlo bajo el capó. Permitir un rango de dos columnas en i que se una a las columnas x correspondientes podría ser bastante útil y ha surgido varias veces.

Las aceleraciones en v1.9.2 debían hacerse primero antes de que pudiéramos pasar a cosas como esas. Si prueba setkey en estos datos en v1.8.10 encontrará que v1.9.2 es significativamente más rápido.

Ver también :

Cómo unirse automáticamente a una tabla de datos en una condición

Eliminar un rango en data.table


Según lo solicitado por Matt Dowle, volví a ejecutar el código y los tiempos para incluir una comparación con la función between ahora incluida en el paquete data.table. Parece que el escaneo vectorial de una columna de punto flotante sigue siendo el enfoque más eficiente.

#OP''s example data require(data.table) set.seed(1) x<-runif(10000000,min=0,max=10) y<-runif(10000000,min=0,max=10) DF<-data.frame(x,y) DT<-data.table(x,y)

Subconjunto como un data.frame

system.time(DFsub<-DF[DF$x>5 & DF$y<7,]) # user system elapsed # 0.506 0.062 0.576

Subconjunto como data.table con escaneo vectorial.

system.time(DTsub<-DT[x>5 & y<7]) # user system elapsed # 0.213 0.024 0.238

Subconjunto de DT con entre (tanto para x como para y)

system.time(DTsub<-DT[between(x ,5, max(x)) & between(y, 0,7), ]) # user system elapsed # 0.242 0.036 0.279

Alternativa de escaneo vectorial mixta y entre

system.time(DTsub<-DT[x > 5 & between(y, 0,7), ]) # user system elapsed # 0.203 0.017 0.221

Alternativa entre sintaxis

system.time(DTsub<-DT[x %between% c(5, max(x)) & y %between% c(0, 7)]) # user system elapsed # 0.227 0.016 0.244

Escaneo vectorial mixto y entre (con sintaxis alternativa)

system.time(DTsub<-DT[x>5 & y %between% c(0, 7)]) # user system elapsed # 0.203 0.017 0.221

Evaluación un poco más completa

library(microbenchmark) mbm<-microbenchmark( "DFsub"={b1<-DF[DF$x>5 & DF$y<7,]}, "DTsub1"={b2<-DT[x>5 & y<7]}, "DTsub2"={b3<-DT[between(x ,5, max(x)) & between(y, 0, 7), ]}, "DTsub3"={b4<-DT[x > 5 & between(y, 0,7), ]}, "DTsub4"={b5<-DT[x %between% c(5, max(x)) & y %between% c(0, 7)]}, "DTsub5"={b5<-DT[x>5 & y %between% c(0, 7)]} ) mbm Unit: milliseconds Unit: milliseconds # expr min lq mean median uq max neval # DFsub 527.6842 582.3235 635.8846 622.1641 664.3243 1101.2365 100 # DTsub1 220.5086 245.7509 279.5451 263.5527 296.5736 411.5833 100 # DTsub2 249.2093 283.2025 325.4845 304.2361 333.6894 660.5021 100 # DTsub3 215.5454 243.3255 281.3596 270.1108 300.8462 491.8837 100 # DTsub4 250.9431 282.1896 330.0688 305.2094 352.9604 736.2690 100 # DTsub5 218.5458 238.8931 276.7932 262.6675 293.3524 467.5082 100 library(ggplot2) autoplot(mbm)