Checking all function arguments length at once in R - r

As a sanity check, I want when any arguments in function seda below is a vector of length larger than 1, the function stops.
Question: Instead of individually listing all function arguments (here x, y, z), is there a way to use match.call or formals etc. such that ALL function arguments could be checked at once?
I tried the below with no success:
seda <- function(x, y, z){
is.v <- function(...) lengths(list(...)) > 1
if(is.v(match.call())) stop("Error") # instead of `is.v(x, y, z)`
x + y + z
}
seda(2, 2, 3)
seda(c(2, 3), 2, 3)

match.call() will capture the arguments to the function, which can then be tested for length. We use sapply to return a vector with the length of each function argument, and the any function to test whether any of the arguments have a length greater than 1.
seda <- function(x, y, z){
if(any(sapply(match.call()[-1], length) > 1)) stop("All arguments must be length 1")
x + y + z
}
seda(2, 2, 3)
[1] 7
seda(c(2, 3), 2, 3)
Error in seda(c(2, 3), 2, 3) : All arguments must be length 1
Thanks to #erocoar for pointing out that match.call can be used instead of sys.call and that as.list is unnecessary.

You can tweak what you have a bit to get it to work:
seda <- function(...){
stopifnot(lengths(list(...)) == 1)
sum(...)
}
seda(1, 1, 1)
#> [1] 3
seda(1, 1, 1:2)
#> Error: lengths(list(...)) == 1 are not all TRUE
...or with named parameters,
seda_named <- function(x, y, z){
stopifnot(lengths(list(x, y, z)) == 1)
x + y + z
}
seda_named(1, 1, 1)
#> [1] 3
seda_named(1, 1, 1:2)
#> Error: lengths(list(x, y, z)) == 1 are not all TRUE
To use stop instead of stopifnot so as to control the error message, wrap the condition in all.

If they are tested all at once then the error message won't say which argument was the problem. The following tests them in a loop and does indicate which was the offending argument in the error message.
seda <- function(x, y, z) {
argnames <- names(match.call()[-1])
for(nm in argnames) if (length(get(nm)) != 1) stop(nm, " does not have length 1")
x + y + z
}
# test - note that the error message names z
seda(10, 20, 1:2)
## Error in seda(10, 20, 1:2) : z does not have length 1
Of course if you really had 3 arguments it would be a lot simpler just to write it out. This also gives argument specific error messages:
seda <- function(x, y = 1, z) {
stopifnot(length(x) == 1, length(y) == 1, length(z) == 1)
x + y + z
}
seda(10, 20, 1:2)
## Error: length(z) == 1 is not TRUE

I highly appreciate the input of my expert colleagues. Using your valuable comments, I guess what I want is the following:
seda <- function(x, y, z){
if(lengths(list(get(names(match.call()[-1])))) > 1) stop("Error")
x + y + z
}
seda(c(2, 3), 2, 3)
seda(2, 2, 3)
Also we could perhaps use formals as well:
seda <- function(x, y, z){
if(lengths(list(get(names(formals(seda))))) > 1) stop("Error")
x + y + z
}
seda(c(2, 3), 2, 3)
seda(2, 2, 3)
Or formalArgs like this:
seda <- function(x, y, z){
if(lengths(list(get(formalArgs(seda)))) > 1) stop("Error")
x + y + z
}
seda(c(2, 3), 2, 3)
seda(2, 2, 3)

Related

Continuous function in R Programming

I'm trying to code the following continuous function in R Programming.
I was trying to create a function called fun1 that takes a single argument vecA. I want the function to return the values of f(x) evaluated at the values of vecA.
fun1(vecA) <- function(x){
x^2+2x+3
}
I don't know how I can continue it.
Ideally your function should be able to take vectorized input, in which case you should use ifelse or case_when.
For example:
f <- function(x) {
ifelse(x < 0, x^2 + 2*x + 3,
ifelse(x >= 2, x^2 + 4 * x - 7,
x + 3))
}
Or
f <- function(x) {
dplyr::case_when(x < 0 ~ x^2 + 2*x + 3,
x > 2 ~ x^2 + 4 * x - 7,
TRUE ~ x + 3)
}
both of which produce the same output. We can see what the function looks like by doing:
plot(f, xlim = c(-5, 5))
Created on 2022-09-25 with reprex v2.0.2
Try studying the patterns here:
fun1 <- function(x){
if (x < 0) {
x^2+2*x+3
} else if (x < 2) {
x + 3
} else {
# Your turn
}
}

