4

I am looking for a smart way to generate all pairwise combinations of two vectors of length n, where only one value is not zero.

For now I am doing something quite desperate with loops through each combination with: n <- 3; z <- rep(0,n); m <- apply(combn(1:n,1),2,function(k) {z[k]=1;z}) but there must be a better way without loops?

This is what I'm after for example for n=3:

     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0  

[1,]    1    0    0
[2,]    0    0    1

[1,]    0    1    0
[2,]    1    0    0

[1,]    0    1    0
[2,]    0    0    1

[1,]    0    0    1
[2,]    1    0    0

[1,]    0    0    1
[2,]    0    1    0

Thanks so much for the help.

user971102
  • 3,005
  • 4
  • 30
  • 37

2 Answers2

2

The astute reader will notice that the question can be reduced to: "How to generate all pairwise permutations of powers of 2?" By viewing it this way, we can avoid initially dealing with binary vectors and save this for the last step.

Using the base R function intToBits, this answer to the question How to convert integer numbers into binary vector?, and any function that can generate permutation of a specific length (There are many packages for this: gtools::permutations, RcppAlgos::permuteGeneral, iterpc, and arrangements::permutations), we can obtain the desired result in one line.

library(gtools)
t(sapply(t(gtools::permutations(3, 2, 2^(0:2))),  
         function(x) {as.integer(intToBits(x))})[1:3, ])

      [,1] [,2] [,3]
 [1,]    1    0    0
 [2,]    0    1    0

 [3,]    1    0    0
 [4,]    0    0    1

 [5,]    0    1    0
 [6,]    1    0    0

 [7,]    0    1    0
 [8,]    0    0    1

 [9,]    0    0    1
[10,]    1    0    0

[11,]    0    0    1
[12,]    0    1    0

Generalizing is easy.

bitPairwise <- function(numBits, groupSize) {
    t(sapply(t(gtools::permutations(numBits, groupSize, 2^(0:(numBits-1)))), 
                 function(x) {as.integer(intToBits(x))})[1:numBits, ])
}

 bitPairwise(numBits = 6, groupSize = 3)[1:12, ]
      [,1] [,2] [,3] [,4] [,5] [,6]
 [1,]    1    0    0    0    0    0
 [2,]    0    1    0    0    0    0
 [3,]    0    0    1    0    0    0

 [4,]    1    0    0    0    0    0
 [5,]    0    1    0    0    0    0
 [6,]    0    0    0    1    0    0

 [7,]    1    0    0    0    0    0
 [8,]    0    1    0    0    0    0
 [9,]    0    0    0    0    1    0

[10,]    1    0    0    0    0    0
[11,]    0    1    0    0    0    0
[12,]    0    0    0    0    0    1


UPDATE

I'm only posting this to point out how @Suren's answer could be made correct.

The OP is looking for permutations not combinations

From the conversation in the comments, you will see that @Suren's solution does not give correct results when the number of groups increases ("I am also trying to get groupings of three instead of 2 (or any number)" and "This is cutting off some solutions").

It appears that @Suren's answer gives correct results with g = 2. This is so, because the permutations of 1:n choose 2 is equal to the combinations of 1:n choose 2 combined with the combinations of n:1 choose 2 (notice that 1:n is reversed). This is precisely what @Suren's answer is doing (i.e. generate combinations choose 2, write them in reverse order, and combine).

## original version
surenFun <- function(n, g) {
    m <- combn(n, g)
    mm <- as.numeric(m)
    mat <- matrix(0, nrow = g * ncol(m), ncol = n)
    mat[ cbind(1:nrow(mat), mm)] <- 1
    soln <- rbind(mat, mat[nrow(mat):1, ])
    split(data.frame(soln), rep(1:(nrow(soln)/g), each=g))
}

## Here is the corrected version
surenFunCorrected <- function(n, g) {
    ## changed combn to gtools::permutations or any other
    ## similar function that can generate permutations
    m <- gtools::permutations(n, g)
    ## you must transpose m
    mm <- as.numeric(t(m))
    ## change ncol(m) to nrow(m)
    mat <- matrix(0, nrow = g * nrow(m), ncol = n)
    mat[ cbind(1:nrow(mat), mm)] <- 1
    ## removed soln
    split(data.frame(mat), rep(1:(nrow(mat)/g), each=g))
}

With the given example from the OP, it gives the same result just in a different order:

## The order is slightly different
match(surenFunCorrected(3, 2), surenFun(3, 2))
[1] 1 2 6 3 5 4

all(surenFunCorrected(3, 2) %in% surenFun(3, 2))
[1] TRUE

all(surenFun(3, 2) %in% surenFunCorrected(3, 2))
[1] TRUE

Let's test this with g = 3 and n = 4.

## N.B. all of the original output is
## contained in the corrected output
all(surenFun(4, 3) %in% surenFunCorrected(4, 3))
[1] TRUE

## However, there are 16 results
## not returned in the original
leftOut <- which(!(surenFunCorrected(4, 3) %in% surenFun(4, 3)))
leftOut
[1]  3  5  6  7  8  9 11 12 13 14 16 17 18 19 20 22

## E.g. 3 examples that were left out
surenFunCorrected(4, 3)[leftOut[c(1,8,16)]]
$`3`
  X1 X2 X3 X4
