Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Loops in R
First, if you are new to programming, you should know that loops are a way to tell the computer that you want to repeat some operation for a number of times. This is a very common task that can be found in many programming languages. For example, let’s say you invited five friends for dinner at your home and the whole cost of four pizzas will be split evenly. Assume now that you must give instructions to a computer on calculating how much each one will pay at the end of dinner. For that, you need to sum up the individual tabs and divide by the number of people. Your instructions to the computer could be: start with a value of x=zero, take each individual pizza cost and sum it to x until all costs are processed, dividing the result by the number of friends at the end.
The great thing about loops is that the length of it is dynamically set. Using the previous example, if we had 500 friends (and a large dinner table!), we could use the same instructions for calculating the individual tabs. That means we can encapsulate a generic procedure for processing any given number of friends at dinner. With it, you have at your reach a tool for the execution of any sequential process. In other words, you are the boss of your computer and, as long as you can write it down clearly, you can set it to do any kind of repeated task for you.
Now, about the code, we could write the solution to the pizza problem in R as:
pizza.costs <- c(50, 80, 30, 60) # each cost of pizza n.friends <- 5 # number of friends x <- 0 # set first cost to zero for (i.cost in pizza.costs) { x <- x + i.cost # sum it up } x <- x/n.friends # divide for average per friend print(x) ## [1] 44
Don’t worry if you didn’t understand the code. We’ll get to the structure of a loop soon.
Back to our case, each friend would pay 44 for the meal. We can check
the result against function sum
:
x == sum(pizza.costs)/n.friends ## [1] TRUE
The output TRUE
shows that the results are equal.
The Structure of a Loop
Knowing how to use loops can be a powerful ally in a complex data related problem. Let’s talk more about how loops are defined in R. The structure of a loop in R follows:
for (i in i.vec){ ... }
In the previous code, command for
indicates the beginning of a loop.
Object i
in (i in i.vec)
is the iterator of the loop. This
iterator will change its value in each iteration, taking each individual
value contained in i.vec
. Note the loop is encapsulated by curly
braces ({}
). These are important, as they define where the loop
starts and where it ends. The indentation (use of bigger margins) is
also important for visual cues, but not necessary. Consider the
following practical example:
# set seq my.seq <- seq(-5,5) # do loop for (i in my.seq){ cat(paste('\nThe value of i is',i)) } ## ## The value of i is -5 ## The value of i is -4 ## The value of i is -3 ## The value of i is -2 ## The value of i is -1 ## The value of i is 0 ## The value of i is 1 ## The value of i is 2 ## The value of i is 3 ## The value of i is 4 ## The value of i is 5
In the code, we created a sequence from -5 to 5 and presented a text for
each element with the cat
function. Notice how we also broke the
prompt line with '\n'
. The loop starts with i=-5
, execute command
cat(paste('\nThe value of i is', -5))
, proceed to the next iteration
by setting i=-4
, rerun the cat
command, and so on. At its final
iteration, the value of i
is 5
.
The iterated sequence in the loop is not exclusive to numerical vectors. Any type of vector or list may be used. See next:
# set char vec my.char.vec <- letters[1:5] # loop it! for (i.char in my.char.vec){ cat(paste('\nThe value of i.char is', i.char)) } ## ## The value of i.char is a ## The value of i.char is b ## The value of i.char is c ## The value of i.char is d ## The value of i.char is e
The same goes for lists
:
# set list my.l <- list(x = 1:5, y = c('abc','dfg'), z = factor('A','B','C','D')) # loop list for (i.l in my.l){ cat(paste0('\nThe class of i.l is ', class(i.l), '. ')) cat(paste0('The number of elements is ', length(i.l), '.')) } ## ## The class of i.l is integer. The number of elements is 5. ## The class of i.l is character. The number of elements is 2. ## The class of i.l is factor. The number of elements is 1.
In the definition of loops, the iterator does not have to be the only object incremented in each iteration. We can create other objects and increment them using a simple sum operation. See next:
# set vec and iterators my.vec <- seq(1:5) my.x <- 5 my.z <- 10 for (i in my.vec){ # iterate "manually" my.x <- my.x + 1 my.z <- my.z + 2 cat('\nValue of i = ', i, ' | Value of my.x = ', my.x, ' | Value of my.z = ', my.z) } ## ## Value of i = 1 | Value of my.x = 6 | Value of my.z = 12 ## Value of i = 2 | Value of my.x = 7 | Value of my.z = 14 ## Value of i = 3 | Value of my.x = 8 | Value of my.z = 16 ## Value of i = 4 | Value of my.x = 9 | Value of my.z = 18 ## Value of i = 5 | Value of my.x = 10 | Value of my.z = 20
Using nested loops, that is, a loop inside of another loop is also possible. See the following example, where we present all the elements of a matrix:
# set matrix my.mat <- matrix(1:9, nrow = 3) # loop all values of matrix for (i in seq(1,nrow(my.mat))){ for (j in seq(1,ncol(my.mat))){ cat(paste0('\nElement [', i, ', ', j, '] = ', my.mat[i,j])) } } ## ## Element [1, 1] = 1 ## Element [1, 2] = 4 ## Element [1, 3] = 7 ## Element [2, 1] = 2 ## Element [2, 2] = 5 ## Element [2, 3] = 8 ## Element [3, 1] = 3 ## Element [3, 2] = 6 ## Element [3, 3] = 9
A Real World Example
Now, the computational needs of the real world is far more complex than
dividing a dinner expense. A practical example of using loops is
processing data according to groups. Using an example from Finance, if
we have a return dataset for several stocks and we want to calculate the
average return of each stock, we can use a loop for that. In this
example, we will use Yahoo Finance data from three stocks: FB, GE and
AA. The first step is downloading it with package BatchGetSymbols
.
library(BatchGetSymbols) ## Loading required package: rvest ## Loading required package: xml2 ## Loading required package: dplyr ## ## Attaching package: 'dplyr' ## The following objects are masked from 'package:stats': ## ## filter, lag ## The following objects are masked from 'package:base': ## ## intersect, setdiff, setequal, union ## my.tickers <- c('FB', 'GE', 'AA') df.stocks <- BatchGetSymbols(tickers = my.tickers, first.date = '2012-01-01', freq.data = 'yearly')[[2]] ## ## Running BatchGetSymbols for: ## tickers = FB, GE, AA ## Downloading data for benchmark ticker | Found cache file ## FB | yahoo (1|3) | Found cache file - Good job! ## GE | yahoo (2|3) | Found cache file - Nice! ## AA | yahoo (3|3) | Found cache file - You got it!
It worked fine. Let’s check the contents of the dataframe:
dplyr::glimpse(df.stocks) ## Observations: 21 ## Variables: 10 ## $ ticker <chr> "AA", "AA", "AA", "AA", "AA", "AA", "AA", ... ## $ ref.date <date> 2012-01-03, 2013-01-02, 2014-01-02, 2015-... ## $ volume <dbl> 2217410500, 2149575500, 2146821400, 268355... ## $ price.open <dbl> 21.48282, 21.33864, 25.30359, 38.13561, 22... ## $ price.high <dbl> 25.85628, 25.68807, 42.29280, 41.01921, 32... ## $ price.low <dbl> 19.27206, 18.50310, 24.27030, 18.79146, 16... ## $ price.close <dbl> 22.17969, 21.60297, 25.30359, 38.15964, 23... ## $ price.adjusted <dbl> 20.89342, 20.62187, 24.48568, 37.24207, 23... ## $ ret.adjusted.prices <dbl> NA, -0.01299715, 0.18736494, 0.52097326, -... ## $ ret.closing.prices <dbl> NA, -0.02600212, 0.17130149, 0.50807215, -...
All financial data is there. Notice that the return series is available at column ret.adjusted.prices.
Now we will use a loop to build a table with the mean return of each stock:
# find unique tickers in column ticker unique.tickers <- unique(df.stocks$ticker) # create empty df tab.out <- data.frame() # loop tickers for (i.ticker in unique.tickers){ # create temp df with ticker i.ticker temp <- df.stocks[df.stocks$ticker==i.ticker, ] # row bind i.ticker and mean.ret tab.out <- rbind(tab.out, data.frame(ticker = i.ticker, mean.ret = mean(temp$ret.adjusted.prices, na.rm = TRUE))) } # print result print(tab.out) ## ticker mean.ret ## 1 AA 0.24663684 ## 2 FB 0.35315566 ## 3 GE 0.06784693
In the code, we used function unique
to find out the names of all the
tickers in the dataset. Soon after, we create an empty dataframe to
save the results and a loop to filter the data of each stock
sequentially and average its returns. At the end of the loop, we use
function rbind
to paste the results of each stock with the results of
the main table. As you can see, we can use the data to perform group
calculations with loop.
By now, I must be forward in saying that the previous loop is by no
means the best way of performing the data operation. What we just did by
loops is called a split-apply-combine procedure. There are base
function in R such as tapply
, split
and lapply
/sapply
that can
do the same job but with a more intuitive and functional approach. Going
further, functions from package tidyverse
can do the same procuedure
with an even more intuitive approach. In a future post I shall discuss
this possibilities further.
I hope you guys liked the post. Got a question? Just drop it at the comment section.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.