How define a multivariate function in R

I have the following function:
I would like to write a function in R for f. It should take as arguments x, c1, ..., cn and n.
If the argument lengths are variable, use 3 dots (...)
f1 <- function(x, ...) {
2 * x + sum((x - c(...))^2)
}
-testing
f1(5, 10, 5, 2, 3)
[1] 48

How to solve "non-numeric argument.." error in numerical integration?

I want to calculate the following integral in R.
I tried to use Vectorize and integrate functions but I got error
Error in (log(z)) * (InIntegl2) : non-numeric argument to binary operator
fxyz= function(x,y,z) { (x*y*z)+z+x+2*y}
InIntegl1 = Vectorize(function(x) { integrate(fxyz, 0,5)$value})
InIntegl2 = Vectorize(function(y) { integrate( InIntegl1, 0,3)$value})
InIntegl3 = Vectorize(function(z) { integrate((log(z))*(InIntegl2), 2,6)$value})
Integral = integrate(InIntegl3 , 2, 6)$value
The first integral must be parameterized by y and z and the second by z. Then we can perform the final integration.
int1 <- Vectorize(function(y, z) integrate(fxyz, 0, 5, y = y, z = z)$value)
int2 <- Vectorize(function(z) integrate(int1, 0, 3, z = z)$value)
integrate(function(z) log(z) * int2(z), 2, 6)$value
## [1] 2071.71
In the spirit of Numerical Triple Integration in R
integrate(Vectorize(function(z) {
log(z)*integrate(Vectorize(function(y) {
integrate(function(x) { x*y*z +x + 2*y + z}, 0, 5)$value }), 0,3)$value }), 2,6)
Package cubature can solve triple integrals with one call.
library(cubature)
f <- function(X){
x <- X[1]
y <- X[2]
z <- X[3]
log(z)*(x*y*z + x+ 2*y + z)
}
loLim <- c(0, 0, 2)
hiLim <- c(5, 3, 6)
tol <- .Machine$double.eps^0.5
hcubature(f, loLim, hiLim, tol = tol)
#$integral
#[1] 2071.71
#
#$error
#[1] 2.059926e-05
#
#$functionEvaluations
#[1] 165
#
#$returnCode
#[1] 0
If only the integral's value is needed,
hcubature(f, loLim, hiLim, tol = tol)$integral
#[1] 2071.71

Function scope: using results from high-level function in low-level function

