0

I found this life-saving code created by @QuantIbex. It creates combinations of variables iteratively to be used in a loop without generating them in advance or storing them.

First answer here

## Function definition
gen.next.cbn <- function(cbn, n){
    ## Generates the combination that follows the one provided as input
    cbn.bin      <- rep(0, n)
    cbn.bin[cbn] <- 1
    if (tail(cbn.bin, 1) == 0){
        ind <- tail(which(cbn.bin == 1), 1)
        cbn.bin[c(ind, ind+1)] <- c(0, 1)
    }else{
        ind <- 1 + tail(which(diff(cbn.bin) == -1), 1)
        nb  <- sum(cbn.bin[-c(1:ind)] == 1)
        cbn.bin[c(ind-1, (n-nb+1):n)] <- 0
        cbn.bin[ind:(ind+nb)]         <- 1
    }
    cbn <- which(cbn.bin == 1)
}

## Example parameters
n   <- 6
k   <- 3

## Iteration example
for (i in 1:choose(n, k)){
    if (i == 1){
        cbn <- 1:k
    }else{
        cbn <- gen.next.cbn(cbn, n)
    }
    print(cbn)
}

# [1] 1 2 3
# [1] 1 2 4
# [1] 1 2 5
# [1] 1 2 6
# [1] 1 3 4
# [1] 1 3 5
# [1] 1 3 6
# [1] 1 4 5
# [1] 1 4 6
# [1] 1 5 6
# [1] 2 3 4
# [1] 2 3 5
# [1] 2 3 6
# [1] 2 4 5
# [1] 2 4 6
# [1] 2 5 6
# [1] 3 4 5
# [1] 3 4 6
# [1] 3 5 6
# [1] 4 5 6

The code itself is beyond my own technical capabilities in R. I have been able to adapt it for my use, and insert my analyses in the loop and it works very well. One of the things I have not been able to figure out is how to make it go through more than x number of variables per combination at a time.

In the code above n = the number of variables to be used to generate the combinations. k = the number of variables per combination. In the example, k=3. I can change k to anything I want, but is there a way for k to a equal a range such as k = 3:10?

I set the values:

n <- 31
k <- 3:10

It stops once done with combinations of 3 and gives me this:

Warning messages:
1: In 1:choose(n, k) :
  numerical expression has 8 elements: only the first used
2: In 1:k : numerical expression has 8 elements: only the first used

I know a lot of people warn of how slow loops are, and will probably warn me that combinations of 10 will take forever, but these are things I accept.

2 Answers2

1

Since you don't mind loops, why not just iterate over the elements of k?

## Example parameters
n   <- 6
k <- 1:3

for(j in seq_along(k))
{
  ## Iteration example
  for (i in 1:choose(n, k[j])){
      if (i == 1){
          cbn <- 1:k[j]
      }else{
          cbn <- gen.next.cbn(cbn, n)
      }
      print(cbn)
  }
}

Output:

#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
#> [1] 1 2
#> [1] 1 3
#> [1] 1 4
#> [1] 1 5
#> [1] 1 6
#> [1] 2 3
#> [1] 2 4
#> [1] 2 5
#> [1] 2 6
#> [1] 3 4
#> [1] 3 5
#> [1] 3 6
#> [1] 4 5
#> [1] 4 6
#> [1] 5 6
#> [1] 1 2 3
#> [1] 1 2 4
#> [1] 1 2 5
#> [1] 1 2 6
#> [1] 1 3 4
#> [1] 1 3 5
#> [1] 1 3 6
#> [1] 1 4 5
#> [1] 1 4 6
#> [1] 1 5 6
#> [1] 2 3 4
#> [1] 2 3 5
#> [1] 2 3 6
#> [1] 2 4 5
#> [1] 2 4 6
#> [1] 2 5 6
#> [1] 3 4 5
#> [1] 3 4 6
#> [1] 3 5 6
#> [1] 4 5 6

This will work when k is a vector but will keep the same functionalty you had before using a single integer.

As a footnote, choosing a set of 10 from 20 elements using this method (without my addition) takes my computer about a minute, which I would live with if I only had to run it now and again.

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • Thank you for the simple and elegant solution. As a complete novice who learned R for my MA thesis analyses, I'm finding loops to be a godsend. I have so many different combinations to test and I'll likely let run overnight once the rest of the script is complete. Thanks again! – bobbytreefish Jan 15 '20 at 19:59
1

I believe

  • one way to accomplish what you want to start using combn;
    • But I noticed you are avoiding repeating the elements in the combinations. So you will need to exclude repetitions... I include example code for a function for it.
  • Afterwards, you can substitute the loop for lapply for efficiency and use the function.

    n<-31
    combnWoRepl<-function(n,k){
        abc<-combn(1:n,k)
        abc<-t(abc)
        abc<-data.frame(abc)
        colnames(abc)<-c("a","b","c")
        abc[!abc$a==abc$b,]
    }
    
    ResultList<-lapply(3:10,function(x){
        combnWoRepl(n,x)
    })
    
hamagust
  • 728
  • 2
  • 10
  • 28