2010-11-29 10 views
53

J'ai trois ou plusieurs variables indépendantes représentées en tant que vecteurs R, comme ceci:trame de données produit cartésien R

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 en données cadre, 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 les 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 façon plus élégante de le faire, oui? product dans itertools fait partie du travail, mais je ne trouve aucun moyen d'absorber la sortie d'un itérateur et de le mettre dans une trame de données. Aucune suggestion?

p.s. L'étape suivante dans 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 aussi utile.

+0

il serait utile si vous spécifiez ce que fait la fonction f. – Ramnath

+0

'f' est un espace réservé pour l'un des différents calculs mathématiques poilus, mais pour les besoins de cette question, je * pense * la chose que vous devez savoir est qu'ils prennent tous N vecteurs de type approprié et produisent un vecteur; toutes les entrées doivent avoir la même longueur, et la sortie est aussi cette longueur. – zwol

+0

Je recommanderais de changer le titre de cette question ... "table de données" signifie maintenant quelque chose de différent dans R. –

Répondre

57

vous pouvez utiliser

EDIT: une alternative à l'utilisation do.call pour atteindre 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 trivial « coller », vous pouvez essayer

d = mdply(d, 'paste', sep = '+'); 
+0

Aha! Je savais qu'il devait y avoir une routine de bibliothèque standard qui faisait cela, mais je ne trouvais pas comment elle s'appelait. Je vais laisser la question ouverte au cas où quelqu'un aurait une réponse à la deuxième partie, cependant. – zwol

+0

Si f est une fonction personnalisée, vous pouvez la modifier pour accepter une trame de données en tant qu'argument et laisser la fonction gérer le découpage en vecteurs de composant – Ramnath

+0

Étudie la documentation de plyr, mais n'a pas compris que c'était ce que mdply C'était pour. Merci. – zwol

0

Je ne me souviens jamais que fonction standard expand.grid. Alors, voici 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='_')) 
5

Voici une manière 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 travaux sur d "en l'état" parce qu'un data.frame est un list. Mais do.call s'attend à ce que les noms de colonnes de d correspondent aux noms d'arguments de f.

+0

'd' est seulement défini par l'appel' expand.grid' ... – zwol

+0

@Zack: Merci; J'ai mis à jour ma réponse. Ce n'est pas un one-liner, mais évaluer 'f' est encore plus facile avec' do.call' que de taper dans chaque argument. –

+0

le bon vieux truc de do.call. joli! – Ramnath

13

Il existe une fonction de manipulation de données, ce qui est utile dans ce cas.

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

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

donc quelque chose comme cela va faire:

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 classés comme vous DÉPEINTE. 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 deux ou by.x et par.y sont de longueur 0 (longueur zéro vecteur 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

3

Vous pouvez utiliser les merveilleux data.table bibliothèque pour l'expressivité et la vitesse. Il gère de nombreux cas d'utilisation de plyr (groupe relationnel par), ainsi que transformer, sous-ensemble et relationnelle se joindre à l'aide d'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 dans une ligne

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