I would like to use the calculations from high-level (outer) function high_lvl_fun in in a low-level (inner) function low_lvl_fun. The low-level function is an argument of the high-level one (I would like to use different functions with different sets of arguments). My reproducible example:
set.seed(101)
low_lvl_fun <- function(x, y){ # low-level (inner) function
sum((x-y)^2) # Mean Squared Error
}
high_lvl_fun <- function(x, y = NULL, FUN, args){ # high level (outer) function
# Just some toy changes in y to check if the code works
if(length(y) == 0){
y <- rep(1, length(x))
}else{
y <- rep(2, length(x))
}
do.call(FUN, args = args) # Call of low_lvl_fun
}
The low-level function computes Mean Squared Error. The high-level function performs some operations on vector y and calls the low-level function. Declaration of such an argument and the high-level function call:
x <- rnorm(100)
high_lvl_fun(x, y = NULL, FUN = "low_lvl_fun", args = list(x, y))
results in such an error:
Error in do.call(FUN, args = args) : object 'y' not found
I understand that the low-level function assumes that the value of y is NULL (as declared in high-level function call), however, I don't know how to change the scope in which the low-level function searches for y.
The only solution I came up with would be to declare y in the global environment:
high_lvl_fun2 <- function(x, y = NULL, FUN, args){ # high level (outer) function
if(length(y) == 0){
y <<- rep(1, length(x))
}else{
y <<- rep(2, length(x))
}
do.call(FUN, args = args) # Call of low_lvl_fun
}
however, I would like to avoid modifying y in the global environment.
EDIT: (more details)
The low-level function can take arguments other than x and y. It may also require only x and other arguments, and not y, for example:
low_lvl_fun2 <- function(x){sd(x)/mean(x)}
The other important thing is that high and low-level functions can have the arguments with the same names (like above, where both functions have arguments called x and y) and it would be good not being forced to rewrite low-level function. Unfortunately, the implementation in the comments suggested by #Andrea does not meet this condition, since matching two arguments with the same names throws an error:
high_lvl_fun <- function(x, y = NULL, FUN, ...){ # Function suggested by #Andrea
dots <- lazy_eval(lazy_dots(...))
# Just some toy changes in y to check if the code works
if(length(y) == 0){
y <- rep(1, length(x))
}else{
y <- rep(2, length(x))
}
args <- c(list(x , y) , dots)
do.call(FUN, args = args) # Call of low_lvl_fun
}
# Calling the low-level function at the beginning of the post
high_lvl_fun(x = 1:10, y = 2:11, FUN = "low_lvl_fun", x = x, y = y)
Error in high_lvl_fun(x = 1:10, y = 2:11, FUN = "low_lvl_fun", x = x,
: formal argument "x" matched by multiple actual arguments
Assuming that low_lvl_fun() takes x and y only. This should do the job
high_lvl_fun <- function(x, y = NULL, FUN ){ # high level (outer) function
# Just some toy changes in y to check if the code works
if(length(y) == 0){
y <- rep(1, length(x))
}else{
y <- rep(2, length(x))
}
args <- list(x = x, y = y)
do.call(FUN, args = args) # Call of low_lvl_fun
}
As a more general solution I would suggest
The use of the ... argument
require(lazyeval)
high_lvl_fun <- function(x, y = NULL, FUN, ...){ # high level (outer) function
dots <- lazy_eval(lazy_dots(...))
# Just some toy changes in y to check if the code works
y <- y+1
args <- c(list(x , y) , dots)
do.call(FUN, args = args) # Call of low_lvl_fun
}
# Ex 1
f <- function(x, y , z) {x+y+z}
high_lvl_fun (x = 1, y = 2, FUN = f, z = 3)
# Ex 2
g <- function(x, y , z, mean , sd) {
n <- x+y+z
sum(rnorm(n , mean , sd))
}
high_lvl_fun (x = 1, y = 2, FUN = g, z = 3, mean = 100, sd = 1)

Implementing ECDF in R

I'm trying to implement the R function ecdf().
I'm considering two cases: one with t 1-dimensional, the other with t as a vector.
#First case
my.ecdf<-function(x,t) {
indicator<-ifelse(x<=t,1,0)
out<-sum(indicator)/length(x)
out
}
#Second case
my.ecdf<-function(x,t) {
out<-length(t)
for(i in 1:length(t)) {
indicator<-ifelse(x<=t[i],1,0)
out[i]<-sum(indicator)/length(t)
}
out
}
How can I check whether I'm doing the right thing with the R function ecdf() or not? This function take as argument just x, therefore I can't specify the value of t.
You could just plot the results and see that it gives something very similar:
# slightly improved version of my.ecdf
my.ecdf<-function(x,t) {
out<-numeric(length(t))
for(i in 1:length(t)) {
indicator <- as.numeric(x<=t[i])
out[i] <- sum(indicator)/length(t)
}
out
}
# test 1
x <- rnorm(1000)
plot(ecdf(x))
lines(seq(-4, 4, length=1000),
my.ecdf(x, seq(-4, 4, length=1000)),
col='red')
# test 2
x <- rexp(1000)
plot(ecdf(x))
lines(seq(0, 8, length=1000),
my.ecdf(x, seq(0, 8, length=1000)),
col='red')
A general tip - you can view the source code of any function by typing its name into the console without parentheses or arguments:
edcf
function (x)
{
x <- sort(x)
n <- length(x)
if (n < 1)
stop("'x' must have 1 or more non-missing values")
vals <- unique(x)
rval <- approxfun(vals, cumsum(tabulate(match(x, vals)))/n,
method = "constant", yleft = 0, yright = 1, f = 0, ties = "ordered")
class(rval) <- c("ecdf", "stepfun", class(rval))
assign("nobs", n, envir = environment(rval))
attr(rval, "call") <- sys.call()
rval
}

Resources