Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function to split a matrix into sub-matrices in R

Tags:

r

matrix

I have a matrix M with 16 rows and 12 columns and I want to split it into a array of 16 matrices, each with 4 rows and 3 columns. I can do it manually by:

M = matrix(sample(0:127,16*12,replace=TRUE), c(16,12))

ma1 = M[1:4,1:3]
ma2 = M[1:4,4:6]
ma3 = M[1:4,7:9]
ma4 = M[1:4,10:12]

ma5 = M[5:8,1:3]
ma6 = M[5:8,4:6]
.....

But how can I create a generic function matsplitter(M, r, c) which splits M into an array of matrices, each with r rows and c columns?

Thanks for your help.

like image 254
rnso Avatar asked Jun 19 '14 05:06

rnso


People also ask

How do you break a matrix in R?

To convert Matrix to List in R, use the split() function. The split() is an inbuilt R function that divides the data in the vector into the groups defined by f. The replacement forms replace values corresponding to such a division. The unsplit() function reverses the effect of the split.

How do you split matrices?

Definition: A = B − C is a regular splitting of A if B−1 ≥ 0 and C ≥ 0. The matrix D = B−1C has nonnegative entries if (2) represents a regular splitting of A. represents the spectral radius of D, and thus D is a convergent matrix. As a consequence, the iterative method (5) is necessarily convergent.

How do you split a Dataframe in R?

Use the split() function in R to split a vector or data frame. Use the unsplit() method to retrieve the split vector or data frame.

How do you find the number of subs of a matrix?

The number of submatrices There are (n – k + 1) sequences of consecutive rows of length k, such as 1:k, 2:(k+1), and so forth. Similarly, there are (m – k + 1) sequences of consecutive columns of length k. So the following SAS/IML function counts the number of submatrices of order k.


5 Answers

If you have a 16x12 array like this

mb <- structure(c("a1", "a2", "a3", "a4", "e1", "e2", "e3", "e4", "i1", 
"i2", "i3", "i4", "m1", "m2", "m3", "m4", "a5", "a6", "a7", "a8", 
"e5", "e6", "e7", "e8", "i5", "i6", "i7", "i8", "m5", "m6", "m7", 
"m8", "a9", "a10", "a11", "a12", "e9", "e10", "e11", "e12", "i9", 
"i10", "i11", "i12", "m9", "m10", "m11", "m12", "b1", "b2", "b3", 
"b4", "f1", "f2", "f3", "f4", "j1", "j2", "j3", "j4", "n1", "n2", 
"n3", "n4", "b5", "b6", "b7", "b8", "f5", "f6", "f7", "f8", "j5", 
"j6", "j7", "j8", "n5", "n6", "n7", "n8", "b9", "b10", "b11", 
"b12", "f9", "f10", "f11", "f12", "j9", "j10", "j11", "j12", 
"n9", "n10", "n11", "n12", "c1", "c2", "c3", "c4", "g1", "g2", 
"g3", "g4", "k1", "k2", "k3", "k4", "o1", "o2", "o3", "o4", "c5", 
"c6", "c7", "c8", "g5", "g6", "g7", "g8", "k5", "k6", "k7", "k8", 
"o5", "o6", "o7", "o8", "c9", "c10", "c11", "c12", "g9", "g10", 
"g11", "g12", "k9", "k10", "k11", "k12", "o9", "o10", "o11", 
"o12", "d1", "d2", "d3", "d4", "h1", "h2", "h3", "h4", "l1", 
"l2", "l3", "l4", "p1", "p2", "p3", "p4", "d5", "d6", "d7", "d8", 
"h5", "h6", "h7", "h8", "l5", "l6", "l7", "l8", "p5", "p6", "p7", 
"p8", "d9", "d10", "d11", "d12", "h9", "h10", "h11", "h12", "l9", 
"l10", "l11", "l12", "p9", "p10", "p11", "p12"), .Dim = c(16L, 
12L))

You can define matsplitter as

