¿Cómo hacer una combinación cruzada en R?

¿Cómo puedo lograr una unión cruzada en R? Sé que “fusionar” puede hacer unión interna, unión externa. Pero no sé cómo lograr un cruce en R.

Gracias

¿Es solo all=TRUE ?

 x<-data.frame(id1=c("a","b","c"),vals1=1:3) y<-data.frame(id2=c("d","e","f"),vals2=4:6) merge(x,y,all=TRUE) 

De la documentación de merge :

Si by o ambos by.x y by.y son de longitud 0 (un vector de longitud cero o NULL), el resultado, r, es el producto cartesiano de xey, es decir, dim (r) = c (nrow (x) ) * nrow (y), ncol (x) + ncol (y)).

Si la velocidad es un problema, sugiero verificar el excelente paquete data.table . En el ejemplo al final, es ~ 90x más rápido que merge .

No proporcionó datos de ejemplo. Si solo desea obtener todas las combinaciones de dos columnas (o más individuales), puede usar CJ (combinación cruzada):

 library(data.table) CJ(x=1:2,y=letters[1:3]) # xy #1: 1 a #2: 1 b #3: 1 c #4: 2 a #5: 2 b #6: 2 c 

Si quiere hacer una combinación cruzada en dos tablas, no he encontrado una manera de usar CJ (). Pero aún puedes usar data.table :

 x2<-data.table(id1=letters[1:3],vals1=1:3) y2<-data.table(id2=letters[4:7],vals2=4:7) res<-setkey(x2[,c(k=1,.SD)],k)[y2[,c(k=1,.SD)],allow.cartesian=TRUE][,k:=NULL] res # id1 vals1 id2 vals2 # 1: a 1 d 4 # 2: b 2 d 4 # 3: c 3 d 4 # 4: a 1 e 5 # 5: b 2 e 5 # 6: c 3 e 5 # 7: a 1 f 6 # 8: b 2 f 6 # 9: c 3 f 6 #10: a 1 g 7 #11: b 2 g 7 #12: c 3 g 7 

Explicación de la línea res :

  • Básicamente se agrega una columna ficticia (k en este ejemplo) a una tabla y se establece como la clave ( setkey(tablename,keycolumns) ), se agrega la columna ficticia a la otra tabla y luego se une a ellos.
  • La estructura data.table utiliza posiciones de columna y no nombres en la unión, por lo que debe colocar la columna ficticia al principio. La parte c(k=1,.SD) es una de las formas que he encontrado para agregar columnas al principio (el valor predeterminado es agregarlas al final).
  • Una unión estándar de data.table tiene un formato de X[Y] . La X en este caso es setkey(x2[,c(k=1,.SD)],k) , y la Y es y2[,c(k=1,.SD)] .
  • allow.cartesian=TRUE le dice a data.table que ignore los valores duplicados de las claves y realice una unión cartesiana (las versiones anteriores no lo requerían)
  • El [,k:=NULL] al final solo elimina la clave ficticia del resultado.

También puede convertir esto en una función, por lo que es más limpio de usar:

 # Version 1; easier to write: CJ.table.1 <- function(X,Y) setkey(X[,c(k=1,.SD)],k)[Y[,c(k=1,.SD)],allow.cartesian=TRUE][,k:=NULL] CJ.table.1(x2,y2) # id1 vals1 id2 vals2 # 1: a 1 d 4 # 2: b 2 d 4 # 3: c 3 d 4 # 4: a 1 e 5 # 5: b 2 e 5 # 6: c 3 e 5 # 7: a 1 f 6 # 8: b 2 f 6 # 9: c 3 f 6 #10: a 1 g 7 #11: b 2 g 7 #12: c 3 g 7 # Version 2; faster but messier: CJ.table.2 <- function(X,Y) { eval(parse(text=paste0("setkey(X[,c(k=1,.SD)],k)[Y[,c(k=1,.SD)],list(",paste0(unique(c(names(X),names(Y))),collapse=","),")][,k:=NULL]"))) } 

Aquí hay algunos puntos de referencia de velocidad:

 # Create a bigger (but still very small) example: n<-1e3 x3<-data.table(id1=1L:n,vals1=sample(letters,n,replace=T)) y3<-data.table(id2=1L:n,vals2=sample(LETTERS,n,replace=T)) library(microbenchmark) microbenchmark(merge=merge.data.frame(x3,y3,all=TRUE), CJ.table.1=CJ.table.1(x3,y3), CJ.table.2=CJ.table.2(x3,y3), times=3, unit="s") #Unit: seconds # expr min lq median uq max neval # merge 4.03710225 4.23233688 4.42757152 5.57854711 6.72952271 3 # CJ.table.1 0.06227603 0.06264222 0.06300842 0.06701880 0.07102917 3 # CJ.table.2 0.04740142 0.04812997 0.04885853 0.05433146 0.05980440 3 

Tenga en cuenta que estos métodos data.table son mucho más rápidos que el método de merge sugerido por @ danas.zuokas. Las dos tablas con 1.000 filas en este ejemplo dan como resultado una tabla cruzada con 1 millón de filas. Entonces, incluso si las tablas originales son pequeñas, el resultado puede boost rápidamente y la velocidad se vuelve importante.

