非冗余版本的expand.grid

52

R 函数 expand.grid 返回所提供参数的所有可能组合。例如:

> expand.grid(c("aa", "ab", "cc"), c("aa", "ab", "cc"))
  Var1 Var2
1   aa   aa
2   ab   aa
3   cc   aa
4   aa   ab
5   ab   ab
6   cc   ab
7   aa   cc
8   ab   cc
9   cc   cc

你知道一种高效的方法,可以直接获取所提供向量之间的“唯一”组合(因此在expand.grid之后不需要进行任何行比较)吗?输出将为:
  Var1 Var2
1   aa   aa
2   ab   aa
3   cc   aa
5   ab   ab
6   cc   ab
9   cc   cc
编辑 每个元素与自身的组合最终可能会从答案中被排除。实际上,即使(在数学上) aa aaVar1 中一个元素和 var2 中另一个元素之间的一个(正常)唯一组合,但我在我的程序中并不需要它。
解决方案需要从两个向量中产生元素对(即一个来自每个输入向量-以便可以应用于多个输入)。

这不就是同一个问题吗?链接 - 5th
我不这么认为。这是关于单个向量元素的问题。接受的答案还提供了一种从多个输入(2个或更多)的元素生成组合的方法。 - Michele
10个回答

33

使用outer怎么样?但是这个特定的函数会将它们连接成一个字符字符串。

outer( c("aa", "ab", "cc"), c("aa", "ab", "cc") , "paste" )
#     [,1]    [,2]    [,3]   
#[1,] "aa aa" "aa ab" "aa cc"
#[2,] "ab aa" "ab ab" "ab cc"
#[3,] "cc aa" "cc ab" "cc cc"

如果您不想保留重复元素(例如aa aa),则还可以在两个向量的唯一元素上使用combn

vals <- c( c("aa", "ab", "cc"), c("aa", "ab", "cc") )
vals <- unique( vals )
combn( vals , 2 )
#     [,1] [,2] [,3]
#[1,] "aa" "aa" "ab"
#[2,] "ab" "cc" "cc"

哦不,谢谢。我需要它们分开以传递给其他函数。之前的很完美!我实际上意识到我不需要 aa aabb bbcc cc。所以如果你重新编辑回来,我会接受它 :-) - Michele
4
不想冒犯,但我不明白为什么这个答案是最受赞同的并且继续获得点赞。两种方法都是错误的。第一个方案中的“outer”几乎与“expand.grid”完全相同,只是格式不同。它也仅限于2个输入。对于第二种方法,“combn”只能作用于单个向量。如果您有不同元素的向量,那么这里建议的方法将会给出许多错误的结果。接下来... - Joseph Wood
4
最极端的情况是当我们有不相交的向量,例如 v1 = 1:5, v2 = 6:10。正确的解决方案应该与 expand.grid(1:5, 6:10) 同构,它只有25个结果,但是 combn(1:10, 2) 却有45个结果。再次声明,我并不是想冒犯,我只是希望为未来的用户澄清一下。 - Joseph Wood
我已经添加了一个回答来解决这些问题:https://dev59.com/DGQn5IYBdhLWcg3wAjPJ#68050873 - Joseph Wood

26
在基础 R 中,您可以使用以下方法:

expand.grid.unique <- function(x, y, include.equals=FALSE)
{
    x <- unique(x)

    y <- unique(y)

    g <- function(i)
    {
        z <- setdiff(y, x[seq_len(i-include.equals)])

        if(length(z)) cbind(x[i], z, deparse.level=0)
    }

    do.call(rbind, lapply(seq_along(x), g))
}

结果:

> x <- c("aa", "ab", "cc")
> y <- c("aa", "ab", "cc")

> expand.grid.unique(x, y)
     [,1] [,2]
[1,] "aa" "ab"
[2,] "aa" "cc"
[3,] "ab" "cc"

> expand.grid.unique(x, y, include.equals=TRUE)
     [,1] [,2]
[1,] "aa" "aa"
[2,] "aa" "ab"
[3,] "aa" "cc"
[4,] "ab" "ab"
[5,] "ab" "cc"
[6,] "cc" "cc"

我注意到它缺少一种方法来获取包括自关系但不包括重复关系的内容。例如,("aa", "aa") 不是一个重复关系,因为它只出现了一次。在某些情况下,我们需要包括自关系,但不包括重复关系。 - CoderGuy123

18
如果这两个向量是相同的,那么在包中有一个combinations函数:

library(gtools)
combinations(n = 3, r = 2, v = c("aa", "ab", "cc"), repeats.allowed = TRUE)

