笛卡尔积产品数据框

时间:2010-11-29 23:41:00

标签: r data-structures

我有三个或更多个表示为R向量的自变量,如下所示:

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

我希望得到所有这些产品的笛卡尔积,并将结果放入数据框中,如下所示:

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

我可以通过手动写出对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))

但必须有更优雅的方式来做,是吗? product中的itertools完成了部分工作,但我找不到任何方法来吸收迭代器的输出并将其放入数据框中。有什么建议吗?

P.S。此计算的下一步看起来像

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

所以,如果您知道一次执行这两个步骤的方法,那也会有所帮助。

7 个答案:

答案 0 :(得分:65)

您可以使用expand.grid(A, B, C)

编辑:使用do.call实现第二部分的替代方法是函数mdply。这是代码

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

使用一个简单的函数'paste'来说明它的用法,你可以试试

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

答案 1 :(得分:16)

有一个操作数据帧的函数,在这种情况下很有用。

它可以产生各种连接(在SQL术语中),而笛卡尔积则是一种特殊情况。

您必须首先将varible转换为数据帧,因为它将数据帧作为参数。

所以这样的事情会发生:

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);

唯一需要关心的是行没有按照您的描述进行排序。 您可以根据需要手动对它们进行排序。

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, ...)

“如果x和by.y之间或两者的长度为0(长度为零的向量或NULL),则结果r为x和y的笛卡尔乘积”

请参阅此网址了解详细信息:http://stat.ethz.ch/R-manual/R-patched/library/base/html/merge.html

答案 2 :(得分:5)

这是两种方法,使用Ramnath的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)

请注意do.call按原样使用d“,因为data.framelist。但是do.call期望d的列名称与f的参数名称匹配。

答案 3 :(得分:4)

考虑使用精彩的data.table库来表达性和速度。它使用相当简单的统一语法处理许多plyr用例(关系组),以及变换,子集和关系连接。

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

或一行

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

答案 4 :(得分:3)

使用库tidyr可以使用tidyr::crossing(订单将与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 

下一步是使用tidyverse,尤其是purrr::pmap*系列:

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

答案 5 :(得分:0)

我永远记不起那个标准函数expand.grid。所以这是另一个版本。

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='_'))

答案 6 :(得分:0)

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')