How to fill off-diagonals and ignore diagonals in matrix in R?

Viewed 731

I am trying to fill a matrix in R where the final result will ignore the diagonal entries and the values will be filled in around the diagonal. A simple example of what I mean is, if I take a simple 3x3 matrix like the one shown below:

ab <- c(1:9)
mat <- matrix(ab,nrow=3,ncol=3)
colnames(mat)<- paste0("x", 1:3)
rownames(mat)<- paste0("y", 1:3)
mat

    x1 x2 x3
y1  1  4  7
y2  2  5  8
y3  3  6  9

What I want to achieve is to fill the diagonals with 0 and shift all the other values around the diagonal. So, for example if I just use diag(mat)<-0 that results in this:

   x1 x2 x3
y1  0  4  7
y2  2  0  8
y3  3  6  0

Whereas, the result I'm looking for is something like this (where the values get wrapped around the diagonal):

   x1 x2 x3
y1  0  3  5
y2  1  0  6
y3  2  4  0

I'm not worried about the values that are pushed out of the matrix (i.e., 7,8,9).

Any suggestions?

Thanks

EDIT: The upvoted solution below, seems to have solved the problem

3 Answers

One solution that works for your example is to first declare a matrix full of ones except on the diagonal:

M <- 1 - diag(3)

And then to replace all the ones by the desired off-diagonal values

M[M == 1] <- 1:6
M
#      [,1] [,2] [,3]
# [1,]    0    3    5
# [2,]    1    0    6
# [3,]    2    4    0

A more complicated scenario (e.g. diagonal coefficients that are not 0, or an unkonwn number of off-diagonal elements) might need a little bit of additionnal work.

You may need a loop:

n <- 9
seqs <- seq(1:n)
mats <- matrix(0, nrow = 3, ncol = 3)
ind <- 0
for(i in 1:nrow(mats)){
     for(j in 1:nrow(mats)){

         if(i == j) {
             mats[i,j] <- 0 }
             else {
                 ind <- ind + 1
                 mats[j,i] <- seqs[ind]
             }
         }
     }

Resulting in:

>mats
     [,1] [,2] [,3]
[1,]    0    3    5
[2,]    1    0    6
[3,]    2    4    0

This will work ok for your example. Not sure I needed n1 & n2, could be altered to one value if always symmetric

# original data
ab <- c(1:9)
n1 <- 3
n2 <- 3

# You could add the 0's to the diagonal, by adding a 0 before every n1 split
# of the data e.g. 0,1,2,3 & 0,4,5,6 & 0,7,8,9
split_ab <- split(ab, ceiling((1:length(ab))/n1))
update_split_ab <- lapply(split_ab, function(x){
  c(0, x)
}) 
new_ab <- unlist(update_split_ab)

mat <- matrix(new_ab, nrow=n1, ncol=n2)
colnames(mat)<- paste0("x", 1:n2)
rownames(mat)<- paste0("y", 1:n1)
mat

# turn this in to a function

makeShiftedMatrix <- function(ab=1:9, n1=3, n2=3){

  split_ab <- split(ab, ceiling((1:length(ab))/n1))
  update_split_ab <- lapply(split_ab, function(x){
    c(0, x)
  }) 
  new_ab <- unlist(update_split_ab)

  mat <- matrix(new_ab, nrow=n1, ncol=n2)
  colnames(mat)<- paste0("x", 1:n2)
  rownames(mat)<- paste0("y", 1:n1)
  mat
  return(mat)
}

# default
makeShiftedMatrix()

# to read in original matrix and shift:
old_mat <- matrix(ab, nrow=n1, ncol=n2)
makeShiftedMatrix(ab=unlist(old_mat))
Related