#      [,1] [,2]
# [1,] "aa" "aa"
# [2,] "aa" "ab"
# [3,] "aa" "cc"
# [4,] "ab" "ab"
# [5,] "ab" "cc"
# [6,] "cc" "cc"

没有 "aa" "aa",等等。

combinations(n = 3, r = 2, v = c("aa", "ab", "cc"), repeats.allowed = FALSE)

1
我注意到repeats.allowed还会删除自身成对的元素,比如("aa", "aa"),但实际上它们并不是重复的。这个函数存在一个缺失的中间状态。 - CoderGuy123

16
之前的答案缺乏获取特定结果的方法,即保留自身对但移除顺序不同的对。 gtools 包有两个函数可用于此目的,combinationspermutations。根据 这个网站 的说法:
  • 当顺序无关紧要时,它是组合。
  • 当顺序很重要时,它是置换。
在这两种情况下,我们需要决定是否允许重复,相应地,两个函数都有一个 repeats.allowed 参数,产生 4 种组合(美妙的元) 。值得一提的是,我们将向量简化为单个字母以便于理解。

有重复的排列

最广泛的选项是允许自反关系和顺序不同的选项:

> permutations(n = 3, r = 2, repeats.allowed = T, v = c("a", "b", "c"))
      [,1] [,2]
 [1,] "a"  "a" 
 [2,] "a"  "b" 
 [3,] "a"  "c" 
 [4,] "b"  "a" 
 [5,] "b"  "b" 
 [6,] "b"  "c" 
 [7,] "c"  "a" 
 [8,] "c"  "b" 
 [9,] "c"  "c" 

这给我们提供了9个选项。这个值可以通过简单的公式n^r找到,即3^2=9。对于熟悉SQL的用户来说,这是笛卡尔积/连接

有两种限制方式:1)去除自我关联(不允许重复),或2)去除不同顺序的选项(即组合)。

带重复的组合

如果我们想要去除不同顺序的选项,我们使用:

> combinations(n = 3, r = 2, repeats.allowed = T, v = c("a", "b", "c"))
     [,1] [,2]
[1,] "a"  "a" 
[2,] "a"  "b" 
[3,] "a"  "c" 
[4,] "b"  "b" 
[5,] "b"  "c" 
[6,] "c"  "c" 

这给了我们6个选项。计算这个值的公式为(r+n-1)!/(r!*(n-1)!),即(2+3-1)!/(2!*(3-1)!)=4!/(2*2!)=24/4=6

无重复排列

如果我们不允许重复,则使用以下公式:

> permutations(n = 3, r = 2, repeats.allowed = F, v = c("a", "b", "c"))
     [,1] [,2]
[1,] "a"  "b" 
[2,] "a"  "c" 
[3,] "b"  "a" 
[4,] "b"  "c" 
[5,] "c"  "a" 
[6,] "c"  "b" 

这也给了我们6个选项,但是不同的选项!选项数量与上面相同,但这只是一个巧合。值可以从公式n!/(n-r)!中找到,即(3*2*1)/(3-2)!=6/1!=6

无重复组合

当我们既不想要自身关系/重复,也不需要不同顺序的选项时,最为严格限制,此时我们使用以下方式:

> combinations(n = 3, r = 2, repeats.allowed = F, v = c("a", "b", "c"))
     [,1] [,2]
[1,] "a"  "b" 
[2,] "a"  "c" 
[3,] "b"  "c" 

这给我们只留下了3个选项。选项的数量可以通过相对复杂的公式来计算:n!/(r!(n-r)!),即 3*2*1/(2*1*(3-2)!)=6/(2*1!)=6/2=3


14

尝试:

factors <- c("a", "b", "c")

all.combos <- t(combn(factors,2))

     [,1] [,2]
[1,] "a"  "b" 
[2,] "a"  "c" 
[3,] "b"  "c"

这不会包括每个因子的重复项(例如,“a”“a”),但如果需要,您可以轻松添加它们。
dup.combos <- cbind(factors,factors)

     factors factors
[1,] "a"     "a"    
[2,] "b"     "b"    
[3,] "c"     "c"   

all.combos <- rbind(all.combos,dup.combos)

     factors factors
[1,] "a"     "b"    
[2,] "a"     "c"    
[3,] "b"     "c"    
[4,] "a"     "a"    
[5,] "b"     "b"    
[6,] "c"     "c" 

1
这绝对是最简单和最直接的方法 - 不需要任何额外的包,并且可以在一行代码中完成所需的操作。 - Ava
@Ava - 我同意。我正在寻找 t(combn(1:nrow(df),2)) 的等效方法,但在我看来有很多过于复杂的方法。这个基于 R 原生函数的一行代码解决了问题。 - Leroy Tyrone

