25

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.

Community
  • 1
  • 1
Beaker
  • 2,804
  • 5
  • 33
  • 54
  • 1
    Why aren't you looking at @GavinSimpson's answer there? – A5C1D2H2I1M1N2O1R2T1 Jan 14 '15 at 03:55
  • I did read that answer as well which is why I understand the solution involves incrementing based on the number of rows and columns, however how to package this up as a function that returns all the diagonals in one matrix is what is evading me here. I read all the answers repeatedly. – Beaker Jan 14 '15 at 04:02
  • 5
    Side note: `for` loops are not frowned upon. That's a myth (IMO). They're all over the source code. They just need to be used appropriately, like any other function. :-) – Rich Scriven Jan 14 '15 at 04:37
  • 3
    @RichardScriven - it's not a myth that `for` loops are frowned upon. It is however a myth that all that frowning necessarily has any impact on the speed of the code. – thelatemail Jan 14 '15 at 05:36

3 Answers3

64
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
user20650
  • 24,654
  • 5
  • 56
  • 91
6

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
A5C1D2H2I1M1N2O1R2T1
  • 190,393
  • 28
  • 405
  • 485
3

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
Karolis Koncevičius
  • 9,417
  • 9
  • 56
  • 89