Replacing part of a triangle matrix by a vector

Viewed 40

I have v<- c(2,4,5) and mat<- matrix(0,n,n). and i want to replace vector in the upper of the matrix, each place which i identify, the arrays are replacing as length number of vector. for example ,for n=5 the output can be :

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

I try with this code:

mat <- matrix(0, nrow = 5, ncol = 5)
for (i in 1:5){
  for (j in 1:5){
    if (i<j & j<= 5){
      mat [upper.tri(mat, diag = FALSE)]<- v
    }
  }
}

but output is:

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

I have two problems:

  1. I want the vector replaced just one time in matrix and not to repeat.

  2. I don't know where I have to replace i and j in the loop for the starting point of matrix to replace vector.(maybe I want to start the vector from [3,4] in matrix.).for example:

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

2 Answers

When you fill matrices from vectors, two good things to know:

  1. It fills them in by-column; and
  2. If the vector is shorter than the portion of the matrix to fill, then the vector will be recycled (repeated).

Further, in your code, you use two for loops, nested, with the apparent intent of operating only on those indices within the matrix ... but then you do not reference i and j in the matrix subset or assignment, so every time it tries to replace a single value, it's replacing the entire matrix upper-tri with the entire vector.

Here's a speedier way:

mat <- matrix(0, nrow = 5, ncol = 5)
v <- c(2, 4, 5)
mat[upper.tri(mat)][seq_along(v)] <- v
mat
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    0    2    4    0    0
# [2,]    0    0    5    0    0
# [3,]    0    0    0    0    0
# [4,]    0    0    0    0    0
# [5,]    0    0    0    0    0

The first indexing on mat is the upper-triangle, as you know. The second subsets to just length(v) of that subset.

If you want to replace it at arbitrary places in the upper-triangle, though, here's a shot:

mat <- matrix(0, nrow = 5, ncol = 5)
ut <- upper.tri(mat)
fullv <- integer(sum(ut)) # the size of the matrix subset
fullv
#  [1] 0 0 0 0 0 0 0 0 0 0
fullv[5 + seq_along(v)] <- v
fullv
#  [1] 0 0 0 0 0 2 4 5 0 0
mat[ut] <- fullv
mat
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    0    0    0    0    4
# [2,]    0    0    0    0    5
# [3,]    0    0    0    2    0
# [4,]    0    0    0    0    0
# [5,]    0    0    0    0    0

Here we demonstrate that the filling is being done by column, which is not what you were asking for. To better see how this filling is happening,

mat <- matrix(0, nrow = 5, ncol = 5)
ut <- upper.tri(mat)
mat[ut] <- seq_len(sum(ut))
mat
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    0    1    2    4    7
# [2,]    0    0    3    5    8
# [3,]    0    0    0    6    9
# [4,]    0    0    0    0   10
# [5,]    0    0    0    0    0

So if you want your v in very specific places and they are not consecutive per this ordering, then you'll need to be specific:

mat <- matrix(0, nrow = 5, ncol = 5)
ut <- upper.tri(mat)
mat[ut][c(6,9,10)] <- v
mat
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    0    0    0    0    0
# [2,]    0    0    0    0    0
# [3,]    0    0    0    2    4
# [4,]    0    0    0    0    5
# [5,]    0    0    0    0    0

You can figure out the {r,c} indices for each element of a length n vector (you will also need the dimensions of a square triangle required to fit such a vector.

For example, for a length 3 vector starting at {3,4} (assuming column-major as in R), you will need the indices

i r c
1 3,4

2 3,5
3 4,5

4 3,6
5 4,6
6 5,6
...

You can probably see the patterns already. For the rows, there is a common r function which gives the pattern 3 3 4 3 4 5 ... which is sequence specifically sequence(1:3) + 2.

Next the columns have a simpler solution in 4 5 5 6 6 6 ... which is just rep(1:3, 1:3) + 3, so we can just combine these two to get the indices for each element of the vector.

## use 1:(n - 1) for a length n vector
mat <- matrix(0, 5, 5)
v <- c(2, 4, 5)
i <- cbind(sequence(1:2) + (3 - 1), rep(1:2, 1:2) + (4 - 1))
mat[i] <- v
mat

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

You can see your starting positions in the above code, so we can generalize this logic:

f <- function(matrix, vector = 1:3, start = c(1, 1)) {
  i <- sequence(rep(1:nrow(matrix), 1:ncol(matrix))[length(vector)])
  i <- cbind(
    sequence(i) + start[1] - 1,
    rep(i, i) + start[2] - 1
  )
  ## recycle vector so there are no incompatible length errors
  matrix[i] <- rep_len(vector, nrow(i))

  matrix
}

mat <- matrix(0, 5, 5)
f(mat, v, c(3, 4))
f(mat, v, c(2, 2))
f(mat, c(2, 4, 5), c(1, 1))
f(mat, c(v, v), c(1, 1))
f(mat, c(v, v, v), c(1, 2))
Related