Does R have a way to say "do n times" without having to write a for loop with a redundant variable?
Asked Answered
T

6

5

I recently wrote this line of code: for(i in seq_len(exponent)){out<-squareMat%*%out}. Clearly, i is never used and I just wanted to say "exponent times, do out<-squareMat%*%out". Was there a way to do this without the redundant counter i? For example, is there an apply family function for this?

Example - I have:

squareMat<-matrix(c(0,10,20,30),2,2)
out<-diag(nrow = nrow(squareMat))
exponent<-5
for(i in seq_len(exponent)){out<-squareMat%*%out}
out

What I want is:

squareMat<-matrix(c(0,10,20,30),2,2)
out<-diag(nrow = nrow(squareMat))
exponent<-5
[do exponent times]{out<-squareMat%*%out}
out
Thea answered 4/2, 2021 at 13:19 Comment(9)
There are a lot of ways to do things without doing for loops, but it depends a bit on object classes. It is a bit hard to understand exactly what you want to do. Could you please provide a reproducible example, with sample data? If we can run your code and see the output it's much easier to answer.Proudhon
@LeonardoViotti Better?Thea
Don't avoid for loops unless there are compelling reasons to do so.Celtic
@Celtic Readability and beauty are compelling for me. What cost would I be paying for them?Thea
not base R, but maybe check out CRAN.R-project.org/package=iteratorsCalcic
Loops are very elegant. Behind 1:2 + 3:4 is a for loop in C. There are sometimes where R vectorization makes for more concise code. There are other times when a simple loop can make more concise code. What’s more, your i counter is not redundant, it’s use allows for the actual calculation to happen!Graniela
Is your question about any loops, or about matrix power ? Because I see my proposal has been down voted, so should I understand that you don't really care about matrix ?Inopportune
@Inopportune Not my down vote, but the title should answer your question - I'm interested in the loops.Thea
I think avoiding loops is often possible, and often leads to less time for doing the same thing, but the way to do it is strongly related to what you are doing .. for exemple, my question here was a way to avoid a long an terribly annoying loop .. but you can see that the very insteresting answers I receive will be of no use in most of the cases ..Inopportune
M
7

Yes, it does. There exist several useful functions in base R that are not used that often these days. One of them does exactly what you want. The replicate function replicates an expression (expr) n times. It works as follows, let's say that I want to generate 3 different samples of size 5 from a uniform (0 to 1) distribution. It can be easily done using replicate. Take a look at the piece of code below

replicate(n = 3, expr = {runif(n = 5)})
#            [,1]      [,2]       [,3]
# [1,] 0.1944426 0.5158065 0.39892501
# [2,] 0.5676580 0.9940599 0.97385575
# [3,] 0.5570141 0.2274214 0.60239883
# [4,] 0.5074303 0.3526040 0.95445298
# [5,] 0.1931812 0.4593620 0.03283596

The results are automatically organized in an array (matrix in this case). However, you can set the parameter simplify = FALSE. Then, the return will be a list

replicate(n = 3, expr = {runif(n = 5)}, simplify = FALSE)
# [[1]]
# [1] 0.4694347 0.9559887 0.8110113 0.7528089 0.6639614
# 
# [[2]]
# [1] 0.8731027 0.7295846 0.3773571 0.5394776 0.6792322
# 
# [[3]]
# [1] 0.3463870 0.3776352 0.3895620 0.2166284 0.5065204

It is important to notice that each of these replications are independent of each other. If you want something to be replicated sequentially, you have to use either a for loop or another suitable function. There exists, for example, a function called rapply (recursive lapply). However, it has never been clear to me the best way to use it.

Margertmargery answered 4/2, 2021 at 13:54 Comment(4)
I've added an example to my question to make things more clear. Could variable assignment be done inside of replicate to remove the independence issue?Thea
replicate(exponent, out<<-squareMat%*%out) works, but I find the use of <<- worrisome.Thea
I do not think you can assign variables to a variable using this function. The <<- does the job but, as you said, this operator is dangerous. Thinking about what you said and your problem, the Reduce function might be more appropriate here. You can use: Reduce(f = function(x) squareMat %*% x, x = out, accumulate = TRUE). The problem with this approach is that it relies on squareMat from the global environment.Margertmargery
actually, the reduce in the previous comment is wrong. Try the following: Reduce(f = function(x) squareMat %*% x, x = replicate(n = seq_len(exponent), out, simplify = FALSE), accumulate = TRUE) . Alternatively, take a look at this responsehttps://mcmap.net/q/844333/-a-k-for-matrix-multiplication-in-r . I think it can be usefulMargertmargery
D
6

