Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get all diagonal vectors from matrix

Tags:

r

matrix

I'm trying to figure out how to get all the diagonals of a matrix. For example, say I have the following matrix: A <- matrix(1:16,4)

using the diag(A) function will return

[1]  1  6 11 16

In addition to the primary diagonal, I would like a list of all the diagonals above and below it.

5 10 15
2  7 12
9 14
3  8
4
13

I found the following link https://stackoverflow.com/a/13049722 which gives me the diagonals directly above and below the primary one, however I cannot seem to figure out how to extend the code to get the rest of them for any size matrix. I tried two nested for loops since it appears that some kind of incrementing of the matrix subscripts would produce the result I am looking for. I tried using ncol(A), nrow(A) in the for loops, but couldn't seem to figure out the right combination. Plus I am aware that for loops are generally frowned upon in R.

The code given was:

diag(A[-4,-1])
diag(A[-1,-4])

which returned the two diagonals, both upper and lower

Of course this is a square matrix and not all of the matrices I want to perform this on will be square. Filling in the non-square area with NAs would be acceptable if necessary. The answer I need may be in one of the other answers on the page, but the original question involved means, sums, etc. which added a layer of complexity beyond what I am trying to do. I have a feeling the solution to this will be ridiculously simple, but it just isn't occurring to me. I'm also surprised I was not able to find this question anywhere on SO, it would seem to be a common enough question. Maybe I don't know the proper terminology for this problem.

like image 613
Beaker Avatar asked Jan 14 '15 03:01

Beaker


3 Answers

A <- matrix(1:16, 4)

# create an indicator for all diagonals in the matrix
d <- row(A) - col(A)

# use split to group on these values
split(A, d)

# 
# $`-3`
# [1] 13
# 
# $`-2`
# [1]  9 14
# 
# $`-1`
# [1]  5 10 15
# 
# $`0`
# [1]  1  6 11 16
# 
# $`1`
# [1]  2  7 12
# 
# $`2`
# [1] 3 8
# 
# $`3`
# [1] 4
like image 161
user20650 Avatar answered Nov 15 '22 05:11

user20650


Since you're dealing with square matrices, it should be really easy to convert Gavin's answer into a small function that first calculates the range that should be used as the offset values. Here's such a function:

AllDiags <- function(inmat, sorted = TRUE) {
  Range <- ncol(inmat) - 1
  Range <- -Range:Range
  if (isTRUE(sorted)) Range <- Range[order(abs(Range))]
  lapply(Range, function(x) {
    inmat[row(inmat) == (col(inmat) - x)]
  })
}

Here's the output on your sample matrix "A".

AllDiags(A)
# [[1]]
# [1]  1  6 11 16
# 
# [[2]]
# [1]  2  7 12
# 
# [[3]]
# [1]  5 10 15
# 
# [[4]]
# [1] 3 8
# 
# [[5]]
# [1]  9 14
# 
# [[6]]
# [1] 4
# 
# [[7]]
# [1] 13
like image 32
A5C1D2H2I1M1N2O1R2T1 Avatar answered Nov 15 '22 05:11

A5C1D2H2I1M1N2O1R2T1


Here is one solution based on an observation that you can get all the diagonals by shrinking and expanding the matrix. That is first consider row N col 1 (get diag of that) then rows (N-1): and cols (1:2). Get diagonal of that. etc..

N <- ncol(A)
rows <- cbind(c(N:1, rep(1,N-1)), c(rep(N,N), (N-1):1)) # row indeces
cols <- apply(rows, 2, rev)                             # col indeces

diagMatSubset <- function(mat, i1, i2, j1, j2) diag(mat[i1:i2, j1:j2, drop=FALSE])

Map(diagMatSubset, list(A), rows[,1], rows[,2], cols[,1], cols[,2])

[[1]]
[1] 4

[[2]]
[1] 3 8

[[3]]
[1]  2  7 12

[[4]]
[1]  1  6 11 16

[[5]]
[1]  5 10 15

[[6]]
[1]  9 14

[[7]]
[1] 13
like image 22
Karolis Koncevičius Avatar answered Nov 15 '22 07:11

Karolis Koncevičius