Mathematical functions are an essential part in all computing environments. R provides several groups of basic math functions.
The basic functions include square root, and exponential and logarithm functions as the following table shows:
Note that sqrt()
only works with real numbers. If a negative number is supplied, NaN
will be produced:
sqrt(-1) ## Warning in sqrt(-1): NaNs produced ## [1] NaN
In R, numeric values can be finite, infinite (Inf
and -Inf
), and NaN
values. The following code will produce infinite values.
First, produce a positively infinite value:
1 / 0 ## [1] Inf
Then, produce a negatively infinite value:
log(0) ## [1] -Inf
There are several test functions to check whether a numeric value is finite, infinite, or NaN
:
is.finite(1 / 0) ## [1] FALSE is.infinite(log(0)) ## [1] TRUE
Using is.infinite()
, how can we check whether a numeric value is -Inf
? Inequality still works with infinite values in R:
1 / 0 < 0 ## [1] FALSE 1 / 0 > 0 ## [1] TRUE log(0) < 0 ## [1] TRUE log(0) > 0 ## [1] FALSE
Therefore, we can test the number with is.infinite()
and compare the elements to 0 at the same time:
is.pos.infinite <- function(x) { is.infinite(x) & x > 0 } is.neg.infinite <- function(x) { is.infinite(x) & x < 0 } is.pos.infinite(1/0) ## [1] TRUE is.neg.infinite(log(0)) ## [1] TRUE
Like sqrt()
, if the input value goes beyond the domain of log
function, that is, x > 0
, then the function returns NaN
with a warning:
log(-1) ## Warning in log(-1): NaNs produced ## [1] NaN
The following functions are used to round numbers in different ways:
Symbol |
Example |
Value |
[x] log |
|
11 |
[x] log |
|
9 |
truncate |
|
1 |
round |
|
3.142 |
significant numbers |
|
3.14 |
Previously, we showed that using options(digits =)
can modify the number of digits to display, but this does not change the actual number of digits to remember. The preceding functions round the numbers and may cause potential loss of information.
For example, if the input number 1.50021
is already precise, then rounding it to 1
digit will result in 1.5
and the other digits (information) are lost. Therefore, you should make sure if the digits to drop are indeed ignorable due to imprecision or noise before performing any rounding.
The following table lists the most commonly used trigonometric functions:
Symbol |
Example |
Value |
|
|
0 |
|
|
1 |
|
|
0 |
|
|
1.5707963 |
|
|
0 |
|
|
0.7853982 |
R also provides a numeric version of π
:
pi ## [1] 3.141593
In maths, equation sin (π) = 0 strictly holds. However, the same formula does not lead to 0 in R or any other typical numeric computing software due to some precision issues of floating numbers:
sin(pi) ## [1] 1.224606e-16
To compare numbers with near equality, use all.equal()
instead. While sin(pi) == 0
returns FALSE
, all.equal(sin(pi), 0)
returns TRUE
with the default tolerance of 1.5e-8
.
Another three functions are provided to make it precise when the input is a multiple of π
:
Symbol |
Example |
Value |
|
|
0 |
|
|
1 |
|
|
0 |
Similar to other computing software, hyperbolic functions are provided as shown in the following table:
Symbol |
Example |
Value |
|
|
1.1752012 |
|
|
1.5430806 |
|
|
0.7615942 |
|
|
0.8813736 |
|
|
0 |
|
|
0 |
It is common to calculate the maximum or minimum values of some numbers. The following table lists and demonstrates the simple use of max()
and min()
:
Symbol |
Example |
Value |
|
|
3 |
|
|
1 |
These two functions work not only with multiple scalar arguments but also with a vector input:
max(c(1, 2, 3)) ## [1] 3
Also, they work with multiple vector input:
max(c(1, 2, 3), c(2, 1, 2), c(1, 3, 4)) ## [1] 4 min(c(1, 2, 3), c(2, 1, 2), c(1, 3, 4)) ## [1] 1
The output demonstrates that max()
returns the maximal value among all values of all input vectors and min()
returns vice versa.
What if we want to obtain maximal or minimal values of each position among all vectors? Look at the following lines of code:
pmax(c(1, 2, 3), c(2, 1, 2), c(1, 3, 4)) ## [1] 2 3 4
This basically finds the maximal value among all numbers at position 1, then at position 2, and so on, which has the same output as the following code:
x <- list(c(1, 2, 3), c(2, 1, 2), c(1, 3, 4)) c(max(x[[1]][[1]], x[[2]][[1]], x[[3]][[1]]), max(x[[1]][[2]], x[[2]][[2]], x[[3]][[2]]), max(x[[1]][[3]], x[[2]][[3]], x[[3]][[3]])) ## [1] 2 3 4
This is called the parallel maxima. The twin function pmin()
works to find the parallel minima:
pmin(c(1, 2, 3), c(2, 1, 2), c(1, 3, 4)) ## [1] 1 1 2
These two functions can be very useful to quickly compose a vectorized function with specific functions as floor and/or ceiling. For example, suppose spread()
is a piecewise function. If the input is less than -5
, the value is -5
. If the input is between -5
to 5
, the value is input. If the input is greater than 5
, then the value is 5
.
A naive implementation uses if
to branch the pieces:
spread <- function(x) { if (x < -5) -5 else if (x > 5) 5 else x }
The function works with scalar input, but it is not automatically vectorized:
spread(1) ## [1] 1 spread(seq(-8, 8)) ## Warning in if (x < -5) -5 else if (x > 5) 5 else x: the ## condition has length > 1 and only the first element will be ## used ## [1] -5
One method is to use pmin()
and pmax()
, and the function will be automatically vectorized:
spread2 <- function(x) { pmin(5, pmax(-5, x)) } spread2(seq(-8, 8)) ## [1] -5 -5 -5 -5 -4 -3 -2 -1 0 1 2 3 4 5 5 5 5
Another method is to use ifelse()
:
spread3 <- function(x) { ifelse(x < -5, -5, ifelse(x > 5, 5, x)) } spread3(seq(-8, 8)) ## [1] -5 -5 -5 -5 -4 -3 -2 -1 0 1 2 3 4 5 5 5 5
The previous two functions, spread2()
and spread3()
, both have the same graphics: