【问题标题】:Non-redundant version of expand.gridexpand.grid 的非冗余版本
【发布时间】:2013-06-14 19:14:23
【问题描述】:

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

EDIT 每个元素与其自身的组合最终可能会从答案中被丢弃。我的程序中实际上并不需要它,即使(数学上)aa aa 将是Var1 的一个元素和var2 的另一个元素之间的一个(常规)独特组合。

解决方案需要从两个向量中生成一对元素(即每个输入向量中的一个 - 以便它可以应用于超过 2 个输入)

【问题讨论】:

    标签: r combinations combinatorics cartesian-product


    【解决方案1】:

    使用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"
    

    如果您不想要重复元素,也可以在两个向量的唯一元素上使用combn(例如aa aa

    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"
    

    【讨论】:

      【解决方案2】:

      在基础 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"
      

      【讨论】:

        【解决方案3】:

        如果两个向量相同,则gtools 包中有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)
        

        【讨论】:

          【解决方案4】:

          以前的答案缺乏获得特定结果的方法,即保留自对但删除具有不同顺序的自对。 gtools 包有两个用于这些目的的函数,combinationspermutationsAccording to this website:

          • 当顺序无关紧要时,它是一个组合。
          • 当顺序很重要时,它就是一个排列。

          在这两种情况下,我们都可以决定是否允许重复,相应地,两个函数都有一个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^r3^2=9 中找到。这是the Cartesian product/join,供熟悉 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 来计算。

          【讨论】:

            【解决方案5】:

            试试:

            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" 
            

            【讨论】:

              【解决方案6】:

              您可以使用“大于”操作来过滤冗余组合。这适用于数字和字符向量。

              > 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
              

              这不应该过多地减慢您的代码。如果您要扩展包含较大元素的向量(例如两个数据框列表),我建议使用引用原始向量的数字索引。

              【讨论】:

                【解决方案7】:

                TL;DR

                使用来自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 函数返回的 Cartesian product 完全不同。

                笛卡尔积在多个对象上运行,这些对象可能相同也可能不同。一般来说,组合函数应用于单个向量。置换函数也是如此。

                如果提供的向量相同,则使用组合/置换函数只会产生与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 可以解决这个问题。它非常通用、灵活且速度快。

                首先,回答OP提出的具体问题:

                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
                

                所采用的算法避免了生成整个笛卡尔积并随后消除了欺骗。最终,我们使用Fundamental theorem of arithmetic 以及user2357112 supports MonicaPicking unordered combinations from pools with overlap 的答案中指出的重复数据删除创建了一个哈希表。所有这一切,再加上它是用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
                

                【讨论】:

                  【解决方案8】:

                  这是一个非常丑陋的版本,对我来说也适用于类似的问题。

                  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. 
                  
                  

                  【讨论】:

                    【解决方案9】:

                    使用排序

                    只是为了好玩,原则上还可以通过组合 sortuniqueexpand.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"
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2017-02-27
                      • 1970-01-01
                      • 2023-03-26
                      • 2021-10-16
                      • 1970-01-01
                      • 1970-01-01
                      • 2014-11-09
                      • 2011-06-12
                      相关资源
                      最近更新 更多