2012-05-01 13 views
19

Estoy buscando la mayor velocidad posible y permanecer en la base para hacer lo que hace expand.grid. He usado outer para propósitos similares en el pasado para crear un vector; algo como esto:Use outer en lugar de expand.grid

v <- outer(letters, LETTERS, paste0) 
unlist(v[lower.tri(v)]) 

Benchmarking me ha demostrado que outer puede ser drásticamente más rápido que expand.grid pero esta vez quiero crear dos columnas al igual que expand.grid (todos los combos posibles para 2 vectores) pero mis métodos con outer no hacer punto de referencia tan rápido con exterior esta vez.

Estoy esperando para tomar 2 vectores y crear cada posible combinación como dos columnas lo más rápido posible (creo outer puede ser la ruta, pero estoy abierta a cualquier método de base.

Aquí está el método expand.grid y . outer método

dat <- cbind(mtcars, mtcars, mtcars) 

expand.grid(seq_len(nrow(dat)), seq_len(ncol(dat))) 

FOO <- function(x, y) paste(x, y, sep=":") 
x <- outer(seq_len(nrow(dat)), seq_len(ncol(dat)), FOO) 
apply(do.call("rbind", strsplit(x, ":")), 2, as.integer) 

El microbenchmarking muestra outer es más lento:

#  expr  min  lq median  uq  max 
# EXPAND.G 812.743 838.6375 894.6245 927.7505 27029.54 
# OUTER 5107.871 5198.3835 5329.4860 5605.2215 27559.08 

Creo que mi uso de outer es lento porque no sé cómo usar outer para crear directamente un vector de longitud 2 que puedo do.call('rbind' juntos. Tengo que ralentizar paste y ralentizar la división. ¿Cómo puedo hacer esto con outer (u otros métodos en base) de una manera que sea más rápida que expand grid?

EDITAR: Agregando los resultados de microbenchmark.

**

Unit: microseconds 
     expr  min  lq median  uq  max 
1 ERNEST 34.993 39.1920 52.255 57.854 29170.705 
2  JOHN 13.997 16.3300 19.130 23.329 266.872 
3 ORIGINAL 352.720 372.7815 392.377 418.738 36519.952 
4 TOMMY 16.330 19.5960 23.795 27.061 6217.374 
5 VINCENT 377.447 400.3090 418.505 451.864 43567.334 

**

enter image description here

+0

Tyler, ¿te importaría agregar mi método a la lista de referencia? Debería venir a la mitad de la velocidad más rápida que tienes aquí. – John

+0

Sí, acabo de hacer. De hecho, es el más rápido. –

Respuesta

12

Usando rep.int:

expand.grid.alt <- function(seq1,seq2) { 
    cbind(rep.int(seq1, length(seq2)), 
     c(t(matrix(rep.int(seq2, length(seq1)), nrow=length(seq2))))) 
} 

expand.grid.alt(seq_len(nrow(dat)), seq_len(ncol(dat))) 

En mi equipo es como 6 veces más rápido que expand.grid.

+0

Era muy escéptico pero es muy rápido. Buena respuesta. Supongo que lo externo no era el enfoque que debería haber tomado. Publiqué los resultados de microbenchmarking en una máquina WIn 7 anterior. –

+1

@TylerRinker ¡Cuidado, había un error en mi función! El argumento 'nrow' era incorrecto. Lo he arreglado ahora. –

+0

@ErnestA Agregué un paréntesis en el extremo derecho de la última línea de código y luego tuve que agregar un poco más de texto antes de poder enviar la edición. –

3

Puede crear las dos columnas por separado.

library(microbenchmark) 
n <- nrow(dat) 
m <- ncol(dat) 
f1 <- function() expand.grid(1:n, 1:m) 
f2 <- function() 
    data.frame( 
    Var1 = as.vector(outer(1:n, rep(1,m))), 
    Var2 = as.vector(outer(rep(1,n), 1:m)) 
) 
microbenchmark(f1, f2, times=1e6) 
# Unit: nanoseconds 
# expr min lq median uq max 
# 1 f1 70 489 490 559 168458 
# 2 f2 70 489 490 559 168597 
+0

Gracias por la respuesta. Has resuelto mi problema externo y el aprendizaje fue excelente. El enfoque de Ernest es muy rápido, mucho más rápido que el enfoque externo. –

4

@ErnestA tiene una gran solución que merece la respuesta.

... podría ser marginalmente más rápido sin embargo:

expand.grid.alt2 <- function(seq1,seq2) { 
    cbind(Var1=rep.int(seq1, length(seq2)), Var2=rep(seq2, each=length(seq1))) 
} 

s1=seq_len(2000); s2=seq_len(2000) 
system.time(for(i in 1:10) expand.grid.alt2(s1, s2)) # 1.58 
system.time(for(i in 1:10) expand.grid.alt(s1, s2)) # 1.75 
system.time(for(i in 1:10) expand.grid(s1, s2))  # 2.46 
+0

Muy bonito, definitivamente un impulso de velocidad sobre el de Ernest. +1 Voy a mantener el control de Ernest, aunque esto es más rápido porque ya he dado el cheque y me siento gracioso con la reasignación. Me haría sentir completamente tranquilo si basa su respuesta en su respuesta :) ¿Puedo preguntar si lo hizo? –

+1

@TylerRinker - Sí, lo hice. Entonces puedes sentirte completamente tranquilo ;-) – Tommy

+0

@Tommy Hice todo lo posible para evitar 'rep (... each =)' porque supuse que sería más lento. De hecho, no lo es. –

13

La documentación para rep.int no es del todo completa. No es el más rápido en el caso más común porque puede pasar vectores para el argumento de tiempos, al igual que con rep. Puedes usarlo directamente para ambas secuencias, reduciendo el tiempo otro 40% más o menos sobre Tommy's.

expand.grid.jc <- function(seq1,seq2) { 
    cbind(Var1 = rep.int(seq1, length(seq2)), 
    Var2 = rep.int(seq2, rep.int(length(seq1),length(seq2)))) 
} 
+0

en su estado actual, su código arroja un error. Creo que falta un paréntesis en algún lado, pero no pude averiguar dónde. –

+0

+1 ¡Impresionante! * No debería * ser más rápido que el mío, pero sin duda es :-) Aparentemente, 'rep' necesita mejorar la forma en que maneja el argumento 'cada' ... – Tommy

+0

sí Tommy, debería ... De hecho, creo que expand.grid utiliza algo así como lo que escribí internamente ... es simplemente lento debido a la comprobación de errores y la solidez. – John

Cuestiones relacionadas