<- 3
a <- 8
b <- c(1, 9, 3)
c <- c(7, 10, 3) d
6 Code Structure
6.1 Logical Operations
Recall from chapter 4 that a logical object can take two values: TRUE
or FALSE
. Logical operators, those that have logical objects as inputs or outputs, are commonly used and crucial to understand before we discuss conditional statements and loops. We will define \(a\), \(b\), \(c\), and \(d\) to help demonstrate logical operators.
The inequalities, \(<, >, \leq, \geq\), are denoted <, >, <=, >=
, respectively.
> b a
[1] FALSE
<= b a
[1] TRUE
Note that we can also apply these operators to vectors with more than one element.
c
[1] 1 9 3
d
[1] 7 10 3
< d c
[1] TRUE TRUE FALSE
The operator ==
allows us to test if two objects are equal. The operator !=
allows us to test if two objects are not equal.
== b a
[1] FALSE
!= d c
[1] TRUE TRUE FALSE
Generally, !
indicates negation.
is.character(b)
[1] FALSE
!is.character(b)
[1] TRUE
We can combine logical operators using &
(conjunction, “and”) or |
(disjunction, “or”).
> b) & (b != 7) (a
[1] FALSE
> b) | (b != 7) (a
[1] TRUE
If there are many logical comparisons, it is useful to have functions that output TRUE
if any or all of the comparisons are true.
< d c
[1] TRUE TRUE FALSE
any(c < d)
[1] TRUE
all(c < d)
[1] FALSE
If you are comparing objects with more than one element, you may want TRUE
if the two objects are identical and FALSE
otherwise. That is, you do not want a logical vector with more than one element.
== d c
[1] FALSE FALSE TRUE
identical(c, d)
[1] FALSE
The %in%
operator is like the subset operator in mathematics \(in\).
5 %in% c
[1] FALSE
c(1, 12, 0, 7, 9) %in% c
[1] TRUE FALSE FALSE FALSE TRUE
6.1.1 Practice Exercises
- Define a vector
x
of 10 random draws from the uniform \([0,1]\) distribution. Test if each element is greater than or equal to 0.9. Use the commandrunif()
. - Define a variable that is
TRUE
if at least 1 element ofx
is greater than or equal to 0.9.
6.2 Conditional Statements
Conditional statements allow you to dictate different actions depending on the outcome of a condition. The general syntax is as follows.
if (<condition>) {
<command if condition is true>
}
If the condition, denoted in a general way as <condition>
, is true, then the commands inside the brackets are executed. Otherwise, if the condition is false, R will continue executing the next line of code. Here is a concrete example.
<- runif(1, min = 0, max = 1)
f f
[1] 0.1052305
if (f < 0.5) {
print("The random number is less than one half.")
}
[1] "The random number is less than one half."
If you want R to execute a different command if the condition is false, then the general code is as follows.
if (<condition>) {
<command if condition is true>
else {
} <command if condition is false>
}
Here is a concrete example.
f
[1] 0.1052305
if (f < 0.5) {
print("The random number is less than one half.")
else {
} print("The random number is greater than or equal to one half.")
}
[1] "The random number is less than one half."
It is possible that there are more than two logical possibilities.
f
[1] 0.1052305
if (f < 0.25) {
print("The random number is less than one quarter.")
else if (f >= 0.25 & f < 0.5) {
} print("The random number is between one quarter and one half.")
else if (f >= 0.5 & f < 0.75) {
} print("The random number is between one half and three quarters.")
else {
} print("The random number is between three quarters and one.")
}
[1] "The random number is less than one quarter."
Conditional statements can be nested. It is always best practice to have consistent use of indentations and brackets, but these conventions are especially important when nesting conditional statements. The below chunk produces the same results as the above chunk, but using nested conditional statements rather than else if
.
f
[1] 0.1052305
if (f < 0.5) {
if (f < 0.25) {
print("The random number is less than one quarter.")
}else {
print("The random number is between one quarter and one half.")
}else {
} if (f < 0.75) {
print("The random number is between one half and three quarters.")
else {
} print("The random number is between three quarters and one.")
} }
[1] "The random number is less than one quarter."
It is important to be careful with the conditions you use. A common error is to have a condition that has more than one element (i.e., a logical vector with length greater than 1). In this case, R will use the first element and throw a warning.
<- runif(2, min = 0, max = 1)
g
gif (g < 0.5) {
print("The random number is less than one half.")
}< 0.5 g
The function ifelse()
is sometimes useful when applying functions to vectors or assigning values to a variable. It follows the same logic as conditional statements, but allows for a more concise implementation.
<- 2:-1
x <- ifelse(x >= 0, sqrt(x), NA) y
Warning in sqrt(x): NaNs produced
ifelse(is.na(y), 0, 1)
[1] 1 1 1 0
6.2.1 Practice Exercises
- Run the following (incorrect) code. How can you change the condition to avoid the warning and ensure that the print out is correct?
<- runif(2, min = 0, max = 1)
g
gif (g < 0.5) {
print("All the random numbers are less than one half.")
}
6.3 Loops
Loops indicate portions of the code to be executed more than once. The loop ends when the number of iterations has been reached or there is an exit condition.
6.3.1 for
The for
instruction involves running the code a pre-specified number of iterations. The general syntax is as follows.
for (i in <vector>) {
<commands>
}
R iterates through each value of <vector>
and executes the commands inside the brackets. Once the last element of the vector is reached, R executes the next line of code. Here is an example.
for (i in 1:3) {
print(factorial(i))
}
[1] 1
[1] 2
[1] 6
6.3.2 while
The while
instruction involves running the code until an exit condition is satisfied. The general syntax is as follows.
while (<condition>) {
<command>
}
Here is an example.
<- 1
j while (j < 4) {
print(factorial(j))
<- j + 1
j }
[1] 1
[1] 2
[1] 6
6.3.3 Other Loop Instructions
In some contexts, it is useful to further control a loop. The instruction break
tells R to exit the loop.
<- c(2, 4, 7)
l for (i in l) {
if (i == 4) {
<- i
out break
}
} out
[1] 4
The instruction next
tells R to move to the next iteration.
for (i in l) {
if (i == 4) {
next
}print(i)
}
[1] 2
[1] 7
6.3.4 Efficiency
A common maxim that loops are slow in R and it is best to avoid them. It is true that there are faster alternatives. The use of vectorized operations is preferable when possible as these operations are incredibly fast. The function system.time()
allows you to test the speed of your code. Evidently, the vectorized operation factorial(1:100000)
is faster than the loop. The divergence in speeds between the two methods will be larger for more complex functions and more iterations.
system.time(for (i in 1:100000) {
factorial(i)
})
user system elapsed
0.009 0.000 0.009
system.time(factorial(1:100000))
user system elapsed
0 0 0
Regardless, it is still crucial to be comfortable with loops. For smaller computations, like factorial, the difference is very minor. Sometimes, the code is clearer and makes more sense with a loop than with a vectorized operation.
6.3.5 Practice Exercises
- Define an object
out
with the value 0. In afor
loop iterating 1, 2, , 20, add the reciprocal toout
if the number is even. The sum should be \(\frac{1}{2} + \frac{1}{4} + \ldots + \frac{1}{20}\).
6.3.6 Apply Functions
A family of functions allows you to take advantage of R’s efficiency with vectorized operations, rather than relying too heavily on loops. There are several different functions within this family that differ by what object the function is applied and the desired output.
6.3.6.1 apply()
The function apply()
applies a function to the rows or columns (these are called margins) of matrices or data frames. While it is not faster than loops, it allows for more compact code. Following chapter 19 in Boehmke (2016), we will use the built-in data frame mtcars
. To see the first 6 rows of mtcars
, use the head()
function.
head(mtcars)
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
If we want the mean of each column, we can use the apply()
function. Note that 2
corresponds to the second margin of the data frame, i.e., the columns. Notice that the output is a named vector.
<- apply(mtcars, 2, mean)
x x
mpg cyl disp hp drat wt qsec
20.090625 6.187500 230.721875 146.687500 3.596563 3.217250 17.848750
vs am gear carb
0.437500 0.406250 3.687500 2.812500
str(x)
Named num [1:11] 20.09 6.19 230.72 146.69 3.6 ...
- attr(*, "names")= chr [1:11] "mpg" "cyl" "disp" "hp" ...
The first margin of the data frame is row.
apply(mtcars, 1, max)
Mazda RX4 Mazda RX4 Wag Datsun 710 Hornet 4 Drive
160.0 160.0 108.0 258.0
Hornet Sportabout Valiant Duster 360 Merc 240D
360.0 225.0 360.0 146.7
Merc 230 Merc 280 Merc 280C Merc 450SE
140.8 167.6 167.6 275.8
Merc 450SL Merc 450SLC Cadillac Fleetwood Lincoln Continental
275.8 275.8 472.0 460.0
Chrysler Imperial Fiat 128 Honda Civic Toyota Corolla
440.0 78.7 75.7 71.1
Toyota Corona Dodge Challenger AMC Javelin Camaro Z28
120.1 318.0 304.0 350.0
Pontiac Firebird Fiat X1-9 Porsche 914-2 Lotus Europa
400.0 79.0 120.3 113.0
Ford Pantera L Ferrari Dino Maserati Bora Volvo 142E
351.0 175.0 335.0 121.0
If the function to be applied has other arguments, these can be specified separated by commas. Here is an example where we trim 10% of observations from each end.
<- apply(mtcars, 2, mean, trim = 0.1)
out out
mpg cyl disp hp drat wt
19.6961538 6.2307692 222.5230769 141.1923077 3.5792308 3.1526923
qsec vs am gear carb
17.8276923 0.4230769 0.3846154 3.6153846 2.6538462
There are some function that are faster than the analogous implementation in apply()
. These include summary()
, colSums()
, rowSums()
, colMeans()
, and rowMeans()
.
colMeans(mtcars)
mpg cyl disp hp drat wt qsec
20.090625 6.187500 230.721875 146.687500 3.596563 3.217250 17.848750
vs am gear carb
0.437500 0.406250 3.687500 2.812500
summary(mtcars)
mpg cyl disp hp
Min. :10.40 Min. :4.000 Min. : 71.1 Min. : 52.0
1st Qu.:15.43 1st Qu.:4.000 1st Qu.:120.8 1st Qu.: 96.5
Median :19.20 Median :6.000 Median :196.3 Median :123.0
Mean :20.09 Mean :6.188 Mean :230.7 Mean :146.7
3rd Qu.:22.80 3rd Qu.:8.000 3rd Qu.:326.0 3rd Qu.:180.0
Max. :33.90 Max. :8.000 Max. :472.0 Max. :335.0
drat wt qsec vs
Min. :2.760 Min. :1.513 Min. :14.50 Min. :0.0000
1st Qu.:3.080 1st Qu.:2.581 1st Qu.:16.89 1st Qu.:0.0000
Median :3.695 Median :3.325 Median :17.71 Median :0.0000
Mean :3.597 Mean :3.217 Mean :17.85 Mean :0.4375
3rd Qu.:3.920 3rd Qu.:3.610 3rd Qu.:18.90 3rd Qu.:1.0000
Max. :4.930 Max. :5.424 Max. :22.90 Max. :1.0000
am gear carb
Min. :0.0000 Min. :3.000 Min. :1.000
1st Qu.:0.0000 1st Qu.:3.000 1st Qu.:2.000
Median :0.0000 Median :4.000 Median :2.000
Mean :0.4062 Mean :3.688 Mean :2.812
3rd Qu.:1.0000 3rd Qu.:4.000 3rd Qu.:4.000
Max. :1.0000 Max. :5.000 Max. :8.000
summary(mtcars$mpg)
Min. 1st Qu. Median Mean 3rd Qu. Max.
10.40 15.43 19.20 20.09 22.80 33.90
6.3.6.2 lapply()
The lapply()
function is designed to apply functions to lists and return a list. It efficiently loops through a list and applies the specified function to each element.
<- as.list(mtcars[1:5, ]) # Convert the first 5 rows into a list
carlist str(carlist)
List of 11
$ mpg : num [1:5] 21 21 22.8 21.4 18.7
$ cyl : num [1:5] 6 6 4 6 8
$ disp: num [1:5] 160 160 108 258 360
$ hp : num [1:5] 110 110 93 110 175
$ drat: num [1:5] 3.9 3.9 3.85 3.08 3.15
$ wt : num [1:5] 2.62 2.88 2.32 3.21 3.44
$ qsec: num [1:5] 16.5 17 18.6 19.4 17
$ vs : num [1:5] 0 0 1 1 0
$ am : num [1:5] 1 1 1 0 0
$ gear: num [1:5] 4 4 4 3 3
$ carb: num [1:5] 4 4 1 1 2
lapply(carlist, mean)
$mpg
[1] 20.98
$cyl
[1] 6
$disp
[1] 209.2
$hp
[1] 119.6
$drat
[1] 3.576
$wt
[1] 2.894
$qsec
[1] 17.71
$vs
[1] 0.4
$am
[1] 0.6
$gear
[1] 3.6
$carb
[1] 2.4
The elements of the list carlist
are all vectors. A list may contain matrices or data frames as well. In that case, we may want to iterate through each element of the list and apply the function to each item of each element. This is efficiently done through nesting apply functions. To demonstrate, we create a list in which each element is a data frame.
<- list(mazda = mtcars[1:2, ], hornet = mtcars[4:5, ], merc = mtcars[8:14, ])
carlist carlist
$mazda
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21 6 160 110 3.9 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21 6 160 110 3.9 2.875 17.02 0 1 4 4
$hornet
mpg cyl disp hp drat wt qsec vs am gear carb
Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
$merc
mpg cyl disp hp drat wt qsec vs am gear carb
Merc 240D 24.4 4 146.7 62 3.69 3.19 20.0 1 0 4 2
Merc 230 22.8 4 140.8 95 3.92 3.15 22.9 1 0 4 2
Merc 280 19.2 6 167.6 123 3.92 3.44 18.3 1 0 4 4
Merc 280C 17.8 6 167.6 123 3.92 3.44 18.9 1 0 4 4
Merc 450SE 16.4 8 275.8 180 3.07 4.07 17.4 0 0 3 3
Merc 450SL 17.3 8 275.8 180 3.07 3.73 17.6 0 0 3 3
Merc 450SLC 15.2 8 275.8 180 3.07 3.78 18.0 0 0 3 3
The x
is a stand-in value.
lapply(carlist, function(x) apply(x, 2, mean))
$mazda
mpg cyl disp hp drat wt qsec vs
21.0000 6.0000 160.0000 110.0000 3.9000 2.7475 16.7400 0.0000
am gear carb
1.0000 4.0000 4.0000
$hornet
mpg cyl disp hp drat wt qsec vs
20.0500 7.0000 309.0000 142.5000 3.1150 3.3275 18.2300 0.5000
am gear carb
0.0000 3.0000 1.5000
$merc
mpg cyl disp hp drat wt
19.0142857 6.2857143 207.1571429 134.7142857 3.5228571 3.5428571
qsec vs am gear carb
19.0142857 0.5714286 0.0000000 3.5714286 3.0000000
6.3.6.3 sapply()
The function sapply()
is very similar to lapply()
except it outputs a simplified result whenever possible. If the output is a list with elements of length 1 (more than 1), sapply()
returns a vector (matrix). Otherwise, sapply()
returns a list.
sapply(carlist, function(x) apply(x, 2, mean))
mazda hornet merc
mpg 21.0000 20.0500 19.0142857
cyl 6.0000 7.0000 6.2857143
disp 160.0000 309.0000 207.1571429
hp 110.0000 142.5000 134.7142857
drat 3.9000 3.1150 3.5228571
wt 2.7475 3.3275 3.5428571
qsec 16.7400 18.2300 19.0142857
vs 0.0000 0.5000 0.5714286
am 1.0000 0.0000 0.0000000
gear 4.0000 3.0000 3.5714286
carb 4.0000 1.5000 3.0000000
6.3.6.4 tapply()
The function tapply()
efficiently applies functions over subsets of a vector. It is useful when you want to apply functions within groups. The below code calculates the average miles per gallon (mpg
) grouped by the number of cylinders (cyl
).
tapply(mtcars$mpg, mtcars$cyl, mean)
4 6 8
26.66364 19.74286 15.10000
Here is an example of the same situation but for each column in the data frame.
apply(mtcars, 2, function(x) tapply(x, mtcars$cyl, mean))
mpg cyl disp hp drat wt qsec vs
4 26.66364 4 105.1364 82.63636 4.070909 2.285727 19.13727 0.9090909
6 19.74286 6 183.3143 122.28571 3.585714 3.117143 17.97714 0.5714286
8 15.10000 8 353.1000 209.21429 3.229286 3.999214 16.77214 0.0000000
am gear carb
4 0.7272727 4.090909 1.545455
6 0.4285714 3.857143 3.428571
8 0.1428571 3.285714 3.500000
6.3.7 Practice Exercises
Create a list with 4 elements, each containing a vector with 30 numbers: the first element is 30 draws from the uniform \([0,1]\) distribution, the second element is 30 draws from the uniform \([1, 2]\) distribution, the third element is 30 draws from the uniform \([2, 3]\) distribution, and the fourth element is 30 draws from the uniform \([3, 4]\) distribution. How does
lapply
andsapply
differ here?Here is an example of implementing the function
quantile
. Calculate the 25th, 50th, and 75th percentile for the columns ofmtcars
usingapply()
.
quantile(1:100, probs = c(0.10, 0.90))
10% 90%
10.9 90.1
6.4 Functions
By now, you have been exposed to functions, both those built into the base packages of R, and those that are accessible from external packages. Now, you will write your own functions. User-defined functions are crucial in the workflow. They allow for tasks to be more general and automatic. Often, they make the script easier to read and understand. They also make it easier to test and debug, resulting in more correct output. Even if it feels burdensome to write functions, it is usually worthwhile.
The example function we will be using is very simple from Boehmke (2016). Suppose we want a function to calculate the present value of a future value given interest rate and number of periods. The output should be rounded to three digits.
<- function(fv, r, n) {
calc_pv <- fv / ((1 + r)^n)
pv return(round(pv, 3))
}
There are three components of the function. The body
is the meat of the function. It contains all the calculations, that is, everything between {\}
.
body(calc_pv)
{
pv <- fv/((1 + r)^n)
return(round(pv, 3))
}
The formals
are the inputs the function requires. These are also called arguments.
formals(calc_pv)
$fv
$r
$n
Finally, the environment
includes all of the named objects accessible to the function.
environment(calc_pv)
<environment: R_GlobalEnv>
The function is called the same way as built-in functions.
calc_pv(fv = 1000, r = 0.08, n = 10)
[1] 463.193
calc_pv(1000, 0.08, 10) # Positional matching
[1] 463.193
It might be convenient to set default values for some arguments. You can see examples of default values in many built-in functions. In the documentation for mean()
, the arguments trim
and na.rm
are listed in the description with trim = 0
and na.rm = FALSE
. Setting default values for your own code can help prevent errors and make the function easier to use.
<- function(fv, r = 0.08, n) {
calc_pv <- fv / ((1 + r)^n)
pv return(round(pv, 3))
}
Now, you can use the function even without specifying r
.
calc_pv(fv = 1000, n = 20)
[1] 214.548
If you specify a function with an argument that is not used, it will not throw an error or warning. The technical name for this is lazy evaluation.
<- function(fv, r, n, x) {
calc_pv <- fv / ((1 + r)^n)
pv return(round(pv, 3))
}calc_pv(fv = 1000, r = 0.08, n = 10) # No need to pass a value to x
[1] 463.193
In the present value example, the output is one number. Functions can also return more than one object. Above, we used the function return()
to be very clear about what the function is returning. While this is good practice, the function will default to returning the last object of the body.
<- function(x, y) {
arith + y
x - y
x * y
x / y
x
}arith(1, 2)
[1] 0.5
Returning a vector allows the function to return more than one result.
<- function(x, y) {
arith <- x + y
addition <- x - y
subtraction <- x * y
multiplication <- x / y
division c(addition, subtraction, multiplication, division)
}arith(1, 2)
[1] 3.0 -1.0 2.0 0.5
Returning a list allows the function to return more than one result and allows for an easier understanding of the output.
<- function(x, y) {
arith <- x + y
addition <- x - y
subtraction <- x * y
multiplication <- x / y
division c(list(Addition = addition, Subtraction = subtraction,
Multiplication = multiplication, Division = division))
}arith(1, 2)
$Addition
[1] 3
$Subtraction
[1] -1
$Multiplication
[1] 2
$Division
[1] 0.5
6.4.1 Scoping
When writing functions, it is useful to have a sense of the scoping rules. These are the rules that R follows to decide on the value of the objects in a function. R will first search within the function. If all the objects are defined within the function, R does not search anymore.
<- function() {
calc_pv <- 1000
fv <- 0.08
r <- 10
n / ((1 + r)^n)
fv
}calc_pv()
[1] 463.1935
If a value is not present within the function, R will expand the search up one level. The levels are demarcated with {}
.
<- 1000
fv <- function() {
calc_pv <- 0.08
r <- 10
n / ((1 + r)^n)
fv
}calc_pv()
[1] 463.1935
These rules are general, including when the function takes arguments.
<- function(fv, r) {
calc_pv <- 10
n / ((1 + r)^n)
fv
}calc_pv(1000, 0.08)
[1] 463.1935
6.4.2 Niceties
Our example functions are simple, but often actual functions can be complex. Your script may define and use many functions that have many inputs and outputs. Even if you are the only person who will ever read your code, it is useful to include some checks to help ensure you are properly using your function. Even though these checks take time, they can save trouble down the line. The function stop()
stops the execution of the function and throws an error message.
<- function(fv, r, n) {
calc_pv
# Input validation
if (!is.numeric(fv) | !is.numeric(r) | !is.numeric(n)) {
stop("All inputs must be numeric.\n",
"The inputs are of the following classes:\n",
"fv: ", class(fv), "\n",
"r: ", class(r), "\n",
"n: ", class(n))
}
<- fv / ((1 + r)^n)
pv return(round(pv, 3))
}calc_pv("1000", 0.08, 10)
Error in calc_pv("1000", 0.08, 10): All inputs must be numeric.
The inputs are of the following classes:
fv: character
r: numeric
n: numeric
Notice that our function can take in vectors. As discussed above, vectorized operations like this are very efficient in R.
calc_pv(fv = 1:10, r = 0.08, n = 10)
[1] 0.463 0.926 1.390 1.853 2.316 2.779 3.242 3.706 4.169 4.632
But what if one of the elements of the input vector is a missing value?
calc_pv(fv = c(100, NA, 1000), r = 0.08, n = 10)
[1] 46.319 NA 463.193
This is a frequent issue that arises when using functions within a larger script. Adding the argument na.rm
allows you to specify how you want the function to handle NA
values.
<- function(fv, r, n, na.rm = FALSE) {
calc_pv
# Input validation
if (!is.numeric(fv) | !is.numeric(r) | !is.numeric(n)) {
stop("All inputs must be numeric.\n",
"The inputs are of the following classes:\n",
"fv: ", class(fv), "\n",
"r: ", class(r), "\n",
"n: ", class(n))
}
# na.rm argument
if (na.rm == TRUE) {
<- fv[!is.na(fv)] # Only keep non-missing values in fv
fv
}
<- fv / ((1 + r)^n)
pv return(round(pv, 3))
}calc_pv(fv = c(100, NA, 1000), r = 0.08, n = 10)
[1] 46.319 NA 463.193
calc_pv(fv = c(100, NA, 1000), r = 0.08, n = 10, na.rm = TRUE)
[1] 46.319 463.193
With input validation and conditional statements, the code to write functions can be very long. You can write your function in a separate script. The script should contain the entirety of the code for the function.
# Title: calc_pv.R
<- function(fv, r, n, na.rm = FALSE) {
calc_pv
# Input validation
if (!is.numeric(fv) | !is.numeric(r) | !is.numeric(n)) {
stop("All inputs must be numeric.\n",
"The inputs are of the following classes:\n",
"fv: ", class(fv), "\n",
"r: ", class(r), "\n",
"n: ", class(n))
}
# na.rm argument
if (na.rm == TRUE) {
<- fv[!is.na(fv)] # Only keep non-missing values in fv
fv
}
<- fv / ((1 + r)^n)
pv return(round(pv, 3))
}
The function source()
allows you to add the function to your global environment.
# Title: main.R
source("calc_pv.R")
calc_pv(fv = c(100, NA, 1000), r = 0.08, n = 10, na.rm = TRUE)
6.4.3 Practice Exercises
Write a function to convert fahrenheit to celsius. Define a 10 \(\times\) 10 matrix with 100 draws from the uniform \([-10, 100]\) distribution. Test your function on this matrix.
The Fibonacci sequence is recursive: \(x_n = x_{n-1} + x_{n-2}\). Write a function that takes \(n\) as an argument and computes \(x_n\). Recall that \(x_0 = 0\) and \(x_1 = 1\). Use a
for
loop to print the first 15 elements of the Fibonacci sequence. Bonus: try usingsapply
to print the first 15 elements of the Fibonacci sequence.
6.5 Further Reading
The information from this chapter comes from chapters 18, 19 of Boehmke (2016) and chapters 5.2, 5.7-5.8 of Zamora Saiz et al. (2020).