Por último, las versiones recientes de data.table requieren que agregue allow.cartesian=TRUE (como en CJ.table.1) o especifique los nombres de las columnas que se deben devolver (CJ.table.2). El segundo método (CJ.table.2) parece ser más rápido, pero requiere un código más complicado si desea especificar automáticamente todos los nombres de columna. Y puede no funcionar con nombres de columna duplicados. (Siéntase libre de sugerir una versión más simple de CJ.table.2)

Si quieres hacerlo a través de data.table, esta es una forma:

 cjdt <- function(a,b){ cj = CJ(1:nrow(a),1:nrow(b)) cbind(a[cj[[1]],],b[cj[[2]],]) } A = data.table(ida = 1:10) B = data.table(idb = 1:10) cjdt(A,B) 

Habiendo dicho lo anterior, si está haciendo muchas combinaciones pequeñas, y no necesita un objeto data.table y la sobrecarga de producirlo, se puede lograr un aumento de velocidad significativo escribiendo un bloque de código de c++ usando Rcpp y similares:

 // [[Rcpp::export]] NumericMatrix crossJoin(NumericVector a, NumericVector b){ int szA = a.size(), szB = b.size(); int i,j,r; NumericMatrix ret(szA*szB,2); for(i = 0, r = 0; i < szA; i++){ for(j = 0; j < szB; j++, r++){ ret(r,0) = a(i); ret(r,1) = b(j); } } return ret; } 

Para comparar, primero para una unión grande:

C ++

 n = 1 a = runif(10000) b = runif(10000) system.time({for(i in 1:n){ crossJoin(a,b) }}) 

sistema de usuario transcurrido 1.033 0.424 1.462


tabla de datos

 system.time({for(i in 1:n){ CJ(a,b) }}) 

sistema de usuario transcurrido 0.602 0.569 2.452


Ahora para muchas combinaciones pequeñas:

C ++

 n = 1e5 a = runif(10) b = runif(10) system.time({for(i in 1:n){ crossJoin(a,b) }}) 

sistema de usuario transcurrido 0.660 0.077 0.739


tabla de datos

 system.time({for(i in 1:n){ CJ(a,b) }}) 

sistema de usuario transcurrido 26.164 0.056 26.271

Usig sqldf :

 x <- data.frame(id1 = c("a", "b", "c"), vals1 = 1:3) y <- data.frame(id2 = c("d", "e", "f"), vals2 = 4:6) library(sqldf) sqldf("SELECT * FROM x CROSS JOIN y") 

Salida:

  id1 vals1 id2 vals2 1 a 1 d 4 2 a 1 e 5 3 a 1 f 6 4 b 2 d 4 5 b 2 e 5 6 b 2 f 6 7 c 3 d 4 8 c 3 e 5 9 c 3 f 6 

Solo para el registro, con el paquete base, podemos usar el by= NULL lugar de all=TRUE :

 merge(x, y, by= NULL) 

Al usar la función de combinación y sus parámetros opcionales:

Unión interna: fusionar (df1, df2) funcionará para estos ejemplos porque R une automáticamente los marcos por nombres comunes de variables, pero lo más probable es que desee especificar merge (df1, df2, by = “CustomerId”) para asegurarse de que hacían coincidir solo en los campos que deseaba. También puede usar los parámetros by.x y by.y si las variables coincidentes tienen diferentes nombres en los diferentes marcos de datos.

 Outer join: merge(x = df1, y = df2, by = "CustomerId", all = TRUE) Left outer: merge(x = df1, y = df2, by = "CustomerId", all.x = TRUE) Right outer: merge(x = df1, y = df2, by = "CustomerId", all.y = TRUE) Cross join: merge(x = df1, y = df2, by = NULL) 

No sé de una forma integrada de hacerlo con data.frame , pero no es difícil de hacer.

@danas mostró que hay una forma fácil de incorporar, pero dejaré mi respuesta aquí en caso de que sea útil para otros fines.

 cross.join <- function(a, b) { idx <- expand.grid(seq(length=nrow(a)), seq(length=nrow(b))) cbind(a[idx[,1],], b[idx[,2],]) } 

y mostrando que funciona con algunos conjuntos de datos incorporados:

 > tmp <- cross.join(mtcars, iris) > dim(mtcars) [1] 32 11 > dim(iris) [1] 150 5 > dim(tmp) [1] 4800 16 > str(tmp) 'data.frame': 4800 obs. of 16 variables: $ mpg : num 21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ... $ cyl : num 6 6 4 6 8 6 8 4 4 6 ... $ disp : num 160 160 108 258 360 ... $ hp : num 110 110 93 110 175 105 245 62 95 123 ... $ drat : num 3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ... $ wt : num 2.62 2.88 2.32 3.21 3.44 ... $ qsec : num 16.5 17 18.6 19.4 17 ... $ vs : num 0 0 1 1 0 1 0 1 1 1 ... $ am : num 1 1 1 0 0 0 0 0 0 0 ... $ gear : num 4 4 4 3 3 3 3 4 4 4 ... $ carb : num 4 4 1 1 2 1 4 2 2 4 ... $ Sepal.Length: num 5.1 5.1 5.1 5.1 5.1 5.1 5.1 5.1 5.1 5.1 ... $ Sepal.Width : num 3.5 3.5 3.5 3.5 3.5 3.5 3.5 3.5 3.5 3.5 ... $ Petal.Length: num 1.4 1.4 1.4 1.4 1.4 1.4 1.4 1.4 1.4 1.4 ... $ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 ... $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ... 
    Intereting Posts