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.
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.
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.
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.
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.
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"
...
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.
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.
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;}
))
}
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With