section_fun.rmd
section_fun()
The function section_fun
is used to create a new
function that is a section of the original function. The section is
defined by fixing some of the arguments of the original function. The
new function is defined on the remaining arguments.
For example, let \(f(x,y)=x+y\). Then \(f_x(y)=f(10, y)\) is a section of \(f\) to be a function of \(y\) alone.
More generally, let \(E\) be a subset of the cartesian product \(X \times Y\) where \(X\) and \(Y\) are some sets. Consider a function \(f(x,y)\) defined on \(E\). Then for any \(x \in X\), the section of \(E\) defined by \(x\) (denoted \(E_x\)) is the set of \(y\)’s in \(Y\) such that \((x, y)\) is in \(E\), i.e. \[ E_x = \{ y \in Y | (x,y) \in E\} \]
Correspondingly, the section of \(f(x,y)\) defined by \(x\) is the function \(f_x\) defined on \(E_x\) given by \(f_x(y)=f(x,y)\).
section_fun
function in doBy
The function section_fun
is used to create a new
function that is a section of the original function. The section is
defined by fixing some of the arguments of the original function. The
new function is defined on the remaining arguments. There are the
following approaches:
Consider this function:
fun <- function(a, b, c=4, d=9){
a + b + c + d
}
fun_def <- section_fun(fun, list(b=7, d=10))
fun_def
#> function (a, c = 4, b = 7, d = 10)
#> {
#> a + b + c + d
#> }
fun_body <- section_fun(fun, list(b=7, d=10), method="sub")
fun_body
#> function (a, c = 4)
#> {
#> b = 7
#> d = 10
#> a + b + c + d
#> }
fun_env <- section_fun(fun, list(b=7, d=10), method = "env")
fun_env
#> function (a, c = 4)
#> {
#> . <- "use get_section(function_name) to see section"
#> . <- "use get_fun(function_name) to see original function"
#> args <- arg_getter()
#> do.call(fun, args)
#> }
#> <environment: 0x62ac47d31ed0>
In the last case, we can see the section and the original function definition as:
get_section(fun_env)
#> $b
#> [1] 7
#>
#> $d
#> [1] 10
## same as: attr(fun_env, "arg_env")$args
get_fun(fun_env)
#> function(a, b, c=4, d=9){
#> a + b + c + d
#> }
## same as: environment(fun_env)$fun
We get:
fun(a=10, b=7, c=5, d=10)
#> [1] 32
fun_def(a=10, c=5)
#> [1] 32
fun_body(a=10, c=5)
#> [1] 32
fun_env(a=10, c=5)
#> [1] 32
Consider a simple task: Creating and inverting Toeplitz matrices for increasing dimensions:
n <- 4
toeplitz(1:n)
#> [,1] [,2] [,3] [,4]
#> [1,] 1 2 3 4
#> [2,] 2 1 2 3
#> [3,] 3 2 1 2
#> [4,] 4 3 2 1
An implementation is
inv_toep <- function(n) {
solve(toeplitz(1:n))
}
inv_toep(4)
#> [,1] [,2] [,3] [,4]
#> [1,] -0.4 0.5 0.0 0.1
#> [2,] 0.5 -1.0 0.5 0.0
#> [3,] 0.0 0.5 -1.0 0.5
#> [4,] 0.1 0.0 0.5 -0.4
We can benchmark timing for different values of \(n\) as
library(microbenchmark)
microbenchmark(
inv_toep(4), inv_toep(8), inv_toep(16),
inv_toep(32), inv_toep(64),
times=5
)
#> Unit: microseconds
#> expr min lq mean median uq max neval cld
#> inv_toep(4) 11.0 11.1 11.9 11.1 11.6 14.9 5 a
#> inv_toep(8) 13.3 13.8 16.6 14.3 17.1 24.3 5 a
#> inv_toep(16) 19.6 20.5 238.9 20.6 26.5 1107.5 5 a
#> inv_toep(32) 44.1 44.6 45.3 44.6 45.0 48.0 5 a
#> inv_toep(64) 149.3 150.6 152.4 150.6 152.4 158.9 5 a
However, it is tedious (and hence error prone) to write these function calls.
A programmatic approach using is as follows: First create a list of sectioned functions:
n.vec <- c(3, 4, 5)
fun_list <- lapply(n.vec,
function(ni){
section_fun(inv_toep, list(n=ni))}
)
We can inspect and evaluate each / all functions as:
fun_list[[1]]
#> function (n = 3)
#> {
#> solve(toeplitz(1:n))
#> }
fun_list[[1]]()
#> [,1] [,2] [,3]
#> [1,] -0.375 0.5 0.125
#> [2,] 0.500 -1.0 0.500
#> [3,] 0.125 0.5 -0.375
To use the list of functions in connection with microbenchmark we bquote all functions using
We get:
bq_fun_list <- bquote_list(fun_list)
bq_fun_list[[1]]
#> (function (n = 3)
#> {
#> solve(toeplitz(1:n))
#> })()
## Evaluate one:
eval(bq_fun_list[[1]])
#> [,1] [,2] [,3]
#> [1,] -0.375 0.5 0.125
#> [2,] 0.500 -1.0 0.500
#> [3,] 0.125 0.5 -0.375
## Evaluate all:
## lapply(bq_fun_list, eval)
To use microbenchmark we must name the elements of the list:
names(bq_fun_list) <- n.vec
microbenchmark(
list = bq_fun_list,
times = 5
)
#> Unit: microseconds
#> expr min lq mean median uq max neval cld
#> 3 10.3 10.3 10.8 10.4 10.7 12.1 5 a
#> 4 10.6 10.6 12.1 10.9 11.4 17.1 5 a
#> 5 11.0 11.3 24.2 12.4 18.0 68.1 5 a
Running the code below provides a benchmark of the different ways of sectioning in terms of speed.
n.vec <- seq(20, 80, by=20)
fun_def <- lapply(n.vec,
function(ni){
section_fun(inv_toep, list(n=ni), method="def")}
)
fun_body <- lapply(n.vec,
function(ni){
section_fun(inv_toep, list(n=ni), method="sub")}
)
fun_env <- lapply(n.vec,
function(ni){
section_fun(inv_toep, list(n=ni), method="env")}
)
bq_fun_list <- bquote_list(c(fun_def, fun_body, fun_env))
names(bq_fun_list) <- paste0(rep(c("def", "body", "env"), each=length(n.vec)), rep(n.vec, 3))
mb <- microbenchmark(
list = bq_fun_list,
times = 2
)