3.4 More About Arguments

3.4.1 Default Arguments

Sometime around the year 1400CE, the South Indian mathematician Madhava discovered the following infinite-series formula for \(\pi\), the ratio of the circumference to the diameter of a circle:

\[\pi = \frac{4}{1} - \frac{4}{3} + \frac{4}{5} - \frac{4}{7} +\frac{4}{9} - \cdots\] The numerator of each fraction is always 4. The denominators are the odd numbers 1, 3, 5, and so on. The fractions alternate between positive and negative. The idea is that the further you go out in the series, the closer the sum of the fractions will be to \(\pi\). No matter how close you want to get to \(\pi\), you can get that close by adding up sufficiently many of the fractions.

In mathematics courses we learn to write the sum like this:

\[\pi = \sum_{k=1}^{k=\infty} (-1)^{k+1}\frac{4}{2k-1}.\] Here’s how the mathematical notation works:

  • The \(\Sigma\) sign stands for “sum”: it means that we plan to add up a lot of terms.
  • The expression \((-1)^k\frac{4}{2k-1}\) after the sum-sign stands for all of the terms that will be added up to make the infinite series.
  • Underneath the sum-sign, \(k=1\) says that in the expression after the sum sign we will start by letting \(k\) be 1.
  • If we let \(k=1\), then the expression becomes \[(-1)^2\frac{4}{2 \cdot 1 -1} = \frac{4}{1} = 4,\] the first term in the series.
  • If we let \(k=2\), then the expression becomes \[(-1)^3\frac{4}{2 \cdot 2 -1} = -\frac{4}{3}\] the second term in the series.
  • If we let \(k=3\), then the expression becomes \[(-1)^4\frac{4}{2 \cdot 3 -1} = \frac{4}{5}\] the third term in the series.
  • The \(k = \infty\) above the sum-sign says that we are to keep on going like this, increasing \(k\) by 1 every time, without stopping.
  • In this way we get the entire infinite series.

What Madhava discovered was that if you do stop after some large number of terms, then the sum of the terms you have constructed will be close to \(\pi\). The more terms you add up before stopping, the closer to \(\pi\) you will get.

Let’s write a function to compute the sum of the first \(n\) terms of the series, where \(n\) can be any value we choose.

# function to approximate pi with Madhava's series
# (sum the first n terms of the series)
madhavaPI <- function(n) {
  # make a vector of all of the k's we need:
  k <- 1:n
  # make a vector of the first n terms of the sum:
  terms <- (-1)^(k+1)*4/(2*k-1)
  # return the sum of the terms:
  sum(terms)
}

R’s vectorization capabilities make it easy to write the code; in fact, the code is essentially a copy of the sum-formula.

Note also the presence of comments in the code above. Anything that appears on a line after the pound-sign # will be ignored by R. We therefore use the #-sign to insert ordinary-language comments into our code in order to explain to others (and to ourselves when we look at the code much later) what we are doing and why we are doing it.

Let’s try it out by adding the first million terms of the series:

madhavaPI(1000000)
## [1] 3.141592

How close is this to \(\pi\)? R has a built-in constant pi that will tell us:

pi
## [1] 3.141593

Madhava’s approximation was pretty close, although we did have to add up quite a few terms!

The madhavaPI() function has a parameter n that stands for the number of terms we want to add up. If we don’t provide a value for n, then there will be an error:

madhavaPI()
## Error in madhavaPI() : argument "n" is missing, with no default

There is a way, though, to allow a user to avoid providing a value for n. We simply provide a default value for it, like this:

madhavaPI <- function(n = 1000000) {
  k <- 1:n
  terms <- (-1)^(k+1)*4/(2*k-1)
  sum(terms)
}

Now we can call the function without having to specify an argument for the parameter n:

madhavaPI()
## [1] 3.141592

The function used the default value of 1000000, so it summed the first million terms.

On the other hand, if the user provides his or her own value for n then the function will override the default:

madhavaPI(100)   # only the first hundred terms, please!
## [1] 3.131593

If you are writing a function for which some of the parameters will often be assigned particular values, it would be a kindness to your users to write in these common values as defaults.

3.4.2 Argument-Matching

Sometimes you will see functions in R that provide what appears to be a vector of default values. You’ll see this in the following example, which concerns a function that uses a named vector to report the favorite color of each of the major groups of inhabitants of the Land of Oz.

inhabitants <- c("Munchkin", "Winkie", 
            "Quadling", "Gillikin")
favColor <- c("blue", "yellow", "red", "purple")
names(favColor) <- inhabitants

favColorReport <- function(inhabitant = inhabitants) {
  x <- match.arg(inhabitant)
  cat(favColor[x],"\n")
}

Here are a couple of sample calls:

favColorReport("Winkie")
## yellow
favColorReport("Quadling")
## red

It might get tiresome to type out the full name of each group of inhabitants. What would happen if we got a bit lazy?

favColorReport("Win")
## yellow
favColorReport("Wi")
## yellow
favColorReport("W")
## yellow
favColorReport("Qua")
## red
favColorReport("Gil")
## purple

The key to this behavior is two-fold:

  • The vector inhabitants was set as the “default value” of the parameter inhabitant.
  • We called the match.arg() function, which found the element of inhabitants that matched what the user actually submitted for the parameter inhabitant. This element was then assigned to x, and we used x to report the desired color.

Sometimes when you look at R-help you’ll see the default value for a parameter set as a vector—usually a character-vector, as in our example. Most likely what is going on is that somewhere inside the function R will attempt to match the argument you provide with one of the elements of that “default” vector. When a function is written in this way, the possible parameters can have quite long names but the user doesn’t have to type them in all the way, as long as the user types enough characters to pick out uniquely an element of the default vector.

The matching is done by exact match of initial characters. For example, it won’t do for me to enter:

favColorReport("w")  # wants Winkies, but using lower-case w
## Error in match.arg(inhabitant) : 'arg' should be one of 
## “Munchkin”, “Winkie”, “Quadling”, “Gillikin”

Note that the default vector isn’t really a default value for the parameter. Its first element does, however, serve as the default value:

favColorReport()  # defaults to "Munchkin"
## blue

3.4.3 Practice Exercises

  1. Write a function called addSix() that adds six to any number that is is given. The function should have a single parameter named x, and the default value of x should be 5.

  2. Tin Man wants to write a function called addTenAndSquare() that takes any given number, adds ten to it and then squares the result. For example, given 5 the function will return \((5 + 10)^2 = 225\). He decides that input value shall be called n and that the default value of n shall be 4. Here is Tin Man’s code:

    addTenAndSquare <- function(n) {
      n <- 4
      (n + 10)^2
    }

    Has Tin Man accomplished what he set out to do? Why or hy not?

3.4.4 Solutions to the Practice Exercises

  1. Here’s the function:

    addSix <- function(x = 5) {
      x + 6
    }

    Let’s test it:

    addSix()
    ## [1] 11
    addSix(x = 10)
    ## [1] 16
  2. Tin Man’s function does not work correctly. Consider the following calls:

    addTenAndSquare(2)  # should give (2 + 10)^ 2 = 144
    ## [1] 196
    addTenAndSquare(5)  # should give (5 + 10)^ 2 = 225
    ## [1] 196

    The function only returns \((4 + 10)^2\),no matter what it is given! The problem is that n is set to 4 within the body of the function. The correct way to define the function would be:

    addTenAndSquare <- function(n = 4) {
      (n + 10)^2
    }

    This works correctly:

    addTenAndSquare()
    ## [1] 196
    addTenAndSquare(2)
    ## [1] 144