Create a matrix with gradually changing values without a for loop

292 views Asked by At

In R, I am building a matrix M that stores 4 changing weights for each of 20 rounds of a game.

I start with the weight vector W1 (4x1) and end game with weight vector W2 (4x1).

The weight values remain constant until the change initiation round C1 (=5).

After the round C1 the weights start mutating by equal increments w2c and w3c over C (=10) rounds.

The weights have to always add to 1.

I used a for loop to build a matrix M. The result looks like this:

      [,1] [,2] [,3] [,4]
 [1,] 0.30 0.50 0.10 0.10
 [2,] 0.30 0.50 0.10 0.10
 [3,] 0.30 0.50 0.10 0.10
 [4,] 0.30 0.50 0.10 0.10
 [5,] 0.30 0.50 0.10 0.10
 [6,] 0.28 0.46 0.12 0.14
 [7,] 0.26 0.42 0.14 0.18
 [8,] 0.24 0.38 0.16 0.22
 [9,] 0.22 0.34 0.18 0.26
[10,] 0.20 0.30 0.20 0.30
[11,] 0.18 0.26 0.22 0.34
[12,] 0.16 0.22 0.24 0.38
[13,] 0.14 0.18 0.26 0.42
[14,] 0.12 0.14 0.28 0.46
[15,] 0.10 0.10 0.30 0.50
[16,] 0.10 0.10 0.30 0.50
[17,] 0.10 0.10 0.30 0.50
[18,] 0.10 0.10 0.30 0.50
[19,] 0.10 0.10 0.30 0.50
[20,] 0.10 0.10 0.30 0.50

Here is my R code:

Inputs:

N=4 # 4 number of weights
R=20 #rounds
M <- matrix(0, ncol=N, nrow=R) 
C=10 # change duration in rounds
C1=5 # round at which change starts
C2=C1+C # round at which change ends
w1=0.1 #small weight
w2=0.3 #average weight
w3=0.5 #large weight
w2c=(w2-w1)/C #change increment for average weight mutation
w3c=(w3-w1)/C #change increment for large weight mutation
W1<-c(w2, w3, w1, w1) #starting weight vector
W2<-c(w1, w1, w2, w3) #ending weight vector 

For loop, building the matrix M:

for(n in 1:N){
  M[,n]<-W1[n]
  for(r in 1:R) {
    if(r>=C2){M[r,n] <- W2[n]}
  }
}

for(r in C1:(C2-1)) {
  T<-r-C1;
  M[r,1] =  M[r,1]-w2c*T;
  M[r,2] =  M[r,2]-w3c*T;
  M[r,3] =  M[r,3]+w2c*T;
  M[r,4] =  M[r,4]+w3c*T;
}
M

Is there a more efficient way to achieve the same result without a for loop?

2

There are 2 answers

1
flodel On BEST ANSWER

Three calls to fill the top, middle, and bottom respectively:

M[1:C1,  ] <- rep(W1, each = C1)
M[C1:C2, ] <- mapply(seq, from = W1, to = W2, length = C2 - C1 + 1)
M[C2:R,  ] <- rep(W2, each = R - C2 + 1)

If like me you like code that is very symmetric (or consistent), you can generalize and say that the top and bottom parts are like the middle one: they are linearly moving from W1 to W1 and from W2 to W2 respectively. Hence:

M[1:C1,  ] <- mapply(seq, from = W1, to = W1, length = C1 -  1 + 1)
M[C1:C2, ] <- mapply(seq, from = W1, to = W2, length = C2 - C1 + 1)
M[C2:R,  ] <- mapply(seq, from = W2, to = W2, length =  R - C2 + 1)
3
jbaums On

You can do a vectorised multiplication of your weights by the number of change time steps, and then add these vectors to the pre-change values (e.g. with sweep).

x <- rbind(c(0.30, 0.50, 0.10, 0.10))
M <- rbind(x[rep(1, C1), ],
           sweep( sapply(c(-w2c, -w3c, w2c, w3c), '*', seq_len(C)),
                    2, x, '+'))
M <- rbind(M, M[ nrow(M), , drop=FALSE][ rep(1, R-C1-C), ])

#       [,1] [,2] [,3] [,4]
#  [1,] 0.30 0.50 0.10 0.10
#  [2,] 0.30 0.50 0.10 0.10
#  [3,] 0.30 0.50 0.10 0.10
#  [4,] 0.30 0.50 0.10 0.10
#  [5,] 0.30 0.50 0.10 0.10
#  [6,] 0.28 0.46 0.12 0.14
#  [7,] 0.26 0.42 0.14 0.18
#  [8,] 0.24 0.38 0.16 0.22
#  [9,] 0.22 0.34 0.18 0.26
# [10,] 0.20 0.30 0.20 0.30
# [11,] 0.18 0.26 0.22 0.34
# [12,] 0.16 0.22 0.24 0.38
# [13,] 0.14 0.18 0.26 0.42
# [14,] 0.12 0.14 0.28 0.46
# [15,] 0.10 0.10 0.30 0.50
# [16,] 0.10 0.10 0.30 0.50
# [17,] 0.10 0.10 0.30 0.50
# [18,] 0.10 0.10 0.30 0.50
# [19,] 0.10 0.10 0.30 0.50
# [20,] 0.10 0.10 0.30 0.50