Creates a modified version of a log-likelihood function that always returns
either a finite value or -Inf
for each vector of parameters provided.
Usage
create_likelihood(
fn,
rowwise_fn,
...,
.nonfinite_action = c("warn", "quiet", "abort")
)
Arguments
- fn, rowwise_fn
Choose one of
fn
orrowwise_fn
:fn
: A function that takes a vector of parameters and returns a scalar likelihood value (either a finite double or-Inf
).rowwise_fn
: A function that takes a matrix of parameters and returns a vector of likelihood values (all finite doubles or-Inf
).
- ...
Named arguments to
fn
orrowwise_fn
that should be partially applied.- .nonfinite_action
(case-sensitive string) Action to perform when
fn
returns a value that is non-finite and not-Inf
(e.g.,NaN
,NA
,Inf
):"warn"
: Issue a warning and replace values with-Inf
."quiet"
: Silently replace values with-Inf
."abort"
: Stop execution and signal an error.
Value
A function with class ernest_likelihood
. This function is
wrapped in checks to ensure type and size stability:
If provided a vector of doubles, returns a scalar double,
-Inf
, or an error.If provided a matrix of doubles, returns a vector of doubles and
-Inf
of length equal to the number of matrix rows, or an error.Otherwise, it throws an error.
Details
Model likelihoods should be provided as a log-density function. The first
argument of fn
or rowwise_fn
should be a vector or matrix of parameters,
respectively.
If the model likelihood is conditional on some data, then incorporate this
data into the likelihood function here. You can either build an anonymous
function (see rlang::as_function()
), or use the ...
parameters to
partially apply data to fn
or rowwise_fn
(see purrr::partial()
).
It is expected that the log-likelihood function returns a scalar finite
double or -Inf
for each parameter vector. Non-finite values other than
-Inf
, such as NaN
, Inf
, or NA
(i.e. missing values) are handled
with the behavior of .nonfinite_action
.
Ernest will wrap fn
so it can accept a matrix of parameters. If you have
a more efficient implementation of your likelihood function that can handle
vectors and matrices, consider providing rowwise_fn
instead.
Examples
# A 3D Gaussian likelihood function
n_dim <- 3
sigma <- diag(0.95, nrow = 3)
det_sigma <- determinant(sigma, logarithm = TRUE)$modulus
attributes(det_sigma) <- NULL
prec <- solve(sigma)
log_norm <- -0.5 * (log(2 * pi) * n_dim + det_sigma)
fn <- function(theta) {
drop(-0.5 * crossprod(theta, crossprod(prec, theta)) + log_norm)
}
log_lik <- create_likelihood(fn)
log_lik(c(0, 0, 0))
#> [1] -2.679876
# Bind data to the likelihood function using dots or anonymous functions.
y <- 100000000 * runif(11, min = 0.1, max = 0.3)
log_lik <- function(theta, y) {
if (theta[2] <= 0) {
return(-Inf)
}
sum(dnorm(y, mean = theta[1], sd = theta[2], log = TRUE))
}
create_likelihood(log_lik, y = !!y)
#>
#> ── <ernest_likelihood>
#> <partialised>
#> function (...)
#> fn(y = c(11792689.0822127, 14543993.5941249, 29763387.744315,
#> 15531046.660617, 17316007.2788596, 14453893.2293654, 20176399.9927789,
#> 20279952.7114257, 25178754.7301501, 11634861.6639152, 16051442.1574771
#> ), ...)
#> <environment: 0x55f13b4d4a08>
create_likelihood(\(theta) log_lik(theta, y))
#>
#> ── <ernest_likelihood>
#> function (theta)
#> log_lik(theta, y)