matsplitter<-function(M, r, c) {
    rg <- (row(M)-1)%/%r+1
    cg <- (col(M)-1)%/%c+1
    rci <- (rg-1)*max(cg) + cg
    N <- prod(dim(M))/r/c
    cv <- unlist(lapply(1:N, function(x) M[rci==x]))
    dim(cv)<-c(r,c,N)
    cv
} 

Then

matsplitter(mb,4,3)

will return (output clipped)

, , 1

     [,1] [,2] [,3] 
[1,] "a1" "a5" "a9" 
[2,] "a2" "a6" "a10"
[3,] "a3" "a7" "a11"
[4,] "a4" "a8" "a12"

, , 2

     [,1] [,2] [,3] 
[1,] "b1" "b5" "b9" 
[2,] "b2" "b6" "b10"
[3,] "b3" "b7" "b11"
[4,] "b4" "b8" "b12"

, , 3

     [,1] [,2] [,3] 
[1,] "c1" "c5" "c9" 
[2,] "c2" "c6" "c10"
[3,] "c3" "c7" "c11"
[4,] "c4" "c8" "c12"

...
like image 116
MrFlick Avatar answered Oct 11 '22 20:10

MrFlick


Here is a function that uses Kronecker products to do the same thing. Why? Because I enjoy Kronecker products. The bonus here is that if your row and column values don't divide evenly into your input matrix then this function will pad out the smaller matrices at the right and bottom edges with NAs so you can still have an array output.

mat_split <- function(M, r, c){
  nr <- ceiling(nrow(M)/r)
  nc <- ceiling(ncol(M)/c)
  newM <- matrix(NA, nr*r, nc*c)
  newM[1:nrow(M), 1:ncol(M)] <- M

  div_k <- kronecker(matrix(seq_len(nr*nc), nr, byrow = TRUE), matrix(1, r, c))
  matlist <- split(newM, div_k)
  N <- length(matlist)
  mats <- unlist(matlist)
  dim(mats)<-c(r, c, N)
  return(mats)
}

So using the original example:

> M = matrix(sample(0:127,16*12,replace=TRUE), c(16,12))
> mat_split(M, 4, 3)
, , 1

     [,1] [,2] [,3]
[1,]  107   45  107
[2,]   49  119   32
[3,]   79  114   26
[4,]   71  104   16

, , 2

     [,1] [,2] [,3]
[1,]   79   77    4
[2,]   46   55   49
[3,]  122   15    0
[4,]   19   12   34

, , 3

     [,1] [,2] [,3]
[1,]  114   28   74
[2,]  116   28   84
[3,]   80   49   95
[4,]   41    6   82

, , 4

     [,1] [,2] [,3]
[1,]   17   17   13
[2,]  107   78   94
[3,]   22   16   14
[4,]  104   14  117
...

but if you do this:

mat_split(M, 4, 5)

you get:

, , 1

     [,1] [,2] [,3] [,4] [,5]
[1,]  107   45  107   79   77
[2,]   49  119   32   46   55
[3,]   79  114   26  122   15
[4,]   71  104   16   19   12

, , 2

     [,1] [,2] [,3] [,4] [,5]
[1,]    4  114   28   74   17
[2,]   49  116   28   84  107
[3,]    0   80   49   95   22
[4,]   34   41    6   82  104

, , 3

     [,1] [,2] [,3] [,4] [,5]
[1,]   17   13   NA   NA   NA
[2,]   78   94   NA   NA   NA
[3,]   16   14   NA   NA   NA
[4,]   14  117   NA   NA   NA

, , 4

     [,1] [,2] [,3] [,4] [,5]
[1,]  112   56   20   54   68
[2,]   59   37   30  110  126
[3,]   34   22  110   13   73
[4,]  116   57   48   77   41

...

Another useful addition might be to have the option to output as a list of matrices, instead of an array, which means you wouldn't have to pad with NAs.

like image 20
ecologician Avatar answered Oct 11 '22 22:10

ecologician


Initial data

