In R a call is an unevaluated expression which consists of the named function applied to the given arguments. Function call
is used to create a call. It receives a function’s name, plus a series of arguments to be applied to the function. eval
is used to evaluate the final result.
a <- 25
b <- call("sqrt", a)
b
## sqrt(25)
eval(b)
## [1] 5
a <- 16 # does not influence the previous environment
eval(b)
## [1] 5
is.call(b)
## [1] TRUE
is.call(call) # functions are not calls
## [1] FALSE
c <- call("^", 2, 4) # call can receive multiple arguments
eval(c)
## [1] 16
quote
returns the argument as non evaluated:
a <- 25
b <- call("sqrt", quote(a))
eval(b)
## [1] 5
a <- 16 # now it influences, since R still not evaluated the parameter
eval(b)
## [1] 4
eval(quote(b), env=list(b=1)) # reads from an environment (can also be a list or a dataframe)
## [1] 1
do.call
constructs and executes a function call from a name or a function and a list of arguments to be passed to it.
a <- 10
b <- 2
f <- function (a,b) a/b
do.call("f", args=list(a+1, b))
## [1] 5.5
do.call("f", args=list(b=a, a=b))
## [1] 0.2
# make an environment
env <- new.env()
assign("a", 2, envir = env) # same as env$a <- 2
assign("b", 8, envir = env)
assign("f", function(a,b) a+b, envir = env)
as.list(env)
## $a
## [1] 2
##
## $b
## [1] 8
##
## $f
## function (a, b)
## a + b
do.call("f", args=list(quote(a), quote(b)), envir=env)
## [1] 10
env$f <- function(a,b) a^b
do.call("f", args=list(quote(a), quote(b)), envir=env)
## [1] 256
substitute
return the unevaluated expression, replacing any variables bound in the environment. The environment is a given list of assignments, or if omitted is the current evaluation environment.
substitute(a+b, list(a=1))
## 1 + b
a <- substitute(a+b+c, list(a=1,c=5))
a
## 1 + b + 5
eval(a, list(b=10))
## [1] 16
We can use eval
and substitute
to implement subset
, a function that return subsets of vectors, matrices or data frames which meet conditions:
df <- data.frame(x=11:18, y=18:11)
df
## x y
## 1 11 18
## 2 12 17
## 3 13 16
## 4 14 15
## 5 15 14
## 6 16 13
## 7 17 12
## 8 18 11
subset(df, x>y)
## x y
## 5 15 14
## 6 16 13
## 7 17 12
## 8 18 11
my.subset <- function(x, condition) {
condition_call <- substitute(condition)
rows <- eval(condition_call, env=x, enclos=parent.frame()) # parent.frame is the var scope the user needs
x[rows, ]
}
my.subset(df, x>y)
## x y
## 5 15 14
## 6 16 13
## 7 17 12
## 8 18 11
a <- 15
my.subset(df, x>a)
## x y
## 6 16 13
## 7 17 12
## 8 18 11
Notice that these type of functions are no longer referentially transparent. A function is referentially transparent if you can replace its arguments with their values and its behaviour doesn’t change. For example, if a function, f(), is referentially transparent and both x and y are 10, then f(x), f(y), and f(10) will all return the same result. Check here for more info
Expressions are calls in R. Function expression
returns a vector of type “expression” containing its arguments (unevaluated).
expr <- expression(x^2 + b*x)
is.expression(expr)
## [1] TRUE
eval(expr, list(x=10, b=3))
## [1] 130
eval(expr, list(x=c(10,20,30), b=1:3))
## [1] 110 440 990
all.vars
return a character vector containing all the names which occur in an expression or call.
expr <- expression(x^2 + b*x)
all.vars(expr)
## [1] "x" "b"
all.vars(quote(expr))
## [1] "expr"
We can do some basic symbolic computation. D
and deriv
compute the derivate of an expression:
expr <- expression(x^2 + b*x)
de.dx <- D(expr, "x") # for simple variable
de.dx
## 2 * x + b
de.db <- D(expr, "b")
de.db
## x
eval(de.dx, list(b=1, x=10))
## [1] 21
# computing n-th derivative
# pre: n>0
Dn <- function(expr, name, n=1) {
if (n == 1)
D(expr, name)
else
Dn(D(expr, name), name, n-1)
}
Dn(expression(sin(x^2)), "x", 3)
## -(sin(x^2) * (2 * x) * 2 + ((cos(x^2) * (2 * x) * (2 * x) + sin(x^2) *
## 2) * (2 * x) + sin(x^2) * (2 * x) * 2))
expr <- expression(x^2 + b*x*y + y^3)
deriv(expr, namevec=c("x","y")) # for multiple variables
## expression({
## .expr2 <- b * x
## .value <- x^2 + .expr2 * y + y^3
## .grad <- array(0, c(length(.value), 2L), list(NULL, c("x",
## "y")))
## .grad[, "x"] <- 2 * x + b * y
## .grad[, "y"] <- .expr2 + 3 * y^2
## attr(.value, "gradient") <- .grad
## .value
## })
d <- deriv(expr, namevec=c("x","y"), hessian=TRUE) # includes the Hessian, ie, the matrix of second derivatives
d
## expression({
## .expr2 <- b * x
## .value <- x^2 + .expr2 * y + y^3
## .grad <- array(0, c(length(.value), 2L), list(NULL, c("x",
## "y")))
## .hessian <- array(0, c(length(.value), 2L, 2L), list(NULL,
## c("x", "y"), c("x", "y")))
## .grad[, "x"] <- 2 * x + b * y
## .hessian[, "x", "x"] <- 2
## .hessian[, "x", "y"] <- .hessian[, "y", "x"] <- b
## .grad[, "y"] <- .expr2 + 3 * y^2
## .hessian[, "y", "y"] <- 3 * (2 * y)
## attr(.value, "gradient") <- .grad
## attr(.value, "hessian") <- .hessian
## .value
## })
eval(d, list(x=1,y=3))
## [1] 34
## attr(,"gradient")
## x y
## [1,] 8 29
## attr(,"hessian")
## , , x
##
## x y
## [1,] 2 2
##
## , , y
##
## x y
## [1,] 2 18
Ref. We are trying to fit data \(x\) into the nonlinear model:
\[\frac{K y_0 e^{u(x-tl)}}{K + y_0(e^{u(x-tl)}-1)} + b_1 + (b_0-b_1)e^{-kx} + b_2x\]
# the model equation
expr <- expression( (K*y0*exp(u*(x-tl)))/(K + y0*(exp(u*(x-tl))-1)) +
b1 + (b0 - b1)*exp(-k*x) + b2*x )
all.vars(expr)
## [1] "K" "y0" "u" "x" "tl" "b1" "b0" "k" "b2"
The next line produces a list of the partial derivatives of the above equation with respect to each parameter:
ds <- sapply(all.vars(expr), function(v) {D(expr, v)} )
ds
## $K
## y0 * exp(u * (x - tl))/(K + y0 * (exp(u * (x - tl)) - 1)) - (K *
## y0 * exp(u * (x - tl)))/(K + y0 * (exp(u * (x - tl)) - 1))^2
##
## $y0
## K * exp(u * (x - tl))/(K + y0 * (exp(u * (x - tl)) - 1)) - (K *
## y0 * exp(u * (x - tl))) * (exp(u * (x - tl)) - 1)/(K + y0 *
## (exp(u * (x - tl)) - 1))^2
##
## $u
## K * y0 * (exp(u * (x - tl)) * (x - tl))/(K + y0 * (exp(u * (x -
## tl)) - 1)) - (K * y0 * exp(u * (x - tl))) * (y0 * (exp(u *
## (x - tl)) * (x - tl)))/(K + y0 * (exp(u * (x - tl)) - 1))^2
##
## $x
## K * y0 * (exp(u * (x - tl)) * u)/(K + y0 * (exp(u * (x - tl)) -
## 1)) - (K * y0 * exp(u * (x - tl))) * (y0 * (exp(u * (x -
## tl)) * u))/(K + y0 * (exp(u * (x - tl)) - 1))^2 - (b0 - b1) *
## (exp(-k * x) * k) + b2
##
## $tl
## -(K * y0 * (exp(u * (x - tl)) * u)/(K + y0 * (exp(u * (x - tl)) -
## 1)) - (K * y0 * exp(u * (x - tl))) * (y0 * (exp(u * (x -
## tl)) * u))/(K + y0 * (exp(u * (x - tl)) - 1))^2)
##
## $b1
## 1 - exp(-k * x)
##
## $b0
## exp(-k * x)
##
## $k
## -((b0 - b1) * (exp(-k * x) * x))
##
## $b2
## x
class(ds)
## [1] "list"
class(ds[[1]])
## [1] "call"
Each element of this list is itself an expression.
Now, if we assign values to the parameters, we can compute the Jacobian matrix \(J\), necessary to compute the gradient:
jacob <- function(expr, env) {
t( sapply(all.vars(expr), function(v) {eval(D(expr, v), env=env)} ) )
}
So, let’s give them some values:
# this will be the environment for the evaluation of J
ps <- c(y0=0.01, u=0.3, tl=5, K=2, b0=0.01, b1=1, b2=0.001, k=0.1)
x <- seq(0,10)
J <- jacob(expr, env= c(as.list(ps), list(x=x)))
J <- J[names(ps),,drop=F] # drop 'x' row which refers to the independent variable
J
## [,1] [,2] [,3] [,4] [,5] [,6]
## y0 2.249e-01 3.033e-01 4.090e-01 5.513e-01 7.427e-01 1.000000
## u -1.119e-02 -1.207e-02 -1.221e-02 -1.097e-02 -7.390e-03 0.000000
## tl -6.712e-04 -9.054e-04 -1.221e-03 -1.646e-03 -2.217e-03 -0.002985
## K -4.367e-06 -5.299e-06 -6.068e-06 -6.218e-06 -4.813e-06 0.000000
## b0 1.000e+00 9.048e-01 8.187e-01 7.408e-01 6.703e-01 0.606531
## b1 0.000e+00 9.516e-02 1.813e-01 2.592e-01 3.297e-01 0.393469
## b2 0.000e+00 1.000e+00 2.000e+00 3.000e+00 4.000e+00 5.000000
## k 0.000e+00 8.958e-01 1.621e+00 2.200e+00 2.654e+00 3.002327
## [,7] [,8] [,9] [,10] [,11]
## y0 1.345e+00 1.807e+00 2.424e+00 3.2444063 4.3296326
## u 1.338e-02 3.596e-02 7.236e-02 0.1291274 0.2153992
## tl -4.015e-03 -5.395e-03 -7.236e-03 -0.0096846 -0.0129240
## K 1.177e-05 3.714e-05 8.846e-05 0.0001882 0.0003769
## b0 5.488e-01 4.966e-01 4.493e-01 0.4065697 0.3678794
## b1 4.512e-01 5.034e-01 5.507e-01 0.5934303 0.6321206
## b2 6.000e+00 7.000e+00 8.000e+00 9.0000000 10.0000000
## k 3.260e+00 3.441e+00 3.559e+00 3.6225357 3.6420065
The Hessian \(H\) is approximately \(H \approx J^TJ\)
H <- J %*% t(J) # because linear algebra in R is a little strange, the transpose is applied to the 2nd Jacobian
The gradient is \(g = -J r\) where \(r\) are the residuals.
We can box all this into a class:
ModelObject = setRefClass('ModelObject',
fields = list(
name = 'character',
expr = 'expression'
),
methods = list(
value = function(p, data){
eval(.self$expr, c(as.list(p), as.list(data)))
},
jacobian = function(p, data){
J = t(sapply(all.vars(.self$expr), function(v, p, data){
eval(D(.self$expr, v), c(as.list(p), as.list(data)))
}, p=p, data=data))
return(J[names(p),,drop=F])
},
gradient = function(p, data){
r = data$y - value(p, data)
return(-jacobian(p, data) %*% r)
},
hessian = function(p, data){
J = jacobian(p, data)
return(J %*% t(J))
}
)
)
So let’s make some fake data and test the model:
# the model expression
expr <- expression( (K*y0*exp(u*(x-tl)))/(K + y0*(exp(u*(x-tl))-1)) +
b1 + (b0 - b1)*exp(-k*x) + b2*x )
# make some data:
xs <- seq(0,48,by=0.25)
p0 <- c(y0=0.01, u=0.3, tl=5, K=2, b0=0.01, b1=1, b2=0.001, k=0.1) # true values of the parameters
xy <- list(x=xs,
y=eval(expr, envir=c(as.list(p0), list(x=xs)))
)
plot(xy, main='Fit Results', type="l", col="blue", lty=2, lwd=2); # target function
xy$y <- xy$y+rnorm(length(xs),0,.15) # add some noise
points(xy$x, xy$y, pch=19) # observational data
mo <- ModelObject(
name = 'our eg',
expr = expr
)
# initial values for the parameters (we are assuming that we don't know the true values)
ps <- c(y0=0.05, u=1, tl=3, K=1, b0=0.1, b1=1, b2=0.01, k=0.5)
fit <- nlminb(start = ps,
objective = function(p, data){
r = data$y - mo$value(p,data)
return(r %*% r)
},
gradient = mo$gradient,
hessian = mo$hessian,
data = xy)
lines(xy$x, mo$value(fit$par, xy), col="red", lwd=2) # model estimate
And another eg:
# make some data:
xy <- list(x=seq(0,10,by=0.25), y=dnorm(seq(0,10,by=0.25),10,2))
p0 <- c(y0=0.01, u=0.2, l=5, A=log(1.5/0.01))
mo <- ModelObject(
name = 'gompertz',
expr = expression( y0*exp(A*exp(-exp((u*exp(1)/A)*(l-x)+1))) )
)
fit <- nlminb(start=p0,
objective= function(p, data){
r = data$y - mo$value(p,data)
return(r %*% r)
},
gradient = mo$gradient,
hessian = mo$hessian,
data=xy)
plot(xy, main='Fit Results', pch=19);
lines(xy$x, mo$value(fit$par, xy), col="red", lwd=2)