5
您可以使用“大于”操作来过滤冗余组合。这适用于数字向量和字符向量。
> grid <- expand.grid(c("aa", "ab", "cc"), c("aa", "ab", "cc"), stringsAsFactors = F)
> grid[grid$Var1 >= grid$Var2, ]
  Var1 Var2
1   aa   aa
2   ab   aa
3   cc   aa
5   ab   ab
6   cc   ab
9   cc   cc

这不应该让您的代码变慢太多。如果您正在扩展包含较大元素(例如两个数据框列表)的向量,请使用引用原始向量的数字索引。


3

简述

使用来自RcppAlgoscomboGrid

library(RcppAlgos)
comboGrid(c("aa", "ab", "cc"), c("aa", "ab", "cc"))
     Var1 Var2
[1,] "aa" "aa"
[2,] "aa" "ab"
[3,] "aa" "cc"
[4,] "ab" "ab"
[5,] "ab" "cc"
[6,] "cc" "cc"

细节

我最近遇到了这个问题R - Expand Grid Without Duplicates,在寻找重复项时,我发现了这个问题。那里的问题并不完全是一个重复项,因为它更加通用,并且有额外的限制,@Ferdinand.kraft也对此进行了一些阐述。

需要注意的是,这里的许多解决方案都使用了某种组合函数。 expand.grid函数返回基本不同的笛卡尔积

笛卡尔积作用于多个对象,这些对象可能相同,也可能不相同。一般来说,组合函数应用于单个向量。排列函数也可以这样说。

如果提供的向量不相同,则仅使用组合/排列函数将产生可比较的结果expand.grid。作为一个非常简单的例子,考虑v1 = 1:3, v2 = 2:4

使用expand.grid,我们可以看到第3行和第5行是重复的:

expand.grid(1:3, 2:4)
  Var1 Var2
1    1    2
2    2    2
3    3    2
4    1    3
5    2    3
6    3    3
7    1    4
8    2    4
9    3    4

使用combn并不能完全解决问题:

t(combn(unique(c(1:3, 2:4)), 2))
     [,1] [,2]
[1,]    1    2
[2,]    1    3
[3,]    1    4
[4,]    2    3
[5,]    2    4
[6,]    3    4

使用gtools进行重复操作,我们生成了太多的结果:

gtools::combinations(4, 2, v = unique(c(1:3, 2:4)), repeats.allowed = TRUE)
      [,1] [,2]
 [1,]    1    1
 [2,]    1    2
 [3,]    1    3
 [4,]    1    4
 [5,]    2    2
 [6,]    2    3
 [7,]    2    4
 [8,]    3    3
 [9,]    3    4
[10,]    4    4

事实上,我们生成的结果甚至不在笛卡尔积中(即expand.grid解决方案)。
我们需要一个可以创建以下内容的解决方案:
     Var1 Var2
[1,]    1    2
[2,]    1    3
[3,]    1    4
[4,]    2    2
[5,]    2    3
[6,]    2    4
[7,]    3    3
[8,]    3    4

我编写了包RcppAlgos,在最新版本v2.4.3中,有一个名为comboGrid的函数可以解决这个问题。它非常通用、灵活且速度很快。
首先,回答提问者提出的具体问题:
library(RcppAlgos)
comboGrid(c("aa", "ab", "cc"), c("aa", "ab", "cc"))
     Var1 Var2
[1,] "aa" "aa"
[2,] "aa" "ab"
[3,] "aa" "cc"
[4,] "ab" "ab"
[5,] "ab" "cc"
[6,] "cc" "cc"

正如@Ferdinand.kraft指出的那样,有时输出可能需要在给定行中排除重复项。为此,我们使用repetition = FALSE

comboGrid(c("aa", "ab", "cc"), c("aa", "ab", "cc"), repetition = FALSE)
     Var1 Var2
[1,] "aa" "ab"
[2,] "aa" "cc"
[3,] "ab" "cc"

comboGrid 也非常通用,可以应用于多个向量:

comboGrid(rep(list(c("aa", "ab", "cc")), 3))
      Var1 Var2 Var3
 [1,] "aa" "aa" "aa"
 [2,] "aa" "aa" "ab"
 [3,] "aa" "aa" "cc"
 [4,] "aa" "ab" "ab"
 [5,] "aa" "ab" "cc"
 [6,] "aa" "cc" "cc"
 [7,] "ab" "ab" "ab"
 [8,] "ab" "ab" "cc"
 [9,] "ab" "cc" "cc"
[10,] "cc" "cc" "cc"

不需要向量完全相同:

comboGrid(1:3, 2:4)
     Var1 Var2
[1,]    1    2
[2,]    1    3
[3,]    1    4
[4,]    2    2
[5,]    2    3
[6,]    2    4
[7,]    3    3
[8,]    3    4

