3

Reproducible example:

set.seed(1)
testMat <- matrix(round(runif(3*6,1,5)), nrow = 3, ncol = 6)

Output:

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

Here first 3 columns (1,2,3) belong to one set and next 3 columns (4,5,6) belong to another set. I want to add one column from each set and need to do it for all possible combinations.

For this example, I should get 9 resulting vectors as there are 9 combinations:

Combination 1: (1,4) = (3,4,5)
Combination 2: (1,5) = (6,5,7)
Combination 3: (1,6) = (.,.,.)
Combination 4: (2,4) = (.,.,.)
Combination 5: (2,5) = (.,.,.)
Combination 6: (2,6) = (.,.,.)
Combination 7: (3,4) = (.,.,.) 
Combination 8: (3,5) = (.,.,.)
Combination 9: (3,6) = (.,.,.)

Is there any elegant way to do it, especially when the number of columns can be higher? For example 9, 12, etc. would yield combinations of 27 and 81 respectively.

EDIT: More clarification: Each 3 columns (e.g. 1:3, 4:6, 7:9, 10:12,...etc.) construct one set and the objective is to take 1 column from each set and add them together. For example if we have 6 columns at testMat we take 1 column among 1:3 and another from 4:6 add tese 2 columns together. Similarly for 9 we add 3, for 12 we add 4 columns one from each set.

Rel_Ai
  • 581
  • 2
  • 11

3 Answers3

5

Here is one way :

#Create a sequence from 1 to number of columns in the data
cols <- seq(ncol(testMat))
n <- 3
#Create a list of every n columns and use expand.grid 
#to create all possible combinations
vals <- expand.grid(split(cols, ceiling(cols/n)))
#RowSums every combination using `sapply`.
sapply(asplit(vals, 1), function(x) rowSums(testMat[, x]))

#     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
#[1,]    3    6    6    6    9    9    5    8    8
#[2,]    4    4    6    5    5    7    6    6    8
#[3,]    5    7    6    7    9    8    8   10    9
Ronak Shah
  • 377,200
  • 20
  • 156
  • 213
  • This is elegant but it is only working when there are 6 columns in testMat. For more than six columns (e.g. 9, 12, 15, etc.), how can it be generalized? – Rel_Ai Apr 05 '21 at 10:24
  • 1
    Oops..you are right. I updated the answer so it works with any number of columns. @Rel_Ai – Ronak Shah Apr 05 '21 at 10:29
  • if the testMat here is a vector instead of a matrix what should I do then? in this case the result will be a vector of length 9 of course. – Rel_Ai Apr 15 '21 at 10:59
  • 1
    In that case I think `cols <- seq(length(testMat))` and in `sapply` doing `sapply(asplit(vals, 1), function(x) sum(testMat[x]))` should work. ` – Ronak Shah Apr 15 '21 at 11:25
  • This seems to work. Thanks. it was a great help. – Rel_Ai Apr 15 '21 at 11:59
4

expand.grid can be surprisingly helpful:


nc <- ncol( testMat )

if( nc %% 3 != 0 ) {
  stop( "Your data's number of columns should be a multiple of 3!")
}

n <- ncol( testMat ) / 3

args <- sapply( 1:n, function(i) (i*3-2):(i*3), simplify=FALSE )

combs <- do.call( expand.grid, args ) %>% arrange( Var1 )

combs %>% apply( 1, function(r) {
              rowSums( testMat[, r] )
          })



Output:


> combs %>% apply( 1, function(r) {
+               rowSums( testMat[, r ] )
+           })
[,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
[1,]    3    6    5    6    9    8    6    9    8
[2,]    4    5    6    4    5    6    6    7    8
[3,]    5    7    8    7    9   10    6    8    9

Sirius
  • 5,224
  • 2
  • 14
  • 21
  • Your solution works for 9 columns as well. But I have to manually change function argument for expand grid. How can I automate it based on the number of columns available at testMat? – Rel_Ai Apr 05 '21 at 00:58
  • are the 3 first columns always fixed? `expand.grid( 1:3, 4:ncol(testMat) )` # if so – Sirius Apr 05 '21 at 01:21
  • For example, when there are 9 columns, it should be expand.grid(1:3,4:6,7:9). – Rel_Ai Apr 05 '21 at 01:26
  • Now that I rechecked your answer for 9 columns, it doesn't seem like producing result that I was expecting. each 3 columns (e.g. 1:3, 4:6, 7:9, 10:12,...etc.) construct one set and the objective is to take 1 column from each set and add them together. For 6 columns we add 2 columns, for 9 we add 3, for 12 we add 4 columns and so on. – Rel_Ai Apr 05 '21 at 01:38
  • see if it works with the update. It creates sequences of 1:3, 4:6, etc.. that it runs expand.grid on using do.call instead. – Sirius Apr 05 '21 at 01:47
  • sorry made another update, cleaner args generation – Sirius Apr 05 '21 at 01:55
4

Here's an approach with to generate the permutations:

library(RcppAlgos)

my_vals = function (...) {
    dots = list(...)
    ncols = ...length()
    cols = permuteGeneral(3L, ncols, TRUE)
    
    Reduce(`+`, Map(`[`, dots, asplit(cols,2L)))
}

do.call('my_fun', asplit(array(testMat, c(3L, 3L, ncol(testMat) / 3L)), 3L))

This approach turns the matrix into a list of submatrices which are then subsetted by the permutations.

Cole
  • 11,130
  • 1
  • 9
  • 24