For a binary operation like %*% you can use Reduce, optionaly with the init argument (actually here not needed because init is identity matrix, see @nicola's comment, but makes answer more general) :

squareMat<-matrix(c(0,10,20,30),2,2)
exponent<-5
Reduce('%*%', init = diag(nrow = nrow(squareMat)), replicate(exponent, squareMat, simplify=F))
#>          [,1]     [,2]
#> [1,]  7800000 27800000
#> [2,] 13900000 49500000

This can be sped up avoiding the call to replicate by not using the second argument in the calculation (1:exponent only acts as a counter) :

Reduce(function(x,notused) {squareMat %*% x}, init = diag(nrow = nrow(squareMat)), 1:exponent)
#>          [,1]     [,2]
#> [1,]  7800000 27800000
#> [2,] 13900000 49500000

Note that Reduce stays in this case less efficient than a simple loop, so, interesting but probably not optimal:

microbenchmark::microbenchmark(test_reduce(),test_reduce2(),test_loop())
Unit: microseconds
           expr  min   lq   mean median    uq  max neval cld
  test_reduce() 17.2 17.7 21.461   18.0 18.60 97.4   100   c
 test_reduce2()  7.9  8.4  9.463    8.6  8.85 25.9   100  b 
    test_loop()  3.0  3.2  3.786    3.3  3.40 26.2   100 a  
Degauss answered 7/2, 2021 at 0:1 Comment(8)
To answer my question on how to avoid using a for loop with a redundant counter i, you've used Reduce with a redundant counter notused. Annoyingly clever!Thea
It was fun, but in this case I would stick to a loop ;)Degauss
Reduce("%*%", rep(list(squareMat),exponent)) is the way to go here.Tessi
@nicola, thanks, this makes solution 1 much leaner! However solution2 stays much faster because it avoids the replication.Degauss
Yeah, I said the "way to go" in the sense that it answers OP's question (iterate the calculation without an explicit loop). Speed-wise, of course Reduce is not the best and in this case one should use proper algorithms to do exponentiation of a matrix (if I recall correctly, there is a package called maybe expM with a function for this task).Tessi
@Tessi Is there any reason to prefer rep to an equivalent replicate?Thea
rep and replicate are not equivalent in any way. rep is meant to build a vector or list by replicating the elements of an input vector or list. replicate instead is used to run the same expression a fixed number of times, and it's often used to extract random numbers.Tessi
@Tessi You've missed my point. In this answer and its comments, there are some very similar methods, where their only major differences are that one uses rep and the other replicate. I'm asking if there's a reason to prefer one method to another.Thea
G
4

An alternative would be to use Recall() within a function call.

my_mat_power = function(n) {
  if (n == 1) 
    squareMat 
  else 
    Recall(n - 1) %*% squareMat
}

my_mat_power(5)

##          [,1]     [,2]
## [1,]  7800000 27800000
## [2,] 13900000 49500000

If you want to make your matrix a variable, you can make small modifications:

my_mat_power = function(n, mat) {
  if (n == 1) 
    mat 
  else 
    Recall(n - 1, mat) %*% mat
}
Graniela answered 8/2, 2021 at 0:43 Comment(2)
This is just plain and simple recursion, right?Thea
@J.Mini yes. I thought it was interesting and works for this particular case. Note, I think the answer to your overall question is simply replicate(n, fx). But to the question of how to recursively do functions, Reduce per Waldi or your approach in your question is probably easiest. In 3+ years of using R, this is my first time using Recall() so I would not likely actually use my answer unless there was a real performance pinchpoint.Graniela
S
2

Here's an example using sapply.

myfunc <- function(s) {
    return('hello')
}
    
sapply(seq_len(5), myfunc)
    
# [1] "hello" "hello" "hello" "hello" "hello"
Succeed answered 4/2, 2021 at 13:45 Comment(3)
Does that only work because myfunc does not use its argument?Thea
The function provided to sapply does require an argument but its use is optional.Succeed
FWIW, replicate is a wrapper for sapply.Graniela
I
1

let's have a look at this :

library(expm)
squareMat %^% 5
Inopportune answered 10/2, 2021 at 13:49 Comment(2)
This answer looks correct to me - perhaps further explanation would stop the downvotes and prevent it being deletedExteroceptor
@Exteroceptor thank you, It seems that the purpose was not really to find the matrix power, but a more general thing about loop ... so that's probably why I've been downvoted. For the answer about loops, I've nothing more to say that in my comment of the question.Inopportune
M
0

R has almost always a way to avaoid loops. In your case:

library(expm)
out <- (squareMat%^%exponent)%*%out
Madera answered 12/2, 2021 at 18:6 Comment(2)
Try that, then get back to me. squareMat^exponent does not do what you think it does.Thea
You're right, sorry. I just corrected it. Thanks!Misuse

© 2022 - 2024 — McMap. All rights reserved.