7

I want to insert a set of n values expressed as a vector into a corresponding set of locations in a matrix. The real-world application involves inserting a set of n sea surface temperature values into an image of a region that is represented as a grid with dimension nrow x ncol > n in for which I have identified the n water pixels that should receive the temperature values. The problem I've run into is that the temperature values are ordered as if they were from a column-major matrix rather than the row-major ordering used to index the R grid.

Here is a toy example of what I mean.

> grid <- matrix(0,4,4)
> grid                       # define the base grid
     [,1] [,2] [,3] [,4]
[1,]    0    0    0    0
[2,]    0    0    0    0
[3,]    0    0    0    0
[4,]    0    0    0    0

> temps <- c(9,9,9,9,9)     # we have 5 temperature values
> locs <- c(2,3,4,6,7)      # locations in the base grid that are water

> grid[locs] <- temps       # not really what I want - substitution in row-major order
> grid
     [,1] [,2] [,3] [,4]
[1,]    0    0    0    0
[2,]    9    9    0    0
[3,]    9    9    0    0
[4,]    9    0    0    0

The desired result would be rather:

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

I suppose I could play with transposing the grid, doing the substitution and then transposing it back, but I'd think there would be a better way to approach this problem.

Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
Fleetboat
  • 189
  • 7

4 Answers4

6

Here are a couple of options, each of which works on matrices of arbitrary dimension:


arrayIndByRow <- function(ind, dim) {
   arrayInd(ind, rev(dim))[,2:1]
}

grid[arrayIndByRow(locs, dim(grid))] <- temps
grid
#      [,1] [,2] [,3] [,4]
# [1,]    0    9    9    9
# [2,]    0    9    9    0
# [3,]    0    0    0    0
# [4,]    0    0    0    0

f <- function(ind, dim) {
    nr <- dim[1]
    nc <- dim[2]
    ii <- ind - 1
    ((ii %/% nc) + 1) + nr*(ii %% nc)
}

grid[f(locs, dim(grid))] <- 1:5
grid
#      [,1] [,2] [,3] [,4]
# [1,]    0    1    2    3
# [2,]    0    4    5    0
# [3,]    0    0    0    0
# [4,]    0    0    0    0
Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
  • I am pretty sure your second option only works for square matrixes (since it's the same as mine down below :))... For example given a 4 by 5 matrix, 2 should go to 6, but with the above function, `f(2,c(4,5)) == 5`. – jed Nov 06 '14 at 18:29
  • @jed -- Nope, that's not correct. With a 4x5 matrix, 2 *should* map to 5. (It counts down through four elements in the first column, and then arrives back up at the top of the second column as it reaches the fifth element). Try it out yourself to see that this is correct, with `grid <- matrix(0,nr=4,nc=5); grid[f(2, dim(grid))] <- 99; grid`. Also, sorry I didn't see and simply edit your answer when I came back here to add my own quite similar `f` -- I did +1 yours when I noticed it a few hours ago! – Josh O'Brien Nov 06 '14 at 18:37
  • Ah right, I was going the wrong way. As in 6 should go to 2, which does happen in your function! – jed Nov 06 '14 at 18:58
3

One way to do this is to create a new matrix with the required data, specifying byrow=TRUE when it is created. To do this, you have to create an intermediate vector to store and modify the data of grid:

grid <- matrix(rep(0,16),ncol=4)
##
temps <- c(9,9,9,9,9)     
locs <- c(2,3,4,6,7)      
##
#vgrid <- as.numeric(grid)
vgrid <- c(grid)
vgrid[locs] <- temps
##
> matrix(vgrid,ncol=ncol(grid),byrow=TRUE)
     [,1] [,2] [,3] [,4]
[1,]    0    9    9    9
[2,]    0    9    9    0
[3,]    0    0    0    0
[4,]    0    0    0    0
nrussell
  • 18,382
  • 4
  • 47
  • 60
3

If you have a square matrix, you could write a little modulo function that replaces your numbers with the correct ones:

new_num <- function(x,num_rows){
  x = x - 1
  row    <- x %/% num_rows
  column <- x %% num_rows
  newnum <- column * num_rows + row + 1
  return(newnum)
}

temps <- c(9,9,9,9,9)     
locs <- c(2,3,4,6,7)

new_locs <- new_num(locs,4)

M <- matrix(0,4,4)
M[new_locs] <- temps

You can do this with a non-square matrix too, it's just a bit harder.

jed
  • 615
  • 3
  • 11
3

You could do some work with the indices. First we make a sequence the length of the matrix by the number of columns. Then we iteratively add 1 to the sequence. We do that for the number of rows. Then subsetting that vector for the location vector will give us the location in the matrix.

x <- seq(1, length(grid), ncol(grid))
grid[sapply(0:(nrow(grid)-1), "+", x)[locs]] <- temps
grid

#      [,1] [,2] [,3] [,4]
# [1,]    0    9    9    9
# [2,]    0    9    9    0
# [3,]    0    0    0    0
# [4,]    0    0    0    0
Rich Scriven
  • 97,041
  • 11
  • 181
  • 245