Skip to contents

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 or rowwise_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 or rowwise_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)