M = matrix(sample(0:127,16*12,replace=TRUE), c(16,12))


> M
      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]
 [1,]   46   46   64   54   48   78  125   38  103    43    15   125
 [2,]   75    9   10  119  108   29   13  104   51    74    83    86
 [3,]   52   22   97   12   44  115  118  111  114    56    31    36
 [4,]    1  116   70   27   61   22   36   34   16    62    20    23
 [5,]   32   61   11   46   34  120   50   71   44   105    52    81
 [6,]   88    1   60   75   68   85    0    0   66   125    52    65
 [7,]  119   32   75   14  119   57   74  107   21    32   110    39
 [8,]  103   70   18  127   32   44   14  103  118   120     0   119
 [9,]   12   99    0   48   31  126   92   78    9    11    52    21
[10,]   51   73   22   29   53   43   75  110   80    28    26    48
[11,]   64    5   81  127   25   59   50   21   46    87    66   122
[12,]   35    9   26  100    2   97   62  101    9    26    57    58
[13,]   90   16   70  118  122  120   50  125   26    34    54    55
[14,]   40   71   25   67   14   69   39   63  102     3    20   102
[15,]   51   66   92   19    7   53   33  123   50    78    83   111
[16,]   31   10   75   55  115   20   15  126   39   114   115    62

Split by columns

matrices_split_by_col = lapply(1:4, function(col){
  M[,((col-1)*3+1):((col-1)*3+3)]
})


> matrices_split_by_col[[1]]
      [,1] [,2] [,3]
 [1,]   46   46   64
 [2,]   75    9   10
 [3,]   52   22   97
 [4,]    1  116   70
 [5,]   32   61   11
 [6,]   88    1   60
 [7,]  119   32   75
 [8,]  103   70   18
 [9,]   12   99    0
[10,]   51   73   22
[11,]   64    5   81
[12,]   35    9   26
[13,]   90   16   70
[14,]   40   71   25
[15,]   51   66   92
[16,]   31   10   75

Now do two lapplies to split each column into rows

matrices_split_by_row = lapply(matrices_split_by_col, function(mat){

  lapply(1:4, function(row){
    mat[((row-1)*3+1):((row-1)*3+4),]
  })

})

Unlist the result:

matrices_split_by_row_and_col = unlist(matrices_split_by_row,recursive=FALSE)

Check result:

> matrices_split_by_row_and_col[[2]]
     [,1] [,2] [,3]
[1,]    1  116   70
[2,]   32   61   11
[3,]   88    1   60
[4,]  119   32   75

Oops, this gives the matrices going down the columns first, but anyway, you can modify the code and turn it into a function if you want, using the underlying logic.

like image 36
Alex Avatar answered Oct 11 '22 22:10

Alex


Modification of @MrFlick's answer:

matsplitter<-function(M, r, c) {
  simplify2array(lapply(
    split(M, interaction((row(M)-1)%/%r+1,(col(M)-1)%/%c+1)),
    function(x) {dim(x) <- c(r,c); x;}
  ))
} 
like image 4
thelatemail Avatar answered Oct 11 '22 22:10

thelatemail


Using my limited regular programming knowledge, I came up with following code:

matsplitter = function(mat, submatr, submatc){
    matr = dim(mat)[1]
    matc = dim(mat)[2]
    mats_per_row=matc/submatc

    submat = array(NA, c(submatr,submatc,matr*matc/(submatr*submatc)))

    cur_submat=1; k=0
    i=j=a=b=1

    while(TRUE){
        submat[i,j,cur_submat+k] = mat[a,b]

        j=j+1
        if(j>submatc){j=1; k=k+1; if(k>(mats_per_row-1)){k=0; i=i+1; if(i>submatr){i=1;cur_submat=cur_submat+mats_per_row;}}}

        b=b+1
        if(b>matc){b=1;a=a+1; if(a>matr){break};}
    }
    submat
}
like image 3
rnso Avatar answered Oct 11 '22 22:10

rnso