This tutorial introduces you to the package sbim
and
explains how to use it using examples. This package implements parameter
inference methods for stochastic models defined implicitly by a
simulation algorithm, developed by Park, J. (2025). Scalable
simulation-based inference for implicitly defined models using a
metamodel for log-likelihood estimator . First, the methodological and
theoretical framework for inference is explained. Then how to create an
R
object that contains simulation-based log-likelihood
estimates will be explained. How to carry out a hypothesis test will be
explained first for independent and identically distributed (iid) data
using a toy example. Conducting hypothesis tests for a certain class of
models generating dependent observations will be explained next using an
example of stochastic volatility model.
This section provides a mathematical basis for the methods
implemented in the package sbim
. Further details can be
found in the article Park (2025). If you
want to learn only about how to use the package, you may skip to the next section.
We consider a collection of latent random variables X distributed according to Pθ, and partial observations Y whose conditional distributions have density g(y|x; θ). The underlying process Pθ is not assumed to have a density that can be evaluated analytically pointwise. The parameter θ may affect both the latent process X and the conditional measurement process Y given X; however, θ may comprise two components each governing the latent process or the measurement process only.
The goodness of fit to the observed data is assessed for a set of parameter values θ1, …, θM, by simulating the underlying process at the given parameter value and obtaining a log likelihood estimate. There may be duplicates among θ1 : M, meaning that independent simulations were carried out at the parameter value. The reason that estimates of the log likelihood estimates are used is that the variance of the likelihood estimate (on the natural, i.e., non-log scale) often scales exponentially with the size of the data, and the distribution of likelihood estimator is often highly skewed. However, the distribution of a log likelihood estimator is often sufficiently regular. Therefore by modeling the distribution of log likelihood estimator, one might be able to use all simulation-based log likelihood estimates for inference, rather than only a small fraction of simulations where the likelihood estimate is reasonably close to the exact likelihood.
Our approach is based on a simulation metamodel for log likelihood estimates given by $$\ell^S(\theta;y_{1:n}) \sim \mathcal N\left\{a(y_{1:n}) + b(y_{1:n})^\top \theta + \theta^\top c(y_{1:n})\theta, \, \frac{\sigma^2(y_{1:n})}{w(\theta)}\right\}$$ where ℓS(θ; y1 : n) denotes the simulation log-likelihood at θ, or the estimate of the log likelihood ℓ(θ; y1 : n) given the observations y1 : n. The simulation log-likelihood may be obtained in as simple a way as ℓS(θ; y1 : n) := g(y1 : n|X; θ) where X ∼ Pθ is a certain simulation outcome of the underlying process at θ. However, it may be obtained by using a different method, e.g., the particle filter for hidden Markov models. The mean function is a quadratic polynomial in θ. This meta model is supposed to give a local description of the distribution of the simulation log-likelihood around the maximum of the mean function. The variance depends on parameter specific value w(θ), which may be approximated to be a constant if the variance of simulation log-likelihood varies little in this local neighborhood. If simulations were carried out with different precisions at different parameter values, w(θ) may be chosen to reflect the relative differences. For example, if the simulation log-likelihoods are obtained by running the particle filter, then the variance in the log-likelihood estimate approximately scales inversely proportional to the number of particles used. In this case, w(θ) may be chosen proportional to the number of particles.
We assume that the simulation log-likelihood for each observation piece ℓS(θ; yi) is available for i ∈ 1 : n. For instance, it may be given by ℓS(θ; yi) = gi(yi|X; θ) where gi is the measurement density of the i-th observation piece and X is a simulated draw from Pθ. If the observations yi, i ∈ 1 : n are conditionally independent given X, $g(y_{1:n}|X;\theta) = \prod_{i=1}^n g_i(y_i|X;\theta)$, and thus we have $$ \ell^S(\theta; y_{1:n}) = \sum_{i=1}^n \ell^S(\theta;y_i).$$
The maximizer of the mean function μ(θ; y1 : n) := a(y1 : n) + b(y1 : n)⊤θ + θ⊤c(y1 : n)θ is called the maximum expected simulation log-likelihood estimator (MESLE) and denoted by θ̂MESLE.
Parameter inference is carried out by analyzing the distribution of the quadratic mean function μ(θ; Y1 : n) where Y1 : n are partial observations of the underlying system realized at a given parameter value θ0. We use a local approximation drawn from the following asymptotic result, which is satisfied under reasonably mild regulatory conditions. The maximizer of the mean function averaged over the data distribution under θ0 will be referred to as a simulation-based proxy. It will be denoted by 𝒥(θ0) or simply by θ* when the dependence on the true parameter value θ0 is not stressed. 𝒥(θ0) = arg maxθ𝔼X ∼ Pθ0𝔼Y|X ∼ g(⋅|X, θ0) μ(θ; Y1 : n). In general, 𝒥(θ0) ≠ θ0, but there are some simple examples where 𝒥(θ0) = θ0.
The change in the mean function μ(θ) on a $O(1/\sqrt{n})$ scale can be described by $$\mu(\theta_* + \frac{t}{\sqrt n}; Y_{1:n}) - \mu(\theta_*; Y_{1:n}) \approx S_n^\top t - \frac{1}{2} t^\top K_2 (\theta_*; \theta_0) t$$ where the difference between the left and the right hand side converges to zero in probability as n → ∞. The probabilistic statement is with respect to the randomness in the observations generated under a true parameter value denoted by θ0. The random variable Sn given by $$S_n := \frac{1}{\sqrt n} \left.\frac{\partial \mu}{\partial \theta}\right\vert_{\theta=\theta_*}(\theta; Y_{1:n}),$$ which converges in distribution to 𝒩(0, K1(θ*; θ0)), where K1(θ*; θ0) is a positive definite matrix.
The matrix K2(θ*; θ0) is defined by the in-probability limit $$-\frac{1}{n} \frac{\partial^2 \mu}{\partial \theta^2} (\theta_*; Y_{1:n}) \overset{i.p.}{\underset{n\to\infty}{\to}} K_2(\theta_*;\theta_0)$$ where Y1 : n are generated under θ0.
Inference is carried out by considering both the randomness in simulations and the randomness in observed data under a given parameter. The main tool is regression through the points (θm, ℓS(θm; y1 : n)), m ∈ 1 : M by a quadratic polynomial. When w(θm), m ∈ 1 : M are not all the same, we carry out weighted quadratic regression with weights w(θm), m ∈ 1 : M.
The package can be installed from the package author’s github
repository as follows. Doing so requires having the
devtools
package installed.
Then install the sbim
package:
The package source code may be downloaded in tar.gz format from here (not active yet, as of Dec 2023). The package can be loaded as usual:
The simulation-based inference is carried out using the
ht
function. When the parameter of interest is
one-dimensional, confidence intervals may be constructed using the
ci
function.
As an example, we consider a latent process X consisting of n iid copies of gamma random variates, denoted by $(X_1, \dots, X_n)\overset{iid}\sim \Gamma(1, \theta)$ where the shape parameter is unity and the rate parameter is θ. Partial observations Yi depend only on Xi and Poisson-distributed: Yi|Xi ∼ Pois(Xi). The observations y1 : n are generated under θ = θ0 = 1. For this model, the maximum expected simulation log-likelihood estimator (MESLE) is given by $1/\bar y = \frac{n}{\sum_{i=1}^n y_i}$, which is equal to the maximum likelihood estimator (MLE) given the observations y1 : n (see Example 2 in Park (2025) for mathematical details.) Furthermore, the simulation-based proxy 𝒥(θ0) is equal to θ0.
n <- 1000 # number of iid observations
gamma_t <- 1 # true shape parameter
theta_t <- 1 # true rate parameter
set.seed(98475)
## Marginally, observations are distributed following the negative binomial distribution.
y_o <- rnbinom(n=n, size=gamma_t, prob=theta_t/(theta_t+1)) # observed data (y_1,...,y_n).
MESLE <- gamma_t*n/sum(y_o) # MESLE for theta (= MLE)
sims_at <- seq(.8, 1.2, by=.001) # theta values at which simulations are made
llest <- sapply(sims_at, function(tht) {dpois(y_o, rgamma(n, gamma_t, rate=tht), log=TRUE)})
The llest
object is a matrix with n = 1000 rows and M = 401 column. Here M is the number of parameter values
at which simulations are carried out (i.e., the length of
sims_at
). The (i, m)-th entry of
llest
gives the simulation log-likelihood ℓS(θm; yi)
for the i-th observation piece
at θm.
We create a class simll
object which contains both the
simulation log-likelihoods (llest
) and the corresponding
parameter values (sims_at
).
Hypothesis tests can be carried out for both the MESLE and the
simulation-based proxy. Note that the MESLE is a statistic that depends
on the observed data y1 : n, like the
MLE. The MESLE needs to be estimated using simulations, because
the likelihood function is not accessible analytically. We can test
H0 : θ̂MESLE = θMESLE, 0,
H1 : θ̂MESLE ≠ θMESLE, 0
for multiple null values θMESLE, 0 simultaneously.
The null values can be passed to the ht
function as an
argument null.value
in different forms. If we test for a
single null value, it can be a vector of length d, where d is the dimension of the parameter
space (for the current example, d = 1.) If more than null values are
tested, null.value
can be either a matrix having d columns where each row gives a
null value vector, or a list of vectors of length d.
We test about the MESLE for a range of values between 0.8 and 1.2 as
well as the exact value of the MESLE (=y). Since the parameter in the
current example is one-dimensional, nulls_theta
is a
numeric vector. If it is supplied to the ht
function as-is,
ht
will think that we are testing about a single parameter
value of dimension length(nulls_theta)
, which is not what
we want. Thus we supply the null values by coercing
nulls_theta
into a list using as.list
. The
test
argument "MESLE"
indicates that we are
testing about the MESLE.
The output is a list consisting of several components. First, the
estimated regression coefficients â, b̂, ĉ, as well as the estimated error
variance σ̂2 are
given ($regression_estimates
). The simulation metamodel
defines a metamodel likelihood for the obtained simulation
log-likelihoods ℓS(θm; y1 : n).
The maximizer of this metamodel likelihood, which gives a point estimate
for the MESLE, is outputted as well
($meta_model_MLE_for_MESLE
). Next, a table consisting of
the null values and the corresponding p-values are outputted
($Hypothesis_Tests
). Finally, in order to assess the degree
to which the weighted quadratic regression was appropriate, regression
using a cubic polynomial is carried out and the p-value for the
significance of the cubic terms is given ($pval_cubic
). If
pval_cubic
is too small (say < 0.01), then the inference using the
quadratic regression may be considered as biased, and the range of
parameter values for simulation may need to be narrowed down.
ht_result
#> $regression_estimates
#> $regression_estimates$a
#> [1] -2766.172
#>
#> $regression_estimates$b
#> [,1]
#> [1,] 1436.992
#>
#> $regression_estimates$c
#> [,1]
#> [1,] -687.4277
#>
#> $regression_estimates$sigma_sq
#> [1] 3917.005
#>
#>
#> $meta_model_MLE_for_MESLE
#> [1] 1.045195
#>
#> $Hypothesis_Tests
#> MESLE_null pvalue
#> 1 0.800000 0.0019596412
#> 2 0.850000 0.0013381581
#> 3 0.900000 0.0007788392
#> 4 0.950000 0.0005697934
#> 5 1.000000 0.0223814770
#> 6 1.050000 0.8609011185
#> 7 1.100000 0.2019227109
#> 8 1.150000 0.0835907749
#> 9 1.200000 0.0497693858
#> 10 1.051525 0.8200727746
#>
#> $pval_cubic
#> [1] 0.5123048
The ci
function constructs a one-dimensional confidence
interval for the parameter.
The following figure gives the simulation log likelihoods and the
constructed confidence intervals for θ̂MESLE. The vertical
dashed line indicates the MESLE. The blue curve indicates the fitted
quadratic polynomial, and the red curve the exact log-likelihood
function with a vertical shift to facilitate ready comparison of the
curvature between the fitted curve and the exact log likelihood.
The simulation-based proxy 𝒥(θ0) for this example is
equal to θ0, the
true parameter value under which the observations were generated. We can
carry out a hypothesis test H0 : 𝒥(θ0) = θ*, 0,
H1 : 𝒥(θ0) ≠ θ*, 0
using the ht
function with the test
argument
set equal to "parameter"
(the default). We use the same set
of simulation log-likelihoods stored in s
and the same
collection of null values used for the test on the MESLE. For hypothesis
testing for the simulation-based proxy, we need to let the
ht
function know that the observations are iid so that the
uncertainty may be correctly quantified. This can be done by passing the
character string "iid"
to the case
argument.
By default, the ht
function assumes that the observations
form a serially dependent, stationary sequence, which corresponds to
case="stationary"
. This case will be discussed in the next
section.
ht_result <- ht(s, null.value=as.list(nulls_theta), test="parameter", case="iid")
ht_result
#> $regression_estimates
#> $regression_estimates$a
#> [1] -2766.172
#>
#> $regression_estimates$b
#> [,1]
#> [1,] 1436.992
#>
#> $regression_estimates$c
#> [,1]
#> [1,] -687.4277
#>
#> $regression_estimates$sigma_sq
#> [1] 3917.005
#>
#>
#> $meta_model_MLE_for_parameter
#> [1] 1.045195
#>
#> $K1
#> [,1]
#> [1,] 1.895297
#>
#> $K2
#> [,1]
#> [1,] 1.374855
#>
#> $error_variance
#> [1] 3926.798
#>
#> $Hypothesis_Tests
#> parameter_null pvalue
#> 1 0.800000 0.004063236
#> 2 0.850000 0.004471903
#> 3 0.900000 0.006806350
#> 4 0.950000 0.023845583
#> 5 1.000000 0.227609734
#> 6 1.050000 0.908901037
#> 7 1.100000 0.305120673
#> 8 1.150000 0.125547238
#> 9 1.200000 0.068807793
#> 10 1.051525 0.880937861
#>
#> $pval_cubic
#> [1] 0.5123048
When the observations are not iid, the hypothesis test on the MESLE is carried out in the same way as in the iid case, but the hypothesis test for the simulation-based parameter proxy is carried out in a different way. The reason for the difference for the non-iid case is that when estimating the unknown value of K1(θ*; θ0), the auto-correlation among $\frac{\partial \mu}{\partial \theta}(\theta_*; y_{1:n})$ need to be taken into consideration. We demonstrate hypothesis test on the simulation-based proxy for the non-iid case using the following example.
We consider a stochastic volatility model where the distribution of the log rate of return rt of a stock at time t is described by $$ r_t = e^{s_t} W_t, \quad W_t \overset{iid}{\sim} t_5, $$ where st denotes the volatility at time t and t5 the t distribution with five degrees of freedom. The distribution of the stochastic volatility process {st} is described by $$ s_t = \kappa s_{t-1} + \tau \sqrt{1-\kappa^2} V_t ~~\text{for}~ t>1, \quad s_1 = \tau V_1, \quad \quad V_t \overset{iid}{\sim} \mathcal N(0,1). $$ The rates of return rt are observed for t ∈ 1 : T where T = 1000. The stochastic volatility {st; t ∈ 1 : T} is a Markov process with partial observations {rt; t ∈ 1 : T}. We simulate the stochastic volatility process for κ = 0.8, τ = 1 and generate an observed data sequence r1 : T.
Unbliased likelihood estimates for the sequence of observations may
be obtained by the bootstrap particle filter, which uses an ensemble of
Monte Carlo draws called particles simulated under a given set of
parameters. We may use the logarithm of the obtained unbiased likelihood
estimates as the simulation log-likelihoods for inference on the
parameter proxy. The bootstrap particle filter can be run using the
package pomp
with the reasonably small amount of effort of
specifying the underlying process and the measurement process
models.
The stochastic volatility model can be defined in the
pomp
-style as follows.
# Stochastic volatility model
SV_pomp <- function(t0, times, kappa, tau, sim_seed=NULL) {
rinit <- pomp::Csnippet("
s = tau*rnorm(0,1);
")
rproc <- pomp::Csnippet("
s = kappa*s + tau*sqrt(1-kappa*kappa)*rnorm(0,1);
")
rmeas <- pomp::Csnippet("
r = exp(s)*rt(5);
")
dmeas <- pomp::Csnippet("
double logdmeas = dt(r*exp(-s), 5, 1) - s;
lik = (give_log) ? logdmeas : exp(logdmeas);
")
if (!is.null(sim_seed)) {
set.seed(sim_seed)
}
pomp::simulate(t0=t0, times=times,
rinit=rinit,
rprocess=pomp::discrete_time(rproc,delta.t=1),
rmeasure=rmeas,
dmeasure=dmeas,
params=c(kappa=kappa, tau=tau),
statenames = "s",
obsnames = "r",
paramnames = c("kappa", "tau")
)
}
The observations rt for t = 1, …, T = 500 are generated under κ = 0.8 and τ = 1. The parameter κ is constrained to be between 0 and 1, and τ has the positivity constraint. In order to remove the constraints, we estimate κ and τ on the logit and the log scale, respectively.
t0 <- 0
T <- 500
times <- 1:T
kappa_t <- 0.8
tau_t <- 1
theta_t <- c(kappa=kappa_t, tau=tau_t) # true parameter value
theta_Est <- c(kappa=logit(kappa_t), tau=log(tau_t)) # true parameter on the estimation scale
# simulate the process and generate data
SV_t <- SV_pomp(t0, times, kappa_t, tau_t, sim_seed=1554654)
r_o <- obs(SV_t) # generated observation sequence
dat_raw <- data.frame(time=time(SV_t), latent=c(states(SV_t)), obs=c(r_o))
dat <- pivot_longer(dat_raw, -time)
parnames <- c("kappa", "tau")
trans <- list(logit, log) # functions to use for transformations to the estimation scale
btrans <- list(plogis, exp) # functions for transformations back to the original scale
transnames <- c("logit(kappa)", "log(tau)")
The bootstrap particle filter was run at varied parameter values
θ = (κ, τ)
to obtain likelihood estimates using the R
package
pomp
(King, Nguyen, and Ionides
(2016), King et al. (2023)). The
logarithm of the likelihood estimates were used as the simulation
log-likelihoods ℓS(θ; r1 : T).
We first consider estimation of κ on the logit scale. Simulations are carried out at equally spaced points between the true logit(κ) value ± 1. The width for the simulation points is chosen in a way that balance the bias and the variance in the test. Specifically, it is roughly chosen such that it is as large as possible while ensuring that the p-value for the test on the cubic coefficient in the regression polynomial is distributed close to the uniform distribution.
sim_widths <- c(1, .4)
inc <- c("kappa") # parameter being estimated
parid <- which(parnames%in%inc)
M <- 100 # number of simulation points
Np <- 100 # number of particles used by the bootstrap particle filter
# simulation points are uniformly spaced between the true value +-1
sims_incl_Est <- cbind(seq(theta_Est[inc] - sim_widths[parid], theta_Est[inc] + sim_widths[parid],
length.out=M)) |> `colnames<-`(inc)
sims_at_Nat <- outer(rep(1,M), theta_t)
sims_at_Nat[,inc] <- sapply(1:length(inc), function(i) btrans[[parid[i]]](sims_incl_Est[,i]))
set.seed(729875)
llp_est <- simplify2array(lapply(1:nrow(sims_at_Nat), function(i) {
sp <- sims_at_Nat[i,]
## run the particle filter for each simulation point
pfSV <- pfilter(pomp(SV_t, params=sp), Np=Np, save.states="prediction")
## estimate the simulation log-likelihood for each r_t
lme <- sapply(saved_states(pfSV)$weights, logmeanexp)
lme
}))
ll_est <- apply(llp_est, 2, sum)
The individual simulation log likelihood ℓS(θ; rt) for each t was found by the average of the particle weights at time t of the bootstrap particle filter.
The simll
object is created, and hypothesis tests for
the parameter κ is carried out
as follows.
s <- simll(llp_est, params=sims_incl_Est)
null_values_Est <- cbind(theta_Est[inc]+sim_widths[parid]/5*(-10:10))
colnames(null_values_Est) <- inc
ht_result <- ht(s, null.value=null_values_Est, test="parameter", case="stationary")
#> Warning in ht.simll(s, null.value = null_values_Est, test = "parameter", : The
#> slope estimates at consecutive batches had significant correlation. Consider
#> manualy increasing the batch size for estimation of K1.
The scaled variance $K_1(\theta_*;\theta_0)
= \lim_{T\to\infty} \frac{1}{T} \text{Var}\left\{\frac{\partial
\mu}{\partial \theta}(\theta; r_{1:T})\right\}$ can be estimated
by dividing the observation sequence into continuous batches so that the
estimates of the slope of the function μ are almost independent between
batches. The default size of each batch is round(n^{0.4})
where n
is the number of observations. The default batch
size can be overridden by supplying the batch_size
argument. A different method for estimating K1 is to use a truncated
sum of autocovariances. This can be done by setting
K1_est_method="autocov"
.
Confidence intervals for logit(κ) can be constructed as follows.
ci_result <- ci(s, level=c(.9,.95), ci="parameter", case="stationary")
#> Warning in ci.simll(s, level = c(0.9, 0.95), ci = "parameter", case =
#> "stationary"): The slope estimates at consecutive batches had significant
#> correlation. Consider manualy increasing the batch size for estimation of K1.
regcoef_k <- ci_result$regression_estimates
# estimated expected simulation log-likelihood
eesl <- function(x) { regcoef_k$a + regcoef_k$b*x + regcoef_k$c*x^2 }
vline_names <- c("true","CI90","CI95")
dat <- data.frame(simll=ll_est)
dat[inc] <- sims_incl_Est
g1 <- ggplot(dat, aes(x=!!sym(inc), y=simll)) + geom_point() +
geom_function(fun=eesl, linetype="longdash") +
geom_vline(data=data.frame(
kind=factor(c("true",rep("CI90",2),rep("CI95",2)), levels=vline_names),
value=c(theta_Est[inc], ci_result$confidence_interval[1,2:3],
ci_result$confidence_interval[2,2:3])),
aes(xintercept=value, linetype=kind)) +
xlab(transnames[parid]) + ylab('simulation log-likelihood') +
scale_linetype_manual(name="", labels=c("true", "90%CI", "95%CI"),
values=c(true="dashed", CI90="dotdash", CI95="dotted")) +
theme(legend.position="bottom")
g1
Similarly, parameter inference for log (τ) can be carried out as follows. The simulation points are equally spaced within ±0.4 of the log of the true τ value.
sim_widths <- c(1, .4)
inc <- c("tau") # parameter being estimated
parid <- which(parnames%in%inc)
M <- 100
Np <- 100
# simulation points are uniformly spaced between the true value +-.4
sims_incl_Est <- cbind(seq(theta_Est[inc] - sim_widths[parid], theta_Est[inc] + sim_widths[parid],
length.out=M)) |> `colnames<-`(inc)
sims_at_Nat <- outer(rep(1,M), theta_t)
sims_at_Nat[,inc] <- sapply(1:length(inc), function(i) btrans[[parid[i]]](sims_incl_Est[,i]))
set.seed(729875)
llp_est <- simplify2array(lapply(1:nrow(sims_at_Nat), function(i) {
sp <- sims_at_Nat[i,]
pfSV <- pfilter(pomp(SV_t, params=sp), Np=Np, save.states="prediction")
lme <- sapply(saved_states(pfSV)$weights, logmeanexp)
lme
}))
ll_est <- apply(llp_est, 2, sum)
s <- simll(llp_est, params=sims_incl_Est)
null_values_Est <- cbind(theta_Est[inc]+sim_widths[parid]/5*(-10:10))
colnames(null_values_Est) <- inc
ht_result <- ht(s, null.value=null_values_Est, test="parameter", case="stationary")
dat <- data.frame(simll=ll_est)
dat[inc] <- sims_incl_Est
ci_result <- ci(s, level=c(.9,.95), ci="parameter", case="stationary")
regcoef_t <- ci_result$regression_estimates
eesl <- function(x) { regcoef_t$a + regcoef_t$b*x + regcoef_t$c*x^2 } # estimated expected simlation log-likelihood
vline_names <- c("true","CI90","CI95")
g2 <- ggplot(dat, aes(x=!!sym(inc), y=simll)) + geom_point() +
geom_function(fun=eesl, linetype="longdash") +
geom_vline(data=data.frame(kind=factor(c("true",rep("CI90",2),rep("CI95",2)), levels=vline_names),
value=c(theta_Est[inc], ci_result$confidence_interval[1,2:3],
ci_result$confidence_interval[2,2:3])),
aes(xintercept=value, linetype=kind)) +
scale_linetype_manual(name=NULL, labels=c("true", "90%CI", "95%CI"),
values=c(true="dashed",CI90="dotdash",CI95="dotted")) +
ylab('simulation log-likelihood') + xlab(transnames[parid]) + theme(legend.position="bottom")
g2
We can estimate both parameters (logit(κ), log (τ)) jointly. The hypothesis tests H0 : (κ*, τ*) = (κ*, 0, τ*, 0), H1 : (κ*, τ*) = (κ*, 0, τ*, 0) were carried out for varied null value pairs. The plot generated below shows the 80%, 90%, 95% confidence regions, constructed by marking the null value pairs for which the p-value is greater than 20%, 10%, and 5%, respectively. The true parameter pair is marked by an X.
sim_widths <- c(.5, .2)
inc <- c("kappa", "tau") # parameters being estimated
parid <- which(parnames%in%inc)
M <- 400
Np <- 100
sims_incl_Est <- expand.grid(seq(theta_Est[inc[1]]-sim_widths[parid[1]], theta_Est[inc[1]]+sim_widths[parid[1]], length.out=sqrt(M)), seq(theta_Est[inc[2]]-sim_widths[parid[2]], theta_Est[inc[2]]+sim_widths[parid[2]], length.out=sqrt(M))) |> as.matrix() |> `colnames<-`(inc)
sims_at_Nat <- outer(rep(1,M), theta_t)
sims_at_Nat[,inc] <- sapply(1:length(inc), function(i) btrans[[parid[i]]](sims_incl_Est[,i]))
set.seed(729875)
llp_est <- simplify2array(lapply(1:nrow(sims_at_Nat), function(i) {
sp <- sims_at_Nat[i,]
pfSV <- pfilter(pomp(SV_t, params=sp), Np=Np, save.states="prediction")
lme <- sapply(saved_states(pfSV)$weights, logmeanexp)
lme
}))
ll_est <- apply(llp_est, 2, sum)
s <- simll(llp_est, params=sims_incl_Est)
null_values_Est <- expand.grid(theta_Est[inc[1]]+sim_widths[parid[1]]/5*(-10:10), theta_Est[inc[2]]+sim_widths[parid[2]]/5*(-10:10)) |> as.matrix() |> `colnames<-`(inc)
ht_result <- ht(s, null.value=null_values_Est, test="parameter", case="stationary")
dat <- data.frame(null_values_Est)
g3 <- ggplot(dat %>% mutate(pval=ht_result$Hypothesis_Tests[,"pvalue"]) %>%
mutate(conflvl=cut(pval, breaks=c(0,0.05,0.1,0.2,1), right=FALSE)),
aes(x=!!sym(inc[1]), y=!!sym(inc[2]))) +
geom_point(aes(color=conflvl), size=0.6) +
xlab(transnames[parid[1]]) + ylab(transnames[parid[2]]) +
scale_color_manual(name="", labels=c("100%", "95%", "90%", "80%"),
values=rgb(0,0,0,c(0.05,0.25,0.5,1))) +
geom_point(aes(x=theta_Est[inc[1]], y=theta_Est[inc[2]]), shape=4, size=2) +
theme(legend.position="bottom")
g3
The test on the metamodel parameters, namely a, b, c, and σ2 H0 : (a, b, c, σ2) = (a0, b0, c0, σ02), H1 : (a, b, c, σ2) ≠ (a0, b0, c0, σ02).
can be carried out by passing the character string
"moments"
to the test
argument for the
ht
function. The null.value
argument can be
the list of the four elements in the order of (a0, b0, c0, σ02).
The second element (b0) is a vector and the
third element (c0)
is a matrix. When testing more than one null values,
null.value
should be a list of lists of length four. For
tests on the MESLE or the simulation-based proxy, the test statistic
follows the F-distribution.
The test statistic for the metamodel parameters follows what is named
the SCL distribution in Park (2025). The quantiles and the cdf of the
SCL distribution are numerically found
by drawing a number of random values from the distribution. The number
of random variables is roughly chosen such that the Monte Carlo standard
error for the obtained p-value is approximately 0.01. However, if any of
the obtained p-values is less than 0.01, a greater number of random
numbers are drawn from the SCL
distribution so that the Monte Carlo standard error of the p-value is
approximately 0.001.
null_moments <- list(a=-984, b=c(25.8,-23.7), c=matrix(c(-8.25,8,8,-93),2,2), sigsq=5)
ht(s, null.value=null_moments, test="moments")
#> $meta_model_MLE_for_moments
#> $meta_model_MLE_for_moments$a
#> [1] -982.6367
#>
#> $meta_model_MLE_for_moments$b
#> [,1]
#> [1,] 23.52540
#> [2,] -23.91113
#>
#> $meta_model_MLE_for_moments$c
#> [,1] [,2]
#> [1,] -7.310008 7.885767
#> [2,] 7.885767 -91.652205
#>
#> $meta_model_MLE_for_moments$sigma_sq
#> [1] 5.655018
#>
#>
#> $Hypothesis_Tests
#> $Hypothesis_Tests[[1]]
#> $Hypothesis_Tests[[1]]$a_null
#> [1] -984
#>
#> $Hypothesis_Tests[[1]]$b_null
#> [1] 25.8 -23.7
#>
#> $Hypothesis_Tests[[1]]$c_null
#> [,1] [,2]
#> [1,] -8.25 8
#> [2,] 8.00 -93
#>
#> $Hypothesis_Tests[[1]]$sigma_sq_null
#> [1] 5
#>
#> $Hypothesis_Tests[[1]]$pvalue
#> [1] 0.5542071
#>
#>
#>
#> $pvalue_numerical_error_size
#> [1] 0.01
#>
#> $pval_cubic
#> [1] 0.8634763
Finally, the ht
function can be used to test about the
mean and the variance of the simulation log likelihood at a
single parameter value. For this, the type
argument should be set to "point"
. When the
type
argument is not provided, its default value is
"point"
if the object passed to the s
argument
has no params
attribute and "regression"
otherwise. All hypothesis tests described in earlier sections used the
default type="regression"
.
As an example, suppose that the simulation log likelihood is normally
distributed with mean μ = 0
and variance σ2 = 1. We artificially
generate M = 50 simulation log
likelihoods from the standard normal distribution, and create a
simll
object without the params
attribute.
set.seed(1098204)
s <- simll(rnorm(50, 0, 1))
#> Simulation log likelihoods were coerced into a matrix form.
For testing H0 : (μ, σ2) = (μ0, σ02),
H1 : (μ, σ2) ≠ (μ0, σ02),
the test
argument should be "moments"
and the
type
argument should be "point"
. The
null.value
is a vector of length two (one entry for the
mean and the other for the variance of the simulation log likelihoods),
a matrix of two columns (one for the mean and the other for the
variance), or a list of vectors of length two (each entry of the list
gives a null value consisting of the mean and the variance.)
null_values <- rbind(c(0,1), c(0,0.8), c(0.2, 1))
ht(s, null.value=null_values, test="moments", type="point")
#> $meta_model_MLE_for_moments
#> mu sigma_sq
#> -0.2397480 0.6591244
#>
#> $Hypothesis_Tests
#> mu_null sigma_sq_null pvalue
#> 1 0.0 1.0 0.038825758
#> 2 0.0 0.8 0.112926136
#> 3 0.2 1.0 0.001392045
#>
#> $pvalue_numerical_error_size
#> [1] 0.001
Both the ht
and ci
functions have the
option autoAdjust
to automatically adjust the weights of
the simulation points so that the error in quadratic approximation is
small. Specifically, the weights are multiplied by discount factors
given by exp(−{q2(θ̂MESLE) − q2(θ)}/g)
where q2 is the
fitted quadratic polynomial to the simulated log-likelihoods, θ is the given simulation point, and
g is a tuning parameter. These
weight adjustments ensure that points far from θ̂MESLE have small weights
for estimating the parameter. The value of g is tuned iteratively by making
sure that the p-value for the significance of the cubic term is between
0.01 and 0.3. If the p-value is too small (less than 0.01), in order to
reduce the bias introduced, g
is decreased so that parameter estimation is carried out using
effectively only points close to the estimated MESLE where the cubic
term is negligibly small compared to the quadratic approximation.
Conversely, if the p-value is large enough (larger than 0.3), there is a
room to explore a wider region in the parameter sapce to improve
estimation efficiency. Additionally, in order to ensure that the cubic
regression can be carried out without numerical instability, g is guaranteed to not fall below a
value that makes the effective sample size (ESS) less than (d + 1)(d + 2)(d + 3)/6,
which is the number of scalar parameters estimated in cubic regression.
Here the ESS is defined as $(\sum_{m=1}^M
{w_m^{adj}})^2/(\sum_{m=1}^M {w_m^{adj}}^2)$, where wmadj,
m = 1, …, M, are the
adjusted weights.
The next simulation point for optimal estimation efficiency can be
found using the optDesign
function. This function returns a
point that could (approximately) minimize the Monte Carlo variance of
the estimate of the MESLE. If a point is too far from the estimated
MESLE, then the adjusted weight for that point could be very small
because the third order term in a cubic approximation could be
significant at that point. That point could contribute little to
decrease the Monte Carlo uncertainty in the parameter estimate.
Conversely, a point too close to the estimated MESLE does not
substantially reduce the variance of the parameter estimate either,
since it has a low leverage in polynomial regression. The point returned
by optDesign
is a near-optimum balancing these two
factors.