7  1  0  0  0
8  0  0  1  0
9  0  1  0  0

$`12`
   X1 X2 X3 X4
34  0  1  0  0
35  0  0  0  1
36  0  0  1  0

$`22`
   X1 X2 X3 X4
64  0  0  0  1
65  0  1  0  0
66  0  0  1  0
Joseph Wood
  • 7,077
  • 2
  • 30
  • 65
  • @user971102, this answer is more academic than anything. One could easily convert this to a workable solution for larger numbers by explicitly creating a vector of all zeros and a one at the `nth` place (instead of creating a power of 2 and converting to a binary vector). When I get a chance, I’ll update with such a solution. – Joseph Wood Jan 21 '18 at 20:32
  • Is it possible to include REPLACEABLE condition? For example, {1,0,0}, {1,0,0} is also a valid solution. How can we include this situation? – Rel_Ai Dec 11 '20 at 08:03
  • 1
    I got the answer. I just needed to set repeats.allowed = TRUE – Rel_Ai Dec 12 '20 at 10:09
1

Something like this?

n <- 3
g <- 2 # g must be < n 
m <- combn(n, g)
mm <- as.numeric(m)
mat <- matrix(0, nrow = g * ncol(m), ncol = n)
mat[ cbind(1:nrow(mat), mm)] <- 1

mat
#       [,1] [,2] [,3]
#[1,]    1    0    0
#[2,]    0    1    0

#[3,]    1    0    0
#[4,]    0    0    1

#[5,]    0    1    0
#[6,]    0    0    1

# mat is half the answer :)
# the other half is
mat[nrow(mat):1, ]

#      [,1] [,2] [,3]
#[1,]    0    0    1
#[2,]    0    1    0

#[3,]    0    0    1
#[4,]    1    0    0

#[5,]    0    1    0
#[6,]    1    0    0

soln <- rbind(mat, mat[nrow(mat):1, ])

# as suggested by the OP to split the soln 
d <- split(data.frame(soln), rep(1:(nrow(soln)/g), each=g))
kangaroo_cliff
  • 6,067
  • 3
  • 29
  • 42
  • 1
    Thank you headpoint, this is it. The data can then be split in lists of two using: d=split(data.frame(soln),rep(1:(nrow(soln)/2),each=2)). I wonder if there is a way to do it in base R without a package? – user971102 Jan 17 '18 at 04:41
  • 1
    Oops, it seems `combn` function is also in the `utils` . So, the `combinat` is not required. – kangaroo_cliff Jan 17 '18 at 04:51
  • I am also trying to get groupings of three instead of 2 (or any number), just missing what the final number would be to set up the matrix, but almost there: groups = 3; n <- 3; m <- combn(n, 2); mm <- as.numeric(m); mat <- matrix(0, nrow = groups ^2*ncol(m), ncol = n) # this is an overestimate – user971102 Jan 17 '18 at 05:31
  • you mean like (groups of) three rows where each row as one one non-zero element? – kangaroo_cliff Jan 17 '18 at 05:37
  • Yes exactly (or groups of any pre-determined number of rows), as long as the 1 is not overlapping in any of the rows, and the combinations are not repeated. Your code works also with this, for now overestimating the number of rows and removing duplicates – user971102 Jan 17 '18 at 05:44
  • yes, you are right. I've included the variable `g` for groups, which has to be less then `n`. – kangaroo_cliff Jan 17 '18 at 05:51
  • This is cutting off some solutions, I think we still need to keep this "m <- combn(n, 2)", and use a larger value for nrow dimensions of mat – user971102 Jan 17 '18 at 05:58
  • Sorry. I am actually not sure what you mean. – kangaroo_cliff Jan 17 '18 at 13:27
  • This does not return correct results. The OP's requested output has 12 rows, not 6. I haven't tested it, but I think if you generate permutations instead of combinations, you might get the correct results. Also, the use of `cbind(1:nrow(mat), mm)` is really nice. – Joseph Wood Mar 07 '18 at 11:03
  • @Suren, I just updated my answer without reading your answer first and realized I came up with nearly the same thing (except I was using permutations and not combinations) (see my edit history to see what I'm talking about). I rollbacked my answer to the original as I don't want to duplicate an already very nice answer. If you could change `combn` to `gtools::permutations` or some other permutation function, I will gladly upvote. – Joseph Wood Mar 07 '18 at 11:18
  • @JosephWood the output has 12 rows - not six. The OP has accepted the answer. So, I don't get why you are saying the answer is not correct but you didn't test it. I feel like you are not making a very good case here for me to look at this again to see if you are correct. – kangaroo_cliff Mar 07 '18 at 11:53
  • @Suren, sorry for the gaffe. I had many objects in my environment and reported on wrong information. The intent of my original comment still stands though (i.e. this solution is not correct for the general case). I'm sorry for the confusion as I was only trying to help. I have posted an updated answer that explains how your solution could be correct. Again, I'm sorry and I really like the heart of what you were doing. – Joseph Wood Mar 07 '18 at 15:20
  • What if the vectors are REPLACEABLE? For example, {1,0,0}, {1,0,0} is also a valid solution. How can we include this situation? – Rel_Ai Dec 11 '20 at 07:52