web-dev-qa-db-fra.com

Cadre de données produit cartésien

J'ai trois variables indépendantes ou plus représentées comme des vecteurs R, comme ceci:

A <- c(1,2,3)
B <- factor(c('x','y'))
C <- c(0.1,0.5)

et je veux prendre le produit cartésien de tous et mettre le résultat dans un bloc de données, comme ceci:

A B C
1 x 0.1
1 x 0.5
1 y 0.1
1 y 0.5
2 x 0.1
2 x 0.5
2 y 0.1
2 y 0.5
3 x 0.1
3 x 0.5
3 y 0.1
3 y 0.5

Je peux le faire en écrivant manuellement des appels à rep:

d <- data.frame(A = rep(A, times=length(B)*length(C)),
                B = rep(B, times=length(A), each=length(C)),
                C = rep(C, each=length(A)*length(B))

mais il doit y avoir une manière plus élégante de le faire, non? product in itertools fait partie du travail, mais je ne trouve aucun moyen d'absorber la sortie d'un itérateur et de la mettre dans un bloc de données. Aucune suggestion?

p.s. La prochaine étape de ce calcul ressemble à

d$D <- f(d$A, d$B, d$C)

donc si vous connaissez un moyen de faire les deux étapes à la fois, ce serait également utile.

56
zwol

vous pouvez utiliser expand.grid(A, B, C)

EDIT: une alternative à l'utilisation de do.call pour réaliser la deuxième partie, est la fonction mdply. voici le code

d = expand.grid(x = A, y = B, z = C)
d = mdply(d, f)

pour illustrer son utilisation en utilisant une fonction triviale 'coller', vous pouvez essayer

d = mdply(d, 'paste', sep = '+');
66
Ramnath

Il y a une fonction qui manipule la trame de données, ce qui est utile dans ce cas.

Il peut produire diverses jointures (dans la terminologie SQL), tandis que le produit cartésien est un cas particulier.

Vous devez d'abord convertir les variables en trames de données, car elles prennent les trames de données comme paramètres.

donc quelque chose comme ça fera:

A.B=merge(data.frame(A=A), data.frame(B=B),by=NULL);
A.B.C=merge(A.B, data.frame(C=C),by=NULL);

La seule chose à prendre en compte est que les lignes ne sont pas triées comme vous l'avez décrit. Vous pouvez les trier manuellement comme vous le souhaitez.

merge(x, y, by = intersect(names(x), names(y)),
      by.x = by, by.y = by, all = FALSE, all.x = all, all.y = all,
      sort = TRUE, suffixes = c(".x",".y"),
      incomparables = NULL, ...)

"Si par ou les deux by.x et by.y sont de longueur 0 (un vecteur de longueur zéro ou NULL), le résultat, r, est le produit cartésien de x et y"

voir cette URL pour plus de détails: http://stat.ethz.ch/R-manual/R-patched/library/base/html/merge.html

16
misssprite

Voici une façon de faire les deux, en utilisant la suggestion de Ramnath de expand.grid:

f <- function(x,y,z) paste(x,y,z,sep="+")
d <- expand.grid(x=A, y=B, z=C)
d$D <- do.call(f, d)

Notez que do.call fonctionne sur d "tel quel" car un data.frame est un list. Mais do.call attend que les noms de colonne de d correspondent aux noms d'arguments de f.

5
Joshua Ulrich

Pensez à utiliser la merveilleuse bibliothèque data.table pour l'expressivité et la vitesse. Il gère de nombreux cas d'utilisation plyr (groupement relationnel par), ainsi que la transformation, le sous-ensemble et la jointure relationnelle en utilisant une syntaxe uniforme assez simple.

library(data.table)
d <- CJ(x=A, y=B, z=C)  # Cross join
d[, w:=f(x,y,z)]  # Mutates the data.table

ou en une seule ligne

d <- CJ(x=A, y=B, z=C)[, w:=f(x,y,z)]
5
chris

Avec la bibliothèque tidyr on peut utiliser tidyr::crossing (l'ordre sera comme dans OP):

library(tidyr)
crossing(A,B,C)
# A tibble: 12 x 3
#        A B         C
#    <dbl> <fct> <dbl>
#  1     1 x       0.1
#  2     1 x       0.5
#  3     1 y       0.1
#  4     1 y       0.5
#  5     2 x       0.1
#  6     2 x       0.5
#  7     2 y       0.1
#  8     2 y       0.5
#  9     3 x       0.1
# 10     3 x       0.5
# 11     3 y       0.1
# 12     3 y       0.5 

L'étape suivante serait d'utiliser tidyverse et surtout le purrr::pmap* famille:

library(tidyverse)
crossing(A,B,C) %>% mutate(D = pmap_chr(.,paste,sep="_"))
# A tibble: 12 x 4
#        A B         C D      
#    <dbl> <fct> <dbl> <chr>  
#  1     1 x       0.1 1_1_0.1
#  2     1 x       0.5 1_1_0.5
#  3     1 y       0.1 1_2_0.1
#  4     1 y       0.5 1_2_0.5
#  5     2 x       0.1 2_1_0.1
#  6     2 x       0.5 2_1_0.5
#  7     2 y       0.1 2_2_0.1
#  8     2 y       0.5 2_2_0.5
#  9     3 x       0.1 3_1_0.1
# 10     3 x       0.5 3_1_0.5
# 11     3 y       0.1 3_2_0.1
# 12     3 y       0.5 3_2_0.5
4
Moody_Mudskipper

Utilisation de la jointure croisée dans sqldf:

library(sqldf)

A <- data.frame(c1 = c(1,2,3))
B <- data.frame(c2 = factor(c('x','y')))
C <- data.frame(c3 = c(0.1,0.5))

result <- sqldf('SELECT * FROM (A CROSS JOIN B) CROSS JOIN C') 
0
OmG

Je ne me souviens jamais de cette fonction standard expand.grid. Voici donc une autre version.

crossproduct <- function(...,FUN='data.frame') {
  args <- list(...)
  n1 <- names(args)
  n2 <- sapply(match.call()[1+1:length(args)], as.character)
  nn <- if (is.null(n1)) n2 else ifelse(n1!='',n1,n2)
  dims <- sapply(args,length)
  dimtot <- prod(dims)
  reps <- rev(cumprod(c(1,rev(dims))))[-1]
  cols <- lapply(1:length(dims), function(j)
                 args[[j]][1+((1:dimtot-1) %/% reps[j]) %% dims[j]])
  names(cols) <- nn
  do.call(match.fun(FUN),cols)
}

A <- c(1,2,3)
B <- factor(c('x','y'))
C <- c(.1,.5)

crossproduct(A,B,C)

crossproduct(A,B,C, FUN=function(...) paste(...,sep='_'))
0
DamonJW