# fahrenheit degrees
<- 100
far_deg
# convert to celsius
- 32) * (5/9) (far_deg
[1] 37.77778
Stat 133
The goal of this module is to give you practice writing very simple functions. The following exercises are mostly designed for those of you without any programming experience. If you have previous programming experience, you can move to the next module.
To define a new function in R you use the function function()
. Actually, you need to specify a name for the function, and then assign function()
to the chosen name. You also need to define optional arguments (i.e. inputs). And of course, you must write the code (i.e. the body) so the function does something when you use it:
# anatomy of a function
<- function(arguments) {
some_name # body of the function
}
Let’s consider a typical programming example that involves converting fahrenheit degrees into celsius degrees. The conversion formula is \((F - 32) \times 5/9 = C\). Here’s some R code to convert 100 fahrenheit degrees into Celsius degrees:
# fahrenheit degrees
<- 100
far_deg
# convert to celsius
- 32) * (5/9) (far_deg
[1] 37.77778
What if you want to convert 90 fahrenheit degrees in Celsius degrees? One option would be to rewrite the previous lines as:
# fahrenheit degrees
<- 90
far_deg
# convert to celsius
- 32) * (5/9) (far_deg
[1] 32.22222
However, retyping many lines of code can be very boring, tedious, and inefficient. To make your code reusable in a more efficient manner, you will have to write functions.
So, how do you create a function? The first step is to write code and make sure that it works. In this case we already have the code that converts a number in Fahrenheit units into Celsius.
The next step is to encapsulate the code in the form of a function. You have to choose a name, some argument(s), and determine the output. Here’s one example with a function fahrenheit_to_celsius()
<- function(x) {
fahrenheit_to_celsius - 32) * (5/9)
(x
}
fahrenheit_to_celsius(100)
[1] 37.77778
If you want to get the conversion of 90 fahrenheit degrees, you just simply execute it again by changing its argument:
fahrenheit_to_celsius(90)
[1] 32.22222
And because we are using arithmetic operators (i.e. multiplication, subtraction, division), the function is also vectorized:
fahrenheit_to_celsius(c(90, 100, 110))
[1] 32.22222 37.77778 43.33333
Sometimes it is recommended to add a default value to one (or more) of the arguments. In this case, we can give a default value of x = 1
. When the user executes the function without any input, fahrenheit_to_celsius
returns the value of 1 fahrenheit degree to Celsius degrees:
<- function(x = 1) {
fahrenheit_to_celsius - 32) * (5/9)
(x
}
# default execution
fahrenheit_to_celsius()
[1] -17.22222
Write a function miles_to_kms()
that converts miles into kilometers: 1 mile is equal to 1.6 kilometers. Give the argument a default value of 1.
= function(x = 1) {
miles_to_kms * 1.6
x }
Write a function gallon_to_liters()
that converts gallons to liters: 1 gallon is equal to 3.78541 liters:
= function(x = 1) {
gallon_to_liters * 3.78541
x }
According to Wikipedia, in 2015 the life expectancy of a person born in the US was 79 years. Consider the following question: Can a newborn baby in USA expect to live for one billion (\(10^9\)) seconds?
To answer this question, write a function seconds2years()
that takes a number in seconds and returns the equivalent number of years. Assume a year with 365 days. Test the function with seconds2years(1000000000)
= function(x = 1) {
seconds2years / (365 * 24 * 60 * 60)
x
}
seconds2years(1000000000)
Consider the following mathematical functions:
\[ f(x) = x^2, \quad g(x) = 2x + 5 \]
f()
Write a function f()
based on the above equation.
= function(x = 1) {
f ^2
x }
Test your function with:
f(2)
f(-5)
g()
Write a function g()
based on the above equation.
= function(x = 1) {
g 2*x + 5
}
Test your function with:
g(0)
g(-5/2)
fog()
Write code to create the composite function fog()
: \(f \circ g(x)\)
= function(x = 1) {
fog f(g(x))
}
Test your composite function with:
fog(2)
fog(-5)
gof()
Write code to create the composite function gof()
: \(g \circ f(x)\)
= function(x = 1) {
gof g(f(x))
}
Test your composite function with:
gof(0)
gof(-5/2)
In this problem we want to see whether the graph of a given polynomial will cross or touch the x axis in a given interval.
Let’s begin with the polynomial: \(f(x) = x^2 (x -1)\). The first thing to do is write a function for the polynomial, for instance:
<- function(x) {
poly1 ^2) * (x - 1)
(x }
Once you have a function for the polynomial, you can create a set of pairs of points \(x\) and \(y = f(x)\), and then use them to graph the polynomial
# set of points
<- seq(-4, 4, length.out = 30)
x <- poly1(x)
y
# graph polynomial
plot(x, y, type = 'l', lwd = 3, col = "#FB7215", las = 1)
abline(h = 0, v = 0, col = '#888888aa', lwd = 1.5)
title(main = expression(paste(f(x), ' = ', x^2, (x - 1))))
Write functions and graph the following polynomials in the x-axis interval -4 to 4:
<- function(x) {
poly2 ^3
x
}
<- seq(-4, 4, length.out = 30)
x <- poly2(x)
y
# graph polynomial
plot(x, y, type = 'l', lwd = 3, col = "#FB7215", las = 1)
abline(h = 0, v = 0, col = '#888888aa', lwd = 1.5)
title(main = expression(paste(f(x), ' = ', x^3)))
<- function(x) {
poly3 ^2 - 1) * (x + 3)^3
(x
}
<- seq(-4, 4, length.out = 30)
x <- poly3(x)
y
# graph polynomial
plot(x, y, type = 'l', lwd = 3, col = "#FB7215", las = 1)
abline(h = 0, v = 0, col = '#888888aa', lwd = 1.5)
title(main = expression(paste(f(x), ' = ', (x^2 - 1), (x + 3)^3)))
<- function(x) {
poly4 ^2 - 1) * (x^2 - 9)
(x
}
<- seq(-4, 4, length.out = 30)
x <- poly4(x)
y
# graph polynomial
plot(x, y, type = 'l', lwd = 3, col = "#FB7215", las = 1)
abline(h = 0, v = 0, col = '#888888aa', lwd = 1.5)
title(main = expression(paste(f(x), ' = ', (x^2 - 1), (x^2 - 9))))
The Pythagoras formula is used to compute the length of the hypotenuse, \(h\), of a right triangle with legs of length \(a\) and \(b\).
\[ h = \sqrt{(a^2 + b^2)} \]
pythagoras()
that takes two arguments a
and b
, and returns the length of the hypotenuse. Test your pythagoras()
with two leg values: pythagoras(3, 4)
<- function(a, b) {
pythagoras sqrt(a^2 + b^2)
}
pythagoras()
in a way that it will accept one or two leg lengths. If only one leg length is specified, this will be used for both legs. In other words, you should be able to invoke the function with just one leg value: pythagoras(5)
<- function(a, b = a) {
pythagoras sqrt(a^2 + b^2)
}
You can use the summary()
function with a numeric vector to obtain descriptive statistics:
set.seed(345)
<- rnorm(100)
x1 summary(x1)
Min. 1st Qu. Median Mean 3rd Qu. Max.
-2.58206 -0.77032 -0.25206 -0.07596 0.64208 2.81828
We can write a function to compute the first quartile of a numeric vector; this can be easily done with the advantage of the quantile()
function:
<- function(x) {
quartile1 quantile(x, probs = 0.25)
}
# test it
quartile1(mtcars$wt)
25%
2.58125
na.rm
Many functions that work on vectors have a special argument: na.rm
. This parameter is a logical value to indicate whether NAs should be removed or not. Because the quantile()
function does come with the na.rm
argument, we can take advantage of it and pass it to our quartile1()
function:
<- function(x, na.rm = FALSE) {
quartile1 quantile(x, probs = 0.25, na.rm = na.rm)
}
Let’s get the weight of cars and add some missing values:
= mtcars$wt
weight_na c(1, 10, 20)] = NA weight_na[
If you apply quartile1()
on weight_na
using the default call, you will get an error:
quartile1(weight_na)
Error in quantile.default(x, probs = 0.25, na.rm = na.rm): missing values and NaN's not allowed if 'na.rm' is FALSE
To remove missing values, you can use na.rm = TRUE
:
quartile1(weight_na, na.rm = TRUE)
25%
2.77
quartiles()
As you know, there are three quartile values—lower quartile, median, and upper quartile—to divide the data set into four ranges, each containing 25% of the data points.
Write a function quartiles()
that computes the three quartiles of a numeric vector. BTW: take advantage of quantile()
’s vectorization behavior. Include an argument na.rm
to decide whether missing values should be removed. Give a default value of na.rm = FALSE
.
Test quartiles()
on vectors mtcars$wt
and weight_na
.
<- function(x = 1, na.rm = FALSE) {
quartiles quantile(x, probs = c(0.25, 0.50, 0.75), na.rm = na.rm)
}
The Gaussian (Normal) function, given in the equation below, is one of the most widely used functions in science and statistics:
\[ f(x) = \frac{1}{\sqrt{2 \pi} \ s} \ \exp \left \{ -\frac{1}{2} \left (\frac{x - m}{s} \right)^2 \right \} \]
The parameters \(s\) and \(m\) are real numbers, where \(s\) must be greater than zero.
Make a function gaussian()
that takes three arguments: x
, m
, and s
. Evaluate the function with \(m = 0\), \(s = 2\), and \(x = 1\).
<- function(x = 1, m = 0, s = 1) {
gaussian = 1 / sqrt(2*pi)
constant = exp(-0.5 * ((x-m)/s)^2)
exponent = constant * (1/s) * exponent
output
output }
Test your gaussian()
function and compare it with the R function dnorm()
# compare with dnorm()
dnorm(x = 1, mean = 0, sd = 2)
[1] 0.1760327
Now try gaussian()
with a vector seq(-4.5, 4.5, by = 0.1)
, and pass the values to plot()
to get a normal curve. Here’s code with values obtained from dnorm()
# you should get a plot like this one
<- seq(-4.5, 4.5, by = 0.1)
x_values <- dnorm(x_values, mean = 0, sd = 2)
y_values plot(x_values, y_values, las = 1, type = "l", lwd = 2)
Your turn:
<- seq(-4.5, 4.5, by = 0.1)
x_values <- gaussian(x_values, m = 0, s = 2)
y_values plot(x_values, y_values, las = 1, type = "l", lwd = 2)