Gaston Sanchez
- Learn how to write simple functions
- Get into the habit of writing simple functions
- Get into the habit of documenting functions
- Make sure your functions work
- Use conditionals if-then-else
- Write your descriptions, explanations, and code in an
Rmd
(R markdown) file. - Name this file as
lab07-first-last.Rmd
, wherefirst
andlast
are your first and last names (e.g.lab07-gaston-sanchez.Rmd
). - Knit your
Rmd
file as an html document (default option). - Submit your
Rmd
andhtml
files to bCourses, in the corresponding lab assignment. - Due date displayed in the syllabus (see github repo).
In this lab you will practice writing simple functions, and some basic examples to make sure that the functions work as expected. Later in the course we will see how to write assertions (i.e. tests) for your functions in a more formal way.
In addition to writing the functions, you should also practice documenting your functions. Writing this type of documentation should become second nature. To do this, include roxygen comments such as:
#' @title Name of your function
#' @description What the function does
#' @param x input(s) of your function
#' @return output of your function
If you use an Rmd
file to write narrative and code for this practice,
you must include a code chunk at the top of your file like the one in
the following screen
capture:
By setting the global option error = TRUE
you avoid the knitting
process to be stopped in case a code chunk generates an error.
Since you willl be writing a couple of functions with stop()
statements, it is essential that you set up error = TRUE
, otherwise
"knitr"
will stop knitting your Rmd
file if it encounters an error.
Here’s an example of a function rect_area()
that computes the area of
a rectangle. Notice the structure of the code below: the first lines
correspond to the documentation of the function using Roxygen comments;
and then you have the actual code of the function rect_area()
:
#' @title area of rectangle
#' @description calculates the area of a rectangle
#' @param len length of the rectangle (numeric)
#' @param wid width of the rectangle (numeric)
#' @return computed area
rect_area <- function(len = 1, wid = 1) {
if (len < 0) {
stop("len must be positive")
}
if (wid < 0) {
stop("wid must be positive")
}
area <- len * wid
return(area)
}
Once the function has been created, we can test it with a couple of basic examples:
# default
rect_area()
## [1] 1
# len=2, wid=3
rect_area(len = 2, wid = 3)
## [1] 6
# bad len
rect_area(len = -2, wid = 3)
## Error in rect_area(len = -2, wid = 3): len must be positive
Consider the following mathematical functions:
- (f(x) = x^2)
- (g(x) = 2x + 5)
Write two functions f()
and g()
based on the previous equations.
Don’t forget to include roxygen comments to document your function!
Test your functions with:
f(2) # 4
f(-5) # 25
g(0) # 5
g(-5/2) # 0
Use your functions f()
and g()
to create the following composite
functions:
fog()
for the composite function: (f \circ g(x))gof()
for the composite function: (g \circ f(x))
Test your composite functions with:
fog(2) # 81
fog(-5) # 25
gof(0) # 5
gof(-5/2) # 17.5
The pythagoras formula is used to compute the length of the hypotenuse, (c), of a right triangle with legs of length (a) and (b).
Write a function pythagoras()
that takes two arguments a
and b
,
and returns the length of the hypotenuse. Don’t forget to include
roxygen comments to document your function!
Test your pythagoras()
with two leg values: pythagoras(3, 4)
Modify your function pythagoras()
so that argument b
takes the same
value of argument a
. Test it with just one leg value: pythagoras(5)
Consider a circle with radius = 2
. The area of this circle can be
computed in R as:
# area of circle with radius 2
r <- 2
area <- pi * r^2
area
## [1] 12.56637
Write a function circle_area()
that calculates the area of a circle.
This function must take one argument radius
. Give radius
a default
value of 1. Don’t forget to include roxygen comments to document your
function!
For example:
# default (radius 1)
circle_area()
# radius 3
circle_area(radius = 3)
Modify your circle_area()
function in order to include a stop()
statement. If radius
is negative, then the function should stop with a
message like: "radius cannot be negative"
.
Test your modified circle_area()
with radius = -2
; the function
should return a stop message:
# bad radius
circle_area(radius = -2)
For a given cylinder of radius (r) and height (h) the surface area (A) is given in the following diagram:
For example. Say you have a cylinder with radius = 2, and height = 3.
# cylinder variables
r = 2 # radius
h = 3 # height
# area of cylinder
2 * pi * r * h + 2 * pi * r^2
## [1] 62.83185
Notice that the formula of the area of a cylinder includes the area of a
circle: (\pi r^2). Write a function cylinder_area()
, that calls
circle_area()
, to compute the area of a cylinder.
This function must take two arguments: radius
and height
. Give both
arguments a default value of 1. In addition, the function should stop if
any of radius
or height
are negative.
For instance:
# default (radius 1, height 1)
cylinder_area()
# radius 2, height 3
cylinder_area(radius = 2, height = 3)
These should return an error message:
# bad radius
cylinder_area(radius = -2, height = 1)
# bad height
cylinder_area(radius = 2, height = -1)
# bad radius and height
cylinder_area(radius = -2, height = -1)
For a given cylinder of radius (r) and height (h) the volume (V) is given by the product of: pi, radius squared, and height (see diagram above).
Write a function cylinder_volume()
, that calls circle_area()
, to
compute the volume of a cylinder. This function must take two arguments:
radius
and height
. Give both arguments a default value of 1.
For example:
# default (radius 1, height 1)
cylinder_volume()
cylinder_volume(radius = 3, height = 10)
cylinder_volume(height = 10, radius = 3)
The following exercises involve writing simple functions to convert from one type of unit to other.
Write a function miles2kms()
that converts miles into kilometers: 1
mile is equal to 1.6 kilometers. Give the argument a default value of 1.
miles2kms <- function() {
# fill in
}
Use miles2kms()
to obtain mile conversions, in order to create a table
(i.e. data frame) like the one below. The first ten rows range from 1 to
10 miles, and then from 10 to 100 in 10 mile steps. The second column
corresponds to kms.
miles | kms |
---|---|
1 | 1.6 |
2 | 3.2 |
… | … |
10 | 16 |
20 | 32 |
… | … |
100 | 160 |
Write a function gallon2liters()
that converts gallons to liters: 1
gallon is equal to 3.78541 liters:
Use gallon2liters()
to make an inverse function liters2gallons()
.
Use liters2gallons()
to obtain liter conversions, in order to create a
table (i.e. data frame) like the one below. The first ten rows range
from 1 to 10 liters, and then from 10 to 100 in 10 liter steps. The
second column corresponds to gallons.
liters | gallons |
---|---|
1 | 0.2641722 |
2 | 0.5283444 |
… | … |
10 | 2.6417218 |
20 | 5.283444 |
… | … |
100 | 26.417218 |
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. Test the
function with seconds2years(1000000000)
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:
poly1 <- function(x) {
(x^2) * (x - 1)
}
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
x <- seq(-4, 4, length.out = 20)
y <- poly1(x)
# 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:
- (f(x) = x^3)
- (f(x) = (x^2 - 1)(x + 3)^3)
- (f(x) = (x^2 - 1)(x^2 - 9))
Write R code that will “squish” a number into the interval [0, 100], so that a number less than 0 is replaced by 0 and a number greater than 100 is replaced by 100.
z <- 100*pi
# Fill in the following if-else statements. You may (or may not)
# have to add or subtract else if or else statements.
if (TRUE) { # Replace TRUE with a condition.
} else if (TRUE) { # Replace TRUE with a condition.
} else {
}
## NULL
A common situation involves working with multiple conditions at the same time. You can chain multiple if-else statements:
y <- 1 # Change this value!
if (y > 0) {
print("positive")
} else if (y < 0) {
print("negative")
} else {
print("zero?")
}
## [1] "positive"
Write a function is_even()
that determines whether a number is even
(i.e. multiple of 2). If the input number is even, the output should be
TRUE
. If the input number is odd, the output should be FALSE
. If the
input is not a number, the output should be NA
Test your function:
# even number
is_even(10)
# odd number
is_even(33)
# not a number
is_even('a')
Use your function is_even()
to write a function is_odd()
that
determines if a number is odd (i.e. not a multiple of 2). If a number is
odd, the output should be TRUE
; if a number is even the output should
be FALSE
; if the input is not a number the output should be NA
Test is_odd()
with the following cases:
# odd number
is_odd(1)
# even number
is_odd(4)
# not a number
is_odd('a')
Working with multiple chained if’s becomes cumbersome. Consider the following example that uses several if’s to convert a day of the week into a number:
# Convert the day of the week into a number.
day <- "Tuesday" # Change this value!
if (day == 'Sunday') {
num_day <- 1
} else {
if (day == "Monday") {
num_day <- 2
} else {
if (day == "Tuesday") {
num_day <- 3
} else {
if (day == "Wednesday") {
num_day <- 4
} else {
if (day == "Thursday") {
num_day <- 5
} else {
if (day == "Friday") {
num_day <- 6
} else {
if (day == "Saturday") {
num_day <- 7
}
}
}
}
}
}
}
num_day
## [1] 3
Working with several nested if’s like in the example above can be a nigthmare.
In R, you can get rid of many of the braces like this:
# Convert the day of the week into a number.
day <- "Tuesday" # Change this value!
if (day == 'Sunday') {
num_day <- 1
} else if (day == "Monday") {
num_day <- 2
} else if (day == "Tuesday") {
num_day <- 3
} else if (day == "Wednesday") {
num_day <- 4
} else if (day == "Thursday") {
num_day <- 5
} else if (day == "Friday") {
num_day <- 6
} else if (day == "Saturday") {
num_day <- 7
}
num_day
## [1] 3
Write a function grade()
that takes a score
argument (i.e. a numeric
value between 0 and 100), and returns a letter grade (i.e. character)
based on the following grading scheme:
score | grade |
---|---|
[90 - 100] | "A" |
[80 - 90) | "B" |
[70 - 80) | "C" |
[60 - 70) | "D" |
< 60 | "F" |
You should be able to call your grade()
function like this:
# grade "A"
grade(score = 90)
# grade "B"
grade(score = 89.9999)
# grade "C"
grade(score = 70.0000001)
# grade "F"
grade(score = 50)
Modify your grade()
function to include a stop()
condition when the
input score
value is less than zero or greater than 100. The error
message should say something like:
"score must be a number between 0 and 100"
Consider again the chain of if-then-else’s used to convert a name day into a number dat. We have too many if’s, and there’s a lot of repetition in the code. If you find yourself using many if-else statements with identical structure for slightly different cases, you may want to consider a switch statement instead:
# Convert the day of the week into a number.
day <- "Tuesday" # Change this value!
switch(day, # The expression to be evaluated.
Sunday = 1,
Monday = 2,
Tuesday = 3,
Wednesday = 4,
Thursday = 5,
Friday = 6,
Saturday = 7,
NA) # an (optional) default value if there are no matches
## [1] 3
Switch statements can also accept integer arguments, which will act as indices to choose a corresponding element:
# Convert a number into a day of the week.
day_num <- 3 # Change this value!
switch(day_num,
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday")
## [1] "Tuesday"
The table below shows the different formulas for converting miles (mi) into other scales:
Units | Formula |
---|---|
Inches | mi x 63360 |
Feet | mi x 5280 |
Yards | mi x 1760 |
Meters | mi / 0.00062137 |
Kms | mi / 0.62137 |
Write the following five functions for each type of conversion. Each
function must take one argument x
with default value: x = 1
.
miles2inches()
miles2feet()
miles2yards()
miles2meters()
miles2kms()
For example:
miles2inches(2)
miles2feet(2)
miles2yards(2)
miles2meters(2)
miles2kms(2)
Create a function convert()
that converts miles into the specified
units. Use switch()
and the previously defined
functions—miles2inches()
, miles2feet()
, …, miles2kms
—to define
convert()
. Use two arguments: x
and to
, like this:
convert(40, to = "in")
By default, to = "km"
, but it can take values such as "in"
, "ft"
,
"yd"
, or "m"
.
Test convert()
with:
convert(3, "in")
convert(3, "ft")
convert(3, "yd")
convert(3, "m")
convert(3, "km")