Lecture 03
stop
and stopifnot
Often we want to validate user input, function arguments, or other assumptions in our code - if our assumptions are not met then we often want to report/throw an error and stop execution.
Do stuff:
Write a set of conditional(s) that satisfies the following requirements,
If x
is greater than 3 and y
is less than or equal to 3 then print “Hello world!”
Otherwise if x
is greater than 3 print “!dlrow olleH”
If x
is less than or equal to 3 then print “Something else …”
stop()
execution if x is odd and y is even and report an error, don’t print any of the text strings above.
Test out your code by trying various values of x
and y
.
05:00
R has a spectrum of output that can be provided to users,
Printed output (i.e. cat()
, print()
)
Diagnostic messages (i.e. message()
)
Warnings (i.e. warning()
)
Errors (i.e. stop()
, stopifnot()
)
Each of these provides outputs while also providing signals which can be interacted with programmatically (e.g. catching errors or treating warnings as errors).
Functions are abstractions in programming languages that allow us to modularize our code into small “self contained” units.
In general the goals of writing functions is to,
Simplify a complex process or task into smaller sub-steps
Allow for the reuse of code without duplication
Improve the readability of your code
Improve the maintainability of your code
Functions are defined by two components: the arguments (formals
) and the code (body
).
Functions are 1st order objects in R and have a mode of function
. They are assigned names like other objects using =
or <-
.
As with most other languages, functions are most often used to process inputs and return a value as output. There are two approaches to returning values from functions in R - explicit and implicit returns.
Many functions in R make use of an invisible return value
If we want a function to return more than one value we can group results using atomic vectors or lists.
More on lists next time
When defining a function we explicitly define names for the arguments, which become variables within the scope of the function.
When calling a function we can use these names to pass arguments in an alternative order.
It is also possible to give function arguments default values, so that they don’t need to be provided every time the function is called.
R has generous scoping rules, if it can’t find a variable in the current scope (e.g. a function’s body) it will look for it in the next higher scope, and so on until it runs out of environments or an object with that name is found.
Additionally, variables defined within a scope only persist for the duration of that scope, and do not overwrite variables at higher scope(s).
What is the output of the following code? Explain why.
03:00
Another interesting / unique feature of R is that function arguments are lazily evaluated, which means they are only evaluated when needed.
The previous example is not particularly useful, a more common use for this lazy evaluation is that this enables us define arguments as expressions of other arguments.
In R, operators are actually a special type of function - using backticks around the operator we can write them as functions.
Prefixing any function name with a ?
will open the related help file for that function.
For functions not in the base package, you can generally see their implementation by entering the function name without parentheses (or using the body
function).
function (formula, data, subset, weights, na.action, method = "qr",
model = TRUE, x = FALSE, y = FALSE, qr = TRUE, singular.ok = TRUE,
contrasts = NULL, offset, ...)
{
ret.x <- x
ret.y <- y
cl <- match.call()
mf <- match.call(expand.dots = FALSE)
m <- match(c("formula", "data", "subset", "weights", "na.action",
"offset"), names(mf), 0L)
mf <- mf[c(1L, m)]
mf$drop.unused.levels <- TRUE
mf[[1L]] <- quote(stats::model.frame)
mf <- eval(mf, parent.frame())
if (method == "model.frame")
return(mf)
else if (method != "qr")
warning(gettextf("method = '%s' is not supported. Using 'qr'",
method), domain = NA)
mt <- attr(mf, "terms")
y <- model.response(mf, "numeric")
w <- as.vector(model.weights(mf))
if (!is.null(w) && !is.numeric(w))
stop("'weights' must be a numeric vector")
offset <- model.offset(mf)
mlm <- is.matrix(y)
ny <- if (mlm)
nrow(y)
else length(y)
if (!is.null(offset)) {
if (!mlm)
offset <- as.vector(offset)
if (NROW(offset) != ny)
stop(gettextf("number of offsets is %d, should equal %d (number of observations)",
NROW(offset), ny), domain = NA)
}
if (is.empty.model(mt)) {
x <- NULL
z <- list(coefficients = if (mlm) matrix(NA_real_, 0,
ncol(y)) else numeric(), residuals = y, fitted.values = 0 *
y, weights = w, rank = 0L, df.residual = if (!is.null(w)) sum(w !=
0) else ny)
if (!is.null(offset)) {
z$fitted.values <- offset
z$residuals <- y - offset
}
}
else {
x <- model.matrix(mt, mf, contrasts)
z <- if (is.null(w))
lm.fit(x, y, offset = offset, singular.ok = singular.ok,
...)
else lm.wfit(x, y, w, offset = offset, singular.ok = singular.ok,
...)
}
class(z) <- c(if (mlm) "mlm", "lm")
z$na.action <- attr(mf, "na.action")
z$offset <- offset
z$contrasts <- attr(x, "contrasts")
z$xlevels <- .getXlevels(mt, mf)
z$call <- cl
z$terms <- mt
if (model)
z$model <- mf
if (ret.x)
z$x <- x
if (ret.y)
z$y <- y
if (!qr)
z$qr <- NULL
z
}
<bytecode: 0x1289a67f0>
<environment: namespace:stats>
There are the most common type of loop in R - given a vector it iterates through the elements and evaluate the code expression for each value.
while
loopsThis loop repeats evaluation of the code expression until the condition is not met (i.e. evaluates to FALSE
)
repeat
loopsEquivalent to a while(TRUE){}
loop, it repeats until a break
statement is encountered
break
and next
These are special actions that only work inside of a loop
break
- ends the current loop (inner-most)next
- ends the current iterationOften we want to use a loop across the indexes of an object and not the elements themselves. There are several useful functions to help you do this: :
, length
, seq
, seq_along
, seq_len
, etc.
1:length(x)
A common loop construction you’ll see in a lot of R code is using 1:length(x)
to generate a vector of index values for the vector x
.
Below is a vector containing all prime numbers between 2 and 100:
primes = c( 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97)
If you were given the vector x = c(3,4,12,19,23,51,61,63,78)
, write the R code necessary to print only the values of x
that are not prime (without using subsetting or the %in%
operator).
Your code should use nested loops to iterate through the vector of primes and x
.
05:00
Sta 523 - Fall 2023