13

I have like a matrix in R and I want to get:

Max off - diagonal elements
Min off – diagonal elements
Mean off –diagonal elements

With diagonal I used max(diag(A)) , min(diag(A)) , mean(diag(A)) and worked just fine

But for off-diagonal I tried

dataD <- subset(A, V1!=V2)

Error in subset.matrix(A, V1 != V2) : object 'V1' not found

to use:

colMeans(dataD) # get the mean for columns

but I cannot get dataD b/c it says object 'V1' not found

Thanks!

IRTFM
  • 258,963
  • 21
  • 364
  • 487
amhemad ahmad
  • 195
  • 2
  • 2
  • 6
  • Can you clarify what you mean by "off diagonal", do you mean all elements of the matrix **except** the diagonal or do you mean the row/col immediately above/below the diagonal? – Gavin Simpson Oct 24 '12 at 12:53
  • Just loop through the elements in the matrix and ignore the diagonal elements. What's stopping you? –  Oct 24 '12 at 12:54
  • 3
    @JackManey looping when there is a vectorised solution is exceedingly inefficient in R. – Gavin Simpson Oct 24 '12 at 13:05

6 Answers6

30

Here the row() and col() helper functions are useful. Using @James A, we can get the upper off-diagonal using this little trick:

> A[row(A) == (col(A) - 1)]
[1]  5 10 15

and the lower off diagonal via this:

> A[row(A) == (col(A) + 1)]
[1]  2  7 12

These can be generalised to give whatever diagonals you want:

> A[row(A) == (col(A) - 2)]
[1]  9 14

and don't require any subsetting.

Then it is a simple matter of calling whatever function you want on these values. E.g.:

> mean(A[row(A) == (col(A) - 1)])
[1] 10

If as per my comment you mean everything but the diagonal, then use

> diag(A) <- NA
> mean(A, na.rm = TRUE)
[1] 8.5
> max(A, na.rm = TRUE)
[1] 15
> # etc. using sum(A, na.rm = TRUE), min(A, na.rm = TRUE), etc..

So this doesn't get lost, Ben Bolker suggests (in the comments) that the above code block can be done more neatly using the row() and col() functions I mentioned above:

mean(A[row(A)!=col(A)])
min(A[row(A)!=col(A)])
max(A[row(A)!=col(A)])
sum(A[row(A)!=col(A)])

which is a nicer solution all round.

Gavin Simpson
  • 170,508
  • 25
  • 396
  • 453
  • 3
    or `mean(A[row(A)!=col(A)])` etc. – Ben Bolker Oct 24 '12 at 13:12
  • +1 Ben - I saw you'd covered that option so didn't edit it into mine. That didn't even occur to me, even though I was using `row()` and `col()` to get the other bits. Total blank there. – Gavin Simpson Oct 24 '12 at 13:24
  • I deleted mine since you'd covered most of it, with more explanation and examples. – Ben Bolker Oct 24 '12 at 13:38
  • I want to get the sum, min , max and mean for everything under and above diagonal i.e. in A sum everything above diagonal is 5 + 9 +10 +13 +14 + 15 = 66 , mean = 11 ..... – amhemad ahmad Oct 24 '12 at 14:14
  • The last code block shows how to get some of these. use `sum()`, and `min()` for the other two. Also see Ben Bolkers comment here which has a different approach. – Gavin Simpson Oct 24 '12 at 14:15
7

In one simple line of code:

For a matrix A if you wish to find the Minimum, 1st Quartile, Median, Mean, 3rd Quartile and Maximum of the upper and lower off diagonals:

summary(c(A[upper.tri(A)],A[lower.tri(A)])).

B Maunder
  • 71
  • 1
  • 2
4

The diag of a suitably subsetted matrix will give you the off-diagonals. For example:

A <- matrix(1:16,4)
#upper off-diagonal
diag(A[-4,-1])
[1]  5 10 15
#lower off-diagonal
diag(A[-1,-4])
[1]  2  7 12
James
  • 65,548
  • 14
  • 155
  • 193
1

To get a vector holding the max of the off-diagonal elements of each col or row of a matrix requires a few more steps. I was directed here when searching for help on that. Perhaps others will do the same, so I offer this solution, which I found using what I learned here.

The trick is to create a matrix of only the off-diagonal elements. Consider:

> A <- matrix(c(10,2,3, 4,10,6, 7,8,10), ncol=3)
> A
     [,1] [,2] [,3]
[1,]   10    4    7
[2,]    2   10    8
[3,]    3    6   10
> apply(A, 2, max)
[1] 10 10 10

Subsetting using the suggested indexing, A[row(A)!=col(A)] produces a vector of off-diagonal elements, in column-order:

> v <- A[row(A)!=col(A)]
> v
[1] 2 3 4 6 7 8

Returning this to a matrix allows the use of apply() to apply a function of choice to a margin of only off-diagonal elements. Using the max function as an example:

> A.off <- matrix(v, ncol=3)
> A.off
     [,1] [,2] [,3]
[1,]    2    4    7
[2,]    3    6    8
> v <- apply(A.off, 2, max)
> v
[1] 3 6 8

The whole operation can be compactly—and rather cryptically—coded in one line:

> v <- apply(matrix(A[row(A)!=col(A)], ncol=ncol(A)), 2, max)
> v
[1] 3 6 8
garrettmd
  • 11
  • 2
0

Just multiply matrix A by 1-diag (nofelements)

for example if A is a 4x4 matrix, then

mean(A*(1-diag(4)) or A*(1-diag(nrow(A)))

This is faster when you need to run the same line of code multiple times

-1

In addition to James' answer, I want to add that you can use the diag function to directly exclude all diagonal elements of a matrix by use of A[-diag(A)]. For example, consider: summary(A[-diag(A)])

  • I was very excited by the compactness of this answer, but I think it doesn't actually work in general. Compare `m <- matrix(2:17,nrow=4); m[-diag(m)]; m[row(m)!=col(m)]` – Ben Bolker Oct 24 '12 at 15:51
  • Damn, you are right, my bad. For larger matrices, my idea would have to be used in the form of `A[-which(A %in% diag(A))]`, which is far less elegant than yours. – Wolfgang Pößnecker Oct 24 '12 at 16:00
  • I don't think that works either ... `diag(A)` returns the *values* on the diagonal, so if there were entries that were repeated among the diagonal and off-diagonal elements, this would fail ... – Ben Bolker Oct 24 '12 at 16:08
  • Hm, unfortunately, you are correct. Complete failure from my side. A last try: `m[-seq(from=1,by=nrow(m)+1, length.out=nrow(m))]` That should work, although it is very inelegant. – Wolfgang Pößnecker Oct 24 '12 at 16:54