Iteration is core to many calculations. The use of iteration in R is common, but should be avoided whenever possible given vectorized methods that often achieve the same goal.

Iteration, or traditional looping, is a brute force approach to data management that is effective, but costly. Every time a large data set enters an iteration loop, a copy of the data is saved to disk. Thus, iteration consumes time and memory. R supports the following vectorized looping functions: apply(), lapply(), tapply(), sapply() and by(). More traditional functions for iteration in R are described below.

*The repeat() Statement*

*The repeat() Statement*

The repeat() statement is the simplest looping construction in R. It performs no tests, but simply repeats a given expression indefinitely. Because of this, the repeat() function expression must include an exit, typically using either a break() or return() statement. The syntax for repeat() is:

1 |
repeat { expression } |

The custom function below uses Newton’s method to find the positive, real j^{th} roots of a number. A test for convergence is included inside the loop and a break() statement is used to exit the loop.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Newton <- function(n, j = 2, x = 1) { # Use Newton’s method to find the positive, real jth root of n, # where the default is to find the square root of n or j = 2. # x is a seed value to start the search. old.x <- x repeat { # Update x new.x <- old.x - ((old.x^j - n)) / (j * old.x^(j - 1)) # Compute relative error as a 2-norm. conv <- sum((new.x - old.x)^2 / old.x^2) # Exit test if(conv < 1e-10) break # Save iteration result old.x <- new.x } return(new.x) } # Test the function Newton(36, 2, 4) [1] 6 |

We can replace the break statement inside the loop with a return() statement. This makes it clear what the returned value is and avoids the need for any statements outside the loop:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Newton2 <− function(n, j=2, x=1) { # Use Newton’s method to find the positive, real jth root of n, # where the default is to find the square root of n or j = 2. # x is a seed value to start the search. old.x <- x repeat { # Update x new.x <- old.x - ((old.x^j - n)) / (j * old.x^(j - 1)) # Compute relative error as a 2-norm. conv <- sum((new.x - old.x)^2 / old.x^2) # Exit test with return() statement if(conv < 1e-10) return(old.x) # Save interation result old.x <- new.x } } |

The modest change makes clear where the loop ends, but abrupt departures from a loop may be undesirable if additional code after the loop is required.

**The while() Statement**

**The while() Statement**

The while() statement is used to loop over an expression until a condition is false. The syntax is:

1 |
while(condition) {expression} |

For example, the function below returns a vector that corresponds to the binary representation of an integer.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
bit.string <- function(n) { tmp.string <- numeric(32) i <- 0 while(n > 0) { tmp.string[32 - i] <- n %% 2 # modula n <- n %/% 2 # integer divide i <- i + 1 } first.one <− match(1, tmp.string) return(tmp.string[first.one:32]) } # Test the function > bit.string(1) [1] 1 > bit.string(2) [1] 1 0 > bit.string(3) [1] 1 1 > bit.string(4) [1] 1 0 0 > bit.string(5) [1] 1 0 1 |

*Warning*: In practice, the while() statement will not handle vectorized results.

*The for() Statement*

*The for() Statement*

The basic syntax for the for() function in R is simple:

1 |
for(name in expression1) { expression2 } |

R evaluates expression2 once for each name in expression1 (e.g. a vector). For example:

1 |
for(i in 1:10) print(i) |

Note that braces aren’t required for one command.

There are certain situations in which for() loops may be necessary in R:

- when calculation i+1 in a vector depends on the result of the same and previous calculation.
- operations on list components, recognizing lapply() and sapply() perform looping implicitly and are more efficient.

For example:

1 2 3 4 |
> x <- NULL > for(i in 1:10) x[i] <- i > x [1] 1 2 3 4 5 6 7 8 9 10 |

To create a matrix using two for() loops, a null matrix of the correct dimensions must first be created:

1 2 3 4 5 6 7 8 9 10 11 12 |
x <- matrix(0, nrow = 3, ncol = 4) for(i in 1:3) { for(j in 1:4) { x[i,j] <- i+j } } > x [,1] [,2] [,3] [,4] [1,] 2 3 4 5 [2,] 3 4 5 6 [3,] 4 5 6 7 |

*Iteration vs. Vectorized Calculations*

*Iteration vs. Vectorized Calculations*

Programmers with experience in languages other than R are sometimes slow to exploit the power of R to do vectorized calculations. Vectorized methods operate on all elements in a vector simultaneously, rather than on individual components in sequence. The result is simple: faster delivery of results.

The example below determines the elapsed time of a for() loop to generate a large vector where each element is sequentially calculated as the incremental cumulative sum from 1 to N, where N = 10,000,000. The speed test is repeated using a vectorized function and compared to the results from iteration.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# Use for() to create vector of cumulative sums silly.csum <- function(N) { s <- vector("numeric", N) for (i in 1:N){ if(i == 1) s[i] <- 1 else s[i] <- s[i-1] + i; } return(s) } # Test the function silly.csum(10) [1] 1 3 6 10 15 21 28 36 45 55 # Speed test1 system.time(silly.csum(1e7)) user system elapsed 33.127 0.030 33.154 # speed test2 system.time(cumsum(as.numeric(1:1e7))) user system elapsed 0.100 0.019 0.119 |

The elapsed time of the vectorized calculation is so fast (just over 1/10^{th} of 1 second), it could repeat 278 times during one pass of the for() loop.

The functions apply(), tapply(), sapply(), and lapply() are core functions in base R and offer ways around loops. apply() is ideally suited for arrays. tapply() is well suited for factors and jagged arrays (where the dimensions of each element matrix vary). The functions sapply() and lapply() are used with vectors and lists respectively.

*Flow of Control Summary*

*Flow of Control Summary*

The following table summarizes constructions that support overriding and managing the normal flow of control:

Construction | Description |
---|---|

if(condition) {expression} | Evaluates condition. If true, evaluates expression. |

if(condition) {expression1} else {expression2} | Evaluates condition. True evaluates expr1; False, evaluates expr2. |

ifelse(condition, expression1, expression2) | Vectorized version of if statement. Evaluates condition and returns elements of expression1 for true and elements of expression2 for false. |

switch(expression, …) | Matches expression (a character or numeric value) to the remaining arguments and then executes the associated instruction(s). An alternative to multiple if/else statements. |

break | Terminates the current loop and passes control out of the loop. |

next | Terminates the current iteration of the loop and immediately starts the next iteration of a for, while or repeat loop. |

return (expression) | Terminates the current function and returns the value of expression. |

stop("message") | Signals an error by terminating evaluation of the current function, printing the character string in message, and returning to the > prompt. |

while(condition) {expression} | Evaluates condition. If true, evaluates expression then repeats the loop, evaluating condition again until condition is false. |

repeat {expression} | Simple version of while statement. No tests performed. expression is evaluated indefinitely until break, return or stop is encountered. |

for(name in expression1) {expression2} | Evaluates expression2 once for each name in expression1. for() loops are generally less efficient in R than vectorized calculations |