Question

Let A be an n*n diagonal matrix. Say, n=5:

A <- diag(1, 5)
A
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    0    0    0    0
[2,]    0    1    0    0    0
[3,]    0    0    1    0    0
[4,]    0    0    0    1    0
[5,]    0    0    0    0    1 

I need to randomize A by rows, keeping the 1's off the diagonal. I figured out this solution:

n <- 5
count <- c(1:n)
for (i in count) {
  while (A[count[i], count[i]] == 1) {    #avoid 1 in diagonal
    A[count[i],] <- sample(A[count[i],])  #permutes ith row
  }
}

but I am pretty sure that there should be a more efficient way of doing this. Thanks for your attention.

Was it helpful?

Solution

You could sample a random number from the integer sequence 1:n, n times, but each time exclude the cell number that corresponds to the diagonal.

One way to do this is as follows:

n <- 5
rnd <- sample(n-1, repl=T)
i <- c(rnd + (rnd >= seq_len(n-1)) * 1, sample(n-1, 1))  

i
# [1] 3 3 4 5 1

Here, each element of i is prevented from being equal to the element's index (e.g. element 1 cannot be 1, element 2 cannot be 2, etc.). We can consider each element of i to be the selected column for each row in turn.

Next we set up a matrix of zeroes, and (for the above example) fill the cells [1, 3], [2, 3], [3, 4], [4, 5] and [5, 1] to 1.

m <- matrix(0, nc=n, nr=n)
m[cbind(seq_len(n), i)] <- 1

m
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    0    0    1    0    0
# [2,]    0    0    1    0    0
# [3,]    0    0    0    1    0
# [4,]    0    0    0    0    1
# [5,]    1    0    0    0    0

The final row of code uses matrix subsetting to subset the matrix m to the relevant cells.


EDIT

In order to ensure that each column and each row have only a single 1, and that these are kept off the diagonal, the following vectorised approach will work. The trick here is to permute the vector n-1, and consider each element of that permuted vector to be the index, for each row, of the n-1 non-diagonal elements that we will assign 1. We then check, for each element of our permuted vector, whether the value is less than the index of the diagonal for the corresponding row. If so, we leave the element as is, otherwise we add 1. This determines the column indices for the first n-1 rows. The index for the final row is then simply the index of the column that does not yet have a 1.

n <- 5
rnd <- sample(n-1)

i <- union(rnd + (rnd >= seq_len(n-1)) * 1, seq_len(n))

m <- matrix(0, nc=n, nr=n)
m[cbind(seq_len(n), i)] <- 1

m
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    0    0    1    0    0
# [2,]    0    0    0    0    1
# [3,]    0    0    0    1    0
# [4,]    1    0    0    0    0
# [5,]    0    1    0    0    0

OTHER TIPS

Not sure this is the best or most elegant, but you can choose a random (but non diagonal) element with which to replace the diagonal element on each row, and then assign the non-diagonals with the remainder:

count <- c(1:n)
for (i in count) {
    off <- sample(count[-i],1)
    perm <- sample(count[-off])
    tmp <- A[i,off]
    A[i, count[-i]] <- A[i,perm]
    A[i,i] <- tmp
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top