并且可以应用于各种类型的向量:

set.seed(123)
my_range <- 3:15
mixed_types <- list(
    int1 = sample(15, sample(my_range, 1)),
    int2 = sample(15, sample(my_range, 1)),
    char1 = sample(LETTERS, sample(my_range, 1)),
    char2 = sample(LETTERS, sample(my_range, 1))
)

dim(expand.grid(mixed_types))
[1] 1950    4

dim(comboGrid(mixed_types, repetition = FALSE))
[1] 1595    4

dim(comboGrid(mixed_types, repetition = TRUE))
[1] 1770    4

该算法避免生成笛卡尔积并随后去重。最终,我们使用算术基本定理创建哈希表,并采用user2357112 supports Monica从具有重叠的池中选择无序组合的答案中指出的去重方法。所有这些加上它是用C ++编写的事实意味着它快速且内存效率高。
pools = list(c(1, 10, 14, 6),
             c(7, 2, 4, 8, 3, 11, 12),
             c(11, 3, 13, 4, 15, 8, 6, 5),
             c(10, 1, 3, 2, 9, 5,  7),
             c(1, 5, 10, 3, 8, 14),
             c(15, 3, 7, 10, 4, 5, 8, 6),
             c(14, 9, 11, 15),
             c(7, 6, 13, 14, 10, 11, 9, 4),
             c(6,  3,  2, 14,  7, 12,  9),
             c(6, 11,  2,  5, 15,  7))
             
system.time(combCarts <- comboGrid(pools))
   user  system elapsed 
  0.929   0.062   0.992

nrow(combCarts)
[1] 1205740

## Small object created
print(object.size(combCarts), unit = "Mb")
92 Mb
  
system.time(cartProd <- expand.grid(pools))
   user  system elapsed 
  8.477   2.895  11.461 
  
prod(lengths(pools))
[1] 101154816

## Very large object created
print(object.size(cartProd), unit = "Mb")
7717.5 Mb

0

使用排序

仅仅为了好玩,原则上可以通过结合sortunique来从expand.grid中删除重复项。

unique(t(apply(expand.grid(c("aa", "ab", "cc"), c("aa", "ab", "cc")), 1, sort)))

这将会返回:
    [,1] [,2]
[1,] "aa" "aa"
[2,] "aa" "ab"
[3,] "aa" "cc"
[4,] "ab" "ab"
[5,] "ab" "cc"
[6,] "cc" "cc"

0

这是一个非常丑陋的版本,但对我在类似问题上起了作用。

AHP_code = letters[1:10] 
 temp. <- expand.grid(AHP_code, AHP_code, stringsAsFactors = FALSE)
  temp. <- temp.[temp.$Var1 != temp.$Var2, ] # remove AA, BB, CC, etc. 
  temp.$combo <- NA 
  for(i in 1:nrow(temp.)){  # vectorizing this gave me weird results, loop worked fine. 
    temp.$combo[i] <- paste0(sort(as.character(temp.[i, 1:2])), collapse = "")
  }
  temp. <- temp.[!duplicated(temp.$combo),]
  temp. 


0

使用重复(如果您为不同的列指定不同的向量,例如第一列中的值始终大于第二列中的值,则此方法将无法正常工作):

> v=c("aa","ab","cc")
> e=expand.grid(v,v,stringsAsFactors=F)
> e[!apply(e,1,is.unsorted),]
  Var1 Var2
1   aa   aa
4   aa   ab
5   ab   ab
7   aa   cc
8   ab   cc
9   cc   cc

不重复(这需要对每列使用相同的向量):

> t(combn(c("aa","ab","cc"),2))
     [,1] [,2]
[1,] "aa" "ab"
[2,] "aa" "cc"
[3,] "ab" "cc"

带有重复项和不同向量的不同列:

> e=expand.grid(letters[25:26],letters[1:3],letters[2:3],stringsAsFactors=F)
> e[!duplicated(t(apply(e,1,sort))),]
   Var1 Var2 Var3
1     y    a    b
2     z    a    b
3     y    b    b
4     z    b    b
5     y    c    b
6     z    c    b
7     y    a    c
8     z    a    c
11    y    c    c
12    z    c    c

不重复,并且对于不同的列使用不同的向量:

> e=expand.grid(letters[25:26],letters[1:3],letters[2:3],stringsAsFactors=F)
> e=e[!duplicated(t(apply(e,1,sort))),]
> e[!apply(apply(e,1,duplicated),2,any),]
  Var1 Var2 Var3
1    y    a    b
2    z    a    b
5    y    c    b
6    z    c    b
7    y    a    c
8    z    a    c

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接