Stat 579: Writing Functions Ranjan Maitra 2220 Snedecor Hall Department of Statistics Iowa State University. Phone: 515-294-7757 maitra@iastate.edu , 1/13 Writing Functions One of the most important features of R is the ability to write and modify functions to perform computations for which built-in R functiona are not already available. General form of a R function is: function (arguments) expression Simple example – square a number: sqr <- function(x) x*x The function is called using: > sqr(2) > h APE BOX CAT DOG 15.1 11.3 7.0 9.0 > sqr(h) APE BOX CAT DOG 228.01 127.69 49.00 81.00 The function sqr is vectorized because the operation “*” is vectorized. It is important to make sure that all operations in a function definition are vectorized if the function is required to be vectorized. , 2/13 Writing Functions – II A function can take in multiple arguments: logbb <- function(x,b) log(x)/log(b) logbb(8,2) An example of a function using recursion: fact <- function(n) if (n<=1) 1 else n*fact(n-1) In recursion, a function may call itself recursively, and is a capability not available in some programming languages. In general, using a recursion might be less efficient than other methods such as using the prod() function to compute the factorial, depending on how recursion is implemented in a language. The above functions all used a single R expression. Usually, functions for performing many types of computations are more complicated and require a substantial number of R expressions. When more than a single R expression is needed they must all be enclosed in a pair of braces. , 3/13 Writing functions – III The function below, named larger1(), takes two vectors (assumed here to be of the same length) as arguments, compares them element by element, and returns a vector that consists of elements that are the larger in magnitude of the corresponding elements in the two vectors. larger1 <- function(x,y) { n <-length(x) z <- rep(n,0) for (i in 1:n) { if (y[i] > x[i]) z[i] <- y[i] else z[i] <- x[i] } z } The above is not particularly efficient, using, as it does, loops. So, we redo the same, avoiding loops: bigger <- function(x,y) { ifelse(y > x, y, x) } , 4/13 Returning Multiple Items In general, functions return single values, therefore multiple items are returned as (named) lists quad <- function(a, b, c) { x <- NA d <- bˆ2 - 4 * a * c if (d < 0) cat(‘‘*** Real roots do not exist! else x <- (-b + c(-1, 1) * sqrt(d)) / (2 * a) list(Root1 = x[1], Root2 = x[2]) } result <- quad(6, -5, 1) result$Root1 result$Root2 *** \n’’) Our next application will be to write functions to perform some iterative methods. For that, we will need some background. , 5/13 An Overview of Some Iterative Methods A variety of problems exist where the solution to a problem can be improved by applying the same calculation repeatedly, each time using the previous solution as the starting value. Such a mathematical process is said to form a convergent sequence, if the solution converges to a value such that the error becomes uniformly smaller in each iteration. The problem of finding the solution to the equation f (x) = 0 where f is a nonlinear function of a single variable x, is used in this class as an example of application of this method. Example: Find the root of f (x) = x 3 − 3x + 1 = 0 contained in the interval [0, 1] by an iterative method. There are several approaches to obtaining iterative algorithms for this problem. We look at a few of these methods next. , 6/13 The Bisection Algorithm The class of methods where only the value of the function at different points are calculated is known as search methods. The best known iterative search methods are called bracketing methods since they attempt to reduce the width of the interval where the root is located while keeping the successive iterates bracketed within that interval. The simplest bracketing method is the bisection algorithm where the interval of uncertainty is halved at each iteration, and from the two resulting intervals, the one that straddles the root is chosen as the next interval. Example: Consider the function f (x) to be continuous in the interval [x0 , x1 ] where x0 < x1 and that it is known that a root of f (x) = 0 exists in this interval. , 7/13 The Bisection Algorithm – Pseudo-code Select x , f , andstarting values x0 < x1 3 f (x0 ) ∗ f (x1 ) < 0 Repeat 1 2 3 set x2 = (x0 + x1 )/2 if f (x2 ) ∗ f (x0 ) < 0, set x1 = x2 else set x0 = x2 until |x1 − x0 | < x or |f (x2 )| < f Return x2 Stopping Rule: When the bracket is small enough or when the function value is close enough to zero. To use the bisection algorithm, it is easily verified by plotting the function that the f (x) = x 3 − 3x + 1 = 0 has a single root in the interval [0, 1]: > > > > , f <- function(x) { xˆ3 - 3 * x + 1} x <- seq(from = 0, to = 1, length = 100) plot(x = x, y = f(x), type="l") abline(h = 0, lty = 3) 8/13 The Bisection Algorithm bisection <- function(fun, lower, upper, epx = 1e-03, epf = 1e-03) { if (lower > upper) { x1 <- lower lower <- upper upper <- x1 } x0 <- lower x1 <- upper if ((fun(x0) * f(x1)) >= 0) { cat(’No root in interval. Come back with better brackets’) } else { repeat { x2 <- (x0 + x1) / 2 if (f(x2) * f(x0) < 0) { x1 <- x2 } else x0 <- x2 if ((abs(x1 - x0) < epx) | (abs(f(x2)) < epf)) break } x2 } } , 9/13 Fixed-point Iteration By re-expressing f (x) = 0 in the form x = g(x), one can obtain an iterative formula x(i+1) = g(x(i) ) for finding roots of nonlinear equations. Any such function g, called the mapping function, must satisfy conditions so that the sequence x(1) , x(2) , . . . converges to a unique solution in a specified interval [a, b]. To use the fixed point method for f (x) = x 3 − 3x + 1 = 0 , rewrite it in the form x = (x 3 + 1)/3 giving g(x) = (x 3 + 1)/3. It can be verified that this choice of g satisfies the necessary conditions for the iteration to produce a convergent sequence. To obtain a starting value, we use the plot of f (x) in the interval [0, 1]. The plot clearly shows that a root exists is (0.3, 0.4). In practice, several intervals may be searched for the existence of roots by trial and error, and is called a grid search. , 10/13 Fixed-point Iteration Algorithm: R function function(g, x0, nlim, eps) { iterno <- 0 repeat { iterno <- iterno + 1 if (iterno > nlim) { cat(’Iteration Limit Exceeded: Current = ’,iterno, fill = T) x1 <- NA break } else { x1 <- g(x0) cat(’****Iter. No: ’, iterno, ’ Current Iterate = ’, x1, fill = T) if (abs(x1 - x0) < eps || abs(x1 - g(x1)) < 1e-10) break x0 <- x1 } } return(x1) } > simple(g, 0.4, 100, 1e-8) , 11/13 Newton-Raphson Iteration Consider a starting value x(0) for obtaining an iterative formula to solve f (x) = 0. If the next iterate x(1) is very close to x(0) then f 0 (x(0) ) can be approximated as f 0 (x(0) ) ≈ f (x(0) ) x(0) −x(1) . f (x ) This leads to the update x(1) ≈ x(0) − f 0 (x(0) ) , which leads to (0) the Newton-Raphson iterative formula x(i+1) = x(i) − f (x(i) ) f 0 (x(i) ) Newton-Raphson iterations produces a convergent sequence as long as x(0) is chosen to be close to a root of f (x) = 0. This method has quadratic convergence, i.e., the error is reduced quadratically at each iteration. For f (x) = x 3 − 3x + 1, we have f 0 (x) = 3x 2 − 3. The Newton-Raphson iteration for solving x 3 − 3x + 1 = 0 is x(i+1) = x(i) − , 3 x(i) − 3x(i) + 1 2 −3 3x(i) 12/13 Newton-Raphson Iteration: R function newton <- function(fun, derf, x0, eps) { iter <- 0 repeat { iter <- iter + 1 x1 <- x0 - fun(x0) / derf(x0) if (abs(x0 - x1) < eps || abs(fun(x1)) < 1e-10) break x0 <- x1 cat(’****** Iter. No: ’, iter, ’ Current Iterate = ’, x1,fill=T) } return(x1) } > fun <- function(x){xˆ3 - 3*x + 1} > derf <- function(x) { 3*xˆ2 -3 } > newton(fun, derf, 0.4, eps = 0.000001) > fun2 <- function(x) {x*exp(-x) - 0.2} > x <- seq(from = -1, to = 4, length = 100) > plot(x,fun2(x),type="l") > abline(h=0,lty=2) # clear that there is a root near 0.0 and another near 2.0 > derf2 <- function(x) { (1 - x) * exp(-x) } > newton(fun2, derf2, 0.0, 0.000001) > newton(fun2, derf2, 2.0, 0.000001) , 13/13