From 26b36ded53e13d47349b00a38b61ec209cd72f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98ystein=20S=C3=B8rensen?= Date: Thu, 19 Oct 2023 09:55:02 +0200 Subject: [PATCH 1/9] tests passing, also with lldb --- R/RcppExports.R | 188 ++++++++++++++++++++++++++++ R/factor_loadings.R | 7 +- R/formula.R | 3 +- R/galamm.R | 16 +-- R/mgcv-functions.R | 2 + R/misc.R | 1 + R/srr-stats-standards.R | 58 ++------- README.Rmd | 20 ++- README.md | 2 +- man/formula.galamm.Rd | 4 +- src/RcppExports.cpp | 4 +- src/compute_galamm.cpp | 190 ++++++++++++++++++++++++++++- src/parameters.h | 8 +- src/update_funs.h | 21 ++-- tests/testthat/test-galamm-setup.R | 5 +- vignettes-raw/lmm_factor.Rmd | 8 -- 16 files changed, 434 insertions(+), 103 deletions(-) diff --git a/R/RcppExports.R b/R/RcppExports.R index a454b457..784fe33a 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -1,6 +1,194 @@ # Generated by using Rcpp::compileAttributes() -> do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 +#' @title Set up parameter and model family +#' +#' @description +#' Templated wrapper function which sets up the necessary parameters to +#' evaluate the marginal likelihood. The template type \code{T} will typically +#' be one of \code{double}, \code{autodiff::dual1st}, and +#' \code{autodiff::dual2nd}. +#' +#' @srrstats {G1.4a} Internal function documented. +#' +#' @param y Double precision vector of response values. +#' @param trials Double precision vector with number of trials. When trials +#' are not applicable, e.g., with Gaussian or Poisson responses, this should +#' be a vector of ones. +#' @param X Fixed effect model matrix. +#' @param Zt Transpose of random effect model matrix. +#' @param Lambdat Lower Cholesky factor of random effect covariance matrix. +#' @param beta Double precision vector of fixed effects. +#' @param theta Double precision vector with the unique elements of +#' \code{Lambdat}. +#' @param theta_mapping Integer vector mapping elements of \code{theta} to the +#' positions in \code{Lambdat}. +#' @param u_init Double precision vector with initial values of random +#' effects. These random effects should be standardized. +#' @param lambda Double precision vector of factor loadings. +#' @param lambda_mapping_X Integer vector mapping elements of +#' \code{lambda} to elements of \code{X}, in row-major order. +#' @param lambda_mapping_Zt List of integer vectors mapping elements of +#' \code{lambda} to non-zero elements of \code{Zt} assuming compressed +#' sparse column format is used. If \code{lambda_mapping_Zt_covs} is of +#' length zero, then each list element in \code{lambda_mapping_Zt} should be +#' of length one, and it will then be multiplied by the corresponding element +#' of \code{Zt}. +#' @param lambda_mapping_Zt_covs List of double precision vector. Must either +#' be of length zero, or the same length as \code{lambda_mapping_Zt_covs}. +#' Each list element contains potential covariates that the elements of +#' \code{lambda_mapping_Zt} should be multiplied with. If the list is of +#' length 0, all elements of \code{lambda_mapping_Zt} are implicitly +#' multiplied by 1. +#' @param weights Double precision vector of weights, used in heteroscedastic +#' models. +#' @param weights_mapping Integer vector mapping the elements of \code{weights} +#' to the rows of \code{X}. +#' @param family Vector of strings defining the family or families. Each +#' vector element must currently be one of \code{"gaussian"}, +#' \code{"binomial"}, or \code{"poisson"}. +#' @param family_mapping Integer vector mapping elements of \code{family} to +#' the rows of \code{X}. +#' @param k Double precision vector with pre-computed constant term in the +#' log-likelihood for each element in \code{family}. +#' @param maxit_conditional_modes Integer specifying the maximum number of +#' iteration in penalized iteratively reweighted least squares algorithm +#' used to find the conditional modes of the random effects. +#' @param lossvalue_tol Double precision scalar specifying the absolute +#' convergence criterion for the penalized iteratively reweighted least +#' squares algorithm used to find the conditional modes of the random +#' effects. +#' @param reduced_hessian Boolean specifying whether the Hessian matrix of +#' second derivatives should be computed only with respect to \code{beta} +#' and \code{lambda}, in that order. This may be useful for getting a very +#' rough estimate of the inverse covariance matrix, when the full Hessian is +#' not positive definite. +#' +#' @return An \code{Rcpp::List} with the following elements. The element +#' \code{logLik} will always be there, while the other will be there or not +#' depending on the template type \code{T}. +#' * \code{logLik} Laplace approximate marginal log-likelihood at the +#' parameter values specified. +#' * \code{g} If \code{T} is \code{autodiff::dual1st} or +#' \code{autodiff::dual2nd}, the gradient is provided in this element as +#' a double precision vector. +#' * \code{H} If \code{T} is \code{autodiff::dual2nd}, the Hessian matrix +#' is provided in this element as a double precision matrix. +#' * \code{u} If \code{T} is \code{autodiff::dual2nd}, the conditional +#' modes of the standardized random effects are provided as a double +#' precision vector in this element. +#' * \code{V} If \code{T} is \code{autodiff::dual2nd}, the diagonal matrix +#' \eqn{V} with \eqn{b''(v_{i}) / \phi_{g(i)}} on the diagonal is +#' included in this element. See the paragraph below equation (13) in +#' \insertCite{sorensenLongitudinalModelingAgeDependent2023}{galamm} for +#' details. +#' * \code{phi} If \code{T} is \code{autodiff::dual2nd}, double precision +#' scalar containing the dispersion parameter of the model. +#' @noRd +NULL + +#' @title Evaluate the marginal likelihood +#' +#' @description +#' This function evaluate the Laplace approximate marginal likelihood of a +#' generalized additive latent and mixed model at a given set of parameters. +#' The code uses elements generated by \code{lme4::glFormula}, and the +#' documentation of \code{lme4} should be consulted for further details. +#' +#' @srrstats {G1.4a} Internal function documented. +#' +#' @param y Double precision vector of response values. +#' @param trials Double precision vector with number of trials. When trials +#' are not applicable, e.g., with Gaussian or Poisson responses, this should +#' be a vector of ones. +#' @param X Fixed effect model matrix. +#' @param Zt Transpose of random effect model matrix. +#' @param Lambdat Lower Cholesky factor of random effect covariance matrix. +#' @param beta Double precision vector of fixed effects. +#' @param theta Double precision vector with the unique elements of +#' \code{Lambdat}. +#' @param theta_mapping Integer vector mapping elements of \code{theta} to the +#' positions in \code{Lambdat}. +#' @param u_init Double precision vector with initial values of random +#' effects. These random effects should be standardized. +#' @param lambda Double precision vector of factor loadings. +#' @param lambda_mapping_X Integer vector mapping elements of +#' \code{lambda} to elements of \code{X}, in row-major order. +#' @param lambda_mapping_Zt List of integer vectors mapping elements of +#' \code{lambda} to non-zero elements of \code{Zt} assuming compressed +#' sparse column format is used. If \code{lambda_mapping_Zt_covs} is of +#' length zero, then each list element in \code{lambda_mapping_Zt} should be +#' of length one, and it will then be multiplied by the corresponding element +#' of \code{Zt}. +#' @param lambda_mapping_Zt_covs List of double precision vector. Must either +#' be of length zero, or the same length as \code{lambda_mapping_Zt_covs}. +#' Each list element contains potential covariates that the elements of +#' \code{lambda_mapping_Zt} should be multiplied with. If the list is of +#' length 0, all elements of \code{lambda_mapping_Zt} are implicitly +#' multiplied by 1. +#' @param weights Double precision vector of weights, used in heteroscedastic +#' models. +#' @param weights_mapping Integer vector mapping the elements of \code{weights} +#' to the rows of \code{X}. +#' @param family Vector of strings defining the family or families. Each +#' vector element must currently be one of \code{"gaussian"}, +#' \code{"binomial"}, or \code{"poisson"}. +#' @param family_mapping Integer vector mapping elements of \code{family} to +#' the rows of \code{X}. +#' @param k Double precision vector with pre-computed constant term in the +#' log-likelihood for each element in \code{family}. +#' @param maxit_conditional_modes Integer specifying the maximum number of +#' iteration in penalized iteratively reweighted least squares algorithm +#' used to find the conditional modes of the random effects. +#' @param lossvalue_tol Double precision scalar specifying the absolute +#' convergence criterion for the penalized iteratively reweighted least +#' squares algorithm used to find the conditional modes of the random +#' effects. +#' @param gradient Boolean specifying whether to compute the gradient of the +#' log-likelhood with respect to all elements of \code{theta}, \code{beta}, +#' \code{lambda}, and \code{weights}, in that order. If +#' \code{gradient = TRUE}, and \code{hessian = FALSE}, forward mode +#' automatic differentiation with first-order dual numbers are used. If also +#' \code{hessian = TRUE}, then second-order dual numbers are used instead. +#' @param hessian Boolean specifying whether to compute the Hessian matrix of +#' second derivatives of the log-likelihood with respect to all elements of +#' \code{theta}, \code{beta}, \code{lambda}, and \code{weights}, in that +#' order. If \code{hessian = TRUE}, forward mode automatic differentiation +#' with second-order dual numbers are used. +#' @param reduced_hessian Boolean specifying whether the Hessian matrix of +#' second derivatives should be computed only with respect to \code{beta} +#' and \code{lambda}, in that order. This may be useful for getting a very +#' rough estimate of the inverse covariance matrix, when the full Hessian is +#' not positive definite. +#' +#' @return An \code{Rcpp::List}, which will be converted to a \code{list} in +#' \code{R}, the following elements. The element \code{logLik} will always +#' be there, while the other will be there or not depending on arguments +#' \code{gradient} and \code{hessian}. +#' * \code{logLik} Laplace approximate marginal log-likelihood at the +#' parameter values specified. +#' * \code{g} If \code{gradient = TRUE} or \code{hessian = TRUE}, the +#' gradient is provided in this element as a double precision vector. +#' * \code{H} If \code{hessian = TRUE}, the Hessian matrix is provided in +#' this element as a double precision matrix. +#' * \code{u} If \code{hessian = TRUE}, the conditional modes of the +#' standardized random effects are provided as a double precision vector +#' in this element. +#' * \code{V} If \code{hessian = TRUE}, the diagonal matrix \eqn{V} with +#' \eqn{b''(v_{i}) / \phi_{g(i)}} on the diagonal is included in this +#' element. See the paragraph below equation (13) in +#' \insertCite{sorensenLongitudinalModelingAgeDependent2023}{galamm} for +#' details. +#' * \code{phi} If \code{hessian = TRUE}, double precision scalar containing +#' the dispersion parameter of the model. +#' +#' @details +#' For many models, not all parameters exists. For example, without +#' heteroscedastic residuals, the weights don't exist, and other models don't +#' have factor loadings. For these cases, the corresponding argument (to +#' \code{weights} or \code{lambda}) should be a correctly typed vector of +#' length zero. +#' @noRd marginal_likelihood <- function(y, trials, X, Zt, Lambdat, beta, theta, theta_mapping, u_init, lambda, lambda_mapping_X, lambda_mapping_Zt, lambda_mapping_Zt_covs, weights, weights_mapping, family, family_mapping, k, maxit_conditional_modes, lossvalue_tol, gradient, hessian, reduced_hessian = FALSE) { .Call(`_galamm_marginal_likelihood`, y, trials, X, Zt, Lambdat, beta, theta, theta_mapping, u_init, lambda, lambda_mapping_X, lambda_mapping_Zt, lambda_mapping_Zt_covs, weights, weights_mapping, family, family_mapping, k, maxit_conditional_modes, lossvalue_tol, gradient, hessian, reduced_hessian) } diff --git a/R/factor_loadings.R b/R/factor_loadings.R index 3854960d..776b8161 100644 --- a/R/factor_loadings.R +++ b/R/factor_loadings.R @@ -4,13 +4,14 @@ factor_loadings <- function(object) { #' @title Extract factor loadings from galamm object #' -#' @srrstats {G1.4} Function documented with roxygen2. -#' @srrstats {G2.1a} Expected data types provided for all inputs. -#' #' @aliases factor_loadings factor_loadings.galamm #' @export factor_loadings #' @export #' +#' @srrstats {G1.4} Function documented with roxygen2. +#' @srrstats {G2.1a} Expected data types provided for all inputs. +#' +#' #' @param object Object of class \code{galamm} returned from #' \code{\link{galamm}}. #' diff --git a/R/formula.R b/R/formula.R index a88324f9..efe5dfca 100644 --- a/R/formula.R +++ b/R/formula.R @@ -2,6 +2,7 @@ #' #' @srrstats {G1.4} Function documented with roxygen2. #' @param x Object of class \code{galamm} returned from \code{\link{galamm}}. +#' @param ... Optional arguments passed on to other methods. Currently not used. #' #' @return The formula used to fit the model. #' @export @@ -35,6 +36,6 @@ #' # Formula #' formula(mod) #' -formula.galamm <- function(x) { +formula.galamm <- function(x, ...) { x$call$formula } diff --git a/R/galamm.R b/R/galamm.R index 33c0d34f..9c93681f 100644 --- a/R/galamm.R +++ b/R/galamm.R @@ -1,8 +1,7 @@ #' @title Fit a generalized additive latent and mixed model #' -#' @srrstats {G1.4} Function documented with roxygen2. -#' @srrstats {G1.0} Primary references shown in the description. -#' @description This function fits a generalized additive latent and mixed model +#' @description +#' This function fits a generalized additive latent and mixed model #' (GALAMMs), as described in #' \insertCite{sorensenLongitudinalModelingAgeDependent2023;textual}{galamm}. #' The building blocks of these models are generalized additive mixed models @@ -24,6 +23,9 @@ #' package, which is detailed in #' \insertCite{rockwoodEstimatingComplexMeasurement2019;textual}{galamm}. #' +#' @srrstats {G1.0} Primary references shown in the description. +#' @srrstats {G1.3} Statistical terminology defined in detail in the references. +#' @srrstats {G1.4} Function documented with roxygen2. #' @srrstats {G2.3b} Arguments "family", "load.var", "factor", and the #' elements of the "start" argument are case sensitive. This is stated in the #' documentation below. @@ -273,9 +275,6 @@ galamm <- function(formula, weights = NULL, data, family = gaussian, na.action = getOption("na.action"), start = NULL, control = galamm_control()) { # Deal with potential missing values - - #' @srrstats {G2.1} Assertions on inputs implemented here. - #' if (any(vapply(data, function(x) any(is.infinite(x)), logical(1)))) { stop("Infinite values in 'data'. galamm cannot handle this.") } @@ -312,9 +311,6 @@ galamm <- function(formula, weights = NULL, data, family = gaussian, if (!is.vector(family_mapping)) { stop("family_mapping must be a vector.") } - if (is.numeric(family_mapping)) { - family_mapping <- as.integer(family_mapping) - } if (nrow(data) != length(family_mapping)) { stop("family_mapping must contain one index per row in data") @@ -419,7 +415,7 @@ galamm <- function(formula, weights = NULL, data, family = gaussian, theta_mapping = theta_mapping, u_init = u_init, lambda = par[lambda_inds], - lambda_mapping_X = lambda_mappings$lambda_mapping_X, + lambda_mapping_X = as.integer(lambda_mappings$lambda_mapping_X), lambda_mapping_Zt = lambda_mappings$lambda_mapping_Zt, lambda_mapping_Zt_covs = lambda_mappings$lambda_mapping_Zt_covs, weights = par[weights_inds], diff --git a/R/mgcv-functions.R b/R/mgcv-functions.R index be6b70a5..8a8eb739 100644 --- a/R/mgcv-functions.R +++ b/R/mgcv-functions.R @@ -283,6 +283,8 @@ variable.summary <- function(pf, dl, n) { if (v.name[i] %in% p.name) para <- TRUE else para <- FALSE x <- dl[[v.name[i]]] + #' @srrstats {G2.4d} *explicit conversion to factor via `as.factor()`* + #' @noRd if (is.character(x)) x <- as.factor(x) if (is.factor(x)) { x <- x[!is.na(x)] diff --git a/R/misc.R b/R/misc.R index 651e9871..6c4b08c6 100644 --- a/R/misc.R +++ b/R/misc.R @@ -36,6 +36,7 @@ setup_factor <- function(load.var, lambda, factor, data) { return(list(data = data, lambda = lambda)) } + eval(parse(text = paste0( "data$", load.var, "<- factor(data$", load.var, ")" diff --git a/R/srr-stats-standards.R b/R/srr-stats-standards.R index e5130185..6bf398c4 100644 --- a/R/srr-stats-standards.R +++ b/R/srr-stats-standards.R @@ -8,18 +8,26 @@ #' (These comments may be deleted at any time.) #' #' @srrstatsVerbose FALSE +#' +#' @srrstats {G1.2} Life cycle statement is in the file .github/CONTRIBUTING.md +#' @srrstatsTODO {G1.4a} *All internal (non-exported) functions should also be documented in standard [`roxygen2`](https://roxygen2.r-lib.org/) format, along with a final `@noRd` tag to suppress automatic generation of `.Rd` files.* #' @srrstatsTODO {G1.5} *Software should include all code necessary to reproduce results which form the basis of performance claims made in associated publications.* +#' @srrstatsTODO {G1.6} *Software should include code necessary to compare performance claims with alternative implementations in other R packages.* #' @srrstatsTODO {G2.0} *Implement assertions on lengths of inputs, particularly through asserting that inputs expected to be single- or multi-valued are indeed so.* #' @srrstatsTODO {G2.0a} Provide explicit secondary documentation of any expectations on lengths of inputs +#' @srrstatsTODO {G2.1} *Implement assertions on types of inputs (see the initial point on nomenclature above).* +#' @srrstatsTODO {G2.1a} *Provide explicit secondary documentation of expectations on data types of all vector inputs.* #' @srrstatsTODO {G2.2} *Appropriately prohibit or restrict submission of multivariate input to parameters expected to be univariate.* #' @srrstatsTODO {G2.3} *For univariate character input:* #' @srrstatsTODO {G2.3a} *Use `match.arg()` or equivalent where applicable to only permit expected values.* +#' @srrstatsTODO {G2.3b} *Either: use `tolower()` or equivalent to ensure input of character parameters is not case dependent; or explicitly document that parameters are strictly case-sensitive.* #' @srrstatsTODO {G2.4} *Provide appropriate mechanisms to convert between different data types, potentially including:* #' @srrstatsTODO {G2.4a} *explicit conversion to `integer` via `as.integer()`* #' @srrstatsTODO {G2.4b} *explicit conversion to continuous via `as.numeric()`* #' @srrstatsTODO {G2.4c} *explicit conversion to character via `as.character()` (and not `paste` or `paste0`)* #' @srrstatsTODO {G2.4d} *explicit conversion to factor via `as.factor()`* #' @srrstatsTODO {G2.4e} *explicit conversion from factor via `as...()` functions* +#' @srrstatsTODO {G2.5} *Where inputs are expected to be of `factor` type, secondary documentation should explicitly state whether these should be `ordered` or not, and those inputs should provide appropriate error or other routines to ensure inputs follow these expectations.* #' @srrstatsTODO {G2.6} *Software which accepts one-dimensional input should ensure values are appropriately pre-processed regardless of class structures.* #' @srrstatsTODO {G2.7} *Software should accept as input as many of the above standard tabular forms as possible, including extension to domain-specific forms.* #' @srrstatsTODO {G2.8} *Software should provide appropriate conversion or dispatch routines as part of initial pre-processing to ensure that all other sub-functions of a package receive inputs of a single defined class or type.* @@ -65,54 +73,6 @@ #' @srrstatsTODO {G5.11} *Where extended tests require large data sets or other assets, these should be provided for downloading and fetched as part of the testing workflow.* #' @srrstatsTODO {G5.11a} *When any downloads of additional data necessary for extended tests fail, the tests themselves should not fail, rather be skipped and implicitly succeed with an appropriate diagnostic message.* #' @srrstatsTODO {G5.12} *Any conditions necessary to run extended tests such as platform requirements, memory, expected runtime, and artefacts produced that may need manual inspection, should be described in developer documentation such as a `CONTRIBUTING.md` or `tests/README.md` file.* -#' @srrstatsTODO {RE1.0} *Regression Software should enable models to be specified via a formula interface, unless reasons for not doing so are explicitly documented.* -#' @srrstatsTODO {RE1.1} *Regression Software should document how formula interfaces are converted to matrix representations of input data.* -#' @srrstatsTODO {RE1.2} *Regression Software should document expected format (types or classes) for inputting predictor variables, including descriptions of types or classes which are not accepted.* -#' @srrstatsTODO {RE1.3} *Regression Software which passes or otherwise transforms aspects of input data onto output structures should ensure that those output structures retain all relevant aspects of input data, notably including row and column names, and potentially information from other `attributes()`.* -#' @srrstatsTODO {RE1.3a} *Where otherwise relevant information is not transferred, this should be explicitly documented.* -#' @srrstatsTODO {RE1.4} *Regression Software should document any assumptions made with regard to input data; for example distributional assumptions, or assumptions that predictor data have mean values of zero. Implications of violations of these assumptions should be both documented and tested.* -#' @srrstatsTODO {RE2.0} *Regression Software should document any transformations applied to input data, for example conversion of label-values to `factor`, and should provide ways to explicitly avoid any default transformations (with error or warning conditions where appropriate).* -#' @srrstatsTODO {RE2.1} *Regression Software should implement explicit parameters controlling the processing of missing values, ideally distinguishing `NA` or `NaN` values from `Inf` values (for example, through use of `na.omit()` and related functions from the `stats` package).* -#' @srrstatsTODO {RE2.2} *Regression Software should provide different options for processing missing values in predictor and response data. For example, it should be possible to fit a model with no missing predictor data in order to generate values for all associated response points, even where submitted response values may be missing.* -#' @srrstatsTODO {RE2.3} *Where applicable, Regression Software should enable data to be centred (for example, through converting to zero-mean equivalent values; or to z-scores) or offset (for example, to zero-intercept equivalent values) via additional parameters, with the effects of any such parameters clearly documented and tested.* -#' @srrstatsTODO {RE2.4} *Regression Software should implement pre-processing routines to identify whether aspects of input data are perfectly collinear, notably including:* -#' @srrstatsTODO {RE2.4a} *Perfect collinearity among predictor variables* -#' @srrstatsTODO {RE2.4b} *Perfect collinearity between independent and dependent variables* -#' @srrstatsTODO {RE3.0} *Issue appropriate warnings or other diagnostic messages for models which fail to converge.* -#' @srrstatsTODO {RE3.1} *Enable such messages to be optionally suppressed, yet should ensure that the resultant model object nevertheless includes sufficient data to identify lack of convergence.* -#' @srrstatsTODO {RE3.2} *Ensure that convergence thresholds have sensible default values, demonstrated through explicit documentation.* -#' @srrstatsTODO {RE3.3} *Allow explicit setting of convergence thresholds, unless reasons against doing so are explicitly documented.* -#' @srrstatsTODO {RE4.0} *Regression Software should return some form of "model" object, generally through using or modifying existing class structures for model objects (such as `lm`, `glm`, or model objects from other packages), or creating a new class of model objects.* -#' @srrstatsTODO {RE4.1} *Regression Software may enable an ability to generate a model object without actually fitting values. This may be useful for controlling batch processing of computationally intensive fitting algorithms.* -#' @srrstatsTODO {RE4.2} *Model coefficients (via `coef()` / `coefficients()`)* -#' @srrstatsTODO {RE4.3} *Confidence intervals on those coefficients (via `confint()`)* -#' @srrstatsTODO {RE4.4} *The specification of the model, generally as a formula (via `formula()`)* -#' @srrstatsTODO {RE4.5} *Numbers of observations submitted to model (via `nobs()`)* -#' @srrstatsTODO {RE4.6} *The variance-covariance matrix of the model parameters (via `vcov()`)* -#' @srrstatsTODO {RE4.7} *Where appropriate, convergence statistics* -#' @srrstatsTODO {RE4.8} *Response variables, and associated "metadata" where applicable.* -#' @srrstatsTODO {RE4.9} *Modelled values of response variables.* -#' @srrstatsTODO {RE4.10} *Model Residuals, including sufficient documentation to enable interpretation of residuals, and to enable users to submit residuals to their own tests.* -#' @srrstatsTODO {RE4.11} *Goodness-of-fit and other statistics associated such as effect sizes with model coefficients.* -#' @srrstatsTODO {RE4.12} *Where appropriate, functions used to transform input data, and associated inverse transform functions.* -#' @srrstatsTODO {RE4.13} *Predictor variables, and associated "metadata" where applicable.* -#' @srrstatsTODO {RE4.14} *Where possible, values should also be provided for extrapolation or forecast *errors*.* -#' @srrstatsTODO {RE4.15} *Sufficient documentation and/or testing should be provided to demonstrate that forecast errors, confidence intervals, or equivalent values increase with forecast horizons.* -#' @srrstatsTODO {RE4.16} *Regression Software which models distinct responses for different categorical groups should include the ability to submit new groups to `predict()` methods.* -#' @srrstatsTODO {RE4.17} *Model objects returned by Regression Software should implement or appropriately extend a default `print` method which provides an on-screen summary of model (input) parameters and (output) coefficients.* -#' @srrstatsTODO {RE4.18} *Regression Software may also implement `summary` methods for model objects, and in particular should implement distinct `summary` methods for any cases in which calculation of summary statistics is computationally non-trivial (for example, for bootstrapped estimates of confidence intervals).* -#' @srrstatsTODO {RE5.0} *Scaling relationships between sizes of input data (numbers of observations, with potential extension to numbers of variables/columns) and speed of algorithm.* -#' @srrstatsTODO {RE6.0} *Model objects returned by Regression Software (see* **RE4***) should have default `plot` methods, either through explicit implementation, extension of methods for existing model objects, or through ensuring default methods work appropriately.* -#' @srrstatsTODO {RE6.1} *Where the default `plot` method is **NOT** a generic `plot` method dispatched on the class of return objects (that is, through an S3-type `plot.` function or equivalent), that method dispatch (or equivalent) should nevertheless exist in order to explicitly direct users to the appropriate function.* -#' @srrstatsTODO {RE6.2} *The default `plot` method should produce a plot of the `fitted` values of the model, with optional visualisation of confidence intervals or equivalent.* -#' @srrstatsTODO {RE6.3} *Where a model object is used to generate a forecast (for example, through a `predict()` method), the default `plot` method should provide clear visual distinction between modelled (interpolated) and forecast (extrapolated) values.* -#' @srrstatsTODO {RE7.0} *Tests with noiseless, exact relationships between predictor (independent) data.* -#' @srrstatsTODO {RE7.0a} In particular, these tests should confirm ability to reject perfectly noiseless input data. -#' @srrstatsTODO {RE7.1} *Tests with noiseless, exact relationships between predictor (independent) and response (dependent) data.* -#' @srrstatsTODO {RE7.1a} *In particular, these tests should confirm that model fitting is at least as fast or (preferably) faster than testing with equivalent noisy data (see RE2.4b).* -#' @srrstatsTODO {RE7.2} Demonstrate that output objects retain aspects of input data such as row or case names (see **RE1.3**). -#' @srrstatsTODO {RE7.3} Demonstrate and test expected behaviour when objects returned from regression software are submitted to the accessor methods of **RE4.2**--**RE4.7**. -#' @srrstatsTODO {RE7.4} Extending directly from **RE4.15**, where appropriate, tests should demonstrate and confirm that forecast errors, confidence intervals, or equivalent values increase with forecast horizons. #' @noRd NULL @@ -122,7 +82,5 @@ NULL #' to `@srrstatsNA`, and placed together in this block, along with explanations #' for why each of these standards have been deemed not applicable. #' (These comments may also be deleted at any time.) -#' -#' @srrstatsNA {G2.5} Inputs are not expected to be of factor type. #' @noRd NULL diff --git a/README.Rmd b/README.Rmd index 54c2a903..d53dfad1 100644 --- a/README.Rmd +++ b/README.Rmd @@ -27,13 +27,13 @@ knitr::opts_chunk$set( [![Codecov test coverage](https://codecov.io/gh/LCBC-UiO/galamm/branch/main/graph/badge.svg)](https://app.codecov.io/gh/LCBC-UiO/galamm?branch=main) -```{r, eval=FALSE, echo=FALSE} -#' @srrstats {G1.0} Key references introduced below. -#' @srrstats {G1.1} The paragraph below states that this is the first place where the algorithm is implemented. -#' @srrstats {G1.2} Contains repo status badge. -#' @srrstats {G1.3} All statistical terminology clarified and defined in vignettes, as well as in more detail in the open access Psychometrika paper (Sørensen, Fjell, and Walhovd, 2023) which is the key reference. +```{r srr-tags1, eval = FALSE, echo = FALSE} +#' @srrstats {G1.0} Primary and secondary references to literature in the paragraph below. +#' @srrstats {G1.1} It is stated in the paragraph below that this is the first implementation of the algorithm developed in Sørensen, Fjell, and Walhovd (2023). +#' @srrstats {G1.3} Statistical terminology defined in detail in the references in the paragraph below. The main paper, Sørensen, FJell, and Walhovd (2023) is open access. ``` + galamm estimates generalized additive latent and mixed models (GALAMMs). This is the first package implementing the model framework and the computational algorithms introduced in @sorensenLongitudinalModelingAgeDependent2023. It is an extension of the GLLAMM framework for multilevel latent variable modeling detailed in @rabe-heskethGeneralizedMultilevelStructural2004 and @skrondalGeneralizedLatentVariable2004, in particular by efficiently handling crossed random effects and semiparametric estimation. ## What Can the Package Do? @@ -49,12 +49,22 @@ The goal of galamm is to enable estimation of models with an arbitrary number of - [Generalized additive mixed models with factor structures](https://lcbc-uio.github.io/galamm/articles/semiparametric.html). - [Interactions between latent and observed covariates](https://lcbc-uio.github.io/galamm/articles/latent_observed_interaction.html). +```{r srr-tags2, eval = FALSE, echo = FALSE} +#' @srrstats {G1.6} Code for comparing the performance of galamm to the of PLmixed for example models is provided in the vignette on linear mixed models with factor structures. +``` + + Random effects are defined using [lme4](https://cran.r-project.org/package=lme4) syntax, and the syntax for factor structures are close to that of [PLmixed](https://cran.r-project.org/package=PLmixed) [@rockwoodEstimatingComplexMeasurement2019]. However, for the types of models supported by both PLmixed and galamm, galamm is usually considerably faster. Smooth terms, as in generalized additive mixed models, use the same syntax as [mgcv](https://cran.r-project.org/package=mgcv). For most users, it should not be necessary to think about how the actual computations are performed, although they are detailed in the [optimization vignette](https://lcbc-uio.github.io/galamm/articles/optimization.html). In short, the core computations are done using sparse matrix methods supported by [RcppEigen](https://cran.r-project.org/package=RcppEigen) [@batesFastElegantNumerical2013] and automatic differentiation using the C++ library [autodiff](https://autodiff.github.io/) [@lealAutodiffModernFast2018]. Scaling of the algorithm is investigated further in [the vignette on computational scaling](https://lcbc-uio.github.io/galamm/articles/scaling.html). ## Where Do I Start? +```{r srr-tags3, eval = FALSE, echo = FALSE} +#' @srrstats {G1.3} Statistical terminology defined in the introductory vignette. +``` + + To get started, take a look at the [introductory vignette](https://lcbc-uio.github.io/galamm/articles/galamm.html). ## Installation diff --git a/README.md b/README.md index 9e250623..152ddd61 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,7 @@ We finally plot the estimated smooth term. plot_smooth(mod) ``` - + ## How to cite this package diff --git a/man/formula.galamm.Rd b/man/formula.galamm.Rd index 21274709..ed8e0549 100644 --- a/man/formula.galamm.Rd +++ b/man/formula.galamm.Rd @@ -4,10 +4,12 @@ \alias{formula.galamm} \title{Extract formula from fitted galamm object} \usage{ -\method{formula}{galamm}(x) +\method{formula}{galamm}(x, ...) } \arguments{ \item{x}{Object of class \code{galamm} returned from \code{\link{galamm}}.} + +\item{...}{Optional arguments passed on to other methods. Currently not used.} } \value{ The formula used to fit the model. diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 3c467dad..4358b2c1 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -12,7 +12,7 @@ Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); #endif // marginal_likelihood -Rcpp::List marginal_likelihood(const Eigen::Map y, const Eigen::Map trials, const Eigen::Map X, const Eigen::MappedSparseMatrix Zt, const Eigen::MappedSparseMatrix Lambdat, const Eigen::Map beta, const Eigen::Map theta, const std::vector theta_mapping, const Eigen::Map u_init, const Eigen::Map lambda, Rcpp::ListOf lambda_mapping_X, Rcpp::ListOf lambda_mapping_Zt, Rcpp::ListOf lambda_mapping_Zt_covs, const Eigen::Map weights, const std::vector weights_mapping, const std::vector family, const Eigen::Map family_mapping, const Eigen::Map k, const int maxit_conditional_modes, const double lossvalue_tol, const bool gradient, const bool hessian, bool reduced_hessian); +Rcpp::List marginal_likelihood(const Eigen::Map y, const Eigen::Map trials, const Eigen::Map X, const Eigen::MappedSparseMatrix Zt, const Eigen::MappedSparseMatrix Lambdat, const Eigen::Map beta, const Eigen::Map theta, const std::vector theta_mapping, const Eigen::Map u_init, const Eigen::Map lambda, const Eigen::Map lambda_mapping_X, Rcpp::ListOf lambda_mapping_Zt, Rcpp::ListOf lambda_mapping_Zt_covs, const Eigen::Map weights, const std::vector weights_mapping, const std::vector family, const Eigen::Map family_mapping, const Eigen::Map k, const int maxit_conditional_modes, const double lossvalue_tol, const bool gradient, const bool hessian, bool reduced_hessian); RcppExport SEXP _galamm_marginal_likelihood(SEXP ySEXP, SEXP trialsSEXP, SEXP XSEXP, SEXP ZtSEXP, SEXP LambdatSEXP, SEXP betaSEXP, SEXP thetaSEXP, SEXP theta_mappingSEXP, SEXP u_initSEXP, SEXP lambdaSEXP, SEXP lambda_mapping_XSEXP, SEXP lambda_mapping_ZtSEXP, SEXP lambda_mapping_Zt_covsSEXP, SEXP weightsSEXP, SEXP weights_mappingSEXP, SEXP familySEXP, SEXP family_mappingSEXP, SEXP kSEXP, SEXP maxit_conditional_modesSEXP, SEXP lossvalue_tolSEXP, SEXP gradientSEXP, SEXP hessianSEXP, SEXP reduced_hessianSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; @@ -27,7 +27,7 @@ BEGIN_RCPP Rcpp::traits::input_parameter< const std::vector >::type theta_mapping(theta_mappingSEXP); Rcpp::traits::input_parameter< const Eigen::Map >::type u_init(u_initSEXP); Rcpp::traits::input_parameter< const Eigen::Map >::type lambda(lambdaSEXP); - Rcpp::traits::input_parameter< Rcpp::ListOf >::type lambda_mapping_X(lambda_mapping_XSEXP); + Rcpp::traits::input_parameter< const Eigen::Map >::type lambda_mapping_X(lambda_mapping_XSEXP); Rcpp::traits::input_parameter< Rcpp::ListOf >::type lambda_mapping_Zt(lambda_mapping_ZtSEXP); Rcpp::traits::input_parameter< Rcpp::ListOf >::type lambda_mapping_Zt_covs(lambda_mapping_Zt_covsSEXP); Rcpp::traits::input_parameter< const Eigen::Map >::type weights(weightsSEXP); diff --git a/src/compute_galamm.cpp b/src/compute_galamm.cpp index ee35eda4..4a2df4e4 100644 --- a/src/compute_galamm.cpp +++ b/src/compute_galamm.cpp @@ -128,6 +128,90 @@ logLikObject logLik( return ret; } +//' @title Set up parameter and model family +//' +//' @description +//' Templated wrapper function which sets up the necessary parameters to +//' evaluate the marginal likelihood. The template type \code{T} will typically +//' be one of \code{double}, \code{autodiff::dual1st}, and +//' \code{autodiff::dual2nd}. +//' +//' @srrstats {G1.4a} Internal function documented. +//' +//' @param y Double precision vector of response values. +//' @param trials Double precision vector with number of trials. When trials +//' are not applicable, e.g., with Gaussian or Poisson responses, this should +//' be a vector of ones. +//' @param X Fixed effect model matrix. +//' @param Zt Transpose of random effect model matrix. +//' @param Lambdat Lower Cholesky factor of random effect covariance matrix. +//' @param beta Double precision vector of fixed effects. +//' @param theta Double precision vector with the unique elements of +//' \code{Lambdat}. +//' @param theta_mapping Integer vector mapping elements of \code{theta} to the +//' positions in \code{Lambdat}. +//' @param u_init Double precision vector with initial values of random +//' effects. These random effects should be standardized. +//' @param lambda Double precision vector of factor loadings. +//' @param lambda_mapping_X Integer vector mapping elements of +//' \code{lambda} to elements of \code{X}, in row-major order. +//' @param lambda_mapping_Zt List of integer vectors mapping elements of +//' \code{lambda} to non-zero elements of \code{Zt} assuming compressed +//' sparse column format is used. If \code{lambda_mapping_Zt_covs} is of +//' length zero, then each list element in \code{lambda_mapping_Zt} should be +//' of length one, and it will then be multiplied by the corresponding element +//' of \code{Zt}. +//' @param lambda_mapping_Zt_covs List of double precision vector. Must either +//' be of length zero, or the same length as \code{lambda_mapping_Zt_covs}. +//' Each list element contains potential covariates that the elements of +//' \code{lambda_mapping_Zt} should be multiplied with. If the list is of +//' length 0, all elements of \code{lambda_mapping_Zt} are implicitly +//' multiplied by 1. +//' @param weights Double precision vector of weights, used in heteroscedastic +//' models. +//' @param weights_mapping Integer vector mapping the elements of \code{weights} +//' to the rows of \code{X}. +//' @param family Vector of strings defining the family or families. Each +//' vector element must currently be one of \code{"gaussian"}, +//' \code{"binomial"}, or \code{"poisson"}. +//' @param family_mapping Integer vector mapping elements of \code{family} to +//' the rows of \code{X}. +//' @param k Double precision vector with pre-computed constant term in the +//' log-likelihood for each element in \code{family}. +//' @param maxit_conditional_modes Integer specifying the maximum number of +//' iteration in penalized iteratively reweighted least squares algorithm +//' used to find the conditional modes of the random effects. +//' @param lossvalue_tol Double precision scalar specifying the absolute +//' convergence criterion for the penalized iteratively reweighted least +//' squares algorithm used to find the conditional modes of the random +//' effects. +//' @param reduced_hessian Boolean specifying whether the Hessian matrix of +//' second derivatives should be computed only with respect to \code{beta} +//' and \code{lambda}, in that order. This may be useful for getting a very +//' rough estimate of the inverse covariance matrix, when the full Hessian is +//' not positive definite. +//' +//' @return An \code{Rcpp::List} with the following elements. The element +//' \code{logLik} will always be there, while the other will be there or not +//' depending on the template type \code{T}. +//' * \code{logLik} Laplace approximate marginal log-likelihood at the +//' parameter values specified. +//' * \code{g} If \code{T} is \code{autodiff::dual1st} or +//' \code{autodiff::dual2nd}, the gradient is provided in this element as +//' a double precision vector. +//' * \code{H} If \code{T} is \code{autodiff::dual2nd}, the Hessian matrix +//' is provided in this element as a double precision matrix. +//' * \code{u} If \code{T} is \code{autodiff::dual2nd}, the conditional +//' modes of the standardized random effects are provided as a double +//' precision vector in this element. +//' * \code{V} If \code{T} is \code{autodiff::dual2nd}, the diagonal matrix +//' \eqn{V} with \eqn{b''(v_{i}) / \phi_{g(i)}} on the diagonal is +//' included in this element. See the paragraph below equation (13) in +//' \insertCite{sorensenLongitudinalModelingAgeDependent2023}{galamm} for +//' details. +//' * \code{phi} If \code{T} is \code{autodiff::dual2nd}, double precision +//' scalar containing the dispersion parameter of the model. +//' @noRd template Rcpp::List wrapper( const Eigen::VectorXd& y, @@ -140,7 +224,7 @@ Rcpp::List wrapper( const std::vector& theta_mapping, const Eigen::VectorXd& u_init, const Eigen::VectorXd& lambda, - const Rcpp::ListOf& lambda_mapping_X, + const Eigen::VectorXi& lambda_mapping_X, const Rcpp::ListOf& lambda_mapping_Zt, const Rcpp::ListOf& lambda_mapping_Zt_covs, const Eigen::VectorXd& weights, @@ -191,6 +275,108 @@ Rcpp::List wrapper( } +//' @title Evaluate the marginal likelihood +//' +//' @description +//' This function evaluate the Laplace approximate marginal likelihood of a +//' generalized additive latent and mixed model at a given set of parameters. +//' The code uses elements generated by \code{lme4::glFormula}, and the +//' documentation of \code{lme4} should be consulted for further details. +//' +//' @srrstats {G1.4a} Internal function documented. +//' +//' @param y Double precision vector of response values. +//' @param trials Double precision vector with number of trials. When trials +//' are not applicable, e.g., with Gaussian or Poisson responses, this should +//' be a vector of ones. +//' @param X Fixed effect model matrix. +//' @param Zt Transpose of random effect model matrix. +//' @param Lambdat Lower Cholesky factor of random effect covariance matrix. +//' @param beta Double precision vector of fixed effects. +//' @param theta Double precision vector with the unique elements of +//' \code{Lambdat}. +//' @param theta_mapping Integer vector mapping elements of \code{theta} to the +//' positions in \code{Lambdat}. +//' @param u_init Double precision vector with initial values of random +//' effects. These random effects should be standardized. +//' @param lambda Double precision vector of factor loadings. +//' @param lambda_mapping_X Integer vector mapping elements of +//' \code{lambda} to elements of \code{X}, in row-major order. +//' @param lambda_mapping_Zt List of integer vectors mapping elements of +//' \code{lambda} to non-zero elements of \code{Zt} assuming compressed +//' sparse column format is used. If \code{lambda_mapping_Zt_covs} is of +//' length zero, then each list element in \code{lambda_mapping_Zt} should be +//' of length one, and it will then be multiplied by the corresponding element +//' of \code{Zt}. +//' @param lambda_mapping_Zt_covs List of double precision vector. Must either +//' be of length zero, or the same length as \code{lambda_mapping_Zt_covs}. +//' Each list element contains potential covariates that the elements of +//' \code{lambda_mapping_Zt} should be multiplied with. If the list is of +//' length 0, all elements of \code{lambda_mapping_Zt} are implicitly +//' multiplied by 1. +//' @param weights Double precision vector of weights, used in heteroscedastic +//' models. +//' @param weights_mapping Integer vector mapping the elements of \code{weights} +//' to the rows of \code{X}. +//' @param family Vector of strings defining the family or families. Each +//' vector element must currently be one of \code{"gaussian"}, +//' \code{"binomial"}, or \code{"poisson"}. +//' @param family_mapping Integer vector mapping elements of \code{family} to +//' the rows of \code{X}. +//' @param k Double precision vector with pre-computed constant term in the +//' log-likelihood for each element in \code{family}. +//' @param maxit_conditional_modes Integer specifying the maximum number of +//' iteration in penalized iteratively reweighted least squares algorithm +//' used to find the conditional modes of the random effects. +//' @param lossvalue_tol Double precision scalar specifying the absolute +//' convergence criterion for the penalized iteratively reweighted least +//' squares algorithm used to find the conditional modes of the random +//' effects. +//' @param gradient Boolean specifying whether to compute the gradient of the +//' log-likelhood with respect to all elements of \code{theta}, \code{beta}, +//' \code{lambda}, and \code{weights}, in that order. If +//' \code{gradient = TRUE}, and \code{hessian = FALSE}, forward mode +//' automatic differentiation with first-order dual numbers are used. If also +//' \code{hessian = TRUE}, then second-order dual numbers are used instead. +//' @param hessian Boolean specifying whether to compute the Hessian matrix of +//' second derivatives of the log-likelihood with respect to all elements of +//' \code{theta}, \code{beta}, \code{lambda}, and \code{weights}, in that +//' order. If \code{hessian = TRUE}, forward mode automatic differentiation +//' with second-order dual numbers are used. +//' @param reduced_hessian Boolean specifying whether the Hessian matrix of +//' second derivatives should be computed only with respect to \code{beta} +//' and \code{lambda}, in that order. This may be useful for getting a very +//' rough estimate of the inverse covariance matrix, when the full Hessian is +//' not positive definite. +//' +//' @return An \code{Rcpp::List}, which will be converted to a \code{list} in +//' \code{R}, the following elements. The element \code{logLik} will always +//' be there, while the other will be there or not depending on arguments +//' \code{gradient} and \code{hessian}. +//' * \code{logLik} Laplace approximate marginal log-likelihood at the +//' parameter values specified. +//' * \code{g} If \code{gradient = TRUE} or \code{hessian = TRUE}, the +//' gradient is provided in this element as a double precision vector. +//' * \code{H} If \code{hessian = TRUE}, the Hessian matrix is provided in +//' this element as a double precision matrix. +//' * \code{u} If \code{hessian = TRUE}, the conditional modes of the +//' standardized random effects are provided as a double precision vector +//' in this element. +//' * \code{V} If \code{hessian = TRUE}, the diagonal matrix \eqn{V} with +//' \eqn{b''(v_{i}) / \phi_{g(i)}} on the diagonal is included in this +//' element. See the paragraph below equation (13) in +//' \insertCite{sorensenLongitudinalModelingAgeDependent2023}{galamm} for +//' details. +//' * \code{phi} If \code{hessian = TRUE}, double precision scalar containing +//' the dispersion parameter of the model. +//' +//' @details +//' For many models, not all parameters exists. For example, without +//' heteroscedastic residuals, the weights don't exist, and other models don't +//' have factor loadings. For these cases, the corresponding argument (to +//' \code{weights} or \code{lambda}) should be a correctly typed vector of +//' length zero. +//' @noRd // [[Rcpp::export]] Rcpp::List marginal_likelihood( const Eigen::Map y, @@ -203,7 +389,7 @@ Rcpp::List marginal_likelihood( const std::vector theta_mapping, const Eigen::Map u_init, const Eigen::Map lambda, - Rcpp::ListOf lambda_mapping_X, + const Eigen::Map lambda_mapping_X, Rcpp::ListOf lambda_mapping_Zt, Rcpp::ListOf lambda_mapping_Zt_covs, const Eigen::Map weights, diff --git a/src/parameters.h b/src/parameters.h index 6c0f4895..643a4c3e 100644 --- a/src/parameters.h +++ b/src/parameters.h @@ -14,7 +14,7 @@ struct parameters{ const Eigen::VectorXd& lambda, const Eigen::VectorXd& u, const std::vector& theta_mapping, - const Rcpp::ListOf& lambda_mapping_X0, + const Eigen::VectorXi& lambda_mapping_X, const Rcpp::ListOf& lambda_mapping_Zt0, const Rcpp::ListOf& lambda_mapping_Zt_covs0, const Eigen::SparseMatrix& Lambdat, @@ -30,6 +30,7 @@ struct parameters{ lambda { lambda.cast() }, u { u.cast() }, theta_mapping { theta_mapping }, + lambda_mapping_X { lambda_mapping_X }, Lambdat { Lambdat.cast() }, weights { weights.cast() }, weights_mapping { weights_mapping }, @@ -38,9 +39,6 @@ struct parameters{ lossvalue_tol { lossvalue_tol }, n { n } { - for(int i{}; i < lambda_mapping_X0.size(); i++){ - lambda_mapping_X.push_back(Rcpp::as>(lambda_mapping_X0[i])); - } for(int i{}; i < lambda_mapping_Zt0.size(); i++){ lambda_mapping_Zt.push_back(Rcpp::as>(lambda_mapping_Zt0[i])); } @@ -57,7 +55,7 @@ struct parameters{ Vdual lambda; Vdual u; std::vector theta_mapping; - std::vector> lambda_mapping_X = {}; + Eigen::VectorXi lambda_mapping_X = {}; std::vector> lambda_mapping_Zt = {}; std::vector> lambda_mapping_Zt_covs = {}; Eigen::SparseMatrix Lambdat; diff --git a/src/update_funs.h b/src/update_funs.h index 6b16c2ec..0d5673a8 100644 --- a/src/update_funs.h +++ b/src/update_funs.h @@ -23,25 +23,20 @@ void update_Lambdat(SpMdual& Lambdat, Vdual theta, template void update_X(Mdual& X, const Vdual& lambda, - const std::vector>& lambda_mapping_X){ + const Eigen::VectorXi& lambda_mapping_X){ if(lambda_mapping_X.size() == 0) return; if(lambda_mapping_X.size() != X.size()) Rcpp::stop("Mismatch in lambda_mapping_X size."); for(size_t i{}; i < lambda_mapping_X.size(); i++){ - std::vector newinds = lambda_mapping_X[i]; + int newind = lambda_mapping_X[i]; T loading{0}; bool update{false}; - int j{0}; - - for(int newind : newinds){ - if(newind == -2){ - loading = 0; - update = true; - } else if(newind >= 0){ - loading += lambda(newind); - update = true; - } - j++; + if(newind == -2) { + loading = 0; + update = true; + } else if(newind >= 0) { + loading += lambda(newind); + update = true; } if(update){ diff --git a/tests/testthat/test-galamm-setup.R b/tests/testthat/test-galamm-setup.R index d3072c3b..1477b73c 100644 --- a/tests/testthat/test-galamm-setup.R +++ b/tests/testthat/test-galamm-setup.R @@ -282,7 +282,7 @@ test_that("multiple factors and factors in fixed effects are allowed", { ) kyps.model <- galamm( - esteem ~ as.factor(time) + (0 + hs | hid) + formula = esteem ~ as.factor(time) + (0 + hs | hid) + (0 + ms | mid), data = KYPSsim, factor = c("ms", "hs"), @@ -299,7 +299,8 @@ test_that("multiple factors and factors in fixed effects are allowed", { # Model with factor loading on fixed effect KYPSsim$time2 <- as.numeric(KYPSsim$time == 2) - kyps.model <- galamm(esteem ~ 1 + ms:time2 + (1 | sid), + kyps.model <- galamm( + formula = esteem ~ 1 + ms:time2 + (1 | sid), data = subset(KYPSsim, time %in% c(1, 2)), factor = "ms", load.var = "time", lambda = matrix(c(1, NA)), diff --git a/vignettes-raw/lmm_factor.Rmd b/vignettes-raw/lmm_factor.Rmd index 40d3c806..a8ed286d 100644 --- a/vignettes-raw/lmm_factor.Rmd +++ b/vignettes-raw/lmm_factor.Rmd @@ -141,10 +141,6 @@ mod <- galamm( The model could be fit with `PLmixed` using the following call with exactly the same arguments as to `galamm`. The reader is encouraged to try, and confirm that the results are essentially equivalent, but we won't run it in this vignette as it takes 5-10 minutes. -```{r, echo=FALSE, eval=FALSE} -#' @srrstats {G1.6} Users can run the code below and compare the time required. -``` - ```{r, eval=FALSE} kyps_plmixed <- PLmixed( @@ -407,10 +403,6 @@ form <- response ~ 0 + item + (0 + teacher1 + teacher2 | tch) + Using `PLmixed`, we could have estimated the model as follows, and doing it would confirm that the results are the same as with galamm. -```{r, echo=FALSE, eval=FALSE} -#' @srrstats {G1.6} Users can run the code below and compare the time required. -``` - ```{r, eval=FALSE} judge_plmixed <- PLmixed( From b2b314ac28341ce09f9eb8d4cf1c403b33d44522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98ystein=20S=C3=B8rensen?= Date: Thu, 19 Oct 2023 11:44:54 +0200 Subject: [PATCH 2/9] adding more documentation --- R/RcppExports.R | 50 ++++++++++++++++- src/compute_galamm.cpp | 51 +++++++++++++++-- src/misc.h | 124 ++++++++++++++++++++++++++++++++++++++++- src/model.h | 59 +++++++++++++++++--- src/parameters.h | 11 ++++ src/update_funs.h | 78 ++++++++++++++++++++++++++ 6 files changed, 355 insertions(+), 18 deletions(-) diff --git a/R/RcppExports.R b/R/RcppExports.R index 784fe33a..11ec925b 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -1,6 +1,50 @@ # Generated by using Rcpp::compileAttributes() -> do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 +#' Evaluate the deviance at given values of random effects +#' +#' @srrstats {G1.4a} Internal function documented. +#' +#' @param parlist An object of class \code{parameters} containing the +#' parameters at which to evaluate the marginal log-likelihood. +#' @param datlist An object of class \code{data} containing the data with +#' which to evaluate the marginal log-likelihood. +#' @param lp Vector with linear predictor values, with arguments of same +#' type as the template \code{T}. +#' @param modvec Reference to a vector of pointers to objects of class +#' \code{Model}, containing the necessary functions specific to the +#' exponential families used in the model. +#' @param solver A solver for sparse linear systems of type +#' \code{Eigen::SimplicialLDLT >}. +#' @param phi Vector of dispersion parameters, one for each model family. +#' @return Model deviance, after integrating out the random effects. This +#' corresponds to \eqn{-2} times the marginal loglikelihood. +#' @noRd +NULL + +#' @title Evaluate marginal log-likelihood +#' +#' @description +#' Implements penalized iteratively reweighted least squares for finding +#' conditional modes of random effects, and returns the resulting marginal +#' log-likelihood. The template \code{T} will typically be one of +#' \code{double}, \code{autodiff:dual1st}, or \code{autodiff::dual2nd}. +#' +#' @srrstats {G1.4a} Internal function documented. +#' +#' @param parlist An object of class \code{parameters} containing the +#' parameters at which to evaluate the marginal log-likelihood. +#' @param datlist An object of class \code{data} containing the data with +#' which to evaluate the marginal log-likelihood. +#' @param modvec Reference to a vector of pointers to objects of class +#' \code{Model}, containing the necessary functions specific to the +#' exponential families used in the model. +#' @return An object of class \code{logLikObject}. See its definition for +#' details. +#' +#' @noRd +NULL + #' @title Set up parameter and model family #' #' @description @@ -78,7 +122,7 @@ #' modes of the standardized random effects are provided as a double #' precision vector in this element. #' * \code{V} If \code{T} is \code{autodiff::dual2nd}, the diagonal matrix -#' \eqn{V} with \eqn{b''(v_{i}) / \phi_{g(i)}} on the diagonal is +#' \eqn{V} with \eqn{b''(\nu_{i}) / \phi_{g(i)}} on the diagonal is #' included in this element. See the paragraph below equation (13) in #' \insertCite{sorensenLongitudinalModelingAgeDependent2023}{galamm} for #' details. @@ -179,8 +223,8 @@ NULL #' element. See the paragraph below equation (13) in #' \insertCite{sorensenLongitudinalModelingAgeDependent2023}{galamm} for #' details. -#' * \code{phi} If \code{hessian = TRUE}, double precision scalar containing -#' the dispersion parameter of the model. +#' * \code{phi} If \code{hessian = TRUE}, double precision vector containing +#' the dispersion parameter of the model, for each model family. #' #' @details #' For many models, not all parameters exists. For example, without diff --git a/src/compute_galamm.cpp b/src/compute_galamm.cpp index 4a2df4e4..cc6d5110 100644 --- a/src/compute_galamm.cpp +++ b/src/compute_galamm.cpp @@ -9,9 +9,29 @@ using namespace autodiff; // [[Rcpp::depends(RcppEigen)]] +//' Evaluate the deviance at given values of random effects +//' +//' @srrstats {G1.4a} Internal function documented. +//' +//' @param parlist An object of class \code{parameters} containing the +//' parameters at which to evaluate the marginal log-likelihood. +//' @param datlist An object of class \code{data} containing the data with +//' which to evaluate the marginal log-likelihood. +//' @param lp Vector with linear predictor values, with arguments of same +//' type as the template \code{T}. +//' @param modvec Reference to a vector of pointers to objects of class +//' \code{Model}, containing the necessary functions specific to the +//' exponential families used in the model. +//' @param solver A solver for sparse linear systems of type +//' \code{Eigen::SimplicialLDLT >}. +//' @param phi Vector of dispersion parameters, one for each model family. +//' @return Model deviance, after integrating out the random effects. This +//' corresponds to \eqn{-2} times the marginal loglikelihood. +//' @noRd template -T loss(const parameters& parlist, const data& datlist, const Vdual& lp, - std::vector>>& modvec, ldlt& solver, Vdual& phi){ +T loss(const parameters& parlist, const data& datlist, + const Vdual& lp, std::vector>>& modvec, + ldlt& solver, Vdual& phi){ T ret{}; for(int k{}; k < modvec.size(); k++){ @@ -42,6 +62,27 @@ T loss(const parameters& parlist, const data& datlist, const Vdual& lp, } +//' @title Evaluate marginal log-likelihood +//' +//' @description +//' Implements penalized iteratively reweighted least squares for finding +//' conditional modes of random effects, and returns the resulting marginal +//' log-likelihood. The template \code{T} will typically be one of +//' \code{double}, \code{autodiff:dual1st}, or \code{autodiff::dual2nd}. +//' +//' @srrstats {G1.4a} Internal function documented. +//' +//' @param parlist An object of class \code{parameters} containing the +//' parameters at which to evaluate the marginal log-likelihood. +//' @param datlist An object of class \code{data} containing the data with +//' which to evaluate the marginal log-likelihood. +//' @param modvec Reference to a vector of pointers to objects of class +//' \code{Model}, containing the necessary functions specific to the +//' exponential families used in the model. +//' @return An object of class \code{logLikObject}. See its definition for +//' details. +//' +//' @noRd template logLikObject logLik( parameters parlist, data datlist, std::vector>>& modvec){ @@ -205,7 +246,7 @@ logLikObject logLik( //' modes of the standardized random effects are provided as a double //' precision vector in this element. //' * \code{V} If \code{T} is \code{autodiff::dual2nd}, the diagonal matrix -//' \eqn{V} with \eqn{b''(v_{i}) / \phi_{g(i)}} on the diagonal is +//' \eqn{V} with \eqn{b''(\nu_{i}) / \phi_{g(i)}} on the diagonal is //' included in this element. See the paragraph below equation (13) in //' \insertCite{sorensenLongitudinalModelingAgeDependent2023}{galamm} for //' details. @@ -367,8 +408,8 @@ Rcpp::List wrapper( //' element. See the paragraph below equation (13) in //' \insertCite{sorensenLongitudinalModelingAgeDependent2023}{galamm} for //' details. -//' * \code{phi} If \code{hessian = TRUE}, double precision scalar containing -//' the dispersion parameter of the model. +//' * \code{phi} If \code{hessian = TRUE}, double precision vector containing +//' the dispersion parameter of the model, for each model family. //' //' @details //' For many models, not all parameters exists. For example, without diff --git a/src/misc.h b/src/misc.h index 69499dd0..be0cf4a0 100644 --- a/src/misc.h +++ b/src/misc.h @@ -5,6 +5,21 @@ #include "parameters.h" #include "data.h" +//' Compute linear predictor +//' +//' Templated function evaluating the linear predictor, including random +//' effects. Template \code{T} is typically one of \code{double}, +//' \code{autodiff:dual1st}, or \code{autodiff::dual2nd}. +//' +//' @srrstats {G1.4a} Internal function documented. +//' +//' @param parlist An object of class \code{parameters} containing the +//' parameters at which to evaluate the marginal log-likelihood. +//' @param datlist An object of class \code{data} containing the data with +//' which to evaluate the marginal log-likelihood. +//' @return A scalar of the template type \code{T} containing the linear +//' predictor. +//' @noRd template Vdual linpred( const parameters& parlist, @@ -14,7 +29,28 @@ Vdual linpred( parlist.Lambdat.transpose() * parlist.u; }; -// Hessian matrix used in penalized iteratively reweighted least squares +//' Compute Hessian matrix used in penalized iteratively reweighted least +//' squares +//' +//' Templated function evaluating the Hessian used in penalized iteratively +//' reweighted least squares, as defined in the unnumbered equations below +//' equation 13 in +//' \insertCite{sorensenLongitudinalModelingAgeDependent2023}{galamm}. Template +//' \code{T} is typically one of \code{double}, \code{autodiff:dual1st}, or +//' \code{autodiff::dual2nd}. +//' +//' @srrstats {G1.4a} Internal function documented. +//' +//' @param parlist An object of class \code{parameters} containing the +//' parameters at which to evaluate the marginal log-likelihood. +//' @param datlist An object of class \code{data} containing the data with +//' which to evaluate the marginal log-likelihood. +//' @param V A diagonal matrix, whose diagonal has elements +//' \eqn{b''(\nu_{i}) / \phi_{g(i)}}. See the paragraph below equation (13) in + //' \insertCite{sorensenLongitudinalModelingAgeDependent2023}{galamm} for + //' details. +//' @return A matrix of type \code{Eigen::SparseMatrix}. +//' @noRd template SpMdual inner_hessian( const parameters& parlist, @@ -25,6 +61,26 @@ SpMdual inner_hessian( datlist.Zt.transpose() * parlist.Lambdat.transpose(); }; +//' Create object to be returned to R +//' +//' @srrstats {G1.4a} Internal function documented. +//' +//' @param fx Function object for computed the marginal log-likelihood. +//' @param gx Function object for computing the gradient of the marginal +//' log-likelihood. +//' @param parlist An object of class \code{parameters} containing the +//' parameters at which to evaluate the marginal log-likelihood. +//' @param reduced_hessian Boolean specifying whether the Hessian matrix of +//' second derivatives should be computed only with respect to \code{beta} +//' and \code{lambda}, in that order. This may be useful for getting a very +//' rough estimate of the inverse covariance matrix, when the full Hessian is +//' not positive definite. +//' +//' @return An \code{Rcpp::List} object. For this generic function, the list +//' has a single element: +//'. * \code{logLik} Laplace approximate marginal log-likelihood at the + //' parameter values specified. +//' @noRd template Rcpp::List create_result(Functor1 fx, Functor2 gx, parameters& parlist, bool reduced_hessian = false){ @@ -34,7 +90,32 @@ Rcpp::List create_result(Functor1 fx, Functor2 gx, parameters& parlist, ); } -// Specialization for dual1st, gives gradient vector +//' Create object to be returned to R +//' +//' This function is a specialization of the template function for the case +//' where \code{T} is \code{autodiff::dual1st}. +//' +//' @srrstats {G1.4a} Internal function documented. +//' +//' @param fx Function object for computed the marginal log-likelihood. +//' @param gx Function object for computing the gradient of the marginal +//' log-likelihood. +//' @param parlist An object of class \code{parameters} +//' containing the parameters at which to evaluate the marginal +//' log-likelihood. +//' @param reduced_hessian Boolean specifying whether the Hessian matrix of +//' second derivatives should be computed only with respect to \code{beta} +//' and \code{lambda}, in that order. This may be useful for getting a very +//' rough estimate of the inverse covariance matrix, when the full Hessian is +//' not positive definite. +//' +//' @return An \code{Rcpp::List} object. For this generic function, the list +//' has a single element: +//'. * \code{logLik} Laplace approximate marginal log-likelihood at the +//' parameter values specified. +//' * \code{g} The gradient of the marginal log-likelihood at the parameter +//' values specified. +//' @noRd template Rcpp::List create_result(Functor1 fx, Functor2 gx, parameters& parlist, bool reduced_hessian = false){ @@ -50,7 +131,44 @@ Rcpp::List create_result(Functor1 fx, Functor2 gx, parameters ); } -// Specialization for dual2nd, gives Hessian matrix plus some extra info +//' Create object to be returned to R +//' +//' This function is a specialization of the template function for the case +//' where \code{T} is \code{autodiff::dual2nd}. +//' +//' @srrstats {G1.4a} Internal function documented. +//' +//' @param fx Function object for computed the marginal log-likelihood. +//' @param gx Function object for computing the gradient of the marginal +//' log-likelihood. +//' @param parlist An object of class \code{parameters} +//' containing the parameters at which to evaluate the marginal +//' log-likelihood. +//' @param reduced_hessian Boolean specifying whether the Hessian matrix of +//' second derivatives should be computed only with respect to \code{beta} +//' and \code{lambda}, in that order. This may be useful for getting a very +//' rough estimate of the inverse covariance matrix, when the full Hessian is +//' not positive definite. +//' +//' @return An \code{Rcpp::List} object. For this generic function, the list +//' has a single element: +//'. * \code{logLik} Laplace approximate marginal log-likelihood at the +//' parameter values specified. +//' * \code{g} The gradient of the marginal log-likelihood at the parameter +//' values specified. +//' * \code{H} If \code{hessian = TRUE}, the Hessian matrix is provided in +//' this element as a double precision matrix. +//' * \code{u} If \code{hessian = TRUE}, the conditional modes of the +//' standardized random effects are provided as a double precision vector +//' in this element. +//' * \code{V} If \code{hessian = TRUE}, the diagonal matrix \eqn{V} with +//' \eqn{b''(v_{i}) / \phi_{g(i)}} on the diagonal is included in this +//' element. See the paragraph below equation (13) in +//' \insertCite{sorensenLongitudinalModelingAgeDependent2023}{galamm} for +//' details. +//' * \code{phi} If \code{hessian = TRUE}, double precision vector containing +//' the dispersion parameter of the model, for each model family. +//' @noRd template Rcpp::List create_result(Functor1 fx, Functor2 gx, parameters& parlist, bool reduced_hessian = false){ diff --git a/src/model.h b/src/model.h index fc3d4a27..226819e2 100644 --- a/src/model.h +++ b/src/model.h @@ -17,6 +17,19 @@ using Mdual = Eigen::Matrix; template using ldlt = Eigen::SimplicialLDLT >; +//' @name Model +//' @title Encapsulates a model +//' @description The members of this virtual class are different generalized +//' linear models. +//' @field new Constructor. +//' @field cumulant Cumulant function of the model family. +//' @field constfun Constant term in log-likelihood of the model family. +//' @field meanfun Expected value of the response at the given parameters. +//' @field get_V Extract diagonal matrix whose elements are +//' \eqn{\eqn{b''(\nu_{i}) / \phi_{g(i)}}}. +//' @field get_phi Extract dispersion parameter. +//' @srrstats {G1.4a} Internal class documented. +//' @noRd template struct Model { Model() {}; @@ -31,6 +44,22 @@ struct Model { const Vdual& y, const Ddual& WSqrt, int n) = 0; }; +//' @name Binomial +//' @title Member functions for binomial responses +//' @field new Constructor, which takes the constant term \code{k} as argument. +//' @field cumulant Cumulant function for logistic regression. +//' @field constfun Constant term in log-likelihood for logistic regression. +//' @field meanfun Expected value of the response at the given parameters. +//' Includes the number of trials, so it does NOT return a proportion. +//' @field get_V Extract diagonal matrix whose elements are +//' \eqn{\eqn{b''(\nu_{i}) / \phi_{g(i)}}}. Since \eqn{\phi = 1}, what we +//' extract here is the binomial variance function. A comment to the +//' implementation: since \code{meanfun} returns the number of expected +//' successes, \eqn{\mu(\eta) = N \exp(\eta) / (1 + \exp(\eta))}, and hence +//' \eqn{\mu'(\eta) = d''(\eta) = \mu(\eta) * (N - \mu(\eta)) / N}. +//' @field get_phi Extract dispersion parameter, which in this case equals 1. +//' @srrstats {G1.4a} Internal class documented. +//' @noRd template struct Binomial : Model { Binomial(double k) : k { static_cast(k) } {} @@ -48,11 +77,6 @@ struct Binomial : Model { return linpred.array().exp() / (1 + linpred.array().exp()) * trials.array(); }; - // Binomial variance function - // meanfun() includes the number of trials, and hence returns the number of - // expected successes, and not the expected proportion of successes. - // Thus, mu(eta) = N * exp(eta) / (1 + exp(eta)) and - // m'(eta) = d''(eta) = mu * (N - mu) / N. Vdual get_V( const Vdual& linpred, const Vdual& trials, const Ddual& WSqrt) override { @@ -67,6 +91,18 @@ struct Binomial : Model { }; }; +//' @name Gaussian +//' @title Member functions for Gaussian responses +//' @field new Constructor is inherited from the \code{Model} class, and does +//' nothing. +//' @field cumulant Cumulant function for linear regression. +//' @field constfun Constant term in log-likelihood for linear regression. +//' @field meanfun Expected value of the response at the given parameters. +//' @field get_V Extract diagonal matrix whose elements are +//' \eqn{\eqn{b''(\nu_{i}) / \phi_{g(i)}}}. +//' @field get_phi Extract dispersion parameter. +//' @srrstats {G1.4a} Internal class documented. +//' @noRd template struct Gaussian : Model { @@ -83,7 +119,6 @@ struct Gaussian : Model { return linpred; }; - // How to update diagonal variance matrix is model dependent Vdual get_V(const Vdual& linpred, const Vdual& trials, const Ddual& WSqrt) override { return WSqrt.diagonal().array().pow(2); @@ -98,6 +133,17 @@ struct Gaussian : Model { }; +//' @name Poisson +//' @title Member functions for Poisson responses +//' @field new Constructor, which takes the constant term \code{k} as argument. +//' @field cumulant Cumulant function for Poisson regression. +//' @field constfun Constant term in log-likelihood for Poisson regression. +//' @field meanfun Expected value of the response at the given parameters. +//' @field get_V Extract diagonal matrix whose elements are +//' \eqn{\eqn{b''(\nu_{i}) / \phi_{g(i)}}}. +//' @field get_phi Extract dispersion parameter, which in this case equals 1. +//' @srrstats {G1.4a} Internal class documented. +//' @noRd template struct Poisson : Model { Poisson(double k) : k {static_cast(k)} {} @@ -113,7 +159,6 @@ struct Poisson : Model { return linpred.array().exp(); }; - // How to update diagonal variance matrix is model dependent Vdual get_V( const Vdual& linpred, const Vdual& trials, const Ddual& WSqrt) override { return meanfun(linpred, trials).array(); diff --git a/src/parameters.h b/src/parameters.h index 643a4c3e..7e4c515f 100644 --- a/src/parameters.h +++ b/src/parameters.h @@ -6,6 +6,14 @@ #include #include "model.h" +//' @name parameters +//' @title Structure for keeping track of parameters +//' @description See the documentation of other functions, e.g., +//' \code{marginal_likelihood}, for an explanation of what the different +//' parameters represent. +//' @field new Constructor, which takes parameters as arguments, and then +//' if necessary converts them to the template type \code{T}. +//' @noRd template struct parameters{ parameters( @@ -68,6 +76,9 @@ struct parameters{ int n; }; +//' @name logLikObject +//' @title Structure containing values to be returned to R +//' @noRd template struct logLikObject { T logLikValue; diff --git a/src/update_funs.h b/src/update_funs.h index 0d5673a8..73f7ad0a 100644 --- a/src/update_funs.h +++ b/src/update_funs.h @@ -3,6 +3,23 @@ #include "model.h" +//' Update Cholesky factor +//' +//' Updates the Cholesky factor of the covariance matrix of the random effects +//' based on the current values of \code{theta}. Template is typically one of +//' \code{double}, \code{autodiff:dual1st}, or \code{autodiff::dual2nd}. +//' +//' @srrstats {G1.4a} Internal function documented. +//' +//' @param Lambda Lower Cholesky factor, an object of class +//' \code{Eigen::SparseMatrix}. +//' @param theta Vector of unique elements of the Cholesky factor, an object of +//' class \code{Eigen::Matrix}. +//' @param theta_mapping Integer vector mapping elements of \code{theta} to the +//' positions in \code{Lambdat}. +//' @return No return value. \code{Lambdat} is modified in-place. +//' +//' @noRd template void update_Lambdat(SpMdual& Lambdat, Vdual theta, const std::vector& theta_mapping @@ -21,6 +38,23 @@ void update_Lambdat(SpMdual& Lambdat, Vdual theta, } }; +//' Update fixed effect matrix +//' +//' Updates the fixed effect design matrix \code{X} based on the current values +//' of the factor loadings in \code{lambda}. Template is typically one of +//' \code{double}, \code{autodiff:dual1st}, or \code{autodiff::dual2nd}. +//' +//' @srrstats {G1.4a} Internal function documented. +//' +//' @param X Design matrix of class +//' \code{Eigen::Matrix}. +//' @param lambda Vector of factor loadings, of class +//' \code{Eigen::Matrix}. +//' @param lambda_mapping_X Integer vector mapping elements of +//' \code{lambda} to elements of \code{X}, in row-major order. +//' @return No return value. \code{X} is modified in-place. +//' +//' @noRd template void update_X(Mdual& X, const Vdual& lambda, const Eigen::VectorXi& lambda_mapping_X){ @@ -47,6 +81,33 @@ void update_X(Mdual& X, const Vdual& lambda, } }; +//' Update random effect matrix +//' +//' Updates the random effect design matrix \code{X} based on the current values +//' of the factor loadings in \code{lambda}. Template is typically one of +//' \code{double}, \code{autodiff:dual1st}, or \code{autodiff::dual2nd}. +//' +//' @srrstats {G1.4a} Internal function documented. +//' +//' @param Zt Transpose of design matrix for random effects, of class +//' \code{Eigen::SparseMatrix}. +//' @param lambda Vector of factor loadings, of class +//' \code{Eigen::Matrix}. +//' @param lambda_mapping_Zt Vector of integer vectors mapping elements of +//' \code{lambda} to non-zero elements of \code{Zt} assuming compressed +//' sparse column format is used. If \code{lambda_mapping_Zt_covs} is of +//' length zero, then each outer element in \code{lambda_mapping_Zt} should be +//' of length one, and it will then be multiplied by the corresponding element +//' of \code{Zt}. +//' @param lambda_mapping_Zt_covs Vectir of double precision vector. Must either +//' be of length zero, or the same length as \code{lambda_mapping_Zt_covs}. +//' Each element contains potential covariates that the elements of +//' \code{lambda_mapping_Zt} should be multiplied with. If the vector is of +//' length 0, all elements of \code{lambda_mapping_Zt} are implicitly +//' multiplied by 1. +//' @return No return value. \code{Zt} is modified in-place. +//' +//' @noRd template void update_Zt(SpMdual& Zt, const Vdual& lambda, const std::vector>& lambda_mapping_Zt, @@ -80,6 +141,23 @@ void update_Zt(SpMdual& Zt, const Vdual& lambda, } }; +//' Update weight matrix +//' +//' Updates the diagonal matrix \eqn{W} containing weights. Template is +//' typically one of \code{double}, \code{autodiff:dual1st}, or +//' \code{autodiff::dual2nd}. +//' +//' @srrstats {G1.4a} Internal function documented. +//' +//' @param WSqrt Diagonal matrix containing the square roots of the estimated +//' weights on its diagonal, of type +//' \code{Eigen::DiagonalMatrix}. +//' @param weights Vector with weights parameters. +//' @param weights_mapping Integer vector mapping the elements of \code{weights} +//' to the rows of \code{X}, or equivalents, to the diagonal of \code{WSqrt}. +//' @return No return value. \code{WSqrt} is modified in-place. +//' +//' @noRd template void update_WSqrt(Ddual& WSqrt, const Vdual& weights, const std::vector& weights_mapping){ From eee95c602b1b9ac57528cb8dff60c29dca7e6f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98ystein=20S=C3=B8rensen?= Date: Thu, 19 Oct 2023 11:51:28 +0200 Subject: [PATCH 3/9] moved test to where it belongs --- R/galamm.R | 3 --- R/srr-stats-standards.R | 5 ++-- tests/testthat/test-galamm-lmm.R | 40 ++++++++++++++++++++++++++++++ tests/testthat/test-galamm-setup.R | 39 ----------------------------- 4 files changed, 42 insertions(+), 45 deletions(-) diff --git a/R/galamm.R b/R/galamm.R index 9c93681f..e4037129 100644 --- a/R/galamm.R +++ b/R/galamm.R @@ -281,7 +281,6 @@ galamm <- function(formula, weights = NULL, data, family = gaussian, if (any(vapply(data, function(x) any(is.nan(x)), logical(1)))) { stop("NaN in 'data'. galamm cannot handle this.") } - if (!is.character(na.action)) { stop("na.action must be character") } @@ -307,11 +306,9 @@ galamm <- function(formula, weights = NULL, data, family = gaussian, if (!is.null(weights) && !methods::is(weights, "formula")) { stop("weights must be a formula") } - if (!is.vector(family_mapping)) { stop("family_mapping must be a vector.") } - if (nrow(data) != length(family_mapping)) { stop("family_mapping must contain one index per row in data") } diff --git a/R/srr-stats-standards.R b/R/srr-stats-standards.R index 6bf398c4..38bf4a53 100644 --- a/R/srr-stats-standards.R +++ b/R/srr-stats-standards.R @@ -10,9 +10,8 @@ #' @srrstatsVerbose FALSE #' #' @srrstats {G1.2} Life cycle statement is in the file .github/CONTRIBUTING.md -#' @srrstatsTODO {G1.4a} *All internal (non-exported) functions should also be documented in standard [`roxygen2`](https://roxygen2.r-lib.org/) format, along with a final `@noRd` tag to suppress automatic generation of `.Rd` files.* -#' @srrstatsTODO {G1.5} *Software should include all code necessary to reproduce results which form the basis of performance claims made in associated publications.* -#' @srrstatsTODO {G1.6} *Software should include code necessary to compare performance claims with alternative implementations in other R packages.* +#' @srrstats {G1.5} No performance claims made in associated publication. Might +#' be added in the future, if a publication is made based on this package. #' @srrstatsTODO {G2.0} *Implement assertions on lengths of inputs, particularly through asserting that inputs expected to be single- or multi-valued are indeed so.* #' @srrstatsTODO {G2.0a} Provide explicit secondary documentation of any expectations on lengths of inputs #' @srrstatsTODO {G2.1} *Implement assertions on types of inputs (see the initial point on nomenclature above).* diff --git a/tests/testthat/test-galamm-lmm.R b/tests/testthat/test-galamm-lmm.R index b1ce959e..73c709c4 100644 --- a/tests/testthat/test-galamm-lmm.R +++ b/tests/testthat/test-galamm-lmm.R @@ -400,3 +400,43 @@ test_that("Complex LMM works", { tolerance = 1e-4 ) }) + + +test_that("multiple factors in fixed effects works", { + path <- + system.file("testdata", "test_multiple_factors.rds", package = "galamm") + dat <- as.data.frame(readRDS(path)) + + lmat <- matrix(c( + 1, NA, NA, 0, 0, 0, + 0, 0, 0, 1, NA, NA + ), ncol = 2) + + mod <- galamm( + formula = y ~ 0 + x:domain1:lambda1 + x:domain2:lambda2 + + (0 + 1 | id), + data = dat, + load.var = "item", + lambda = lmat, + factor = c("lambda1", "lambda2"), + start = list( + theta = 0.744468091602185, + beta = c(1.03995169865897, 1.87422267819485), + lambda = c( + 0.478791387562245, 1.94779433858618, + 0.466484983394861, 2.02985361769537 + ), + weights = numeric(0) + ), + control = galamm_control( + optim_control = list( + FtolAbs = 1000, + FtolRel = 1000, XtolRel = 1000, + warnOnly = TRUE, xt = rep(1000, 7) + ), + method = "Nelder-Mead" + ) + ) + expect_equal(deviance(mod), 7891.36597569292, tolerance = .001) +}) + diff --git a/tests/testthat/test-galamm-setup.R b/tests/testthat/test-galamm-setup.R index 1477b73c..b1d0a021 100644 --- a/tests/testthat/test-galamm-setup.R +++ b/tests/testthat/test-galamm-setup.R @@ -329,45 +329,6 @@ test_that("functions fail when they should", { }) -test_that("multiple factors in fixed effects works", { - path <- - system.file("testdata", "test_multiple_factors.rds", package = "galamm") - dat <- as.data.frame(readRDS(path)) - - lmat <- matrix(c( - 1, NA, NA, 0, 0, 0, - 0, 0, 0, 1, NA, NA - ), ncol = 2) - - mod <- galamm( - formula = y ~ 0 + x:domain1:lambda1 + x:domain2:lambda2 + - (0 + 1 | id), - data = dat, - load.var = "item", - lambda = lmat, - factor = c("lambda1", "lambda2"), - start = list( - theta = 0.744468091602185, - beta = c(1.03995169865897, 1.87422267819485), - lambda = c( - 0.478791387562245, 1.94779433858618, - 0.466484983394861, 2.02985361769537 - ), - weights = numeric(0) - ), - control = galamm_control( - optim_control = list( - FtolAbs = 1000, - FtolRel = 1000, XtolRel = 1000, - warnOnly = TRUE, xt = rep(1000, 7) - ), - method = "Nelder-Mead" - ) - ) - expect_equal(deviance(mod), 7891.36597569292, tolerance = .001) -}) - - data("IRTsim", package = "PLmixed") IRTsub <- IRTsim[IRTsim$item < 4, ] # Select items 1-3 set.seed(12345) From fd4c9a2705195aba997943e8106e2be3ea1d7867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98ystein=20S=C3=B8rensen?= Date: Thu, 19 Oct 2023 15:01:00 +0200 Subject: [PATCH 4/9] keeping on with srr --- R/VarCorr.R | 3 +- R/confint.R | 1 + R/data.R | 16 +++ R/factor_loadings.R | 7 +- R/galamm.R | 92 ++++++++++++---- R/galamm_control.R | 3 +- R/mgcv-functions.R | 3 +- R/misc.R | 4 +- R/predict.galamm.R | 3 +- R/residuals.galamm.R | 1 + R/smooths.R | 20 ++++ R/srr-stats-standards.R | 169 ++++++++++++++++++----------- man/factor_loadings.galamm.Rd | 4 +- man/galamm.Rd | 22 ++-- man/sl.Rd | 7 ++ man/t2l.Rd | 7 ++ tests/testthat/test-galamm-glmm.R | 6 + tests/testthat/test-galamm-lmm.R | 36 +++--- tests/testthat/test-galamm-setup.R | 126 +++++++++++++++++++++ 19 files changed, 406 insertions(+), 124 deletions(-) diff --git a/R/VarCorr.R b/R/VarCorr.R index a9215b46..af4854ee 100644 --- a/R/VarCorr.R +++ b/R/VarCorr.R @@ -54,8 +54,9 @@ VarCorr.galamm <- function(x, sigma = 1, ...) { #' @title Print method for variance-covariance objects #' #' @srrstats {G1.4} Function documented with roxygen2. -#' @srrstats {G2.3b} Argument "comp" is case sensitive, as is documented here. #' @srrstats {G2.1a} Expected data types provided for all inputs. +#' @srrstats {G2.3a} match.arg() used on "comp" argument. +#' @srrstats {G2.3b} Argument "comp" is case sensitive, as is documented here. #' #' @param x An object of class \code{VarCorr.galamm}, returned from #' \code{\link{VarCorr.galamm}}. diff --git a/R/confint.R b/R/confint.R index 2fcba217..7100ff92 100644 --- a/R/confint.R +++ b/R/confint.R @@ -1,6 +1,7 @@ #' @title Confidence intervals for model parameters #' #' @srrstats {G1.4} Function documented with roxygen2. +#' @srrstats {G2.3a} match.arg() used on "method" argument. #' @srrstats {G2.3b} Arguments parm and method are case sensitive, as stated in #' their documentation. #' @srrstats {G2.1a} Expected data types provided for all inputs. diff --git a/R/data.R b/R/data.R index 459d4bf0..a4818267 100644 --- a/R/data.R +++ b/R/data.R @@ -6,6 +6,8 @@ #' \insertCite{skrondalGeneralizedLatentVariable2004;textual}{galamm}, where #' the dataset is used. #' +#' @srrstats {G5.1} Dataset used to test package is exported. +#' #' @format ## `epilep` A data frame with 236 rows and 7 columns: #' \describe{ #' \item{subj}{Subject ID.} @@ -27,6 +29,8 @@ #' Very basic mixed response dataset with one set of normally distributed #' responses and one set of binomially distributed responses. #' +#' @srrstats {G5.1} Dataset used to test package is exported. +#' #' @format ## `mresp` A data frame with 4000 rows and 5 columns: #' \describe{ #' \item{id}{Subject ID.} @@ -46,6 +50,8 @@ #' responses and one set of binomially distributed responses. The normally #' distributed response follow two different residual standard deviations. #' +#' @srrstats {G5.1} Dataset used to test package is exported. +#' #' @format ## `mresp` A data frame with 4000 rows and 5 columns: #' \describe{ #' \item{id}{Subject ID.} @@ -72,6 +78,8 @@ #' dataset is used. See also #' \insertCite{rabe-heskethCorrectingCovariateMeasurement2003;textual}{galamm}. #' +#' @srrstats {G5.1} Dataset used to test package is exported. +#' #' @format ## `diet` A data frame with 236 rows and 7 columns: #' \describe{ #' \item{id}{Subject ID.} @@ -99,6 +107,8 @@ #' Simulated dataset with residual standard deviation that varies between #' items. #' +#' @srrstats {G5.1} Dataset used to test package is exported. +#' #' @format ## `hsced` A data frame with 1200 rows and 5 columns: #' \describe{ #' \item{id}{Subject ID.} @@ -119,6 +129,8 @@ #' \insertCite{woodGeneralizedAdditiveModels2017a}{galamm}, and depend on the #' explanatory variable x. #' +#' @srrstats {G5.1} Dataset used to test package is exported. +#' #' @format ## `cognition` A data frame with 14400 rows and 7 columns: #' \describe{ #' \item{id}{Subject ID.} @@ -142,6 +154,8 @@ #' Simulated dataset for use in examples and testing with a latent covariate #' interacting with an observed covariate. #' +#' @srrstats {G5.1} Dataset used to test package is exported. +#' #' @format ## `latent_covariates` A data frame with 600 rows and 5 columns: #' \describe{ #' \item{id}{Subject ID.} @@ -166,6 +180,8 @@ #' interacting with an observed covariate. In this data, each response has been #' measured six times for each subject. #' +#' @srrstats {G5.1} Dataset used to test package is exported. +#' #' @format ## `latent_covariates_long` A data frame with 800 rows and 5 #' columns: #' \describe{ diff --git a/R/factor_loadings.R b/R/factor_loadings.R index 776b8161..c5ca9294 100644 --- a/R/factor_loadings.R +++ b/R/factor_loadings.R @@ -10,7 +10,8 @@ factor_loadings <- function(object) { #' #' @srrstats {G1.4} Function documented with roxygen2. #' @srrstats {G2.1a} Expected data types provided for all inputs. -#' +#' @srrstats {G2.4c} as.character() used to define dimnames of the returned +#' object. #' #' @param object Object of class \code{galamm} returned from #' \code{\link{galamm}}. @@ -25,8 +26,8 @@ factor_loadings <- function(object) { #' [confint.galamm()] for confidence intervals, and [coef.galamm()] for #' coefficients more generally. #' -#' @author The example for this function comes from \code{PLmixed}, with -#' authors Nicholas Rockwood and Minjeong Jeon +#' @author The example for this function comes from \code{PLmixed}, with authors +#' Nicholas Rockwood and Minjeong Jeon #' \insertCite{rockwoodEstimatingComplexMeasurement2019}{galamm}. #' #' @family details of model fit diff --git a/R/galamm.R b/R/galamm.R index e4037129..61dbc1c6 100644 --- a/R/galamm.R +++ b/R/galamm.R @@ -1,7 +1,6 @@ #' @title Fit a generalized additive latent and mixed model #' -#' @description -#' This function fits a generalized additive latent and mixed model +#' @description This function fits a generalized additive latent and mixed model #' (GALAMMs), as described in #' \insertCite{sorensenLongitudinalModelingAgeDependent2023;textual}{galamm}. #' The building blocks of these models are generalized additive mixed models @@ -26,10 +25,43 @@ #' @srrstats {G1.0} Primary references shown in the description. #' @srrstats {G1.3} Statistical terminology defined in detail in the references. #' @srrstats {G1.4} Function documented with roxygen2. -#' @srrstats {G2.3b} Arguments "family", "load.var", "factor", and the -#' elements of the "start" argument are case sensitive. This is stated in the +#' @srrstats {G2.0} Assertions on length of inputs are made in the beginning of +#' the galamm() function. +#' @srrstats {G2.0a} Secondary documentation of expectations on lengths of +#' inputs provided for the parameters in the descriptions below. This applies +#' in particular to \code{lambda}, \code{factor}, \code{load.var}, and +#' \code{factor_interactions}, as well as \code{family} and +#' \code{family_mapping}. +#' @srrstats {G2.1} Assertions on types of input implemented in galamm function. +#' @srrstats {G2.1a} Documentation on expected data types provided for all +#' inputs in the documentation below. +#' @srrstats {G2.2} Assertions on the lengths of arguments are implemented in +#' galamm. +#' @srrstats {G2.3a} match.arg() used on "na.action" argument. +#' @srrstats {G2.3b} Arguments "family", "load.var", "factor", and the elements +#' of the "start" argument are case sensitive. This is stated in the #' documentation below. -#' @srrstats {G2.1a} Expected data types provided for all inputs. +#' @srrstats {G2.4a} Internally, objects family_mapping, weights_mapping and +#' lambda_mapping_X are explicitly converted to integer using as.integer(). +#' @srrstats {G2.4b} as.numeric() used multiple places throughout the code for +#' explicitly converting to continuous. +#' @srrstats {G2.6} If \code{lambda} is provided as a vector, it will be +#' converted to a matrix with a single column, and a message will be printed. +#' @srrstats {G2.7} Both \code{tibble}s and \code{data.table}s are accepted in +#' the \code{data} argument. +#' @srrstats {G2.10} \code{drop = FALSE} is used regularly in the code, when +#' extract columns from \code{data.frame}s or \code{matrix}. +#' @srrstats {G2.13} Checks for missing data implemented in the preprocessing +#' steps of galamm. Note that in the argument \code{lambda}, \code{NA} values +#' mean that the matrix element is an unknown parameter. +#' @srrstats {G2.14a} Users can set \code{na.action = "na.fail"}. +#' @srrstats {G2.15} If \code{na.action = "na.omit"} or \code{na.action = +#' "na.exclude"}, missing values in \code{data} are explicitly removed. +#' Otherwise, if \code{na.action = "na.fail"}, missing values in \code{data} +#' cause an error. In any case, data with potential missingness are never +#' provided to any base R functions. +#' @srrstats {G2.16} \code{NaN}, \code{Inf}, or \code{-Inf} in \code{data} +#' causes an error. The same happens with such values in \code{lambda}. #' #' @param formula A formula specifying the model. Smooth terms are defined in #' the style of the \code{mgcv} and \code{gamm4} packages, see @@ -79,13 +111,17 @@ #' #' @param lambda Optional factor loading matrix. Numerical values indicate that #' the given value is fixed, while \code{NA} means that the entry is a -#' parameter to be estimated. Defaults to \code{NULL}, which means that there -#' is no factor loading matrix. +#' parameter to be estimated. Numerical values can only take the values 0 or +#' 1. The number of columns of \code{lambda} must be identical to the number +#' of elements in \code{factor}. Defaults to \code{NULL}, which means that +#' there is no factor loading matrix. If \code{lambda} is provided as a +#' vector, it will be converted to a \code{matrix} with a single column. #' #' @param factor Optional character vector whose \eqn{j}th entry corresponds to -#' the \eqn{j}th column of the corresponding matrix in \code{lambda}. Defaults -#' to \code{NULL}, which means that there are no factor loadings. Argument is -#' case sensitive. +#' the \eqn{j}th column of the corresponding matrix in \code{lambda}. The +#' number of elements in \code{factor} must be equal to the number of columns +#' in \code{lambda}. Defaults to \code{NULL}, which means that there are no +#' factor loadings. Argument is case sensitive. #' #' @param factor_interactions Optional list of length equal to the number of #' columns in \code{lambda}. Each list element should be a \code{formula} @@ -96,9 +132,9 @@ #' @param na.action Character of length one specifying a function which #' indicates what should happen when the data contains \code{NA}s. The #' defaults is set to the \code{na.action} setting of \code{options}, which -#' can be seen with \code{options("na.action")}. The other alternative is -#' \code{"na.fail"}, which means that the function fails if there as -#' \code{NA}s in \code{data}. +#' can be seen with \code{options("na.action")}. The other alternatives are +#' \code{"na.fail"} or \code{"na.exclude"}, which means that the function +#' fails if there as \code{NA}s in \code{data}. #' #' @param start Optional named list of starting values for parameters. Possible #' names of list elements are \code{"theta"}, \code{"beta"}, \code{"lambda"}, @@ -274,6 +310,8 @@ galamm <- function(formula, weights = NULL, data, family = gaussian, factor_interactions = NULL, na.action = getOption("na.action"), start = NULL, control = galamm_control()) { + + na.action <- match.arg(na.action, c("na.omit", "na.fail", "na.exclude")) # Deal with potential missing values if (any(vapply(data, function(x) any(is.infinite(x)), logical(1)))) { stop("Infinite values in 'data'. galamm cannot handle this.") @@ -288,12 +326,6 @@ galamm <- function(formula, weights = NULL, data, family = gaussian, if (nrow(data) == 0) stop("No data, nothing to do.") - if (methods::is(data, "tbl_df")) { - message("Converting tibble 'data' to data.frame.") - } - if (methods::is(data, "data.table")) { - message("Converting data.table 'data' to data.frame") - } if (!inherits(data, "data.frame")) { stop("data must be a data.frame") } @@ -329,8 +361,26 @@ galamm <- function(formula, weights = NULL, data, family = gaussian, if (!is.null(factor) && !is.character(factor)) { stop("factor must be NULL or a character vector") } - if (!is.null(lambda) && !is.matrix(lambda)) { - stop("lambda must be NULL or a matrix") + if (!is.null(lambda) && !is.numeric(lambda)) { + stop("lambda must either be NULL or a matrix or numeric vector") + } + if(!is.null(lambda) && !is.matrix(lambda)) { + lambda <- matrix(lambda, ncol = 1) + message("lambda converted to matrix with one column") + } + if(!is.null(lambda)) { + if(any(is.nan(lambda)) || any(is.infinite(lambda))) { + stop("elements of lambda can be either 0, 1, or NA") + } + if(!all(lambda[!is.na(lambda)] %in% c(0, 1))) { + stop("all non-NA values in lambda must be either 0 or 1") + } + } + if (any(vapply(list(load.var, lambda, factor), is.null, logical(1))) && + any(vapply(list(load.var, lambda, factor), + function(x) !is.null(x), logical(1)))) { + stop("load.var, lambda, and factor must either all have values or ", + "all be NULL.") } tmp <- setup_factor(load.var, lambda, factor, data) diff --git a/R/galamm_control.R b/R/galamm_control.R index e93f5a36..56e42b5d 100644 --- a/R/galamm_control.R +++ b/R/galamm_control.R @@ -1,9 +1,10 @@ #' @title Control values for galamm fit #' -#' @srrstats {G1.4} Function documented with roxygen2. #' @description This function can be called for controling the optimization #' procedure used when fitting GALAMMs using \code{\link{galamm}}. #' +#' @srrstats {G1.4} Function documented with roxygen2. +#' @srrstats {G2.3a} match.arg() used on "method" argument. #' @srrstats {G2.3b} Argument "method" is case sensitive, as documented #' below. #' diff --git a/R/mgcv-functions.R b/R/mgcv-functions.R index 8a8eb739..c86785b8 100644 --- a/R/mgcv-functions.R +++ b/R/mgcv-functions.R @@ -258,6 +258,7 @@ gam.setup <- function(formula, pterms, mf) { #' @noRd #' #' @srrstats {G1.4a} Internal function documented. +#' @srrstats {G2.4d} explicit conversion to factor via `as.factor()` #' #' @references #' \insertRef{woodGeneralizedAdditiveModels2017a}{galamm} @@ -283,8 +284,6 @@ variable.summary <- function(pf, dl, n) { if (v.name[i] %in% p.name) para <- TRUE else para <- FALSE x <- dl[[v.name[i]]] - #' @srrstats {G2.4d} *explicit conversion to factor via `as.factor()`* - #' @noRd if (is.character(x)) x <- as.factor(x) if (is.factor(x)) { x <- x[!is.na(x)] diff --git a/R/misc.R b/R/misc.R index 6c4b08c6..c4898403 100644 --- a/R/misc.R +++ b/R/misc.R @@ -35,7 +35,9 @@ setup_factor <- function(load.var, lambda, factor, data) { if (is.null(factor)) { return(list(data = data, lambda = lambda)) } - + if(ncol(lambda) != length(factor)) { + stop("lambda matrix must have one column for each element in factor.") + } eval(parse(text = paste0( "data$", load.var, diff --git a/R/predict.galamm.R b/R/predict.galamm.R index 5671cb89..f55ae857 100644 --- a/R/predict.galamm.R +++ b/R/predict.galamm.R @@ -1,8 +1,9 @@ #' @title Predictions from a model at new data values #' #' @srrstats {G1.4} Function documented with roxygen2. -#' @srrstats {G2.3b} Argument "type" is case sensitive, which is documented. #' @srrstats {G2.1a} Expected data types provided for all inputs. +#' @srrstats {G2.3a} match.arg() used on "type" argument. +#' @srrstats {G2.3b} Argument "type" is case sensitive, which is documented. #' #' @description Predictions are given at the population level, i.e., with random #' effects set to zero. For fitted models including random effects, see diff --git a/R/residuals.galamm.R b/R/residuals.galamm.R index d53e0055..e3d21176 100644 --- a/R/residuals.galamm.R +++ b/R/residuals.galamm.R @@ -1,6 +1,7 @@ #' @title Residuals of galamm objects #' #' @srrstats {G1.4} Function documented with roxygen2. +#' @srrstats {G2.3a} match.arg() used on "type" argument. #' @srrstats {G2.3b} Argument type is case sensitive, as stated in their #' documentation. #' @srrstats {G2.1a} Expected data types provided for all inputs. diff --git a/R/smooths.R b/R/smooths.R index 5bac18bf..3b1135bc 100644 --- a/R/smooths.R +++ b/R/smooths.R @@ -14,6 +14,10 @@ NULL #' #' @srrstats {G1.4} Function documented with roxygen2. #' @srrstats {G2.3b} Argument "factor" is case sensitive, as is documented here. +#' @srrstats {G2.5} No inputs are explicitly expected to be of factor type in +#' this function, but such expectations exist for arguments forwarded to +#' \code{mgcv::s}. This is elaborated under the "Details" heading in the +#' documentation. #' #' @description This is a very thin wrapper around \code{mgcv::s}. It enables #' the specification of loading variables for smooth terms. The last letter "l", @@ -31,6 +35,12 @@ NULL #' this smooth term should be multiplied with in order to produce the observed #' outcome. #' +#' @details The documentation of the function \code{mgcv::s} should be consulted +#' for details on how to properly set up smooth terms. In particular, note +#' that these terms distinguish between ordered and unordered factor terms +#' in the \code{by} variable, which can be provided in \code{...} and is +#' forwarded to \code{mgcv::s}. +#' #' @export #' @family modeling functions #' @@ -76,6 +86,10 @@ sl <- function(..., factor = NULL) { #' @srrstats {G1.4} Function documented with roxygen2. #' @srrstats {G2.3b} Argument "factor" is case sensitive, as is documented here. #' @srrstats {G2.1a} Expected data types provided for all inputs. +#' @srrstats {G2.5} No inputs are explicitly expected to be of factor type in +#' this function, but such expectations exist for arguments forwarded to +#' \code{mgcv::t2}. This is elaborated under the "Details" heading in the +#' documentation. #' #' @description This is a very thin wrapper around \code{mgcv::t2}. It enables #' the specification of loading variables for smooth terms. The last letter @@ -96,6 +110,12 @@ sl <- function(..., factor = NULL) { #' @export #' @family modeling functions #' +#' @details The documentation of the function \code{mgcv::t2} should be consulted +#' for details on how to properly set up smooth terms. In particular, note +#' that these terms distinguish between ordered and unordered factor terms +#' in the \code{by} variable, which can be provided in \code{...} and is +#' forwarded to \code{mgcv::t2}. +#' #' @references #' #' \insertRef{woodThinPlateRegression2003}{galamm} diff --git a/R/srr-stats-standards.R b/R/srr-stats-standards.R index 38bf4a53..4498a819 100644 --- a/R/srr-stats-standards.R +++ b/R/srr-stats-standards.R @@ -1,77 +1,116 @@ #' srr_stats #' -#' All of the following standards initially have `@srrstatsTODO` tags. -#' These may be moved at any time to any other locations in your code. -#' Once addressed, please modify the tag from `@srrstatsTODO` to `@srrstats`, -#' or `@srrstatsNA`, ensuring that references to every one of the following -#' standards remain somewhere within your code. -#' (These comments may be deleted at any time.) +#' All of the following standards initially have `@srrstatsTODO` tags. These may +#' be moved at any time to any other locations in your code. Once addressed, +#' please modify the tag from `@srrstatsTODO` to `@srrstats`, or `@srrstatsNA`, +#' ensuring that references to every one of the following standards remain +#' somewhere within your code. (These comments may be deleted at any time.) #' #' @srrstatsVerbose FALSE #' #' @srrstats {G1.2} Life cycle statement is in the file .github/CONTRIBUTING.md #' @srrstats {G1.5} No performance claims made in associated publication. Might #' be added in the future, if a publication is made based on this package. -#' @srrstatsTODO {G2.0} *Implement assertions on lengths of inputs, particularly through asserting that inputs expected to be single- or multi-valued are indeed so.* -#' @srrstatsTODO {G2.0a} Provide explicit secondary documentation of any expectations on lengths of inputs -#' @srrstatsTODO {G2.1} *Implement assertions on types of inputs (see the initial point on nomenclature above).* -#' @srrstatsTODO {G2.1a} *Provide explicit secondary documentation of expectations on data types of all vector inputs.* -#' @srrstatsTODO {G2.2} *Appropriately prohibit or restrict submission of multivariate input to parameters expected to be univariate.* -#' @srrstatsTODO {G2.3} *For univariate character input:* -#' @srrstatsTODO {G2.3a} *Use `match.arg()` or equivalent where applicable to only permit expected values.* -#' @srrstatsTODO {G2.3b} *Either: use `tolower()` or equivalent to ensure input of character parameters is not case dependent; or explicitly document that parameters are strictly case-sensitive.* -#' @srrstatsTODO {G2.4} *Provide appropriate mechanisms to convert between different data types, potentially including:* -#' @srrstatsTODO {G2.4a} *explicit conversion to `integer` via `as.integer()`* -#' @srrstatsTODO {G2.4b} *explicit conversion to continuous via `as.numeric()`* -#' @srrstatsTODO {G2.4c} *explicit conversion to character via `as.character()` (and not `paste` or `paste0`)* -#' @srrstatsTODO {G2.4d} *explicit conversion to factor via `as.factor()`* -#' @srrstatsTODO {G2.4e} *explicit conversion from factor via `as...()` functions* -#' @srrstatsTODO {G2.5} *Where inputs are expected to be of `factor` type, secondary documentation should explicitly state whether these should be `ordered` or not, and those inputs should provide appropriate error or other routines to ensure inputs follow these expectations.* -#' @srrstatsTODO {G2.6} *Software which accepts one-dimensional input should ensure values are appropriately pre-processed regardless of class structures.* -#' @srrstatsTODO {G2.7} *Software should accept as input as many of the above standard tabular forms as possible, including extension to domain-specific forms.* -#' @srrstatsTODO {G2.8} *Software should provide appropriate conversion or dispatch routines as part of initial pre-processing to ensure that all other sub-functions of a package receive inputs of a single defined class or type.* -#' @srrstatsTODO {G2.9} *Software should issue diagnostic messages for type conversion in which information is lost (such as conversion of variables from factor to character; standardisation of variable names; or removal of meta-data such as those associated with [`sf`-format](https://r-spatial.github.io/sf/) data) or added (such as insertion of variable or column names where none were provided).* -#' @srrstatsTODO {G2.10} *Software should ensure that extraction or filtering of single columns from tabular inputs should not presume any particular default behaviour, and should ensure all column-extraction operations behave consistently regardless of the class of tabular data used as input.* -#' @srrstatsTODO {G2.11} *Software should ensure that `data.frame`-like tabular objects which have columns which do not themselves have standard class attributes (typically, `vector`) are appropriately processed, and do not error without reason. This behaviour should be tested. Again, columns created by the [`units` package](https://github.com/r-quantities/units/) provide a good test case.* -#' @srrstatsTODO {G2.12} *Software should ensure that `data.frame`-like tabular objects which have list columns should ensure that those columns are appropriately pre-processed either through being removed, converted to equivalent vector columns where appropriate, or some other appropriate treatment such as an informative error. This behaviour should be tested.* -#' @srrstatsTODO {G2.13} *Statistical Software should implement appropriate checks for missing data as part of initial pre-processing prior to passing data to analytic algorithms.* -#' @srrstatsTODO {G2.14} *Where possible, all functions should provide options for users to specify how to handle missing (`NA`) data, with options minimally including:* -#' @srrstatsTODO {G2.14a} *error on missing data* -#' @srrstatsTODO {G2.14b} *ignore missing data with default warnings or messages issued* -#' @srrstatsTODO {G2.14c} *replace missing data with appropriately imputed values* -#' @srrstatsTODO {G2.15} *Functions should never assume non-missingness, and should never pass data with potential missing values to any base routines with default `na.rm = FALSE`-type parameters (such as [`mean()`](https://stat.ethz.ch/R-manual/R-devel/library/base/html/mean.html), [`sd()`](https://stat.ethz.ch/R-manual/R-devel/library/stats/html/sd.html) or [`cor()`](https://stat.ethz.ch/R-manual/R-devel/library/stats/html/cor.html)).* -#' @srrstatsTODO {G2.16} *All functions should also provide options to handle undefined values (e.g., `NaN`, `Inf` and `-Inf`), including potentially ignoring or removing such values.* -#' @srrstatsTODO {G3.0} *Statistical software should never compare floating point numbers for equality. All numeric equality comparisons should either ensure that they are made between integers, or use appropriate tolerances for approximate equality.* -#' @srrstatsTODO {G3.1} *Statistical software which relies on covariance calculations should enable users to choose between different algorithms for calculating covariances, and should not rely solely on covariances from the `stats::cov` function.* -#' @srrstatsTODO {G3.1a} *The ability to use arbitrarily specified covariance methods should be documented (typically in examples or vignettes).* -#' @srrstatsTODO {G4.0} *Statistical Software which enables outputs to be written to local files should parse parameters specifying file names to ensure appropriate file suffices are automatically generated where not provided.* -#' @srrstatsTODO {G5.0} *Where applicable or practicable, tests should use standard data sets with known properties (for example, the [NIST Standard Reference Datasets](https://www.itl.nist.gov/div898/strd/), or data sets provided by other widely-used R packages).* -#' @srrstatsTODO {G5.1} *Data sets created within, and used to test, a package should be exported (or otherwise made generally available) so that users can confirm tests and run examples.* -#' @srrstatsTODO {G5.2} *Appropriate error and warning behaviour of all functions should be explicitly demonstrated through tests. In particular,* -#' @srrstatsTODO {G5.2a} *Every message produced within R code by `stop()`, `warning()`, `message()`, or equivalent should be unique* -#' @srrstatsTODO {G5.2b} *Explicit tests should demonstrate conditions which trigger every one of those messages, and should compare the result with expected values.* -#' @srrstatsTODO {G5.3} *For functions which are expected to return objects containing no missing (`NA`) or undefined (`NaN`, `Inf`) values, the absence of any such values in return objects should be explicitly tested.* -#' @srrstatsTODO {G5.4} **Correctness tests** *to test that statistical algorithms produce expected results to some fixed test data sets (potentially through comparisons using binding frameworks such as [RStata](https://github.com/lbraglia/RStata)).* -#' @srrstatsTODO {G5.4a} *For new methods, it can be difficult to separate out correctness of the method from the correctness of the implementation, as there may not be reference for comparison. In this case, testing may be implemented against simple, trivial cases or against multiple implementations such as an initial R implementation compared with results from a C/C++ implementation.* -#' @srrstatsTODO {G5.4b} *For new implementations of existing methods, correctness tests should include tests against previous implementations. Such testing may explicitly call those implementations in testing, preferably from fixed-versions of other software, or use stored outputs from those where that is not possible.* -#' @srrstatsTODO {G5.4c} *Where applicable, stored values may be drawn from published paper outputs when applicable and where code from original implementations is not available* -#' @srrstatsTODO {G5.5} *Correctness tests should be run with a fixed random seed* -#' @srrstatsTODO {G5.6} **Parameter recovery tests** *to test that the implementation produce expected results given data with known properties. For instance, a linear regression algorithm should return expected coefficient values for a simulated data set generated from a linear model.* -#' @srrstatsTODO {G5.6a} *Parameter recovery tests should generally be expected to succeed within a defined tolerance rather than recovering exact values.* -#' @srrstatsTODO {G5.6b} *Parameter recovery tests should be run with multiple random seeds when either data simulation or the algorithm contains a random component. (When long-running, such tests may be part of an extended, rather than regular, test suite; see G4.10-4.12, below).* -#' @srrstatsTODO {G5.7} **Algorithm performance tests** *to test that implementation performs as expected as properties of data change. For instance, a test may show that parameters approach correct estimates within tolerance as data size increases, or that convergence times decrease for higher convergence thresholds.* -#' @srrstatsTODO {G5.8} **Edge condition tests** *to test that these conditions produce expected behaviour such as clear warnings or errors when confronted with data with extreme properties including but not limited to:* +#' @srrstats {G2.4e} No explicit conversion from factor to other types is done. +#' @srrstats {G2.8} Not directly relevant. +#' @srrstats {G2.9} I'm not aware of any conversions in which information is +#' lost. +#' @srrstats {G2.11} We do not rely on class attributes of columns, except for +#' factors. +#' @srrstats {G2.12} List columns are not supported. +#' @srrstats {G2.14b} Ignoring missing data does not make sense mathematically, +#' so option \code{na.action = "na.omit"} will cause an error, through +#' match.arg(). +#' @srrstats {G2.14c} Replacing missing values with properly imputed values is a +#' modeling step in itself. For this to be valid, a model has to be fitted on +#' each imputed dataset, and the estimates need to be combined. Users who want +#' to do this, would need to set up the infrastructure themselves, potentially +#' using the \code{mice} package. +#' @srrstats {G3.0} No floating point numbers are compared for equality. +#' @srrstats {G3.1} The \code{stats::cov} function is not used. In the models +#' supported by \code{galamm}, the only easily available covariance matrix is +#' the inverse of the Hessian of the marginal log-likelihood at the local +#' optimum. This is the asymptotic covariance matrix. Other ways of computing +#' covariance matrices are not within scope. +#' @srrstats {G3.1a} No alternative covariance methods are supported. +#' @srrstats {G4.0} Output written to local files is not supported. +#' @srrstatsTODO {G5.2} *Appropriate error and warning behaviour of all +#' functions should be explicitly demonstrated through tests. In particular,* +#' @srrstatsTODO {G5.2a} *Every message produced within R code by `stop()`, +#' `warning()`, `message()`, or equivalent should be unique* +#' @srrstatsTODO {G5.2b} *Explicit tests should demonstrate conditions which +#' trigger every one of those messages, and should compare the result with +#' expected values.* +#' @srrstatsTODO {G5.3} *For functions which are expected to return objects +#' containing no missing (`NA`) or undefined (`NaN`, `Inf`) values, the +#' absence of any such values in return objects should be explicitly tested.* +#' @srrstatsTODO {G5.4} **Correctness tests** *to test that statistical +#' algorithms produce expected results to some fixed test data sets +#' (potentially through comparisons using binding frameworks such as +#' [RStata](https://github.com/lbraglia/RStata)).* +#' @srrstatsTODO {G5.4a} *For new methods, it can be difficult to separate out +#' correctness of the method from the correctness of the implementation, as +#' there may not be reference for comparison. In this case, testing may be +#' implemented against simple, trivial cases or against multiple +#' implementations such as an initial R implementation compared with results +#' from a C/C++ implementation.* +#' @srrstatsTODO {G5.4b} *For new implementations of existing methods, +#' correctness tests should include tests against previous implementations. +#' Such testing may explicitly call those implementations in testing, +#' preferably from fixed-versions of other software, or use stored outputs +#' from those where that is not possible.* +#' @srrstatsTODO {G5.4c} *Where applicable, stored values may be drawn from +#' published paper outputs when applicable and where code from original +#' implementations is not available* +#' @srrstatsTODO {G5.5} *Correctness tests should be run with a fixed random +#' seed* +#' @srrstatsTODO {G5.6} **Parameter recovery tests** *to test that the +#' implementation produce expected results given data with known properties. +#' For instance, a linear regression algorithm should return expected +#' coefficient values for a simulated data set generated from a linear model.* +#' @srrstatsTODO {G5.6a} *Parameter recovery tests should generally be expected +#' to succeed within a defined tolerance rather than recovering exact values.* +#' @srrstatsTODO {G5.6b} *Parameter recovery tests should be run with multiple +#' random seeds when either data simulation or the algorithm contains a random +#' component. (When long-running, such tests may be part of an extended, +#' rather than regular, test suite; see G4.10-4.12, below).* +#' @srrstatsTODO {G5.7} **Algorithm performance tests** *to test that +#' implementation performs as expected as properties of data change. For +#' instance, a test may show that parameters approach correct estimates within +#' tolerance as data size increases, or that convergence times decrease for +#' higher convergence thresholds.* +#' @srrstatsTODO {G5.8} **Edge condition tests** *to test that these conditions +#' produce expected behaviour such as clear warnings or errors when confronted +#' with data with extreme properties including but not limited to:* #' @srrstatsTODO {G5.8a} *Zero-length data* -#' @srrstatsTODO {G5.8b} *Data of unsupported types (e.g., character or complex numbers in for functions designed only for numeric data)* -#' @srrstatsTODO {G5.8c} *Data with all-`NA` fields or columns or all identical fields or columns* -#' @srrstatsTODO {G5.8d} *Data outside the scope of the algorithm (for example, data with more fields (columns) than observations (rows) for some regression algorithms)* -#' @srrstatsTODO {G5.9} **Noise susceptibility tests** *Packages should test for expected stochastic behaviour, such as through the following conditions:* -#' @srrstatsTODO {G5.9a} *Adding trivial noise (for example, at the scale of `.Machine$double.eps`) to data does not meaningfully change results* -#' @srrstatsTODO {G5.9b} *Running under different random seeds or initial conditions does not meaningfully change results* -#' @srrstatsTODO {G5.10} *Extended tests should included and run under a common framework with other tests but be switched on by flags such as as a `_EXTENDED_TESTS="true"` environment variable.* - The extended tests can be then run automatically by GitHub Actions for example by adding the following to the `env` section of the workflow: -#' @srrstatsTODO {G5.11} *Where extended tests require large data sets or other assets, these should be provided for downloading and fetched as part of the testing workflow.* -#' @srrstatsTODO {G5.11a} *When any downloads of additional data necessary for extended tests fail, the tests themselves should not fail, rather be skipped and implicitly succeed with an appropriate diagnostic message.* -#' @srrstatsTODO {G5.12} *Any conditions necessary to run extended tests such as platform requirements, memory, expected runtime, and artefacts produced that may need manual inspection, should be described in developer documentation such as a `CONTRIBUTING.md` or `tests/README.md` file.* +#' @srrstatsTODO {G5.8b} *Data of unsupported types (e.g., character or complex +#' numbers in for functions designed only for numeric data)* +#' @srrstatsTODO {G5.8c} *Data with all-`NA` fields or columns or all identical +#' fields or columns* +#' @srrstatsTODO {G5.8d} *Data outside the scope of the algorithm (for example, +#' data with more fields (columns) than observations (rows) for some +#' regression algorithms)* +#' @srrstatsTODO {G5.9} **Noise susceptibility tests** *Packages should test for +#' expected stochastic behaviour, such as through the following conditions:* +#' @srrstatsTODO {G5.9a} *Adding trivial noise (for example, at the scale of +#' `.Machine$double.eps`) to data does not meaningfully change results* +#' @srrstatsTODO {G5.9b} *Running under different random seeds or initial +#' conditions does not meaningfully change results* +#' @srrstatsTODO {G5.10} *Extended tests should included and run under a common +#' framework with other tests but be switched on by flags such as as a +#' `_EXTENDED_TESTS="true"` environment variable.* - The extended tests +#' can be then run automatically by GitHub Actions for example by adding the +#' following to the `env` section of the workflow: +#' @srrstatsTODO {G5.11} *Where extended tests require large data sets or other +#' assets, these should be provided for downloading and fetched as part of the +#' testing workflow.* +#' @srrstatsTODO {G5.11a} *When any downloads of additional data necessary for +#' extended tests fail, the tests themselves should not fail, rather be +#' skipped and implicitly succeed with an appropriate diagnostic message.* +#' @srrstatsTODO {G5.12} *Any conditions necessary to run extended tests such as +#' platform requirements, memory, expected runtime, and artefacts produced +#' that may need manual inspection, should be described in developer +#' documentation such as a `CONTRIBUTING.md` or `tests/README.md` file.* #' @noRd NULL diff --git a/man/factor_loadings.galamm.Rd b/man/factor_loadings.galamm.Rd index e03b215b..6837f7df 100644 --- a/man/factor_loadings.galamm.Rd +++ b/man/factor_loadings.galamm.Rd @@ -69,8 +69,8 @@ Other details of model fit: \code{\link{vcov.galamm}()} } \author{ -The example for this function comes from \code{PLmixed}, with -authors Nicholas Rockwood and Minjeong Jeon +The example for this function comes from \code{PLmixed}, with authors +Nicholas Rockwood and Minjeong Jeon \insertCite{rockwoodEstimatingComplexMeasurement2019}{galamm}. } \concept{details of model fit} diff --git a/man/galamm.Rd b/man/galamm.Rd index 4b715429..bdb58d44 100644 --- a/man/galamm.Rd +++ b/man/galamm.Rd @@ -67,13 +67,19 @@ sensitive.} \item{lambda}{Optional factor loading matrix. Numerical values indicate that the given value is fixed, while \code{NA} means that the entry is a -parameter to be estimated. Defaults to \code{NULL}, which means that there -is no factor loading matrix.} +parameter to be estimated. Numerical values can only take the values 0 or +\enumerate{ +\item The number of columns of \code{lambda} must be identical to the number +of elements in \code{factor}. Defaults to \code{NULL}, which means that +there is no factor loading matrix. If \code{lambda} is provided as a +vector, it will be converted to a \code{matrix} with a single column. +}} \item{factor}{Optional character vector whose \eqn{j}th entry corresponds to -the \eqn{j}th column of the corresponding matrix in \code{lambda}. Defaults -to \code{NULL}, which means that there are no factor loadings. Argument is -case sensitive.} +the \eqn{j}th column of the corresponding matrix in \code{lambda}. The +number of elements in \code{factor} must be equal to the number of columns +in \code{lambda}. Defaults to \code{NULL}, which means that there are no +factor loadings. Argument is case sensitive.} \item{factor_interactions}{Optional list of length equal to the number of columns in \code{lambda}. Each list element should be a \code{formula} @@ -84,9 +90,9 @@ interactions are used.} \item{na.action}{Character of length one specifying a function which indicates what should happen when the data contains \code{NA}s. The defaults is set to the \code{na.action} setting of \code{options}, which -can be seen with \code{options("na.action")}. The other alternative is -\code{"na.fail"}, which means that the function fails if there as -\code{NA}s in \code{data}.} +can be seen with \code{options("na.action")}. The other alternatives are +\code{"na.fail"} or \code{"na.exclude"}, which means that the function +fails if there as \code{NA}s in \code{data}.} \item{start}{Optional named list of starting values for parameters. Possible names of list elements are \code{"theta"}, \code{"beta"}, \code{"lambda"}, diff --git a/man/sl.Rd b/man/sl.Rd index b486e698..9d0dac5a 100644 --- a/man/sl.Rd +++ b/man/sl.Rd @@ -27,6 +27,13 @@ the specification of loading variables for smooth terms. The last letter "l", which stands for "loading", has been added to avoid namespace conflicts with \code{mgcv} and \code{gamm4}. } +\details{ +The documentation of the function \code{mgcv::s} should be consulted +for details on how to properly set up smooth terms. In particular, note +that these terms distinguish between ordered and unordered factor terms +in the \code{by} variable, which can be provided in \code{...} and is +forwarded to \code{mgcv::s}. +} \examples{ # Linear mixed model with factor structures dat <- subset(cognition, domain == 1 & timepoint == 1) diff --git a/man/t2l.Rd b/man/t2l.Rd index a21b11fe..53bc6ad4 100644 --- a/man/t2l.Rd +++ b/man/t2l.Rd @@ -27,6 +27,13 @@ the specification of loading variables for smooth terms. The last letter "l", which stands for "loading", has been added to avoid namespace conflicts with \code{mgcv} and \code{gamm4}. } +\details{ +The documentation of the function \code{mgcv::t2} should be consulted +for details on how to properly set up smooth terms. In particular, note +that these terms distinguish between ordered and unordered factor terms +in the \code{by} variable, which can be provided in \code{...} and is +forwarded to \code{mgcv::t2}. +} \examples{ # Linear mixed model with factor structures dat <- subset(cognition, domain == 1 & timepoint == 1) diff --git a/tests/testthat/test-galamm-glmm.R b/tests/testthat/test-galamm-glmm.R index 8aae5dd2..ee413732 100644 --- a/tests/testthat/test-galamm-glmm.R +++ b/tests/testthat/test-galamm-glmm.R @@ -1,3 +1,9 @@ +#' @srrstats {G5.0} Datasets from PLmixed package are used for testing, and +#' results from the functions in this package are precomputed for comparison, +#' in cases where PLmixed and galamm support the same models. +#' @noRd +NULL + data("IRTsim", package = "PLmixed") test_that("Logistic GLMM with simple factor works", { dat <- subset(IRTsim, sid < 50) diff --git a/tests/testthat/test-galamm-lmm.R b/tests/testthat/test-galamm-lmm.R index 73c709c4..7796a93c 100644 --- a/tests/testthat/test-galamm-lmm.R +++ b/tests/testthat/test-galamm-lmm.R @@ -1,3 +1,9 @@ +#' @srrstats {G5.0} Datasets from PLmixed package are used for testing, and +#' results from the functions in this package are precomputed for comparison, +#' in cases where PLmixed and galamm support the same models. +#' @noRd +NULL + data("IRTsim", package = "PLmixed") test_that("LMM with simple factor works", { @@ -26,30 +32,22 @@ test_that("LMM with simple factor works", { expect_invisible(plot(mod)) dev.off() - # Must test that it works also with tibbles class(IRTsub) <- c("tbl_df", "tbl", "data.frame") - expect_message( - { - mod1 <- galamm( - y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), - data = IRTsub, load.var = "item", - factor = "abil.sid", lambda = irt.lam - ) - }, - "Converting tibble" + mod1 <- galamm( + y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), + data = IRTsub, load.var = "item", + factor = "abil.sid", lambda = irt.lam ) + expect_equal(deviance(mod1), deviance(mod)) class(IRTsub) <- c("data.table", "data.frame") - expect_message( - { - mod2 <- galamm( - y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), - data = IRTsub, , load.var = "item", - factor = "abil.sid", lambda = irt.lam - ) - }, - "Converting data.table" + mod2 <- galamm( + y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), + data = IRTsub, , load.var = "item", + factor = "abil.sid", lambda = irt.lam ) + expect_equal(deviance(mod2), deviance(mod)) + IRTsub <- as.data.frame(IRTsub) expect_equal(mod$hessian, mod1$hessian) diff --git a/tests/testthat/test-galamm-setup.R b/tests/testthat/test-galamm-setup.R index b1d0a021..833e1b04 100644 --- a/tests/testthat/test-galamm-setup.R +++ b/tests/testthat/test-galamm-setup.R @@ -1,3 +1,10 @@ +#' @srrstats {G5.0} Datasets from PLmixed package are used for testing, and +#' results from the functions in this package are precomputed for comparison, +#' in cases where PLmixed and galamm support the same models. In addition, +#' dataset "sleepstudy" from lme4 package is also used here. +#' @noRd +NULL + test_that("wrong input is handled properly", { dat <- subset(cognition, domain == 2) @@ -370,6 +377,20 @@ test_that("missing values are handled appropriately", { "missing values in object" ) + options(na.action = "na.pass") + + expect_error( + galamm( + formula = y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), + data = IRTsub, + load.var = c("item"), + factor = c("abil.sid"), + lambda = irt.lam + ), + "'arg' should be one of" + ) + + # Explicit argument vs relying on default mod <- galamm( formula = y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), @@ -387,8 +408,53 @@ test_that("missing values are handled appropriately", { factor = "abil.sid", lambda = irt.lam ) + options("na.action" = "na.exclude") + mod3 <- galamm( + formula = y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), + data = IRTsub, + load.var = "item", + factor = "abil.sid", + lambda = irt.lam + ) expect_equal(mod$model$deviance, mod2$model$deviance) + expect_equal(mod$model$deviance, mod3$model$deviance) + + irt.lamInf <- irt.lam + irt.lamInf[[1]] <- Inf + expect_error( + galamm( + formula = y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), + data = IRTsub, + load.var = "item", + factor = "abil.sid", + lambda = irt.lamInf + ), + "elements of lambda can be either 0, 1, or NA" + ) + irt.lamInf[[1]] <- NaN + expect_error( + galamm( + formula = y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), + data = IRTsub, + load.var = "item", + factor = "abil.sid", + lambda = irt.lamInf + ), + "elements of lambda can be either 0, 1, or NA" + ) + + irt.lamInf[[1]] <- 2 + expect_error( + galamm( + formula = y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), + data = IRTsub, + load.var = "item", + factor = "abil.sid", + lambda = irt.lamInf + ), + "all non-NA values in lambda must be either 0 or 1" + ) IRTsub$y[1] <- -Inf @@ -517,3 +583,63 @@ test_that("galamm rejects perfectly noiseless input data", { "fixed-effect model matrix is rank deficient so dropping 4 columns / coefficients" ) }) + +test_that("loading and factor dimensions have to be correct", { + data("IRTsim", package = "PLmixed") + + irt.lam = matrix(c(1, NA, NA, NA, NA), ncol = 1) + + expect_error( + galamm( + formula = y ~ 0 + as.factor(item) + (0 + abil.sid |sid) + + (0 + abil.sid |school), + data = IRTsim, + load.var = "item", + factor = "abil.sid", + lambda = irt.lam[1:4, , drop = FALSE]), + "lambda matrix must contain one row for each element in load.var" + ) + expect_error( + galamm( + formula = y ~ 0 + as.factor(item) + (0 + abil.sid |sid) + + (0 + abil.sid |school), + data = IRTsim, + load.var = "item", + lambda = irt.lam + ), + "load.var, lambda, and factor must either all have values or all be NULL." + ) + + expect_message( + galamm( + formula = y ~ 0 + as.factor(item) + (0 + abil.sid |sid) + + (0 + abil.sid |school), + data = IRTsim, + load.var = "item", + factor = "abil.sid", + lambda = as.numeric(irt.lam), + control = galamm_control(optim_control = list(maxit = 1), + maxit_conditional_modes = 1) + ), + "lambda converted to matrix with one column") + + data("KYPSsim", package = "PLmixed") + + kyps.lam <- rbind(c( 1, 0), + c(NA, 0), + c(NA, 1), + c(NA, NA)) + + expect_error( + galamm( + formula = esteem ~ as.factor(time) + (0 + hs | hid) + + (0 + ms | mid) + (1 | sid), + data = KYPSsim, + factor = c("ms", "hs"), + load.var = "time", + lambda = kyps.lam[, 1, drop = FALSE] + ), + "lambda matrix must have one column for each element in factor" + ) + +}) From 55c27bccdf7078bdd54796be88ec18b5ff2c7232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98ystein=20S=C3=B8rensen?= Date: Thu, 19 Oct 2023 15:25:04 +0200 Subject: [PATCH 5/9] increasing test coverage --- R/galamm.R | 31 +++---- R/misc.R | 2 +- README.Rmd | 2 +- tests/testthat/test-galamm-heteroscedastic.R | 10 +++ tests/testthat/test-galamm-lmm.R | 1 - tests/testthat/test-galamm-mixed-resp.R | 64 +++++++++++++++ tests/testthat/test-galamm-setup.R | 85 +++++++++++++++----- 7 files changed, 158 insertions(+), 37 deletions(-) diff --git a/R/galamm.R b/R/galamm.R index 61dbc1c6..cd03dde2 100644 --- a/R/galamm.R +++ b/R/galamm.R @@ -54,6 +54,8 @@ #' @srrstats {G2.13} Checks for missing data implemented in the preprocessing #' steps of galamm. Note that in the argument \code{lambda}, \code{NA} values #' mean that the matrix element is an unknown parameter. +#' @srrstats {G2.14} Users can set options for handling missing values through +#' the argument \code{na.action}. #' @srrstats {G2.14a} Users can set \code{na.action = "na.fail"}. #' @srrstats {G2.15} If \code{na.action = "na.omit"} or \code{na.action = #' "na.exclude"}, missing values in \code{data} are explicitly removed. @@ -310,6 +312,9 @@ galamm <- function(formula, weights = NULL, data, family = gaussian, factor_interactions = NULL, na.action = getOption("na.action"), start = NULL, control = galamm_control()) { + if (!inherits(data, "data.frame")) { + stop("data must be a data.frame") + } na.action <- match.arg(na.action, c("na.omit", "na.fail", "na.exclude")) # Deal with potential missing values @@ -319,16 +324,10 @@ galamm <- function(formula, weights = NULL, data, family = gaussian, if (any(vapply(data, function(x) any(is.nan(x)), logical(1)))) { stop("NaN in 'data'. galamm cannot handle this.") } - if (!is.character(na.action)) { - stop("na.action must be character") - } data <- eval(parse(text = paste0(na.action, "(data)"))) if (nrow(data) == 0) stop("No data, nothing to do.") - if (!inherits(data, "data.frame")) { - stop("data must be a data.frame") - } data <- as.data.frame(data) mc <- match.call() @@ -364,23 +363,27 @@ galamm <- function(formula, weights = NULL, data, family = gaussian, if (!is.null(lambda) && !is.numeric(lambda)) { stop("lambda must either be NULL or a matrix or numeric vector") } - if(!is.null(lambda) && !is.matrix(lambda)) { + if (!is.null(lambda) && !is.matrix(lambda)) { lambda <- matrix(lambda, ncol = 1) message("lambda converted to matrix with one column") } - if(!is.null(lambda)) { - if(any(is.nan(lambda)) || any(is.infinite(lambda))) { + if (!is.null(lambda)) { + if (any(is.nan(lambda)) || any(is.infinite(lambda))) { stop("elements of lambda can be either 0, 1, or NA") } - if(!all(lambda[!is.na(lambda)] %in% c(0, 1))) { + if (!all(lambda[!is.na(lambda)] %in% c(0, 1))) { stop("all non-NA values in lambda must be either 0 or 1") } } if (any(vapply(list(load.var, lambda, factor), is.null, logical(1))) && - any(vapply(list(load.var, lambda, factor), - function(x) !is.null(x), logical(1)))) { - stop("load.var, lambda, and factor must either all have values or ", - "all be NULL.") + any(vapply( + list(load.var, lambda, factor), + function(x) !is.null(x), logical(1) + ))) { + stop( + "load.var, lambda, and factor must either all have values or ", + "all be NULL." + ) } tmp <- setup_factor(load.var, lambda, factor, data) diff --git a/R/misc.R b/R/misc.R index c4898403..f82fec46 100644 --- a/R/misc.R +++ b/R/misc.R @@ -35,7 +35,7 @@ setup_factor <- function(load.var, lambda, factor, data) { if (is.null(factor)) { return(list(data = data, lambda = lambda)) } - if(ncol(lambda) != length(factor)) { + if (ncol(lambda) != length(factor)) { stop("lambda matrix must have one column for each element in factor.") } diff --git a/README.Rmd b/README.Rmd index d53dfad1..f04b3e4b 100644 --- a/README.Rmd +++ b/README.Rmd @@ -28,7 +28,7 @@ knitr::opts_chunk$set( ```{r srr-tags1, eval = FALSE, echo = FALSE} -#' @srrstats {G1.0} Primary and secondary references to literature in the paragraph below. +#' @srrstats {G1.0} Primary and secondary references to literature in the paragraph below. #' @srrstats {G1.1} It is stated in the paragraph below that this is the first implementation of the algorithm developed in Sørensen, Fjell, and Walhovd (2023). #' @srrstats {G1.3} Statistical terminology defined in detail in the references in the paragraph below. The main paper, Sørensen, FJell, and Walhovd (2023) is open access. ``` diff --git a/tests/testthat/test-galamm-heteroscedastic.R b/tests/testthat/test-galamm-heteroscedastic.R index ecec7197..0d7e1a0b 100644 --- a/tests/testthat/test-galamm-heteroscedastic.R +++ b/tests/testthat/test-galamm-heteroscedastic.R @@ -35,6 +35,16 @@ test_that("Heteroscedastic model works", { expect_error(vcov(mod, parm = 5L), "out of bounds") expect_error(vcov(mod, parm = "phi"), "Parameter not found") + expect_error( + galamm( + formula = y ~ x + (1 | id), + weights = list(~ (1 | item)), + data = hsced + ), + "weights must be a formula" + ) + + # Now use initial values mod_start <- galamm( formula = y ~ x + (1 | id), diff --git a/tests/testthat/test-galamm-lmm.R b/tests/testthat/test-galamm-lmm.R index 7796a93c..0cb737c2 100644 --- a/tests/testthat/test-galamm-lmm.R +++ b/tests/testthat/test-galamm-lmm.R @@ -437,4 +437,3 @@ test_that("multiple factors in fixed effects works", { ) expect_equal(deviance(mod), 7891.36597569292, tolerance = .001) }) - diff --git a/tests/testthat/test-galamm-mixed-resp.R b/tests/testthat/test-galamm-mixed-resp.R index da9f1e93..36439a0a 100644 --- a/tests/testthat/test-galamm-mixed-resp.R +++ b/tests/testthat/test-galamm-mixed-resp.R @@ -49,6 +49,18 @@ test_that("Mixed response works", { tolerance = 1e-4 ) + expect_equal( + predict(mod, newdata = subset(mresp, id %in% c(101, 103))), + structure(c( + 0.850193554899423, 0.825301206439717, 0.289802812104655, + 0.525192181355719, 0.237374173176289, 0.466804927622163, 0.507503457053694, + 0.272927548669085 + ), dim = c(8L, 1L), dimnames = list(c( + "401", + "402", "403", "404", "409", "410", "411", "412" + ), NULL)) + ) + expect_equal( tail(fitted(mod)), c( @@ -58,6 +70,58 @@ test_that("Mixed response works", { tolerance = 1e-4 ) + expect_error( + mod <- galamm( + formula = y ~ x + (0 + loading | id), + data = dat, + family = c(gaussian, binomial), + family_mapping = ifelse(dat$itemgroup == "a", 1L, 2L)[1:3], + load.var = "itemgroup", + lambda = matrix(c(1, NA), ncol = 1), + factor = "loading" + ), + "family_mapping must contain one index per row in data" + ) + + expect_error( + mod <- galamm( + formula = y ~ x + (0 + loading | id), + data = dat, + family = c(gaussian, binomial), + family_mapping = matrix(ifelse(dat$itemgroup == "a", 1L, 2L), ncol = 2), + load.var = "itemgroup", + lambda = matrix(c(1, NA), ncol = 1), + factor = "loading" + ), + "family_mapping must be a vector" + ) + + expect_error( + mod <- galamm( + formula = y ~ x + (0 + loading | id), + data = dat, + family = c(gaussian, binomial), + family_mapping = sample(1:4, nrow(dat), replace = TRUE), + load.var = "itemgroup", + lambda = matrix(c(1, NA), ncol = 1), + factor = "loading" + ), + "family_mapping must contain a unique index for each element in family_list" + ) + + expect_error( + mod <- galamm( + formula = y ~ x + (0 + loading | id), + data = dat, + family = c(gaussian, binomial), + family_mapping = rep(1, nrow(dat)), + load.var = "itemgroup", + lambda = matrix(c(1, NA), ncol = 1), + factor = "loading" + ), + "family_mapping must contain a unique index for each element in family_list" + ) + # Now test using initial values mod2 <- galamm( formula = y ~ x + (0 + loading | id), diff --git a/tests/testthat/test-galamm-setup.R b/tests/testthat/test-galamm-setup.R index 833e1b04..2199fede 100644 --- a/tests/testthat/test-galamm-setup.R +++ b/tests/testthat/test-galamm-setup.R @@ -21,6 +21,19 @@ test_that("wrong input is handled properly", { "lambda matrix must contain one row for each element in load.var" ) + expect_error( + mod <- galamm( + formula = as.character(y ~ 0 + item + s(x, by = loading) + + (0 + loading | id / timepoint)), + data = dat, + family = binomial, + load.var = "item", + lambda = matrix(c(1, NA, NA), ncol = 1), + factor = "loading" + ), + "formula must be a formula" + ) + expect_error( mod <- galamm( formula = y ~ 0 + item + s(x, by = loading) + @@ -101,7 +114,7 @@ test_that("wrong input is handled properly", { ) newdat <- dat - newdat$loading <- 1 + expect_error( mod <- galamm( formula = y ~ 0 + item + s(x, by = loading) + @@ -110,9 +123,23 @@ test_that("wrong input is handled properly", { family = binomial, load.var = "item", lambda = matrix(c(1, NA, NA), ncol = 1), + factor = 1L + ), + "factor must be NULL or a character vector" + ) + + newdat$loading <- 1 + expect_error( + mod <- galamm( + formula = y ~ 0 + item + s(x, by = loading) + + (0 + loading | id / timepoint), + data = newdat, + family = binomial, + load.var = "item", + lambda = list(matrix(c(1, NA), ncol = 1)), factor = "loading" ), - "Factor already a column in data" + "lambda must either be NULL or a matrix or numeric vector" ) expect_error( @@ -346,6 +373,19 @@ irt.lam <- matrix(c(1, NA, NA), ncol = 1) # Specify the lambda matrix test_that("missing values are handled appropriately", { + expect_error( + galamm( + formula = y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), + data = IRTsub, + load.var = c("item"), + factor = c("abil.sid"), + lambda = irt.lam, + na.action = na.fail + ), + "'arg' must be NULL or a character vector" + ) + + IRTsub$y[1] <- NA_real_ expect_error( @@ -587,52 +627,58 @@ test_that("galamm rejects perfectly noiseless input data", { test_that("loading and factor dimensions have to be correct", { data("IRTsim", package = "PLmixed") - irt.lam = matrix(c(1, NA, NA, NA, NA), ncol = 1) + irt.lam <- matrix(c(1, NA, NA, NA, NA), ncol = 1) expect_error( galamm( - formula = y ~ 0 + as.factor(item) + (0 + abil.sid |sid) + - (0 + abil.sid |school), + formula = y ~ 0 + as.factor(item) + (0 + abil.sid | sid) + + (0 + abil.sid | school), data = IRTsim, load.var = "item", factor = "abil.sid", - lambda = irt.lam[1:4, , drop = FALSE]), + lambda = irt.lam[1:4, , drop = FALSE] + ), "lambda matrix must contain one row for each element in load.var" ) expect_error( galamm( - formula = y ~ 0 + as.factor(item) + (0 + abil.sid |sid) + - (0 + abil.sid |school), + formula = y ~ 0 + as.factor(item) + (0 + abil.sid | sid) + + (0 + abil.sid | school), data = IRTsim, load.var = "item", lambda = irt.lam - ), + ), "load.var, lambda, and factor must either all have values or all be NULL." ) expect_message( galamm( - formula = y ~ 0 + as.factor(item) + (0 + abil.sid |sid) + - (0 + abil.sid |school), + formula = y ~ 0 + as.factor(item) + (0 + abil.sid | sid) + + (0 + abil.sid | school), data = IRTsim, load.var = "item", factor = "abil.sid", lambda = as.numeric(irt.lam), - control = galamm_control(optim_control = list(maxit = 1), - maxit_conditional_modes = 1) + control = galamm_control( + optim_control = list(maxit = 1), + maxit_conditional_modes = 1 + ) ), - "lambda converted to matrix with one column") + "lambda converted to matrix with one column" + ) data("KYPSsim", package = "PLmixed") - kyps.lam <- rbind(c( 1, 0), - c(NA, 0), - c(NA, 1), - c(NA, NA)) + kyps.lam <- rbind( + c(1, 0), + c(NA, 0), + c(NA, 1), + c(NA, NA) + ) expect_error( galamm( - formula = esteem ~ as.factor(time) + (0 + hs | hid) + + formula = esteem ~ as.factor(time) + (0 + hs | hid) + (0 + ms | mid) + (1 | sid), data = KYPSsim, factor = c("ms", "hs"), @@ -641,5 +687,4 @@ test_that("loading and factor dimensions have to be correct", { ), "lambda matrix must have one column for each element in factor" ) - }) From 317099020a6c001e5cc15e20243de64375ca8405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98ystein=20S=C3=B8rensen?= Date: Thu, 19 Oct 2023 22:04:20 +0200 Subject: [PATCH 6/9] rerunning lmm vignette and expanding tests --- .github/workflows/R-CMD-check.yaml | 2 +- R/RcppExports.R | 3 - R/VarCorr.R | 12 +- R/plot.galamm.R | 6 +- R/srr-stats-standards.R | 88 ++-------- man/VarCorr.Rd | 6 +- man/print.VarCorr.galamm.Rd | 4 +- src/compute_galamm.cpp | 3 - tests/testthat.R | 29 ++++ tests/testthat/test-galamm-glmm.R | 41 +++++ tests/testthat/test-galamm-lmm.R | 59 +++++++ tests/testthat/test-galamm-semiparametric.R | 5 + tests/testthat/test-galamm-setup.R | 11 ++ tests/testthat/test-parameter-recovery.R | 157 ++++++++++++++++++ vignettes-raw/glmm_factor.Rmd | 2 +- vignettes-raw/latent_observed_interaction.Rmd | 2 +- vignettes-raw/lmm_factor.Rmd | 7 +- .../lmm_factor_diagnostic_plot-1.png | Bin 0 -> 98464 bytes vignettes-raw/mixed_response.Rmd | 2 +- vignettes/lmm_factor.Rmd | 29 ++-- 20 files changed, 352 insertions(+), 116 deletions(-) create mode 100644 tests/testthat/test-parameter-recovery.R create mode 100644 vignettes-raw/lmm_factor_diagnostic_plot-1.png diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index b9cd952c..9a98fb81 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -26,7 +26,7 @@ jobs: env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} R_KEEP_PKG_SOURCE: yes - MYPKG_EXTENDED_TESTS: ${{contains(github.event.head_commit.message, + GALAMM_EXTENDED_TESTS: ${{contains(github.event.head_commit.message, 'run-extended')}} steps: diff --git a/R/RcppExports.R b/R/RcppExports.R index 11ec925b..e1d62eb1 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -3,7 +3,6 @@ #' Evaluate the deviance at given values of random effects #' -#' @srrstats {G1.4a} Internal function documented. #' #' @param parlist An object of class \code{parameters} containing the #' parameters at which to evaluate the marginal log-likelihood. @@ -30,7 +29,6 @@ NULL #' log-likelihood. The template \code{T} will typically be one of #' \code{double}, \code{autodiff:dual1st}, or \code{autodiff::dual2nd}. #' -#' @srrstats {G1.4a} Internal function documented. #' #' @param parlist An object of class \code{parameters} containing the #' parameters at which to evaluate the marginal log-likelihood. @@ -53,7 +51,6 @@ NULL #' be one of \code{double}, \code{autodiff::dual1st}, and #' \code{autodiff::dual2nd}. #' -#' @srrstats {G1.4a} Internal function documented. #' #' @param y Double precision vector of response values. #' @param trials Double precision vector with number of trials. When trials diff --git a/R/VarCorr.R b/R/VarCorr.R index af4854ee..ddbc069e 100644 --- a/R/VarCorr.R +++ b/R/VarCorr.R @@ -15,7 +15,7 @@ NULL #' @name VarCorr #' @aliases VarCorr VarCorr.galamm #' -#' @return An object of class \code{VarCorr.galamm}. +#' @return An object of class \code{c("VarCorr.galamm", "VarCorr.merMod")}. #' @export #' #' @seealso [print.VarCorr.galamm()] for the print function. @@ -33,6 +33,10 @@ NULL #' # Extract information on variance and covariance #' VarCorr(mod) #' +#' # Convert to data frame +#' # (this invokes lme4's function as.data.frame.VarCorr.merMod) +#' as.data.frame(VarCorr(mod)) +#' VarCorr.galamm <- function(x, sigma = 1, ...) { useSc <- Reduce(function(`&&`, y) y$family == "gaussian", family(x), @@ -46,7 +50,7 @@ VarCorr.galamm <- function(x, sigma = 1, ...) { names(x$model$lmod$reTrms$cnms) ), useSc = useSc, - class = "VarCorr.galamm" + class = c("VarCorr.galamm", "VarCorr.merMod") ) } @@ -58,8 +62,8 @@ VarCorr.galamm <- function(x, sigma = 1, ...) { #' @srrstats {G2.3a} match.arg() used on "comp" argument. #' @srrstats {G2.3b} Argument "comp" is case sensitive, as is documented here. #' -#' @param x An object of class \code{VarCorr.galamm}, returned from -#' \code{\link{VarCorr.galamm}}. +#' @param x An object of class \code{c("VarCorr.galamm", "VarCorr.merMod")}, +#' returned from \code{\link{VarCorr.galamm}}. #' @param digits Optional arguments specifying number of digits to use when #' printing. #' @param comp Character vector of length 1 or 2 specifying which variance diff --git a/R/plot.galamm.R b/R/plot.galamm.R index 24fd779a..8e8c2c54 100644 --- a/R/plot.galamm.R +++ b/R/plot.galamm.R @@ -1,14 +1,14 @@ #' @title Diagnostic plots for galamm objects #' -#' @srrstats {G1.4} Function documented with roxygen2. -#' @srrstats {G2.1a} Expected data types provided for all inputs. -#' #' @param x An object of class \code{galamm} returned from \code{\link{galamm}}. #' @param ... Optional arguments passed on to the \code{plot} function. #' #' @return A plot is displayed. #' @export #' +#' @srrstats {G1.4} Function documented with roxygen2. +#' @srrstats {G2.1a} Expected data types provided for all inputs. +#' #' @seealso [residuals.galamm()] for extracting residuals and [plot()] for the #' generic function. #' diff --git a/R/srr-stats-standards.R b/R/srr-stats-standards.R index 4498a819..48731a6f 100644 --- a/R/srr-stats-standards.R +++ b/R/srr-stats-standards.R @@ -6,7 +6,7 @@ #' ensuring that references to every one of the following standards remain #' somewhere within your code. (These comments may be deleted at any time.) #' -#' @srrstatsVerbose FALSE +#' @srrstatsVerbose TRUE #' #' @srrstats {G1.2} Life cycle statement is in the file .github/CONTRIBUTING.md #' @srrstats {G1.5} No performance claims made in associated publication. Might @@ -34,88 +34,20 @@ #' covariance matrices are not within scope. #' @srrstats {G3.1a} No alternative covariance methods are supported. #' @srrstats {G4.0} Output written to local files is not supported. -#' @srrstatsTODO {G5.2} *Appropriate error and warning behaviour of all -#' functions should be explicitly demonstrated through tests. In particular,* -#' @srrstatsTODO {G5.2a} *Every message produced within R code by `stop()`, -#' `warning()`, `message()`, or equivalent should be unique* -#' @srrstatsTODO {G5.2b} *Explicit tests should demonstrate conditions which -#' trigger every one of those messages, and should compare the result with -#' expected values.* -#' @srrstatsTODO {G5.3} *For functions which are expected to return objects -#' containing no missing (`NA`) or undefined (`NaN`, `Inf`) values, the -#' absence of any such values in return objects should be explicitly tested.* -#' @srrstatsTODO {G5.4} **Correctness tests** *to test that statistical -#' algorithms produce expected results to some fixed test data sets -#' (potentially through comparisons using binding frameworks such as -#' [RStata](https://github.com/lbraglia/RStata)).* -#' @srrstatsTODO {G5.4a} *For new methods, it can be difficult to separate out -#' correctness of the method from the correctness of the implementation, as -#' there may not be reference for comparison. In this case, testing may be -#' implemented against simple, trivial cases or against multiple -#' implementations such as an initial R implementation compared with results -#' from a C/C++ implementation.* -#' @srrstatsTODO {G5.4b} *For new implementations of existing methods, -#' correctness tests should include tests against previous implementations. -#' Such testing may explicitly call those implementations in testing, -#' preferably from fixed-versions of other software, or use stored outputs -#' from those where that is not possible.* -#' @srrstatsTODO {G5.4c} *Where applicable, stored values may be drawn from -#' published paper outputs when applicable and where code from original -#' implementations is not available* -#' @srrstatsTODO {G5.5} *Correctness tests should be run with a fixed random -#' seed* -#' @srrstatsTODO {G5.6} **Parameter recovery tests** *to test that the -#' implementation produce expected results given data with known properties. -#' For instance, a linear regression algorithm should return expected -#' coefficient values for a simulated data set generated from a linear model.* -#' @srrstatsTODO {G5.6a} *Parameter recovery tests should generally be expected -#' to succeed within a defined tolerance rather than recovering exact values.* -#' @srrstatsTODO {G5.6b} *Parameter recovery tests should be run with multiple -#' random seeds when either data simulation or the algorithm contains a random -#' component. (When long-running, such tests may be part of an extended, -#' rather than regular, test suite; see G4.10-4.12, below).* -#' @srrstatsTODO {G5.7} **Algorithm performance tests** *to test that -#' implementation performs as expected as properties of data change. For -#' instance, a test may show that parameters approach correct estimates within -#' tolerance as data size increases, or that convergence times decrease for -#' higher convergence thresholds.* -#' @srrstatsTODO {G5.8} **Edge condition tests** *to test that these conditions -#' produce expected behaviour such as clear warnings or errors when confronted -#' with data with extreme properties including but not limited to:* -#' @srrstatsTODO {G5.8a} *Zero-length data* -#' @srrstatsTODO {G5.8b} *Data of unsupported types (e.g., character or complex -#' numbers in for functions designed only for numeric data)* -#' @srrstatsTODO {G5.8c} *Data with all-`NA` fields or columns or all identical -#' fields or columns* -#' @srrstatsTODO {G5.8d} *Data outside the scope of the algorithm (for example, -#' data with more fields (columns) than observations (rows) for some -#' regression algorithms)* -#' @srrstatsTODO {G5.9} **Noise susceptibility tests** *Packages should test for -#' expected stochastic behaviour, such as through the following conditions:* -#' @srrstatsTODO {G5.9a} *Adding trivial noise (for example, at the scale of -#' `.Machine$double.eps`) to data does not meaningfully change results* -#' @srrstatsTODO {G5.9b} *Running under different random seeds or initial -#' conditions does not meaningfully change results* -#' @srrstatsTODO {G5.10} *Extended tests should included and run under a common -#' framework with other tests but be switched on by flags such as as a -#' `_EXTENDED_TESTS="true"` environment variable.* - The extended tests -#' can be then run automatically by GitHub Actions for example by adding the -#' following to the `env` section of the workflow: -#' @srrstatsTODO {G5.11} *Where extended tests require large data sets or other -#' assets, these should be provided for downloading and fetched as part of the -#' testing workflow.* -#' @srrstatsTODO {G5.11a} *When any downloads of additional data necessary for -#' extended tests fail, the tests themselves should not fail, rather be -#' skipped and implicitly succeed with an appropriate diagnostic message.* -#' @srrstatsTODO {G5.12} *Any conditions necessary to run extended tests such as -#' platform requirements, memory, expected runtime, and artefacts produced -#' that may need manual inspection, should be described in developer -#' documentation such as a `CONTRIBUTING.md` or `tests/README.md` file.* +#' @srrstats {G5.10} Environment variable GALAMM_EXTENDED_TESTS used. It +#' can be triggered by adding "run-extended" in the Git commit message, in +#' case of which it will be run on GitHub Actions. +#' @srrstats {G5.11} No large datasets required for extended tests. +#' @srrstats {G5.11a} No data for extended tests needs to be downloaded. +#' @srrstats {G5.12} No special conditions necessary to run extended tests. #' @noRd NULL #' NA_standards #' +#' @srrstatsNA {G5.4c} Published paper based on data which cannot be shared, +#' for privacy reasons. +#' #' Any non-applicable standards can have their tags changed from `@srrstatsTODO` #' to `@srrstatsNA`, and placed together in this block, along with explanations #' for why each of these standards have been deemed not applicable. diff --git a/man/VarCorr.Rd b/man/VarCorr.Rd index 5ac34b92..f29cb2c6 100644 --- a/man/VarCorr.Rd +++ b/man/VarCorr.Rd @@ -16,7 +16,7 @@ to 1.} \item{...}{Other arguments passed onto other methods. Currently not used.} } \value{ -An object of class \code{VarCorr.galamm}. +An object of class \code{c("VarCorr.galamm", "VarCorr.merMod")}. } \description{ Extract variance and correlation components from model @@ -32,6 +32,10 @@ mod <- galamm( # Extract information on variance and covariance VarCorr(mod) +# Convert to data frame +# (this invokes lme4's function as.data.frame.VarCorr.merMod) +as.data.frame(VarCorr(mod)) + } \seealso{ \code{\link[=print.VarCorr.galamm]{print.VarCorr.galamm()}} for the print function. diff --git a/man/print.VarCorr.galamm.Rd b/man/print.VarCorr.galamm.Rd index d6928447..4e56cb16 100644 --- a/man/print.VarCorr.galamm.Rd +++ b/man/print.VarCorr.galamm.Rd @@ -13,8 +13,8 @@ ) } \arguments{ -\item{x}{An object of class \code{VarCorr.galamm}, returned from -\code{\link{VarCorr.galamm}}.} +\item{x}{An object of class \code{c("VarCorr.galamm", "VarCorr.merMod")}, +returned from \code{\link{VarCorr.galamm}}.} \item{digits}{Optional arguments specifying number of digits to use when printing.} diff --git a/src/compute_galamm.cpp b/src/compute_galamm.cpp index cc6d5110..9942199d 100644 --- a/src/compute_galamm.cpp +++ b/src/compute_galamm.cpp @@ -11,7 +11,6 @@ using namespace autodiff; //' Evaluate the deviance at given values of random effects //' -//' @srrstats {G1.4a} Internal function documented. //' //' @param parlist An object of class \code{parameters} containing the //' parameters at which to evaluate the marginal log-likelihood. @@ -70,7 +69,6 @@ T loss(const parameters& parlist, const data& datlist, //' log-likelihood. The template \code{T} will typically be one of //' \code{double}, \code{autodiff:dual1st}, or \code{autodiff::dual2nd}. //' -//' @srrstats {G1.4a} Internal function documented. //' //' @param parlist An object of class \code{parameters} containing the //' parameters at which to evaluate the marginal log-likelihood. @@ -177,7 +175,6 @@ logLikObject logLik( //' be one of \code{double}, \code{autodiff::dual1st}, and //' \code{autodiff::dual2nd}. //' -//' @srrstats {G1.4a} Internal function documented. //' //' @param y Double precision vector of response values. //' @param trials Double precision vector with number of trials. When trials diff --git a/tests/testthat.R b/tests/testthat.R index 976e0c79..e57db913 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -1,3 +1,32 @@ +#' @srrstats {G5.2} All errors and warnings are tested. +#' @srrstats {G5.2a} Every message produced within R code by stop(), +#' warning(), or message(), is unique. +#' @srrstats {G5.2b} All stop(), warning(), and message() calls are tested, +#' as can be seen in the CodeCov report on GitHub. +#' @srrstats {G5.3} Tests have explicit expectations about return objects. +#' @srrstats {G5.4a} These are new methods, but they have been used in the +#' paper Sørensen, Fjell, and Walhovd (2023), in which extensive simulation +#' studies confirmed the correctness of the implementation. Furthermore, the +#' simulated datasets, which are documented in "R/data.R" and exported, have +#' known ground truth and we confirm in the vignettes that the obtained +#' estimates are close to the true values. +#' @srrstats {G5.4b} Wherever there is overlapping functionality, results from +#' galamm() have been confirmed to be identical to those of lme4::lmer() for +#' linear mixed models, to those of lme4::glmer() for generalized linear +#' mixed models with binomial or Poisson responses, to those of +#' nlme::lme() for linear mixed models with heteroscedastic residuals, and +#' to those of PLmixed::PLmixed() for linear mixed models with factor +#' structures and generalized linear mixed models with factor structures. +#' @srrstats {G5.5} Random seed is set when simulating data, but the +#' algorithms are determinstic, and hence don't depend on random numbers. +#' @srrstats {G5.6} Implemented in the tests, both through data simulated +#' for this package, and through simulated data from PLmixed and lme4. +#' @srrstats {G5.6a} Tolerance in testthat() set to relatively high values, +#' since the outcome is platform dependent. +#' +#' @noRd +NULL + library(testthat) library(galamm) diff --git a/tests/testthat/test-galamm-glmm.R b/tests/testthat/test-galamm-glmm.R index ee413732..64676611 100644 --- a/tests/testthat/test-galamm-glmm.R +++ b/tests/testthat/test-galamm-glmm.R @@ -1,6 +1,8 @@ #' @srrstats {G5.0} Datasets from PLmixed package are used for testing, and #' results from the functions in this package are precomputed for comparison, #' in cases where PLmixed and galamm support the same models. +#' @srrstats {G5.4} It has been confirmed that PLmixed returns the same results. +#' PLmixed is not run inside the tests, because it is too slow for that. #' @noRd NULL @@ -112,3 +114,42 @@ test_that("Poisson GLMM works", { ) ) }) + +#' @srrstats {G5.9b} Algorithms are determinstic, so changing random seeds does +#' not affect the results. This is tested here. +#' @srrstats {G5.9} Noise susceptibility tests here. +#' @noRd +NULL + +test_that( + "Algorithm is robust to trivial noise", { + dat <- subset(IRTsim, sid < 50) + dat$item <- factor(dat$item) + irt.lam <- matrix(c(1, NA, NA, NA, NA), ncol = 1) + + form <- y ~ item + (0 + abil.sid | school / sid) + set.seed(1) + mod <- galamm( + formula = form, + data = dat, + family = binomial, + load.var = "item", + factor = "abil.sid", + lambda = irt.lam + ) + set.seed(2) + mod0 <- galamm( + formula = form, + data = dat, + family = binomial, + load.var = "item", + factor = "abil.sid", + lambda = irt.lam + ) + + expect_equal(deviance(mod), deviance(mod0)) + expect_equal(coef(mod), coef(mod0)) + expect_equal(vcov(mod), vcov(mod0)) + expect_equal(factor_loadings(mod), factor_loadings(mod0)) + } +) diff --git a/tests/testthat/test-galamm-lmm.R b/tests/testthat/test-galamm-lmm.R index 0cb737c2..e580aab7 100644 --- a/tests/testthat/test-galamm-lmm.R +++ b/tests/testthat/test-galamm-lmm.R @@ -1,6 +1,8 @@ #' @srrstats {G5.0} Datasets from PLmixed package are used for testing, and #' results from the functions in this package are precomputed for comparison, #' in cases where PLmixed and galamm support the same models. +#' @srrstats {G5.4} It has been confirmed that PLmixed returns the same results. +#' PLmixed is not run inside the tests, because it is too slow for that. #' @noRd NULL @@ -437,3 +439,60 @@ test_that("multiple factors in fixed effects works", { ) expect_equal(deviance(mod), 7891.36597569292, tolerance = .001) }) + + +#' @srrstats {G5.9b} Algorithms are determinstic, so changing random seeds does +#' not affect the results. This is tested here. +#' @srrstats {G5.9} Noise susceptibility tests here. +#' @srrstats {G5.9a} Adding trivial noise and testing here. +#' @noRd +NULL + +test_that( + "Algorithm is robust to trivial noise", { + IRTsub <- IRTsim[IRTsim$item < 4, ] # Select items 1-3 + set.seed(12345) + IRTsub <- IRTsub[sample(nrow(IRTsub), 300), ] # Randomly sample 300 responses + + IRTsub <- IRTsub[order(IRTsub$item), ] # Order by item + irt.lam <- matrix(c(1, NA, NA), ncol = 1) # Specify the lambda matrix + + set.seed(1) + mod <- galamm( + formula = y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), + data = IRTsub, + load.var = "item", + factor = "abil.sid", + lambda = irt.lam + ) + set.seed(2) + mod0 <- galamm( + formula = y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), + data = IRTsub, + load.var = "item", + factor = "abil.sid", + lambda = irt.lam + ) + + expect_equal(deviance(mod), deviance(mod0)) + expect_equal(coef(mod), coef(mod0)) + expect_equal(vcov(mod), vcov(mod0)) + expect_equal(factor_loadings(mod), factor_loadings(mod0)) + + # Add trivial noise + dat <- IRTsub + dat$y <- dat$y + rnorm(nrow(dat), sd = .Machine$double.eps) + mod0 <- galamm( + formula = y ~ 0 + as.factor(item) + (0 + abil.sid | school / sid), + data = IRTsub, + load.var = "item", + factor = "abil.sid", + lambda = irt.lam + ) + + expect_equal(deviance(mod), deviance(mod0), tolerance = 1e-8) + expect_equal(coef(mod), coef(mod0), tolerance = 1e-8) + expect_equal(vcov(mod), vcov(mod0), tolerance = 1e-8) + expect_equal(factor_loadings(mod), factor_loadings(mod0), tolerance = 1e-8) + } +) diff --git a/tests/testthat/test-galamm-semiparametric.R b/tests/testthat/test-galamm-semiparametric.R index f609216a..47eee1b9 100644 --- a/tests/testthat/test-galamm-semiparametric.R +++ b/tests/testthat/test-galamm-semiparametric.R @@ -1,3 +1,8 @@ +#' @srrstats {G5.4} These tests compare the output of galamm() to output from +#' gamm4(), whenever gamm4() supports the type of model. +#' @noRd +NULL + test_that("galamm reproduces gamm4", { dat <- subset(cognition, domain == 1 & item == "11" & id < 50) diff --git a/tests/testthat/test-galamm-setup.R b/tests/testthat/test-galamm-setup.R index 2199fede..95e4c269 100644 --- a/tests/testthat/test-galamm-setup.R +++ b/tests/testthat/test-galamm-setup.R @@ -2,6 +2,15 @@ #' results from the functions in this package are precomputed for comparison, #' in cases where PLmixed and galamm support the same models. In addition, #' dataset "sleepstudy" from lme4 package is also used here. +#' @srrstats {G5.4} It has been confirmed that PLmixed returns the same results. +#' PLmixed is not run inside the tests, because it is too slow for that. +#' @srrstats {G5.8} Edge condition tests implemented here. +#' @srrstats {G5.8a} Tested below. +#' @srrstats {G5.8b} Data of wrong type is being tested. +#' @srrstats {G5.8c} All-NA fields will cause an error, since there will be no +#' data. +#' @srrstats {G5.8d} Data with more columns than rows will cause failure, +#' because the design matrix will be rank deficient. This is tested here. #' @noRd NULL @@ -544,6 +553,8 @@ test_that("missing values are handled appropriately", { }) + + test_that("edge conditions tests for data", { expect_error( { diff --git a/tests/testthat/test-parameter-recovery.R b/tests/testthat/test-parameter-recovery.R new file mode 100644 index 00000000..776d0aa3 --- /dev/null +++ b/tests/testthat/test-parameter-recovery.R @@ -0,0 +1,157 @@ +#' @srrstats {5.6} Parameter recovery tests are done here. +#' @srrstats {G5.6a} We are testing recovery within a relatively wide tolerance +#' here. +#' @srrstats {G5.6b} Parameter recovery with multiple random seeds tested here. +#' @srrstats {G5.7} Tests here show that parameter recovery becomes more +#' accurate as the data size increases. +#' @noRd +NULL + +lmm_simulator_function <- function(n_ids, repetition) { + dat <- merge( + data.frame( + id = seq_len(n_ids), + random_intercept = rnorm(n_ids) + ), + expand.grid( + id = seq_len(n_ids), + timepoint = 1:3 + ), + by = "id" + ) + dat$x <- runif(nrow(dat)) + dat$y <- dat$x + dat$random_intercept + rnorm(nrow(dat), sd = 1) + + mod <- galamm(formula = y ~ x + (1 | id), data = dat) + vc <- as.data.frame(VarCorr(mod)) + + data.frame( + n_ids = n_ids, + beta_estimate = coef(mod)[["x"]], + theta_estimate = vc[1, "sdcor"] + ) +} + +test_that("LMM parameters are within a tolerance of their generated value", { + test <- lapply(1:10, function(i) { + set.seed(i) + res <- lmm_simulator_function(n_ids = 100, repetition = 1) + expect_gt(res$beta_estimate, 0) + expect_lt(res$beta_estimate, 2) + expect_gt(res$theta_estimate, 0) + expect_lt(res$theta_estimate, 2) + }) +}) + + +test_that( + "LMM parameters are recovered with increasing precision", + { + #skip_extended() + + # Simulate data with repeated measurements + set.seed(10) + sim_params <- expand.grid( + n_ids = c(100, 200, 400), + repetition = 1:10 + ) + + simres_list <- Map(lmm_simulator_function, n_ids = sim_params$n_ids, + repetition = sim_params$repetition) + + simres <- do.call(rbind, simres_list) + # True value is 1 for both beta and random intercept standard deviation + simres_rmse <- aggregate( + x = cbind(beta_estimate, theta_estimate) ~ n_ids, + data = simres, + FUN = function(x) mean((x - 1)^2) + ) + + simres_rmse <- simres_rmse[order(simres_rmse$n_ids), , drop = FALSE] + + # Expect decreasing RMSE with increasing sample size + expect_true(all(diff(simres_rmse$beta_estimate) < 0)) + expect_true(all(diff(simres_rmse$theta_estimate) < 0)) +}) + +lmm_factor_simulator_function <- function(n_ids, repetition) { + dat <- merge( + data.frame( + id = seq_len(n_ids), + random_intercept = rnorm(n_ids) + ), + expand.grid( + id = seq_len(n_ids), + timepoint = 1:3 + ), + by = "id" + ) + lambda_true <- c(1, .8, 1.2) + dat$x <- runif(nrow(dat)) + dat$y <- dat$x + lambda_true[dat$timepoint] * dat$random_intercept + + rnorm(nrow(dat), sd = 1) + + mod <- galamm(formula = y ~ x + (0 + loading | id), data = dat, + load.var = "timepoint", + lambda = matrix(c(1, NA, NA), ncol = 1), + factor = "loading") + vc <- as.data.frame(VarCorr(mod)) + fl <- factor_loadings(mod)[, 1] + + data.frame( + n_ids = n_ids, + beta_estimate = coef(mod)[["x"]], + theta_estimate = vc[1, "sdcor"], + lambda2_estimate = fl[[2]], + lambda3_estimate = fl[[3]] + ) +} + +test_that("LMM with factor structure parameter recovery", { + skip_extended() + test <- lapply(1:10, function(i) { + set.seed(i) + res <- lmm_factor_simulator_function(n_ids = 1000, repetition = 1) + expect_gt(res$beta_estimate, 0) + expect_lt(res$beta_estimate, 2) + expect_gt(res$theta_estimate, 0) + expect_lt(res$theta_estimate, 2) + expect_lt(res$lambda2_estimate, res$lambda3_estimate) + expect_lt(res$lambda2_estimate, 2) + expect_gt(res$lambda2_estimate, .2) + }) +}) + +test_that( + "LMM with factor structure increasing precision", + { + skip_extended() + + # Simulate data with repeated measurements + set.seed(10) + sim_params <- expand.grid( + n_ids = c(100, 200, 400), + repetition = 1:10 + ) + + simres_list <- Map(lmm_factor_simulator_function, n_ids = sim_params$n_ids, + repetition = sim_params$repetition) + + simres <- do.call(rbind, simres_list) + # True value is 1 for both beta and random intercept standard deviation + simres_rmse <- aggregate( + x = cbind(beta = beta_estimate - 1, theta = theta_estimate - 1, + lambda2 = lambda2_estimate - .8, + lambda3 = lambda3_estimate - 1.2) ~ n_ids, + data = simres, + FUN = function(x) mean(x^2) + ) + + simres_rmse <- simres_rmse[order(simres_rmse$n_ids), , drop = FALSE] + + # Expect decreasing RMSE with increasing sample size + expect_true(all(diff(simres_rmse$beta) < 0)) + expect_true(all(diff(simres_rmse$theta) < 0)) + expect_true(all(diff(simres_rmse$lambda2) < 0)) + expect_true(all(diff(simres_rmse$lambda3) < 0)) + }) diff --git a/vignettes-raw/glmm_factor.Rmd b/vignettes-raw/glmm_factor.Rmd index 03cf11d7..c21a4384 100644 --- a/vignettes-raw/glmm_factor.Rmd +++ b/vignettes-raw/glmm_factor.Rmd @@ -89,7 +89,7 @@ mod <- galamm( ) ``` -A couple of things in the model formula are worth pointing out. First the part `(0 + ability | school / sid)` in the model formula specifies that student ability varies between students within schools. It corresponds to the term $\mathbf{x}_{ijk}^{T}\boldsymbol{\lambda} (\eta_{j} + \eta_{jk})$ in the mathematical specification of the model. The variable `ability` is not part of the `IRTsim` dataframe, but is instead specified in the argument `factor = list("ability")`. The argument `load.var = "item"` specifies that all rows in the dataframe with the same value of "item" should get the same element of $\boldsymbol{\lambda}$, and hence it defines the dummy variable $\mathbf{x}_{ijk}$. Finally, `lambda = list(loading_matrix)` provides the matrix of factor loadings. Note that we must explicitly add a zero in `(0 + ability | school / sid)` to avoid having a random intercept estimated in addition to the effect for each value of "item"; such a model would not be identified. The fixed effect part of the model formula, which is simply `item`, specifies the term $\mathbf{x}_{ijk}^{T} \boldsymbol{\beta}$. +A couple of things in the model formula are worth pointing out. First the part `(0 + ability | school / sid)` in the model formula specifies that student ability varies between students within schools. It corresponds to the term $\mathbf{x}_{ijk}^{T}\boldsymbol{\lambda} (\eta_{j} + \eta_{jk})$ in the mathematical specification of the model. The variable `ability` is not part of the `IRTsim` dataframe, but is instead specified in the argument `factor = "ability"`. The argument `load.var = "item"` specifies that all rows in the dataframe with the same value of "item" should get the same element of $\boldsymbol{\lambda}$, and hence it defines the dummy variable $\mathbf{x}_{ijk}$. Finally, `lambda = loading_matrix` provides the matrix of factor loadings. Note that we must explicitly add a zero in `(0 + ability | school / sid)` to avoid having a random intercept estimated in addition to the effect for each value of "item"; such a model would not be identified. The fixed effect part of the model formula, which is simply `item`, specifies the term $\mathbf{x}_{ijk}^{T} \boldsymbol{\beta}$. We can start by inspecting the fitted model: diff --git a/vignettes-raw/latent_observed_interaction.Rmd b/vignettes-raw/latent_observed_interaction.Rmd index 6e826355..79c88e6e 100644 --- a/vignettes-raw/latent_observed_interaction.Rmd +++ b/vignettes-raw/latent_observed_interaction.Rmd @@ -132,7 +132,7 @@ $$ \end{pmatrix}. $$ -We specify the factor interactions with a list of lists. The reason for this notation is that we need one list to hold the regression terms for each loading variable specified in `load.var`. +We specify the factor interactions with a list, one for each row of `lambda`: ```{r} factor_interactions <- list(~ 1, ~ 1, ~ x) diff --git a/vignettes-raw/lmm_factor.Rmd b/vignettes-raw/lmm_factor.Rmd index a8ed286d..e88387db 100644 --- a/vignettes-raw/lmm_factor.Rmd +++ b/vignettes-raw/lmm_factor.Rmd @@ -93,7 +93,7 @@ $$ where $N_{3}(a, b)$ denotes a trivariate normal distribution with mean $a$ and covariance $b$. -In order to fit the model with galamm, we use the same syntax as PLmixed, and start by defining the loading matrix. The first column contains $\boldsymbol{\lambda}_{m}$ and the second column contains $\boldsymbol{\lambda}_{h}$. Numerical values in this matrix means that the entry is fixed to the given value, whereas `NA` means that the value is unknown, and should be estimated. +In order to fit the model with galamm, we use syntax similar to PLmixed, and start by defining the loading matrix. The first column contains $\boldsymbol{\lambda}_{m}$ and the second column contains $\boldsymbol{\lambda}_{h}$. Numerical values in this matrix means that the entry is fixed to the given value, whereas `NA` means that the value is unknown, and should be estimated. ```{r} @@ -139,7 +139,7 @@ mod <- galamm( ) ``` -The model could be fit with `PLmixed` using the following call with exactly the same arguments as to `galamm`. The reader is encouraged to try, and confirm that the results are essentially equivalent, but we won't run it in this vignette as it takes 5-10 minutes. +The model could be fit with `PLmixed` using the following call, which differs from the call to `galamm` in that `factor` and `lambda` must be put inside lists. The reader is encouraged to try, and confirm that the results are essentially equivalent, but we won't run it in this vignette as it takes 5-10 minutes. ```{r, eval=FALSE} @@ -403,7 +403,6 @@ form <- response ~ 0 + item + (0 + teacher1 + teacher2 | tch) + Using `PLmixed`, we could have estimated the model as follows, and doing it would confirm that the results are the same as with galamm. - ```{r, eval=FALSE} judge_plmixed <- PLmixed( formula = form, @@ -414,7 +413,7 @@ judge_plmixed <- PLmixed( ) ``` -We get identical results using `galamm` in less than five minutes. +We get identical results using `galamm`, and it takes less than five minutes. ```{r} judge_galamm <- galamm( diff --git a/vignettes-raw/lmm_factor_diagnostic_plot-1.png b/vignettes-raw/lmm_factor_diagnostic_plot-1.png new file mode 100644 index 0000000000000000000000000000000000000000..d1b7c4c4eaf3cd6396819a835439f0cee72dc337 GIT binary patch literal 98464 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⪼2m`roIS^#P;`I1*mj+v!82jJ$eT`~FRxd7D6!)6>Y z>E^~JL9u5$EF}vd%OzT&ROBA%0c5#MDS$nfJXx|87F_O$?)~VNj@dykto__+&GI<; zgwm0l%2?_alYZ`C7fMLxABsmV?{kbvKR1yHMIx7tX^&-O?v05%mv9A`WZeVEa@kZ& ztoc0vBsVq+@XTd>0gAa4%kn_W74=g9S(X_VdiH^PV-n5Rs|Mg@KH6+)pXF};d{=&e zX}-!fsPZX5cnWJYcPEQUJ(pyuza>!aSAR?G0x)xVo<0oF%6!5?&vL@b&T?y68#!63 z7m85k;hC@obBSGmU&>V8mfBq{#)GADHhZy)n`iBur+jq{Ydn|QDc@!`mY~__N8V&X zHle^x5%ZAO+^0SA*A8_LxA{3%QF2Y+;2=7d&Zsp z|BY924^}BR`tkdyC2acA*bIduH-a&~!{wB6vQbb3&E<}P0qD7eGL)bB?v8oWhyIbf z<%gjtwFvFk9GH*tjYsP>&oqj2G~rb1*Xa+F0K*{89GP8@Bb7A4-q)NfC1DJ{5 zL`a#izFIPY0u;~MO)VIRT=Psx2rDmlhO>^@{nk#Kv`t!GfTGSpp(b#L6_xAZ?J5U= z$wD61AX8SfMOi=tr~~@2yt58~)VgRpwEY|)#DIx4;bcN~;l@aC4y!JK2e5_Zm07s%=%X`T>tliwXPJQJG6pRDw^(hk)P?V?s z0Fso!uxPUnfS!6&3iK1X15@X$ao5Mzm8-vTssC)3YOePL>Md`E`fcB89`@-^{C==vb$zB20?i){jljK4nqNI?evAmx|cE+cj z{EUZAE0RD6^F*Yk=(nevQO79rF$uon8(fw|5Tm~NJiRhCemY4#qP_(oEhcpAB7Kh@ zM^#q}AkBP~DQF4R8CGce;N`DAs7|zPOsGk-qni0kT}PeDF90pqt)se~=Z97=)UZD^ zv2%Q|g1-D|gEipV6hM-cFACoCF%f6b7}tR30xXiE4sc07WlY3*pXUM;lj^pcnP-)S z`VPQKnmxcPKL-FL^{yT63*gG5&jCJJrhL)T0kBDjYlF7C$6w{)bLHz;c{;CL#{hpF3HokLLsMvfk?M&ppcJpGLRy|Eq&%xHi!Du8Tld zZ+$JPljlX{zbW1oZ&@WdsIPVI=y3IOk8u^OK)M+!sL=Qn2&%s3LTxoBf(^(eW#r^L zI$yq)v~edGgIodfRfb4^V~QI>EI_$uN=D)oxDYD%uFmgul&7V5Tnv7ret(vbZcADx zMNeBg5$PlVJYTa&Uq49xSAkQ0=n}s(UzOl20V>BinP5ern?S?DNZ+psVzKc>HR|MFZkOceid0LmxcWR;sw#wwQ21EeMgzTK< zh3eowN4`-CfnXv2O!osZ6R&41W0Q|4Zh>NeK|ZdTkbPDj0P&eGKK8E9Nm!mHYUe2= z?hi^gzbo5w>c&SaNQ@s{{=fmCdJc#=`YUC^Iq?U;%z28AK&POuQvp%8YPbALxv6}B zHV~`t%F)egPn2Ei1KbRsAR^%RpaDiMo(UC3BdVf^A9b1o#WZ zV7*@qh^uw5=+-&WlMk8AYxvesX1@lKFk*8vv9q$qCR%Hv~U@fLh)gR%24ho>Biw0Y2GI^$80& zV+Oo^jvE3MAJN$<%b4i%5%NPVOU6I|c8+&t$=h{X-;ygTTD?5W)u7H~A52WjhTQat zay-kYZXC&rEJFE6+4<~waw``Mmj-u+vOLGVK`zSk99mzVURLD0HY?k2QQSl*3Q5@; zC+Zv)Z@LWhQ78%N;z7rg3zrBvqpkzg(|uq(8WZUGP)za|pqu5O`6IwC+iUEh?v1~A zU2wW1tI zy9FOUtfZYsIk2QeNn3(6YRJ~+q6cHr9&bMupJNit7uv9d(DX6sp0?hipBLMT0kHNQ zt;}*`d1E`!>{t#F!y=biU#QK+=c`b(W&CK&ug z7Yk8hCF>V?v$pj|J?osZSnke|i~Px!B~H$)Rn|RLg+=NSc_x&TXe# z$-WI-2wtM&1w`a)t-0Tw--&T5zfczHkvv-M;`*3E5zbWt<2mky2+AB!<U-he@#z{5E{Y2N`FkntS| z)j2+2{!tUA$<0KKmIpNMl{ZkSe8NIZrNZ+jdfzKAM<7QrvGEFU4+}KG8EB*H3t-g+ zh}aybfXVYfYy|VlAARro_y_0;IF&nItIzUsPvzsh&m-__2T7FY>GB$f)uJ zuJyT&1&ov@%2rsHDNEP6K!CbDlm*oFmG4zI_0$G(Co}aUD+)$1GP!SoHShXMo_xlX zKmcnTc#hJf-e`13eFo^A_XjAARr{y|P;Jw0bwa1BmpY*2DLH)FXlu`?D`i0Yj8iFC zhg@puGq;R-QB=7Y^i8lzxh#`w4&aMPk&JbK&!yzIB>`$O;J%92>ZT!Jhd9a7P!#`| z^pke`J&wuP^*jn*YydZedUAh_$#0T7jI1fkc7vo`a)(q>PfJq$mCLcN9f#vO0luT? z+MRO1AmD^Ug~O*cbV|DJXM!SoT)+tk(}xGu;r6qb|~*H^Hgc}=_gX2XKd@BX3Ul|e>h;sUgsQPjP0&-D_@R4#8F?;~Ho zFK#I=Ew031Jkl-UKCTtZH7!YvErHV|=J(ZUpAt9@8qg~0xa|}@8O0AfwRdf038L=M zytPvrYZKMTYKcj26=FT>JOcD`tJm-+W?vqtl>($U$hHH?7oF&WRRpxg>FnxFj82kZcAi;Sh1%(HH+ zGS)7Zgmt5i{#f*m%1|DQish!y>Y4DfS#!6bY3sV9`%%I^mt~6~ zVDYg)F$#{#S6)oW^XFNKEJ)9>K-HbKh>lfH-{tFhN8eezMH@TkPx*E4;<8BttK7Y0XiQ-Zlu2gJ%iA?>8ZI&xvHaCdy{mr&lF5(EF>06q0gnzA;35v0;muXp^xy2Ez(Q#c&-bOMaaed{gv`qw1J{M{lC80k|J|B|mbsok=Au zwK~1Fqeymsz)H32mbaBHCe$4_1An1O5=iq)n?StsvZzc-4cZ+*j>!b+c@!((y@>mo zq)k@-Oe7|ApG6Lj$%JAe_N<-8!it_>wzD0D$)qkO0CkJPm5&JxNCFl>VvOodYMtBdNU<27-R-_Q4{YV_=s7^^b6sCN+Lyl0p~-z# zreoESe92UPWLEc=EPQv(_%eRT#{EDRXuIZ*5~VJbm(geMdE;66?g8lXB40r4S+Yb| z`+e$n%NY5tMWS;Pi8QBJ$cIndpK+%ylsffsl%H}axTt>kha%UbIUBv_24yr_f8y};J9 z0H}bvCf_hlQKu}uDp#8p9}1LcCWVAXW!LTzI@8cBUg%9f#-ca}DO^FRpz3$ZgTOofbak)#8$X_-WU=h!?|abo3H%fS z`LU9hB^IyWOC{sj_!Wq|-$bEQ&d3bdDpza@u;!QF>%}e!>6=`Y zA&}AJY;zda1*_YHTIB*{Oo_?Rqz_1d1z@NNoYh_cWwP_EKqqxlHU**p*U`NNcJ3|E za$XtBK$Of1Xjz+{r*u#n)Wb85@~UWld5`7P^Uj~mAHW=+t9p|=d5qVT<+>wz)wODy zy1M4Cz*-(-ee0Pj*L|LI&-fhRPyOOJ@2!z?soxLttIDT!xEv#joW33rEFEm zm~8HICRRM9ckzA-whfw2m$<_y@MJ)CSTB2UXjidcloP+XWl7n^Dq-EotCGC@K8{mr zfIouXI<5d8K{~mN#1D%lfRaASgbJgv`>_L|RV5P+?c9%m~=)p00 z|2FG~b#F5BEUFqk4P1dP3!Sw9=uw(zRkSI20yGo0$YhhW=w9WP1!f|Z03vX=%t~U- zu)s~?e4>E963UUq<3%W5z~KcT>ID$E6V$~7=TDiACT^CP`+$i0 zvI;#zUOof3qT$s`o+i}-BMJb31RUz(vuBh8WXmEhkSg#3utiPF6Hsw4P?i>f+4cIX z`quY4R(*W;{Fp}pxNFK8fw-Pm-va%r>-e1e>blPb*gn_&WtEpg;OK93OYByE`DlOn z&VBa>Xvt9hl%r3uBkG2V#^(6l0&;$HN-ePR3}ul#mFZ9a7z6rGe~I>Yk2?BYltQTA z!~__l^1}ca<6}|=g*4XXCsV}Yl?N++kUy8s;>5ca-`%GDpr%4ttqPzvjjh2ZY zI%~jWRf*pZJqmzLxu2+M1a5UwMggt-C~W$)6aoFOKgg!!<34p?)~2dw0jp>9-&j``d9BYU z-}2wP$L~d7`V6eqL!PCG7-RhBuB)d$#~S$^DNEm}SGBE_qNjfuRB>oImHHW5m>#43W^-gmtJxO{GaKZ2e*jsVXOEP!iFtX2SW z*op6qe-o%xVx!U_dgMgQ{gcXtBRf9q`37Uw2u0M)^HUaCJ+)chF zVt<}Bp#ow+f%*sZC?;eCH05Vr-+c5yp@lRE^v3B=lxN9E1|!!AL2}azUL_2Trm@BK_??_AurP{Ogs#l6K%N@fe0bzHQ>8^oxEtzC%jFVp68b#$M_u^)cBL10X7!HE%)%nk?R0GDk@ml_e3%wX{uM zv3@!Ip=02ZiP2=KJ}6ofZPAvNAn~7?+*vsV92gf9CU=1Q3q(!Y?)9fn1=%@AsQ~OI z^a5ctta59T_t_uda8ACSqhO3yYu)EqmLk9zgT;WXa>je>b3Iev3&Ov{4XufAVCe^&hGXa2kk7HP$`AXSPHgXe|aYLAx?6Qt|^TI!EtfWrLlNJy@SaCkAvy_3hs&h>2xv7u)lqETD10b6j#RQ*i)z`*#0NwmP_Zm~$YXc$mQLcNm zSw3N{yDgLZs$AEkl2v~B(7yoE@#~JyCAT^$zr9kG;~p}I<(;|begJj$oxZ4kj^&xT zIgO2D{GObbzd!8?Aj^I1ja&Q5hjNn}>&QEl(QJ!nj9=rB{M;9An|w8FD5?3%+5qF+ z;3@z;pELf#+Rk^z2P2gm=*Tm`GdHjacTR406UuCEauaNo%k!kHaYG{)+!IV)=B01O zG3B~F{q<3-$jEmXgaR^!fsh;2_$_FM^8AjC+i|RUUeYel=r0>!c`b5S`uWP=|Gne? z{+k1AIOM)iHnJUiy^rGmo;+cn)~($fRr$x!f+w2*xYX#qQc(aF>zzMpRY|~_04-Jm zda$TD`JWd-WY*)N1Gx)OkmK?je!s;4I94Se;C_=xdTl~QvbfRqmG~EF@BEo%h1&Q- z5};RG^V-5PezrRzhxyI5iJz0Tfr4iZS(~d3?q?~BTxVU10bo6`K3O0E6lrleqRLrF zEGqS238CXzZ|dtBYm{Y<;#U^y*1fDeWywe0tT^9Uo$e!3-&s`rmwcP7RnONFFhNr+ zTd@%GaKFC*o2K@PgTzS`d zR-rc6_tPC&o5p0_=X-swW677istt?flE-+?XXh%9{T%Cl{im$)8SxJz`{7=H#+Q3t zGqxNtOx1__N2o9^niuJoQ*7Senzwvt-6&%^-mCz|sPC&qf?;ieUb#uKy3y_S! z;#vV5YaC1A-0A*U%~ij;cM;_4yyIBsIuDc<^Qdc%1uAuJuY2nr$HjV(d-Z`l#3|JM z1(KuEQP=8N@~Q82EJdNd?{zEy-|e2oI=gT2T-969nfmHq_sJV5lc#oijx}5?lzTku zI*Z;oqm&uH+8|(!buIYS`0;xMz{ZSmNAU#W*f8S{_!}#pp)?zBz!hLqep&XC-;oWM z;#I__tc3Af#fDKR!~)zP0yp&qg_O~8xm}~o=?_nJh$J^LH8$XhUut8>A+eD z8r-zh0KcS1(9k+^RjC2~R7tWT{Gi9=2aFn9MM8%IQ?xS{Lb8{<$p%0JbU#wxSqv;= zz$;4EWMVQBK;xO3Y%Lo@*#lM&W+$j z(E$$HsqFE(^JBSyfs89Z&nyB`m3cNt?FC?tXY%|qMWPfgGL;t@0_Vkk44gqO6Ro#VC7iQO5#v<3c_doEpD?UB22T4?yl7t}cKq;H}E$_K^S< zp!OYuqmJZExy3F?Tx=9nCe)Br)`%XN-8@#3A zvAzsm1IG`WmO0WzXWJPK@TW=+xZC`qcD+I~n+#UrxC-S44NaU+;SsTE8On#&$ z96$O>#)FBMRpL5p)kJGzG+tO0sJ}`q5XyjZOt!3U5$&ublV*Xa$+9N&#fjS{GM;t5 zt`!KGOwfYrVh1}`JR07TAWNaJA=aQPOa4_xDFN!GF4YcTi_$kSyH?=q^RmBX*UQqa zx{?hb^JhXXkaT_7^UKOP+jCWiy`BepdtI+<^3q==`(i|Z+g|M`naih?9-k>6H6W})@iar9k=8?St z4Ed@GD$iFs1_AD;wqw3ANoGWHtX-d<4oi26jXCl)l%5rdENV6;iYcste0^v9 zz~}eJBsfJN;uMqR6xw{gc2tyefbb-uHzvG{L>8qTlYKrH6KO_htCM7`#blhH(lX-ObJ|i7@d1+aEXH!&caWrv zut@jJB6e!q^LkyB7ujf=6u^>4k=NA@Cl>2GMrMoJt`wHtJhHvcjmHQIO&O7mGL=zf zizctn0oaqj0O5>;_v+M8q_V%rJA%ye)v4+gAe?!|Qm!d1>3n6ah;P3W}C1;5PcuuoWGfHe`SA$5}EfIN9yUdx)B#Mg+h__2}L2x3z|JwviKfB zaal%y;1q_^6w5;Ql*1?!?GE=?wnzQL=bkGj)YIyZa8+f*KY4^=l5HQKug?**I|Z=k z^I@^)Z`gbzLf(oBS?@~%|^eCXpd{DJ)LEPIqZ%hyIb zMa#2X(Cw9s8UAtBPtlyLBdkD7xJhlY;$lL|jd=w9$eX3)9E*%K#`1ALt-Gus&#}<3 zNh~_wSz*_YAUQ!ts8{r8OdRt+^z+%_W`cJt6zA#}}ALGb<{*1G#yO;c6qV%`^Qzi>pU5wr8Q$BiY z(<@;#j7PrkB@Vgy&6_K%rx!0kUh>@5v`!12*0=Tq;Q4(~ z#Rirr$x4=O5zs+X1N=cHxbt~S=TM7d0qN_l= z%5U+g%qmwsmEj)G0{bebKG(hOscVaQuKK$&kE*P(?quLQ;2qB|`>cLsRPsR?lRbZU z1!sB=kdDv0zn*bkzm+23e1W;TptkijV0SNt%NQGFW9+$R+yQ1~qp+Q$P*z!4e)0xx#iE90BRljct${$<=K-v9j-K(k@*9D{WN%Uj#`1TM&#W}hupAxh&u_r}Rrfl- z?C2hflRVXK+DQt<<@!1s=0;?s~y$9 z?ioKDC-q$AQC57N-|dLqsxhJ*L9NDK$=QW{m!7X=+vHEZ6D}gCq#Zl0iX3loQuXvhs8+Fm|40-XOqJ3QdlaSbh88^LxJ82xsx!kX_WgM&cPoOznN^3dp_{rlawx$h7yzB>Wtan|Iy+OFPHM zW|Bz*VzNV2whK+{y%UURjnwC_^KJOp?*pbrTEhyMJ|gLlrh{qH=&Mz;9$=b?G6Gtr z>KFowvD2iw(iZK)bFnJ1QXrHFJi?2J>%%>@VPda_UM5y%;6CGgukC zd(z_+?(vZR?rY=xUEihS*lV}FP8;~sYn<;m z_r?LIno&`+Jv?BV$NcOQP^Htgs0@vlOUz>7VkzS;bDofEK1fx@@H;owdDne(Ky|tmX#Mb3YAT_ErGU2WJ|f=<;+Q-W zJ8d6LKnETQs_ktB0(`==UmLt7ga5ySfL9G7FGVMZ0N*=KQJ?Ws?Y0xK8f|R%+?Re( zWO+1#i3koedhqLr25`z6&r`?VyG?-IgWj>I+h{!J`-J$N;x9kN)OQLoI3M5J3Gq8+ zBA+4<%xTff@ySobxC5~$OjNPx-J}mq^nHlh)V>yV@A*V%hcVANk=-G3r&KUPigqyf2=SN*X_*6I>=Vr#Eq1q!rmnv8-o*;8(@w;{j1+V(!sJARyT5Z^fBp4*cNrV8A`MQ9){pu76zzT5 zgwapE_L%f%>>~H|9n=3jhq)5V!6}xn$@&nj?|!2C!*mBPWxBYB@ym>n*%1rEWWva> ziGZ*U{nmb5S*HZa4ULX4G8s`}w5PbyW^S5GvPne#dk=6sR)h5zvAgfnbfO==#3Ol z6J7&lSYi0@nch}l?&A}uQ3$fj1fD;^MNE!N>6!dfG=vL*`MPcRNuBfCmT8{WP(LkG zXU+3841RciP+AXDyz7|Q_1UO*$7o;v?9G*!!w>Uh9!}w0-Yw<6Pgd`_rgt z{j5sv&opnJf0^cM-pJN4FZLj5lB0cZ&(XkL!?a&bTo5cDytzlyX65n><{OhSyuq*S?2NSu8#$4_X(dENr z9z2!SW z@DL7Pf@sGR!M?_`@TtQwbKHD~-$1aR{+ay=js$GxeVHvx^a@2i2h{IwTd)i>chw3Q zKH-Bf;vAPBmqX(Rl;rg-R)K@5-BI0Qx-^AZ{7YL5wN27)F`xOWnm^QM)SLGOrxVUJMLbXF#&*9Rjs=W z#Q73qnp<($F^q+8(>Mtrju}(iuWfs5-9D!6dfndsefQeC=Gu>a&NE@Am!3<17%3(T zpZwLr(`zbh6wk`IyKOstt>atwwb}l53}c6*n7;gSjF&Jgh>wtZ2{1?&C;@E70e@wp zm^)oz%^Y$b6L;P`BV?Exgl(}X=3U!gy4PclD@R+aL9Y`ZAXT-1ewZ7|*-|pl1@FRm zRuCA7(XYVTAkgfKI?DP%7|%6<8Gn|E(Zk z?h?Drgxaq9;-pXeT9yxeg)Yr$ZP^Tx}5##MOQYw&z;g_>HkM zeM%4Vn{Y#mux~7g@v6P`dgt4(Eh1e1cnPaE{aZE0OH4x_CG@>+Z;S9(h}k>;B_Hz+ z*XLh0;&&7u*Uz{pn9NLttRRV)934Pu6ia>S0i0e;V97jI)4Tv63ahQq76^Wk7e-d$iUnKkXej#AD>Pd?R&IFyh?SJ~G ze>#!I+iSMJ|NY!07o#LN6r8veZfj1NNo=HCu zv>_s=2q+WnY(E;!DLL^I@$7fBm5Jt!>u2s4EqtQ#Md?P1n0q zLQv9^sb+5MHZX;woxSsg-+K>L*JM`@e ze)~pY*q=n+JGVkyJM+^vn)a>lBkRI^FxFG^quqwjXkL|w*pvtu3J8H{y9FfEeN2Y z0npmHSxDYAU|PM(bNqhyMa|MqSOUUCZv^3p>{SGW`H<9KVChk@PM{u5fQi1rNz9X; z#kH9N^B2N4zDU}QV>BnZ!>VcdO?%?=gx}2cj?v&e0}TpZwlBxK?_A5uw(n~AG`Y63 zPXlWkzvTscE*iUj1X1;Tue-Ee$8f35ar{T?Nv$1qr#$4{^c~k4<9CnsdfIuvcaJr5 zVtX&&xNYv+>Z7xC7tyUu>2O5xjCDc^>fvX*3$ilSFxXgfMyON@7x7ZhdJD>5BHX^I7!5vd zM3nDRDiFZ{v188G2ZC0Uz27yX);G?)$iykFZZ(s`f5g=_{#LW9-%^;$KuVNL%xiAd z&N1zJeYY3{#zDa7n0l{c`y-RS>s|DHUuskH_oefW>!S?ZH-Rf~W6QwTKKA-`UE9D1 z=Jn!}Z_Wqb^VUhIK;T&KZ{M};AM@@t@RMKKF-#o2bPsMUIL;M-S-U63i%E)KAqbmC zqRbsL7Ad6-+|$2cTdpdm@rM9mT(rF`6mBg7dd=)o?YI#x0kgfdJ%6ju-}spL#VuGH z!hHUmjGjyPeXHYF$>0%O!9+B8h$EtrB(>HgeybPuIrlvn{O7#2oB8GlfR&;E#x5oT zQP~5M`<_2u>e(Jhulr>J{b&OucqL|SX|*(S40{jcy%IcV-H0CIgc*zHAdJ)}j$kR3 zTSh=QVIdH>f5bC7MceiRzVF;ZNKTZzy9ub)L!2-ag3`m9r`X4cTELE`yG>O;R`F3&CrX_*UFaO6{dfL5hcUC*0g50C zo%kJEI1mND4Xm3J`KQvQ7#jkBG&t|MeXAuenx3V>9QDksZG8N11+2^JVW3N_%VHoN z3s=eD5m@FD1^r1SX0n?j@BRm zhdTyyM!;yzCKUYJTg;}{+A=G`Pk2fjLJSB62o~bNyiQAD5=TI*IU~4;C_?GJCBoX2 zfN#yL`n&5-Ux<}9CGPrTVQ??n+kMRauKw3^D>GP+f7Jbz5kB@AeNWe~n(%#n_)fAo zF!zw<>up(|dY%;u9@1(tU-(S8hG%GU;#&sa8#u>g z&#W&`eOa11eMoR>Gb1|Y}gp=*N zy6^s1$>5nwm|Gzg=@23?D?K(x`o4#Mt@8(i|GYPCm8Y#+hEz-X-UE_6lQSl5LSVvM zVStt(g$~i+TO_qhO@nJ{(pM8ltXk+|?_MNoCVd(TB1W5WZ-N5No~wX)lKGu$!F>>K zM204X`5Hr+xoiD)uE+c?0Y;#W(U#EQc$v2LW$>=)AA!YwHOQ4=^jzB>+aKTCYrl(C zi0C(E4Wh03-p1n_7Z=v?^;&q>eb*3D*LK}+oBr zSp?QBwZ7}(efmH*Q{Cq28{qBR2ZdwOd3;{DS^<@44CWc zHxvuf@w<*@K{G)N5MlS^Ll-rS*^B6P4BmO(1LItecfZ@MhSuZTZO7l^6(sumAzRPHjNQvI8a2owHd9NU-(4mWS_cRQ z1aie;Tm$D>u>{_kFQAN^R{oQp{A7+14hY9v>zO$zkd_63=^g_#CkVv%<)@q9#>+IX zy;vT91SaR2I` zbDI#o$$(}Zep?aFxFVF!YHSgzr)Gq47@zTWzIawEx<%ukIT_((R*;M7 ze(DIwUx%Monv<8F;Mc)DE7i%{H&5?^2=SAf?|uE^7r&VMn1^!U(Jhd`!Jz@M`kj($ zcS_LS%za~h3s4s$!r0W&h~jaICE*lieTqQx7Q^|hd;SJVWW6$V!S5{U5I@NjHdwED z5Brb@gD{~!8YH-8mSv33dI&*5 z%l#rE2$zW$&EZ_k!+j6|=Y3-+jgZ8Ieu`Ey?5e-Bap+s;I(fCk@ z`5V8n5SsSDyRw`of(H<4?{W`}Ycs|6ntj%>m+bqq2K0e0T7{~9-)P!XFb9JtOj2|R zjeFpF?coJ{PQLjx(-&}kw80S`quGuy9Ibr{yD08O;DlfO#u%C`h8OqzU+&imtQl!C*&$lO?SI#aiPm_wQTybt&89~XfA5XjjwEApQ+|1n*9Zu{Ek zOFQP(UT@ob*=oO@>;9$VJ^!3r@8dc9JNo(&U-~ZirTd=YjN@>_AAdJ|>o00qE#v&EYB2GYxD|0=wI+xf1`)}Ya zP4xd)KI$%z$l|{|1u4edOR`IpJt`o~F#IKyqdxXQtFQErLb>XgW1*$KN<6&prd~K>uxL8)7y%?YOZ5 z)(ZA)>pXNF(>GU)b~J9+>ys;#g@|Sc9AmBeI+iQeDL6(u{~@KAS)YKu;I8ts zTvD`Z76kLi{AIC=@H}H>HDf)X-P1f1i9T~%>1Tg5bmn$+-g%mM0_R5KpPS{MImc8E zjENZOQcld1yQ=4zw$HEr`gt`krVw+Ul34R84BV|3mgWo`Ih!3yv zE~?G1_o~Hb?>qVa=BP9>_()q8sLIj?&z%qRAFZ3_tzZ3xk)ADC5gx|MMB|qKZe=y? zX!>wjpqf8(ixnWucBY>B=-!r&RmY^jYUg?ZbyC>O19;w58>OAHrkTe;KLS=o(#xum zVc_0^&_vPu?c4#MzJaUM;015+9enf6H=~u&toUn_$`rgf;F{h*QprXG<&#&`d*(QDbO?LELsyGeZ~pTlly&2>Dkfp zjZeIbMV%5+KEb(s{}!aO6>P^6m&||@KehSw*u`q*#>c-Gdjl6u^hrq?ghaf3xM+w& z94`ePz1Rqkj_-W~OxuEAnBAD)tPB_5*<6R&Gi@Ud!S<4}O@fKp=#L zNfI%0FG7I(F&zqgiU02uAOA^|EIBp=GYa0ZpoC-k76iyt9-=il zwvPK_LJZNFG02s-qL!`u>YMvWkdnZln&msD_F=0JTkYljGL1&?I0xXZ*K!N&^|AK% zIs5B9KIi)5nwK%jhv^M&ywv+GuEK5i@Vhw1B=0vj5K}w+WZG4JmMI*bMqrq7v)oPc z>D!O3W9^Fzftenj*|%l7=W;UVfHm0NQz5BXW+$M}n3TdTnDbn-g$Uq77nu6hr%>fm zz@FecbJq2OX`c$*jp&bcBrH^=-zXzb?e@*Ce@henuWU$pgIf?(N5cyeoKjq2%rwlW z%)SSFoHxL{EzpM)UZ5c}g$tOY@six7(^z9#E-3Av)j&XzAUzfy8UV9AZJTLPIvcGa z41cr){V{#hN)p{axw*6t{Uugl+&}KYOvubf!=QPD=#A!0(~#yyyU>^QqiHvpwG;d! z&z<(otjHW+U^-RInZ(1?=|*8 z2=KIZ57#iE)}rvM7RUc><*s8IYD%;PgS~cU6}_FqQ~;3Ej$&3%dMp*g?XA9_Y?SP?x)oA;3Dmv zrC_bA;97GIt_q}OX)x!_8)>oT0Sln}aCu3!(}eF>5T3VkDB(LQF2P$4wYO2)0=84yg&86ir#-I?jSPO%JtI2JJZXhkC+cCIEz`FTO3d5F_z$@LH$cdI2o&cpwS_+bErsJX zqdNvy;H~)uN13JJJX|v``MJ5beuzNI9i|H^pPz(>TPQ=$mk%Kld$|b16-G%9;-|M+h>}{AZ4NH<-Wc+%wfC=h>M1jrp;0 zJiVu_OqMctzh&5P?|F9zJnju##9+RbM9|M4?T+LJ@fwivxZ519ui*_~!2j|u|1#B> zFfwV67%GXGzl{%?Z(Mpz7J(RlJ!VA8PZaJHtikX}`O@tW8X$;I(2o=~S^x>&^%yzd z93p|p3G5-|ixkL9@pzC2*R)8f%#zmNzbe$%KPKbH(xMg^Mg2)hPHtKn7oJ)25yVU2U)sejl~n=f21FIt(5z$j~UWhdVz+a~4fsT(j+YStYWV%sFOyOq`jV zDV=~T(w)T#?lFGytZ|o0D!3yI#?0_VIK~=hdx@G1*O?yzO2fONhhd=c+y@ z7$;12f?wpkT1PsB=;qJ5N6ND!?FAR0Y5N3E2 zO=ZTV>YiZEFu@6Wi^hEpezP`qCh9D<;T?pi%%U&y@E!LT&E~w6rr*6s&kI&sj|l|q zIos{|_1wB|d}}`uFebohZ4<3*0;9BbJjCE>#L+zHqdTo-0LL3a^&r#^-n81Ca5ZNpG+ zBGBJNpq>D~F#cH(3cXk1(c(C2p_r@Y=r>tpCT9Pcg92>L?J)nD+cB+A-&TTq3aG4^ zjGoEik9BZz^`Fw;TVDH?L3T8TUGRbxCh4CjO$ZV#Y`6WjAAIi&UNH;Lpd_uxRueS7 z(|=h09nqSX?K z@!wCBtjJyjBtE|L{V{HgH(J9md_)K1vjQ9(60t7gKH9}FdPGO0Fv5UvVE)Q*8lOnw zm<5MmAt1`wDyQjMB|f9&&wV4+&wA1daCjfC(J!W{KgU)=iGe$|{X;-|P9NLzd%xXh z?|6@SCXxT^@0Eb}_*-rd;o?(V_?+*tzQ-;8#N<8rsNZPYH%a|G|K8pU&N!yXn10Y) z^Q`U6A^W|D_QfglLiS25Xg5ih9YzjE3C8AguTg#%W0!HF;LaK)A1!}yHtx5ZSZs$j zr0)JOS_~UL$D%NOq8Wft5UboASGG1*gc0D)Vc9tMLA3N4cQpSq&AIQI_jdj&8N832 z5~JrosSzct@9hN}?|WwOgesEu3W+|ySamLeI%8?Uh!dzY8h7$xC#?lRpkb5PF;o({ zNh~d;>l~|M6*J&jYL0n*2#czQw1}9>ZdJvKPj31&J}EvXH>n1@4NCwX<9K>gT#!GX;{EsVO7%u_8)-vKa-cj8Wz4-RqZ|BeT z_|8Xzw`A}aeej|d`Dpv6?y-ts2fY3p5Dm+}wSowBKI%Q-JF|L}fHr<`{yP3PfiO~K z_@5QU@yGWaO@932qGADGw}LqQ>A-b-0^A!en;q=t$6_V_?i6`k3`V}zb%Pu-r9E^-#q`f3=;E(uNuUJ zKSiR%!3c~s|6Qjq@hbx4fM|F70_;?9MCYwV0gnao8LR&J)6odyi*P;jaGgi)tslXu zic*PwM+?N;+p!WVC z$pNqX2BOgp@~=z+qmCx9I}ijzFr27Y`LR;lq^X&t0@(zoVT5vSrM~?TCaJ`$(y;|Z zY@KMBs%Uw~q)>{4uYiG}%V)=k2?L1F-~HX+OJR-Qd3 zFQv|XrPv?>vARq^ov~s1f_Vh;wHlwXA|#^WRrpdq(;D;u!#2KH0Vd$cF_HEN?h1Rv zS{u(Kc+YkZ5HUox#Mr%zZwrXAu^;fdZ=m00y{m8W9I)+s?HBI&Ya9ERxu|w?241Qx z34c1D;TSx1t`P0}9H@rj60YHxIbu%9DuM6*+@COQfhp}o*PDwCDut)UVXmnFDgD_T zbx%UQlvMK#y(n1LRU3hOgmnUR)8QN|I406x5Feq~ye7aXdFy(?et>YVs$Sd-Bis`_ zJO`RmFcvgT9|t_s8wir!0=`+*>zGrSPx<2cKrJZ1m%~r<9^o2H^eR6wNio$iNoT@7 z_*F8YF{x#Bwh5Ng4|Cccs4~Ue#!RMC9n)MG*^TOiD}>SBB-;HZ5vx;~s^=`p8uS`7 zKA&EJd5(`Q6LC!G+JuOVkDqzpJ(wDqA)WKbjOciTlrcXtDMqrNbA0stlKkZYe?o-j zxP5K|AV4pIz${=Rz^K4&AufSw(jQ!95_C@{bbW>JjoCA)cIUpGcf8}XH9q5D9$k+= z=YF5+H&E-w8!rcW_10Bwji-|02m2q8m5xY@#pCx9=4 z`mHwoVNQ!Tu=rEsw8Zp?+X3himv2F!FIpiOSBy}EoH^xptP*YZA+~R6q9;Y}0wxhn z@76htUexk?gtun!n5GEVXu$LO__<9Qljxzqza?Uw|N1WBKs%G1pIyL>Nh7*dkXwq) z%-=eRO!7$gBM2Z)7_>Fs1?}jEP!a88R(j)8<0tF7?zRw^zdUBaSyhU!E?Uc6>w3hC zaKit`hiFX6r#~}Ygbb5M5D*`N31Kp>gSoc=yA{MAG6rwhVyrS$#JjW|#u`m}eCX~?F(B~6TQn%z@_A~RcYDAm zFU}?whP~70W6IweH(F<|;~@pvHR1sPA;I$#&?!2ydj$8I(`e#HgR5ll2xok^x85~v zFgc00j=h&3Z_VH#@>b*aj+0mV?5|KToRl_%jdn3$<BsO@K8I?E7n96-ME#>j>8N;k~GHmaX0&vxNKcrJscZb=)yc zj8;mh=y~n*udUR(T4`nEu+TjfC;-XrAKD`Fo<=W#H?F}Eu=S4?OS3~$WZs|~G6n?l z#cco7Aza<{SIOXgaD)(J@KklPW7nK_&mQIPZQcY=ZDP<-a|jS(JNH0`~>@<8h_8CSrhYmPKQX&!4D z96}$|iKMM_ z+ElB8$BrYKj;~tMdfl;qF!>b+FxU0>y1$;o)O!x@nKu1jL@NaV>@q zkBh4;R2ZQ4n05T-BX|?WwI?K-TbQNjbP@1qwcp0ZWX~c-AaDP+sV+Hlj3E7-v|Z+n zxhYjH@c=inT__mdh2ty{GR>r_ z*!V+fZ#A)e@&xbTz)LcCrYo!LVRlSRCL^R?ftD*(tsw2t1fwB8HzcrSBzIl{Pp z9i!2!VWFnRW5F1d=_1hjFXRMAVPwf9v2g{_viB7w*ljmdTn?-~Th->XWXn zmcf5A(sz8|B^f+Z7jt_->;lPoHOZ^I28o%g$M|XP zWw?YOl04#8Ed^miNL+(yBW}iK-#c4<@B)4iG(rK5z5A}!uWe1D?&&;&=6L_~YrjNz zW%?z=ZM$yu)oa${$NrD+_9MRw%=fP6D~@BvE1nd;;2kE+RR!m@=X>Ys64=aY#d-#> znc>ZY;xy(2Xurllq2PieJoDiT;MFHh{{Kk@NCH5GUCf_g%$mlp&zffbs(}Ic?pYH7 zx}ks9Bz0}f`+ke-c?@9Ut80z0fi@6sydRbc`SZ;A1P`?8CBgF%m{t(TYuV|ShPPz! zpDY(EzuAgG(kb#n@@;QJ2=vsXzkT$}{uN*L)QRp}NPx+tg-C~qG(Lwz944j3A`H@| zPLUH|cEB z!NNpmodT(3@$pUioE%?u;^pi{Q<&$qyrg@|q4m6pq@6UkxH2X`cskBMDQwbeEDLLV zVc?T~6YXKrd0Za`JE=t>YV!_5I41=u()n~FCC7UXftyr3eaJ^Po)x6$fK2R3ABt&n zp4G9Qv%iI=KJ~bFrjG+Y_8SOeo^;9L*`=o~?wBWqFz87Y(}#35bsgZT)Hr`Ji_biW zW;?j=7@Ue`KWUm$+oZ&X8K1%gCyzeXw8=Zy&UNOpMWAB-9@-60lLd8(a6a?UoQy?n z?xlS+*GXp$lbsq8Qsktrf(Q+h3ooTF0{GOtP=f+ZlltZvB2Y}KU7vAp8=-o}APrZ3 zy|pZ)?pbHT{8eisLj2^~%gK*Gu-DDA9Ya^Vw-vfH?|ST`?SI1HeE^7o*hX9WyZfqJOT_xFCNRQa;h<>UGX~$;1eOPjAVmw9yjHok7<2(6 z#6ab|TEGJHwRi;r;+mMgW`PKd&mUn1(TfB-IkJcsB4pWEW6%Z@c03x(5H&?{VqKVA zSOSR^>!O(qv53oI)}ba~AT$ci*sh>o#|a=MTI+fH>$maU2r`%8E++0?2YlQ&0N3Fc z9QLgVn9;f?hkbo&Io+7o9zI~^a7;f0Qhh~x9>%H<=iEoZ8W0VBa^@9?A}Gi+kJW5e zT7=UCa&r24Tc>p{1hv|F#Ne_=U=RXGaiBoIzYgoP*hnkCi4(Icnv^96an&% z3TWX=3l_cCn{D&4F9H)z5Oad0{_3y(>YU$r@q#Np@scKb<|n4`0+V$<=q(d{&t2nI z@Ch@a;2?fC&!XJz7MQcts1`KGFUO3|{LPo=xMyZEbewNd+6>c}Oi2u<_lL~vti6taWQ=AaNXih@Spix#)x~m z#{A;r=R#w`m$G_SiY=2lS6h^Ui5cP=FgETqeMqZg?icXKCIA+tzJbdjSw}}+rItw+uf>Ydwo6i7|kL?U|b1I zfwTqXDQVJMAkovXuCPZxd^{!~v)x>E;AzGuI(6Ag=@#`*deSv0m z!0`swH^7VxXII$biMa#Xjth#W3b$7L(gt1vb4wc*HMEqOJPc;$8+uDSx74yW@Ejef zrHz|PMLg#AF!M3j(@f2iC>7(jqV=0wtPQLI<`Yexl`f3_#0rM)Tmo7>H?8}!DxeLX z&z$!R=AxyfJqHWlU7$V7pz^a!{t*nOIZL0acHF(M1zd?}k@uRq9`l}AAh+M?wZ`*m z_qim!$ek30O$w8KVKp*o6r{NK2rsF@_kGAy6*!lP*+ZXfQ#y7eb-^|wXGPd%QjR1} z+x4DV1W5HSwYZPMHG>VjPa?kHFz(qHhM6p{wrtPdc%m-Me-j%s!^4%%}V^Jof! zW)#cNE@r=}y5&9shTx5~Iw?M$ITLFv1|nH$>p}R@L~J7*Ouzc}M-$P9;y1Ju+KXT$ zePqI(1Ce^IEzQPu{S)xF{5TZG*zwP1?Hi)tYPM-{V>YW%xRshYmP^On7nq@D|5n_ zwKLEBb|1$gG>jH+E(zQ-ckfz>Qb3*S^=ls4H*bwYrVMLAEpQPoRuXY>Ux6*xT1?9L zUNXS=;vnAW#9M0cq{m%g%8?5VnS2-^(pvuIOG11_x1ukf>t3(H;O`r&v@;Wq@FZ%M znF!Ot1m&8q5N-Kf7-BvC)P1QdCb(Q#>3ven5F`b66v#1&7126SEWXD;-%iR4IU61kGXMS&Fyl0@8+y%xO%fPvs=Ur_-(z&Z- z@Cu*s0g?Wc6(w=`(A)C0&+#Gnbd*A^Rt9-UwC>Np`y6l$4BlkkR*RDE!smrqnp7Ab z-!)C1R{h*SkdUSW>L0UgETKbgDvg?Sj_LA~t-13o6NrULpVgq|`$&6}_E$eT=4+pJ zLLg|WAb%Z#MJsU*annw`0s%+bpuQc)xG{63Wf3OCjQ`Megv}CF#IFREH38o670SVP zAHSdL50QPYzK(vL@4LLjyDU!K*EdE7-|lO-_J`?e4|m}xT=$!=o0Y)6`PUc{43(v? zRwi5hxt2=yA@UOQyw!eM@Z^_^<`rxC%&&pge)lPawKJYBz$*1z%5Jom> z6e6ajHSwW}=H$;!e&^ixx$pTF{fQ0_gSvI=Oyu!N^HYhi7w^DF|8-JP_}88r_zfEy zAX3+T`SAGHtsa<(K0dqxR)XZ8Hs_6vj~bmYm&_sADCWKEtUM{tthp?$MJl5783Ah6MK)RendsJSHknK?$DSLaDV!_?iRd6Wm^Z=_ZF__( z0Wbmu5w4ishCvH%i`j0Lo)x@?s9@q4x|#{o8d ze)D0f<^h~WBQaS0z+v-Q%?JoZulJ z-Z?@-H1nZN=v6Fir)cgo|DuhKaA3}xuTu32j}fAK&9QyiH*gWNF(KMksvOM-5a6F>*LvuuoW^+3tb+uuj1_Ox^Wx*cKBvFIb}BJdN7? zb6>))d4(Bc#H^fh%US%)8`dxPCH#K>{r8i{POGP(D>8*?!*v!fwJ!X{-8FLu!)L+6 ztO*xr0UBVwQa)VkUJCRm7DGu%s@@3x=D0Bx;@2Mq5PdVZ2`LA>?Hl-wAkeNoiU#KI z>zIlWf5aW~A?yjfd)84yV9br=ND75u%tO*JAUov7aGnCBTnH*H>n#D(N?!Lcc9Tf~ z2~x0$OS>)5hDqGgR$c@$y9*E}c71A2j$>BCrm@rz%Ku;7?B`arv1 zGaat)`YBuAOmQFI);H>V{M^tIt4gU4JTq1^W}c!VF~$a8*ffFKb6hxnW@ zVlNc}lWa@apK{M@Veos;V1jy98a>8J`Uc;{Wd9n#3`YY;G)z+W=WoX}Rir{RAUtgW zLnU;;2^xYNUHb?OR|@=)->VP8p;DVn=&nly{RjdGqi6Pw5eike2n{p|%v)dXjX=`W z^-mCB(V&UgB4TCgh@RuxmZ`&;3$p8Lywc+y@9Ygc7GEeKaJYyr*tdUP;lGEWU z{No}`Ao9>Iw?qHXuLSXa_T8_N!6V3|O~n0&FkKADmPuET#cKhg51DD0_W_2;Otc3+ z(*OBC--TKH=HBTyKZa27!(mz&)l2x_?|MiAR^uy5Y>cUd#I;O(tST5Z{2+|*#q*#0 z1AKSPb|!o^Zxfw~-NeVttJRbNmk9C4uR%-GVg4Bh^Cu#PNC5=NamOA@oTr21-|+^X z_y)Fkx5v=8x4?|e@5Yr7kO3yCKUyAZX$1%ZtGS73W4`WT&cS^^+ozf97tJsaS?1h> zwalCX<~I6GC~$A{kuRUXVJ@5J<~`v8BX?i(6RjtBnD^$pHh_>#0O7hb7mY#8jG*Ba z&|QLuv0poQrvw(y(D#NOv0}1(cvqhT|NDRcdxFLP`*msXJ{WVBAS@r;hx7-*UK7+p zX0ByAx(^WhHJEB`PZIlI|Lgxd{mtL}?Rxmwzvm4QI9NYO@(6*6ioqig5FSGpu}ct3 zx!YmxDrWKZ^Yyvzn?&yN-DCLLAqWT!ivXbpp6Qe14Brqbc*Y0FTnJo)=pj@z8U&~s z{HnR%^Ek)p;|)CN4Xn6^=<_q12agdnF>CmaIcIgZ`4eQEwAHCJhlw)ju6<(qG;hpy z53;WNNia0`mob?uENPfBP26z|m`hG2I``ui6)eXRCz#oM6>ukTR&bv7v`;PpzWk)$ z%_1?Z3QYQ*JL!0%{1GZtwq(*Tb0jp>9VhnS+JR!?xwdXx?|?hQ_x>FCAOGX`LJ$An zH(~Ikv|W%lE=T*Lz%}H-zxQ{k2!?#~nC5Q6h^IJ>_Zp%jj$@XIRc%huikI@JKFse^3YER$5faL0i0@kA*yq&b|G?8=tDdG+4|F_ z`?evHw|=kZj{WP-L%+uF6AgLlG(^xm@u^|qJ_^``xleH^#jAhXQc_ z;eE!gsF`YWD&d*ag$NE)ufo{Wp@>y?){`)|W8Izn_qMg{totfZ72#qk?L~XP$;xpz z5#-zKYPWG;OwphyiMPgxo<%Xab?h-eA~c+9k38nO=V*H|ie3N{sqs03Qu6Wd1zOpj znfBfbj|MOL2Ker1*R+BVfH5h;4Vr);IuZ0z=>!eY7-$~?_DH=Xe_D6Vc~W>>$InM< z4iTHwIPMi%3hf3kP0~#G>1hB#_&Fb9HL)4ecM3H65W2I^k#?~MpJRcdaUJk+-vGS1 z4``!}0VxhKKf`M#b5>4PDYybp?Za#ItnJr(xTh==7Ae7P=7Bk8NmkZMmNE6)SIOyo3f_cK?_8|z`1r`l;_uJOxvPPwN!Wqho(=4(phj%nH)67RCQ(5xQE zEylWFjP~^@)m6Sci$);}Nlgl^_#M_bb6=eeetUs-_(?&`jOP>szl ztcc!&)#g|pxhLNvTx9T&Ly!hjj}OWh43L+10vlO z-tb)@INzP1IjL++?cyR334}(NNDFbqQSI>NHN(n zLHhQtg9`wIXBH$7xQDhTA40|VxSIc*Lp(3JYPJF^)Bk*IbVgW6olqFjf8Q2cFBU!N8f~%>_Pp z?l7@Z!2IFMmoE2D|MXA8WX&6MuC*M% zZijaZL9$)QOP^)+LcDKnA12vc5@ z`H)$$*Mt$zJre9QOE>x*0yp<@t#-_U_Pd& zg;?CTxNG0CvHhLQrkiM#)0bnh9?ZH9%=}F6@#l}*EWY%iq1Ig39HfrHV=s*AdNIP$7q9;YZIeeAHNutT^j)Q=qR{2xmOJwl;ZYHcX zo)G{OnSbmdpf3Gy|MqXsq*sqRlI@vDVaV1eIcGXc002M$NklJwM&q7^ zh@w>t;VFUh+aKcL7y&^oYeWh2N7x)k=n$-E>%-(%Ldh?vj=gq{BUJ6bTgTUHdi=ia zDjeL`w#4|pcE|ab7YGQOFa4g&^u?XTnt1DegjV>A(ZV}S-rrqg!qlCIbC|cS=YUs) zh2D>_9Bq8!Rod2d?&CW1&w1wv57Dg8QRc>;v)+dB zQ0BzxSggw0Chdn%VwK8|2wyydPeFuPns)Ewl0ZqFPlb=b;03CfB#C;R%*Ut;?jOywoU{iXEzTsWYg_5~N=yLVUyjv<#BTkyqk?FG%jeK=|^IOey&G|YJY z4z*0J#|X)UT*okObIpC?8=gg$)-hnB?o0T#LR>+hn{xtK&Ci(dhYlo~ein#oJ6poJ zg{D>@0HkE?`4HyhzJlh^0<@Y2zPFroFG_sPTw@NWNY*F-1bl&l78Mgns4x2h3Tg6a z3k$(VgfE`KL&W%fW+E_2k@7@>Vj`qx?t-VnduQ-m7bcojoIreYW9A}N??C{YKxDuC zUb7VC_FPKO-eB}AaEEy3n7Ro}x{fbyCMMy^YE&39f`!>4EC?IoVFf7@Io0gW(e0#u zZ4kQnv*tQ&N&cQev~T==GX$%}`Sut)#;=X*N)Qo3gwJ&dE9Q;}>fbSc>wb^f>Sx`q z=MY}MUE6V$s5`#yuh$*>FRz_n<2$~~dIOh4l(!8HH9qy37jT=k$}?fu@OIg&(-fw<`Pt8YcK&UL z3EdnkSWyz=6*cdE3B;TJ?o+y~N=Hy2Kv*RcJrYe15~naLK3c&Nt-$ELzXaa(wjXNy z#WVPRbW8*ylKK*1ITGgTr+La(fA0*Ql+G-^0*(99L`yK*X!<5(fl$C5!emyUxzn&o z%(+Hm+r1(AK!x|L1NAkX#tm#@2pHXxC%M{_8OJYk2nryt{>>$3tYyoZZS=3D}u2j&GGOssT#AX<& zHQ-lBs?SLIJ!XRQN*;+!o)=$?M}57RgC-P18v2hu(wS&h4lx!YcATYu+zY~4pJ*WcZ5;M5)= zZkuEK<9GUK?_&M#wes{Ci+9~Rj^WmQvZHoix!i&XswKEn%h7Mqm2q z{4lo&)C6gGN!VrXH}5JGxE8}*fl_1~Tnj9rNn@RUDNV^%TASV^<7LLkGKk@_-qpIM zA+4GVP5_)BUBUwagrEPhko2=zd%o9MhaR2z=dem_|_SSF=GiiWg_XjM7#D+uy+ z_t?JT8;~9o33$>~;@_J)t3;)CvJ{grz)3BWf@8rKAk{-#d9fijlf$m&`xc_1&mpM;&aXox624ji9G%0R!XQctjfwgFcTzKh}Qeor(t8X2GI5;F#PD+n7 z9joSr5YBy7D%7{CTNcGI4%bS{Q$5e~ct&}*`g2Ymt$Tzp-m29QLV7M?yM4~}w>;N9 z@H`>DbC13A$FcYG2Ha4f9X^_z2&V>Kd!W(258KJw4ARkKJ_^or+*FmEO$5$AT5+ zBFIk)oqHL(S{Vnt;2XFkJqhMY9{i=#@9y|}*WkTyE)3cOa~-LiRKJ?nvW(;S#P9kz zq!?HJRV}>I4$9E(j#lzFJB({Se8~O5EEY4XOK| zyWRHrx&0j1y^V(dQr{15(XhAQb=Bl)+C49z4PIA!cbsNSYxn23ZLLhUwF0n)v6AUS zJW~Bw@xIS3S%p<;Yb6MrRyRv9hauWp~LFO(J^5k>e zhnKRZp4|Brc5sy%e9S72$}?c2~2ur?Lq?iyy8 z=v2fCp*W>H=IPt43Z<63s#75*BgxaaN!VfZQ|N*Pz-n&pM}wgis9u*qn$frw!AS1= zXc2HG=KLwrp@4_g<&4`_0S<*Y+*`FM8aXXSAsyO_>UQo;W1v;Iuj*G;vr=IyCd^sw z&hup+Pg~j$3j&R%pbtfKX!Bc*t8MnS`aIzMzJZteesGJmjP`0N)B?*UJaeAq445~G zP9M5Si>AS=%&9;VO8_f?e&DU5PKigEIYFB>ugs4G!p>jsLu&_#JDH_jBN)#zrYu}6 ztmd1!p&mS|mdcTqbv371z|1YQK@lEf*aOcM!F|a{ihV57)Hp% z|3l1+)`>@SFj!?O6PzOF1|4Q5fl5nE=_ z1HRlh0LL)oJs=dq6HI%rqS|%=67*1C1Z?dqVEU~R9M>6qXYS^5B9y~HE-&YlsD-Pl zK$_2llLW7h3rq>zg5{gPiMtF9#;{$7VH5O?0i7mXm@iyjm_245E8om5?fjYF=BM)o z^Ot+T7%+cx);r=pW4c%6j!<#HJAMN}tX&ZGC^&2Tmzv1C?XUgdM=;TE-?5+-Iph{X z$f|9VLK6*O+wA;Xz>V`N1V+=oiO+6Q?WD4akdG!jZB?RU7H85ET_5xwGrlb|o@$0n z>3%aq{^ei(<(LygBqtDvNz=IypfN4FPmeKGs&?l*v%D0Vn(+Vr@BjWBki^{D)H;QZ zm?=f!I?lYQKV#HS{~Q-|)ZfO|{l{*ZO&@#B(f-G8@!sA=@w4NG%iXtK&F%0Seh741 zaZDdIwfEZg>o~J{aXk$Ce61ijm(e%BoDghPx-Ia}uc@)o_UE4BYk+dK>RBik z3n+KyJ-Wo9U|g)&U(buW7^oH7%&`au=SO4Cm+itt&xK_%C=1ds z%>(W;^FBec<6;v(kevE)Eu1^~Ks($ZT!}KrxDeMki}(JR7a6>OCrcDc&k)SvgF`N< z4hLMv8^8!tjcUG2O8O2XzBIB_)*6CfVq5angvF?(owagR{oTq-u9k?!L~t6Wc;I;s@?? z0rnp6X{*o=H^d6ukQUc^F1Sh)Jt@+9zAN}0L|6|U`)~9HM7v94TY)K^#J&e2=1Kfg z;Y8jlXrqXRlpERvErA(c>Kv^>v@K1Ywm`!c5l^ZWHSZjA7IV98h|riSYeEdM8Lg#J z>&|DgoVWwxUp$2Xji%x}&4e}quUv0zA)ccZc@FzDZ)rp;gjt_|>RYND5V5Qe=}EMs zZfPhTPcIC??Cxk{KO~}`Szmwm!9z5_`}#P}zs?(At-0@e`R4Fug}biN9{!jcv~G9@ z6mFsY(#D%r5+*;|zxFh5cuA|K*}F%C-8sgMCLNTC*f}^0e^W(n(&y}Z7U^}O=~)Bd zy=)qmINB&PyZOv=##)!M(eu}5W8UYYQG$3tKRn8_HEa#@fmM_cUuj=zx7L# z+V_}<#(Piw!EJNOG4o2$UFW)BH^*7)%ykPv!DSY&2(Yv4tKaCg1z!Y?v9y`nfHtq> zGfjR4#{|k+S3>5LAXwv;d85Sbw*7kTd)|-RVdzoP80Rr$5?JsoF%Qr7SZ}Gp|F`&S zz74{B0wl<9;+qY!Oz@O|DUzjPCGQbP%%o~L-X8%-@2M3(zCRiOe_Enm#}7x!-vdQ% zd{YHvQk`zJV#R1;raFE7_1EJIq?H4C?)=!KTcu@b9)g4TtF%#4mNr1E;L{hVLffaw zz#Zd*D+23i=d1^`0Kr0hdB&_*jmmt0Cnrl7x(KLAPncs~h&!6Y{np$(|8-y54iradg_TW#-4*y|H?xWR` zV#922LQuy4$EO(N9Dkj*?p@%!?Xl-mMax+W6aQTjB&* z4csdPaklnpOSZHl+x4&K)??d!*GZwHeXZw0?9TrCy}sAB$Nug2{@&lmieG&e7Ow7h z3rAUi;Dt7Gc=E@JbJUBnBVN;3gJ{n@H}Hv-;3izGs>f^cKI{xr_MSZX(%_sZMbX%-_Ui z_Qsgim){B!CNvWvApkL9+C$V3BFj~}UhXb|QJ$`qsSqkAdIXah5+QUg)24GJl=l0F za1jdnyIVp<_ig+8A&kG*xxf8AqTe>h_Q!AW5#I&eE6!8^?Q<3{;43_AJLkEfFmE{P zyklj?z1|j6?{&pv!UHBNFiv1AmzlL61n4<#F6px_DRazu44*l4TrCVjxus?a4wr&<*J~4VFm43G1W+i8aQ_lHp4Do5%MOrYY`>_0isBwh-rR&f-DHtQV=)Sz16-eY_2F~587$6Rx?{q%dh#5=j~+c;kV z)BXLvbPeHDn`Ujmvoh~XV=sXbNfG6WLz|2`0@Pmo_hbUI7H z0XIY2WGB1@{1`@@ue$=@D&MKd`ACmbo1C8~AAN0h?7#9G;6u-(pU7H1cs}Gm`IA4H z?R`VhK15*r+=_4Tee<2$7hl02Xz_|5;`r;@pJ;o2>JW?`y0w+}%Rd-xWFmk0oYYPT zu^NA#G0L(1K8tYh>4nM8J^0G`Bh}{)(K$yt!!u(DaUH)EpCkwY9X}=irSl=G zDZI0kax~rXohvQ_&(;`@;)DyLP0t@(gCiDhl1U;aNEH0P{oB7yWV?IAL-8+SM8u_- zH~Qxzjx}xOuX)Qa?;hr^6|fPRzv8@NRMFli znx6kY>&Bn!(eJ&l^_XWsS61-5?{YnU?7!|C_%YXLb6*G*t#=RyQ&I3numvPJ;PM+V z$*r4g5@S>tiQt>(BL31{e20QS8YyrJ(L|c>;oU6)BcMlcoUutgGd{wcidxcZq`)a= zBz;ElnrIk9KrrHhz&XJ@2$UinQr#4ZFg6wN1oP-$&<`dr{SKju8E^y$_~d>Fs3IMT zr2oF(3bOrNCO}YfJ44j3Bwuv=ih_h;azK!ud1Q6AZvF-c& zp?&naWBYt>0G^d$!3nte7{K`8x)s%myA>eygIU4R} zjV4eY?gDc*OOg+dN&LX9z;{>sRSKNAN|po44Lf&Br#|5?}hNqH6eQE?ce;(-^{+g zwtw0&K|0RI*y~rbs{IHupIqCvYoA;K#-p$G_y&+$aV&%RVSK5=7n%p4Sa2h7<9!^H|X z0kG@rcYi+q`v7h7{f)p8PToO4w$h=xVrCF@ACXr`rt9dyK<`E+nat3 z2Cw>)$+rc_Pe6JM@Qd@8$7DXiGKdDD%Or@TKb5AU!Op5rR;KEtbq`FNza0}d;Ts8S z#W_)~vBun_SjI%e3&hb6(1&qi%9ntTE^eFiX@U%*WL%=bNt3TR)m>fVuJae((3j$Ikr7*UnrBRISS-n|jnt zc6@dEa9?9q+nVWf1^B=eFTa+LkhxDDsf91NapVTuMk5)sIag0gT#lKZDUnH@JAuDY zE-xHo#$<}OX0}{ZK7D?9`Mpe_#$a4r3GVN?Jr`482O~9|ujn=s`jqdr!W$+N-7Gu`;{-Z?BoQ(EA&w~#5CI>vv z8~BYN`mbijraz0#2+Bh2_?yb)`AP&ty*IFenEh=D~|ysyS82ww3X5r!?Z zyfhI0Iqw}4EtnLM!pCRTW9t^P74&0*L{lH1A*OBOnzRaYU|yI!LS|fo;SeW9j106L5#g^9uskDv7%? zRX$Yyb;NpQs3qjyqw{6z`sg`?jKF}A+h6;2zY=Xk+Bh)lGHmBehYM4}<)482BH)e#q&us4a<}0pYx|sM@V9=*vQl(<~Gacj>Twjp5LC|pO0IS8+{QN9REJSs7lYe-fC~=SX@^l z3^+%aHh-0!77v42iVaBZ`HLR_{ZOJ-8_(c9Vfe=U+8{ncj1S?Ol0l#mr-R{pR`1~> zfudp*Ht|ib4Hq@|n1@eE=lEnidm8E|jlR$W7=!>d?~P_$LLeySq0HT@i}`M};Po{k zB>KMu7%xI+)he1d;lKo48T40U<7n5B@E5ya*FLQSf+BPe}3WNtt-B=JX z_Y!&QSw-d|9R39E6n6yIX{$fy9A}{rScj-$)Pw@W%6J_^ECFqXpjoC&5E1FG9j%10 z;2nFnjse6~a9{U(ZHc$vh&O^qo3V0Kg@ZDNx%h$qjQ3#f4}4iK)C>~@W#qk$-(YIjy~bT7Co zD9xN?al_07xe8WGp86>!`)PyGTj7mz!dmqY|L_lEwKJ#D8uO7=kMMsNuml=!gXUC~ z#8BS?2=Ig_b6$pyaZrj_Hdqlp8iRj}cYMBQ2-Ks1ANW1OYxv;%UP2&l!oX%_rcC@E ze(=^i=0D?0bA2Z9`0iA%+Jj8ov%ifmkH21F3qE{#?P?nEiHB&7A3Q{F0&x_@2qMfX zbMn*05EMeFX63*z!U=*AoWqA6W;oY`P|kgH>$gB5*Yh{Z@fU>SIRByh^cvfB-`{e+rka)wOh}vu^C}V(`M}#zWHtITKzBe2gL5ddetY$4e!H$VBwPtJ!U*pB~R)jM-v z5UikFL44+neD-Ml=l0jKgH~9}LI5tmzcDI+m8kk_8RD~z<1Kyg7ga*$RAyEh7oRAJ zk6$;)6}&y*t~Wqi^xjD4kI~%QCsU)eG}^TkIHq@gbY}5O;674$NX0Y?ZUH_v*U);^ zmX>nEIzZDmKB+e(dK!O-)a1ste{Pr%rqTYHF=-UEWWI4Grx3%bcU?0kQzWg&denS@ ze25?n^_0RHlNQ8R&*$!5fVM;PP^VeCjB7%W#$->D=Zi~~yfK|h3j$1>-Y+en{jdz^ zizR^eWxu9J<1}tsJ79&VhUQ#m@f`2J?Qid^pdZ&C@H%e*{_TNpzk=_534Bi)vukMZ z?GqlsyK4Dx$hrFA&6NU^a<6j?PFRxx=+oR&S03n>MGKCxT)8*1yZ+5Na|zCep$)#X zh)SOWTxzUa<{FKg@14fJ0@J#=R77AH!%5BXWB;p}IO-S*dh@FO#LX(uLN{L+(FQ^tllA}|OB zkiOzK_ks3IVh9Sx`paMba?<4F<3^gEb0)ENh#A5azu~0LV45{iGG#JhCZc!RB6^4* zVK6@PF|}i?+F_Q=nEnWja^9Im5n5x=h8qI$BN#9{GBs*LFmOG=!85=~WAlfZODF2@ z`?l`WHYj{i?2F2;?h@|__(2%SkTRzshBiE`gXsmG)^0tbH} zf1~jsI-->k8^lPK0RossG-kZoN}WOI5I9l9KmF-Xr;U5@djft<<3PN$+xo{H$NYh7 z5Wos1bMV5ym7!f|@@j|6#9r-&uXrPPwgSKG7FOX3Zmr-uVb);%W^il$?VH05bj2arx&?44_Ky3a=;lxO_$AmqPdCli- zE(7l?n)syViQw1I3Q}6d184_~S?a5p7obo4Vk=b5$>ZPKzJVa%0f&j{6LNB zzIeeEpLj1O`b?giOz4|1={w(MnAiB2naG&uM4(4opRMH}nX~XK{di}3vSte8C@SLvGpX=O{Db~Jz8?oPe z*fR02zwL*>pG~mabI0%J_rFJTy|JeF`Yqtb-n)ip+vAS)8$0KB82A|vYf5(9ADp!9 zJ&LdJ-Ep|Rx`C8lWfs?t>D@iTpywX0VOHl-;;Q1uXR0@6RFrHPS-5RJXairl(BLdH zej(|5)3>-25%UoWZo*9Gv^lEuHCpCcZd85AILYEOQ-(GI_f69ACT@kBj9UQk@f$Bc zw+eXrRCO~#^tcFONtk_A?~Gl{)e77SvIE3qxu-TOa4-D)=RaQ$9s9q218-@fCxPyQ zAPygup9P{@q7dVVw7T2=xevNeB*tg!_B}B8RI?g?8h@9-8-6*=dk;)@WhsGF8-&9I zEK@g;5ur%c(k&(T0`m0ZdcRAE-AiBo5J;su^^Y(ka@t|W zj$!0{fh(bRownL_TVi6n0 zEof~En0_p9bC~cihDv{g5nu^fK5*k(0X-oMY{5N}yFcZ#&AneHZU>)0w>+15eZZ%0 z;4K;aZJSpxr=U`jvZE@Wfgt7hI30g(;L}wM0`R3WXYo}@ZDO9SY3RA{drkdG5lg=M z_|vWGNLc=Yo<` zkb`MI()rv|AsEM*9>4qUyD3L&Tuh9BJyWhOZN_; zo&K2rVhNgYy4G>eq40-m{qc`_1|~~p&Je~inev(PRl5eG{5^VM19Que!Hjmf`V&+_q+x7x^{?w@}wd9E-wwfN#CzIt{6-Ko^4a6!c@GS9M)(Ru(UmHGc_yE6R`5C(68o+O@FXr?x@AI+Q$-h{q<~nYk zwv`GRVRV|nQ7cIDL95K>KIRDjGV?pULf6bWcqNW0!tKO0aXpz|(ptsGxIWn`W4>3$ z*0{}Wrg&y}c&Y@oJ|aBNbDP&j0qKwQ`P@LNt^`WbCh?FWrC%^1|M4IH z@sn?P?HW93n)bOuCip~*ugBJX1#E&&qm73^&AakD`S+tyTWyLtt>%7Fwjyr<(;SP8 zcPzqIkqqyMCKI3hv=QM;gO{qqA|P!||1>1#aoQA(Tl!I$>qOYoY?#t%Szjl5mlc3n zlZ5YD=R3wQk=dt(xrT=BT>p$kd(S{?axa>-eb?9;hpoPP4{d1v?aw)FjKh|8#(L2E zbn80L)ZfSU$8Yfx?}8S~a&xKg)dl0b_?|TVZTm3s!CUy!bMPk2c;<#<%+^sPMk|H` zj7`qsCDYbf&MfTYdYLVNX3v@m%w-linI|Iaw-Dw%+PQQ#g|AoyS<}pYrhNVCOF^s% z4Cl+yeQghTo;Prn8axeJfZYltM_SiQf++59Q~ZDwnaMV#P=n|%dCRNlJ49l%)pale z(zaUXdOnkIBH~#&l(dxTAbOqF;+P0{S~{(n_MyN{nC^5D!ZS6pwHIt7>Q+$=nz?9v z1!+Xe`sOqsb+j!$p$)45>w+=UvS}JDJdP_bOG^+P(49Km;hc&X|mkrrCBOy65X?YVA)St~V}Q=hmoX7rCb~tU+DV+41k$ z-hgjatyib;w3!v#n`p}#RbZW=dHaUNkDl}W`{$Tz;S~I2brMi&KG32Cl)?dhu^6!a zXb<33#{*}=^Ih>~S( zRi=&LJ5~g9$#GT(<#cPg-S&C>erIpsl8nN)L+T`rl0pTH$Y18WeQuz^TNbq?SNV1d zv|19-Q{mb*c+$3fSivp)?v`}rn-g?%2^5J~19WIaStV-vaSeYUt(GRi=Pej#3+@@F zKY>B~eD15frroAU zh`E0J_WXh33}`d(iuUZ<^)JNq>|YPD8x1HG6-Rqt+jXBd)&1?(SlnaP@>nNW8rs$~ zxMzQ_+e^pSHZQ%uj(zW5&hNd)y|$lw?5Xde-?aDV+xBhx+g6)Zv+h0NvbO!L?e?8G zPumwegnq#{C75ZjG-$FqxTz4IWdc z7y-VE$vyPG*UsQQN3@4w#LR@4wMotVb_skR5%M+JVaf=I_X!9J(aAF_|zsi zFOvP77L13GVY*4H8RAtFzXp-^&w+b>3nYqmG=xs{KVpZ;5DXDRrc!@N1DbD%;fn+& z3=r%}tbLy~!d@S)Z-0ai`v{<_c=mfOoUz~b&iPvjw}3x{yXUkeKxyMy5zL-*Y`s?B zy_e(NZ~N``DNOdhzPI;!t>2Fs*L`iBho>ET-}U)E8q_rT!2U!`IRIDBHPB?v$xVfWrY220Z@k#Q}e#iuS379qXsJI35N znhpt`07vUV%rJgwlBo$l1jly)QkiJcF@Miu83-qWhN0VHlmrvR?Eh!)-a_r~s`~MN zw7(b_R)UnMm>EPxK{pXZ@mxd}(gBLX(Eeu7!3wE}crdYUB+&#(slZUOx`>J{j4&&o zyUOSQR?kJyV_g(kS_nyX(f++(L&tLN&v&i8*FLYc_Z+bITI+L|hw+)8`5wnP{Z^Jg z_4wU?G&hrIYh)%nzE2aXKR&<)0cDz4P1Y;}CP&a()W`aByuXj#_fLH8XViTqf*5*4}wxBtA ztjw0omrRO+olH7Rk2DdJ3JU;|3KxHRS9_8zTx;SqxzeVAz974v0JQ;tv&eLtOw7I$ zSb%M7?fgIQn;+x(eaauNKcw&Yx~o0&`o_jPuGeo|Ge-KiJuJPCB)oBd%^D@S1_U1+7hhXddA@nlEvosMfSd6?Ijx)&{zpPkksmUsvs*GJiv17^;>%zxvff~O7wC!IL)Y-2{eHwoRZGdmX7?wJ2IYK~!1y5ajoj+tm z5f})d2@pK7fZ3#*=fFV$Jag9g;F*<}Z;yfaqwJ}??T_i3lzi6&7-<(w&{-b#l<*`i zOd@|OUj+POlAZCz)3SQ5vs?;N4qvzsssOfvhE3y6~UxQs8L@}vcA)j#1Hs0@w>ML z5kT*n^ZvR#*J}$f5G>l0zWYtGUO;thD{nmB?O$J~k0aQv&p*`PBk$Hc0K&%BE}pvH zGi!klf6Yb47(vmqG>*I*fM0puZy-tVb$^c&{&C}ouN$ZXahf~}R+mEvpQg`=edT8ifb~I&(WMo#0GBRhQqaaP3$3n^0jqBk!K}ntN&d&a;SJCvm^w zlQ;zPJ1d=cfu&&5&2r!_0sfKIFr*1(;AXsilJc@t%1Qse=}&1e@lDl25ErdW8cd>7 zx85ezBog=5bGLQSe#g?z5=~wyU(#))%1GVG1Xigk(#)iuS^PrEjubcPENjZdve+{{ zLvxenBUQ*c(V3J^U2d_9OiD}gU2E|fCo_Rn3SuCg1yzQ$H0`mdM%)LLb|%$Hx?CpbO8JZ1pwgeD5J}I|52-mmjXmqz zYpVr5EVkiTCep|`Dh1Jbsg%acF7nLOf%l$= zG_QbejWua-%C!D@gvm-9^KN-20m({(bZ>yT>WR==$BZ*6W)Ti5tu*eeN)wanCUw-@ zr)+7M#&Lv{N*C_;%xA#6_F9*MX5l68f`zIqNaOlg(3WHZ)%cD}s`hb+S-$p)h-IzT zaS!k>S^2RRnMq(?y_Ztfj1_ZWtZj=$#q{AiNSZ4?SwsiLics&G{>SBZ9aYRS4v_;H zu&0P*OQW+8fwWz3$IEn@m${@_(mC^%XwmDSLom_PbS>aBlGGYn z`bZ>tfjR;CQzr278sC`!X%`l*5Ddq?V)2YMF#|H$Gog3HJyRf!JriD~?C{r*mK$YA zU9<-GHd0^4b6Ff{JOZDX{+Qq$=Od>%X`kORBA7>8>!ICo1<;ykGyU9YK(67Brv(YT zbN~3)*N(^fdw}EAwT>$<6J=?9${T&_&ePKRexJttSPqR)Tb1Fw`une~5q*3t$M@DE z*T)a__t>AYjUzwCF+WcWUU&u&N4~6okLT9k&$|{t zdzPMs3=x8%XXUvVKm5zwK0ur4S$dkW<$A(`@|EEkyUsanU>Z07^lyT)dz9@yL7%;Pf&A@dxAE=x4NZv zdOy{Bl}LKAI_3xhNt0k%u`!cwmIDU`@PejjnY;cvwc|QjV$vi{ZX;5xXw<9HAppDw zZE=exfo@*zPC@JA>rXrov zd&htR4cnw^94Lz>=2+h&OTa|y_eK7c$EVran0uA84ctw(&YR@nOs%y5XnTDK<3Sy)LjY4*${{4P7J5g3FAF9gxH8=@(2n1oB4Xj2CYDD6u;5j~ zu=PT;#kePQs6%@Q*aDT+O`z}hC0i*Mz-y1|k!t~Q_p{$~^D?bpe9=XSq9oQve_r>x z*HxSFi9lj*xXS?Qi}d4|uRSP$zY9jn0bQGqx*Z0ksMV{|Apkt>7igLcOe!=(YfYa; zUQi%22E$kd3rL$_c2R?^g<%jO39tcqCTONi$HpjlAaAlU*+~Ohn+2Vh*NXtZ=b}GA zn$~0zr@fn?O>7pxF}awm03dTJ4W0!;8+sxK;DilzoAd#xNlzp zP^=u~DpQc2@7!m$UghpG-U$VSUfDjZWWe5aT#mGPsdL_6?}xIDDS`(|Oirb-N_S;h zuvR|7#Czg?U_TOz<|tr{=eB*f3Mfm5{^`GTV}iMMNQb`eK)!8ln8!{^=(E7mUC<*Y z`nza^fk%SnqXqz8dM^;^+)K(leN{RHfY%3Fumvob+r|wZcJ)tsnn`0#{CU807cs+I zmJ^9|H^~tum>Q9ZLd3k~fk&c+jQP0ZIf4lgrb&1<>h~SM)6NJFQs~4c}zkle%;M-0RadeT;BmVs?({RUOW)fuOG+0o*n-F07*naRIMpNm}FkpR?+9~a}Di`#tlfByz$T+2Ao|ZLxeEjTeLFOi@QOh?rHnp zH?A%K&XU2ZAi{s`y0ZY8`t~7l0ov6TR{*1L*D;swQ~$i?(;T=<4Swf#u^F7VMllQ5 zR*QKNYRklc!V*BLNN&;Ac|mWjEjL|JCXI?_k14RW#67RRNcOcgWPIzJOGPK!CN^5i z=Ggj61c^;HL;$rSb^}foq3c@PUBg zqVh%gigM0`Sy=)yAEG5yqfc5PG@b9r4YLLDze(%TG1mW51)2nBUqUGC{R@!GS* zf5-jy9r-PrBU*BeN8?n)y}wunYTVk0DS}3A(4tKdpz54wst)Hp@0jE3)9;wr>pin= zwsO?3ts>BE%o3olx@{9{tjRzLh}Jf>0oSV6rZEvVHqSZ&LS?B$v#&Cf?z+UsUgKX@ ziOrm%_?_q*$&%JkEaDO;(5CD&zCTD3J zQng~9s@;?Tf4|^&#d2UFJnQExdNGB4A0mi~qkSEtAcwU;XNuB(zCdkQB3+ z`f7jZ=7n@SyIrYM{eEk6%#L-8Kw$DQ(K!x4?bKyLjrLPJ;3XF^SEMgcxS;R_=8 zsoNx-g)rdvH->nD2|&eL*-x6U%$Q6Jfe1ka+HBa6Z+gT+`f0RFAj}tA z@9hoU3})q>$$cI^vnxNVDC5u_XsDL63(Q^vrze~MFDg`?UwLNF9z4104KR_+;<8v% zbl8}vH4E&1xg8Tma`6a3CP26cm=;cs`gCGsa-*T^_ccnahBg}fD(nCfoV48}F;{5@ zeq0ujQ&{_Y0AbZf1y?Iqx*hj#M2;WN%7!umb^V}uCU2^$ePY&Cnvb1(-&wRZmc;|G zKl}(|x(o>&eZ&Is7XcZr8}tIS{GAZ9Olr~eIGR0eT{`VS+`_EpQ1o_9e zf>d?%ELkzNgW2J{=}qtRe~Q5S?ulGe@WccmI;5SBP(>~2zuKy3KC6P=+?|(+Ht@Q+ z9d3#W()6GuHmA7X@ntS+cE&phIoHwP(2o}zX=j&@gi+iyX}MWl*($l0|AAfM6R#$Q z!VCu9YPjY?%-@(loLo2nm|i};{B!v*Ay0ZoK}N$%AVFxzW_mSE>huHk7g^oFDyvP~ z#?zHG-@t$4Kh5F6yPyYNX7I|tUagAKG7U?%@WnB&fIZ(yYc8vb@6Us@fbqZ(p@pYP z)akS0As_Y+WSxF_pVZdNBn#&-j`eOGE#1g8uGRl1oEo-wvbGmjMVSd9r-QY z_5;M@Y!9TGVvn4%yd~$QutM34W)JqZr>U`SIoFz&-)iT#dYZS74N}BT0#~`CC6A_# z(+MrH3|z&-PdqthUb@oM%hY#l^~d?Vyd1{iyMr?&&c9t8f9-8aIauQO*G2{N%2HrC z|GI|T8mArEU|AR1BA2HpvI@LWX&MP>6PZd3BKh(NxGtSGuIdDjWTNEs;CUPBd*Huj zk{H##k|xCcnJ_JqZzmH5eK#$b;6+oU%d}w3oq0fy$Y_QV24qy7b0~%~%0~avea^BC z>|6wfejJ*W=R`*2PKVio#B0gd!H=OuNx2hUTxgXRxRH~th2|dIKCxsCjCZHL;AwI- zQA;YIf{EW;GYHXFjoJ+eH8CRP^@u zrMy31NP=bf0B8j%g*Xmak$pfr{KwnBf85JYrma&(kyNj-`HQo-<$mpk*?t;1HAlca zXbb6l=23nA{n(SlqQ%;-Fx?;5*M)lX{g2+hwNc zlXN5dD>LG*-S1Zvyr~U23L_89^1QykWRS8{ut4)+og~V4TRPHSn-up%0qbd5JyuxL z*1!wkXJfr+mx*tB0l$G*A=P6%8y2&_e;0<0rrJ~Oq)s!}lDLw~gj>!VURc6WS7R<2 zL!G0uz~4e#LgIhRk8#2ujgv?AjQp~PQ}5*U>tBwp)PB% z|05G|_$YtpxZ}Jn(I1c#anjn~fQf4IEmecN-B=l)$E+pmF=x)kSL?UE)Nj=3Z-bfi z5Pyh~-fr%H<(?y09{~Qbhj-3&u0c^)R@ayGi?#_SESZ|PMy53qP!(|S*eCS)i0{uw z(TDm6%I+Ij`Yi|7N7Mtu>vvc;9ggwKk{rmR(@rYd#mf=n?0VDV3A*hMuczO=4?LVFw;vR-paI~XC;Hj1 zsC^k-7#B2~ek)IHOI}`=Rk$L2-E{Vo+Lbz@ol;QEq;N=l>a+3pw;6h=-k0xiY7Y6{ zD1uaIBg*!)W!?giS4BcP=TdCEW~ORYnB3N1f^As2bqU0paPJ!V+ga@u0Tm&f#9d31 zUKgG*6dHv(Ictvfb=dh}j4Xp=kYurw%ug>fl=}x3PsR zYgURsb1hruo5P8#<5ezyrNLTT-g+9&a^#xY*){2Th?uuSMF-Q{2U$&@`em#q9PdAC zK4smBt&CtZg7tJ#b+yq=hL5keG*A++4?rl_ET!z&lxsi0WUEfIuL9y6&CGE*#9xMIgKzgG0yaNtZc8lSyx%S4I?@K17CPsmofo99lgicdW}NZ(lKJX^O(P4o z3!(x|$hqB#t3Qp{aBRTYny-b0I0J2$$B12_cZA<>8FIR9ci1<2VFKuz{<^cLsenD1 z^??eKKfIfp5?fl*IgCI&h=1I?yK9H-9|rr6LU_0mC%~wJ*#oc$k}6dHn@XlCcp z;Q}J(LiH!IASY^@a7|dcEX^xNWYA16Qph#W%%paNg#oeV1>d+BQ~my?@o_rIWP^${ zV)o}FSDI!!4Me&a`v*BRLw%=m;5l&CIsUJbr9+Kq`TPuC3NtWcKc)QkXn$a%xX&W{t9TijiPS0UCzJy$3E?pcYHwfNd8 z8KgC5USmWQuGr}y+@!UDeg3?ybc*J z4q$=WV#+|!7jw=$+{eFPV&9gu{^I;(x2@>Q|tf-8c;p)vioLTax6sdtWEz$@op zsE-3ci)p5p7V55WHB5qc+N3?gsgv+6T*cDGeKt6+#GK~QI{XCSk*6}FO|U*m;fUc* z1xzxHD-HjtB1RU5yf7o?+nYJ-BoN>-@V9;*Y~!7 zS^x3Af+RPKfo_F@=;q6ch@VB@_1@OpV>O0YJ|r`)`SPAIyGtbgEQ2_lM!nH+&{QCZ z&Wh)%JA3Qn0zWmb>1Uyv5_8BsLz*ena?2*ZQwWr#z?D$^wfvNP=|JsH6}8P?JR4Q#%>yt^SjRBrRdjAz zAS?IkF{Tx?v_bu^YNZlJ0bYiE{7m_21!{_!oDPIH^CSts)pI@w3A-)$4JZUi-Vp{L z5;mkoS^3hB)m3S0BnBzfm(MM{VX0L1D8I!|+iQ`ZEa!1Df=eAde z8zv^H=)L+PBFyC(sZc5+%)P`u(FnI&AbqA*WdtdKtHZvEaSl8{cB+Qp)#+0vR=uqg z(KZgM*I6j2oO=)`cuuKRZh}d6JSFt~rpZ`1l!{LJb)KOMO!h=D;XMNYru7FP4*+Z# z@6>NFA^*?-p!f@7CJ$M2W1ME-bYGDy5MrI4^i_g+p_OtCH5CXTnN0tqet|fc+iHa*F8>0|01hNB|>bh8;ZkA+XkxGOB!c=8Y6-3Qin>Q;M zus~@o25F&)Pw>C#CoFHGCQ6}o?$NpV6{P5~TG3wm?!Mvf4lFx)1+r8d7u|Nwg3%m>2j zFM?`R{U2TyHlQ7{Hpzn2T3H|cytf)lfML=)^ttECHx8ly@x8hU99?9^_Z<%704>@` zJ8stRY`y(!y<<~s{lI#D6l~FXqxK@Xs`ue9xn^3!^YGz9AB8L1a{hg>Uv#mLxJeClL#ezfa90=mF;dxkk0&ZI?NNbWlL!-sYA!pQYZ8@u?zb+27fbpL?x$I-0@f zWxRo1Yna*HY^y!}Mk}oUb*ot}nbtQ(Q-aYk->;)c{q(Rlsoa9Gwk&|3+3s_rcF4hD zUhb2`I-&IP%beujZM#d>Pe0f%`&*6DloWKQ*B&j_x>jZek_xkflmHT^KLSJ* za|Vk!Z`Lup=`E{)WT-tVkLTiXJS4ao20^O5?>-AyN{hyW(Y=0w=_Ux7*YdLs%6Y{# z-n*iQd`|*jnBrmBVs+cYcR%s>!_Sjt6aIpvPc4SC@+40^f?b=Q_y4ORSrsReDSMd* zf3v^9n9jA#x7W_kmePH_85pOf6B3@Y^|It%5}1)<0Mmoe$ALruA+p|ghKb&&L)BwO zLt3c-)0+!Hd;OnP06MZR)*+BUei`GvxW9BxA_WM6dYmjAU#2f#cwSyzE-u?n|K#Uj zO3T7`2a@fo1x>Af<3X-li>v%i%`l`j#1~O`Sy30_O86nYRDE=x%#K4;y9I!tMvS`z z2T861@JWwe&We17KO+;HEvtO?fhz1G50pS z`f9B!+vT%|n&j=NU#^+Uhh%@$f?Mw0MUWYHC0R`~0WuExtWhERZkckd5k26c`{^2A z-MgzzIu0nl0FZm}-+>~hFf(f- za<84S4&Jk2ifCMr9{`SYZT-i6!l6-XEc4cZ^3gnUV~ZRDPs26MliDOME}S%Ze8(4= zE7n=_h31n$R=9Zrt1(H|FFDoA*U?2EqM|6zL8Y-~r@kERs`EN%+IIbk_o{Xy<59U} zZk;4Xj6L;-e!t{hSs)t;zI0LFUzR`*n*>1jYE4@v=)Ik(;KpgbYG8(|nnOW(hn5o* zkcD2A|4?K&)vtzuvxNwxBz@Ipg^@b-{Cxfe|GCoCK-fEZC4jz!EVwpUQ%R z^1!t)Dc(Q;tXT!7JUtj-;H>EyT8QKY2>kAp><+|WC1B5qYk+E=4Qp_%g=f{$Ri_*g z4<}(XYNPI{b1KS_&szXF{nne#OdQI-(3Le~K15NnO1Y3W5ru=me(RNZ&D^6Kr;W0S z(}vsM5;*J{aT=mQuJa?v`U2}+M{p5m;9DNELlm^BlDc&2nCXAA@O;2NjkxD(9t2z+ z@H8{7aRjout1Y!YwDy+nEH|hz&h?2yPf{>;0rPhMNUop6Oj$*G$J6GCcw2q`$X-=6 zOlB*J)&zywst>-+eiJh3Jdq4|Y511wDjiHg{to&=`EzO`wAb^|=j%!aG?p4>-kOb3 zNE3KOpPIhe5)ZD4fwnWdDibEslvvCw0bFxpeYy>eOV(noamQvqR-OEtVOs57iE+{Y zyOK8i=*iJ*FML#C;vqw@K?wjtatN%38ngy!a&(NVquMbF6U=`URE-?9hOy zD?1EOtN}pvp3?V+M!_-sMqIen*JOEK(eIVQOo$!_=Q2gqci4Bfo#^|D$oyZ{^g?<3>o@YD{$Y`!a@T z&$RK6-x|*C9#YABc$E*7O3C<{h6T3vJ^$S9pD6CM!nT_Q{qa^sr$!(a^$e~Yq+G1J z4r*8PMgV@L|5EF`-xf9F6ctL&mU!rN5iaXG-6EaZ{%*7b?vdE_Hz4CNl%Q&X0WkFQ zx43|tG;w4~g?VBL*4`zqC1>u1k@h4HN?xlg+xsZv zwH@zc>rUbuOVkD+4>6?^W>p8s$E85EmQ^s#1Rg-RUcxp~ujli9OxcGuwEhn>LMP0* zb9LipVbHy^NzLZio0vMDM+K=rOe3egG^aW!&?sel(+|VV+MdSCle+QCjh}`Q_0X$+ zakDlRBZHt!uI*3zwMu%Z*QQC7K;HmnLE_w>=XQ}z=L5cu(}dgaCwiYWr$oPYdVz0O@N+~IDINyu6>XIkFM(I`%FRP#uKTTS zJ3_|10FlUb|J-wW0dI@jvxVC^?-yb3=@%@})#{kyTm*TVzeW|dZjSifig;-IT43z^ zw%EOstfMJG_zr>hwmaoU<~98S!8{Fyzmq6tmhy#e(?Y{m?h*6hj9>6R-q}@I<(_pv zAqM3Nf?$b386u8|M_Klz!}=}@8@FH0u&H+yw^NAaJA!3xir`l?X34xYUdzmrax$(? zmU?gI9Hu+HS^}MKp#d#K8Gn!^B+C#mXkNgn z4qLOZTc)*VAIK*1!6eq-eA7#X1m9^QtjXsdS#D+!NUfJi1Bf^Q^hSi}=o;Wd)K^i9 zZ2*-U5g63fWPe1QS6S8?M+%66((if1q?D0##$zhh;_xIwUpf9nlK!eu_Iy6)4<)psQb6j^Do}@K@tBB>7X* zsnNM{W-YIN9Iq~Ip}`j>>!;w0M4_fkfpn|ffWJf2&g#0?q+|U*-@Fme&qRG?^BShh z?;duCHzqU#?C8|;1M)~p%BO<&dv*s7ZH*uiymtqndJctlV`*Iiyafc zHKzXrQZf~~Nf59>DP&g4dB^dffGS4fl)Xvu^GnA{ib+vJiB$@GYlrH;0G{Yi$B0_d z_tw9nw=g9nZ}68n0IKsKA~18_M`ABBhr_Q2Ua~bVmODGHF;nWa)<{@StYm2Uoj)`X8`p(UmChe8|u8|TX zvuPXoKdOOSTlg{}TriZoop#lE8^_Vvkp~e#Igzs<5(Xg+?!p49{2L~UaFU6#qri3G*lUB+J;YeK2TXhAU*)pD|vrOtQC?GWJic3SSCh{?u%Fh3V6d{=WVIg zG^LZ4_?{a?!J;ss&c=zAhARP3i}&?PpnGgr=cjqB_~e0=Z$&O3i?sU-@FKY~$XJRw z3JvT{%eEgHvj{~#Bn{AOY;2nQE_(5c8rB&D+|pPX_8<`u)rT16kgUdl*<$fQ`C^+~ z^LWxco-M))tcB98zY-*Yl}k0SKl{&v?M25BbgII8jXH!5v_wd0cEYSEu0G=6 zA2HOJovk?tr5JNgmhp$OJG8OUHFja_;5_mFo)Rq#!sr{JZ>=5z(Tp$W!nrOz)j8oC7_P_DgY_dX; z+LfHGOXdTAZ9E6cnPGa0)?OqD*?kl-2dNPDO}W}{A)n;h9rB3svXu>p4xg7S$Mw0! zW0Tb(oB!Z3Ed#>%Vg0X(7~Y7@m)ZHg9;G_ZX}Oj%39ybN#paU$^e*oVqa@yBqa~{F zy1~_EpvxHXEwirE1;O907}W^+$j<_bv+=uUw;G?B0qH;Iwr)4}Y1UE$M=}=ZIsUI? zJtXy!;zQ|^3JuqI0A#4_enIa6Z$bNzq6oV8n~pkMy-WtJsFfJ$vP?EC=+_751bTw< zQNQtiC=fxP7Cf0=Oou6a!+Qo3W2#Lju2iEABe>7YC-3FhOixiD&Fc_L6Bq$WS|4V6 z(>&%5X%zb1I8QuyVbegJcL9zCJ6`~ z-2FxTSt~=t5W_EbU=H?3F1rFxL=P|_fk#DV=|+zWrYA)5^XfJ$O48V6>+nr-2wWa# zn-It%X}CXJ0c0n22!v~Dk&I~Fwg$cH!+LR*f{)(WD|}uv$dGb(X%nwarMCOHOYF4F zi%;+E_AxwPk9g{6vP9G@dGyptbBKmQ5AYBh|b)lS%t_j3VM_yTHrgX#*i9;1P4|o@qBKbyhP|)o^wI+ zOx*=pLQ=t0DkmJ>TWvuMM$)a<&`d6;Z8fa0$PI#3n)70T-g#gil*KiGk<^0(o!`*( zX-xiM48ZzB6&8ThM6TrWa$c-#U&`E-8MMt!0WA<}a8NAF7nN>i0zU8s$`lf8#AJ zL*9IWlD5EUSBr>wIl#)p>ze7a&Q&*RR$x#7xhQ{64#7)F z_lii_)uLC&^pRp)#u#Yj6QpNGw(0wIm2xE!!Gu$|8gd2D|MpeAa3ttC?0L=h8a@ru zkIk4q55Nb=JbPDHBA5{XW6BZaKgXV1uUdJg!hI{A%C6*7ky`fs@#g zOb*{Ad8YRjQnp`$h=PbL+6cC>AWZ$?9F>Yskn2sx_^+*w=Tv!^1G3NJ_B8ZK>_pq& zADJG98J0mSAs^bV0xja#*wGU!KZQoT%)<uj9uRIh_itzC43l_6@Zb{Heg zllTtjN;zYXi=cR2;0+-v)iWui=C6G?JMI!mT)MIgQfzR{FzbBts9K1XNmo*+WnRDd zYKx!73TI=JT95QNo#L}kKXeS2mXZV3VD1g1f7lUPyDWA*ZuqkrHr`c4$c5wHUArJ6 z25#A&5pt$dUIn~WRke+|rlL=C`rm-H>`2qoXB3sqoo$*%oJb2?+-Dt=F2!eME5|XJ z5DPCJnm^>KFU_j6?EcQ>LZtQLJ2`4xpqBHZWSkBQLKB?E*IzsQt7&NCo8?<(v6ibJrBV^NI*)qKyQT9R$X z+i4C<+p5<&~9XR~k$Gvico2^ncx$o;{ou9WGSo4#!9S&N=(h>F)HEeN$A%Q0gPRf)VAN zQP+t27}^(RQyF00X0XR%@thJu84XZ$%E!4UxYF9;<+O%#E4Iza74DFfbO^CVSLSKg zM+vA1FFA@6XL^0V&0!n?M}vdbzakI^neI|Lw}Sdi|6efmuf#`5c)(mtumrqg zdf!e1wGP<1|I}-5sD^$^0lc<%K8iKyfZ9AI7KQ1~Ig;Q2-Z%xuV>kPEsM0hW#{(y0 ztI;pkUU^;vF-GU{vBh(2m1QEQX(#0mZ;A6}JO2)!OCxo!gO<3Zn6&(7W+3)DZWg+d zNzvlKPZ%Pps#tdT_%xM0gGhg`VA8zu_qgc%v!}My5w6eU~q2d+tCbRK^8ztc%oRbzfP1#P9v)IVF~km;FtA^GyE-@W@m0#^|a zsfdd8^!p8v zNw@;5-lvK29Q?a;{i@aiQ~19P7e#(H6ne%1kd#(CU{C@|0dL z9uZBPwI5aFDYNS4{9->PZj$eIIxaBcKJq=Rcw)1AdefYOd+QgEP*A(h^=G#g3Yvv- zCCOMNFSC>w=80GTq83Z_Q`Z8oQ(4lz!9lDD7$b0RWt5HE&&~fab7& zrHU#Pl2bb7{~Rx7U}L`^aNbH-18BA@R~12Lgk=#1?FqC9%HqKi6J<|HJpo@MC4+{a zruse0cpYLcGL%6Fq+5SmC+6oM>%mpupc0oZ6XHsg8oq}3q8j42+7b3}yMLhE$SXK- zs4FMHY~;p~oL$W|I87e-CSWvLMcCLA2uBj|O*uyo3=6ntlYe#k0kQD}f=rN$kT^{%$i(6E5h8cb2^ADP%se8Y@DKS%7*9eIGFK)_bqc2{TYTV^q zp#Sg*9~7JEgmt@Ty7DRjDgYJ-Xr~s)Jg$YL>tI@5R`jA>JzN=Ok-<I#l>UOS<1`|H}}{yWwYf zS8B8dsdqCM4T)tmvJE=4cEg}SJEy%!k`5kl1xqN8eV!?Y%8zmx_NF!rMU6R2T2jim zklghaC{J%Q|46>j%8QgU{O|>%{ol|n6hCOO-#CmC6OK~2wwnQs9~v{!(bkis_6AE4 zitMR|0?F*wWGaCj^HPyHXFf*cOh1F{MfTWMRG*NyE7F!2DUp_K;=$vU@1_sz0;;}x zO?%p7fW_0McAMkS&H|@87Q+JF{fp9%kgEde2N#ZY4fc);`(s#LV@E_D%7jLUSkp3X zRsiU>*LaoHrwW#93AdS;mOG|kD((Ocp;RU97DdXdzDtW(?%K(O7T8r(2<**pq>y4y z{AH?0(E8|%1K{BFPy5;+6TLY07j2c)h^4RUs&bwh>nz++GIm7Vp8!(rwt}y5t{qIW zA^!K5mn+VT!No`zrB_oVahk5kdsth^5XW8e;p%WW;~@`eD9zs{>4E%6_R0?8`G zqFdx4T-+e1ESso*{i=!U(i302-V{L|B86tob>K2yf$gAG_OOh+^A)M|A+lT<C2dkWT_^5P+Fk%i^ zdTY~Z`}f?;0`hot9T{yRFO6LF!)<)~y{4b0L$bc3vEhB#E=_qVSlt>wxgp%SwdcQp zp9Eyj2mM(k%;@1;x2h6?h(RM`4qb1kdtI;(BYYZ-#A#Umx6+iM~!9Z+Ex1_ zbJ-BmboR;`tOlTtM?oXJqo^&aZF|Y{O!XgxKG`7RHxxuo`AiW1isk`1_yd4>mIkEdHz9x#;lD_W%hlrx3 z!`LPWNlp`DJ{Wwxuj{~VpPv?Qy^FKi z4sAGy)UFy2=psK2R<}n3B1+E^>@*x>1Ejj{_zLJJe=z^SGipo}BMh5h&9m-ddk7%S zD6}O;BR_r=wHq@fXN{be8F5i21vyg;3R3&;sNL=3MNmKrvhnx_LU_mKi6Ebo)J=ND zUjTE2|AEY%bQpM34(INCdysZsp^s$%te}j}shlO#2Vrv~ALU5h9+3c8YZfd(NZawf zb*T;HXH`HRdt1Cp=GpLa{F*6ACD)Ak(VAd80+E(C3yQNskfX)O>L7xcPqM>v!4oN` zFa5xTU8mUsO!eI^$noIx5%XGKcJ)A$i)r1^K+=aTo?7Uct5JK5E)s z@KVcB$a@|=zgvY(+-ZY{?in2{UR_~toC08$KjDM ztgAn;s+qmu1kVA=%cpCBHwss(wBkS^?e}FJ7rQDrxGR@n1qb3o8SSs5ujXNCC?G*o zsNSw3(+|Bz7Ch)eYlkN21LWnS_GT79S!56!fNe0^$bed{o0a+{m=aTScN~qkLhv{R z#54eNvu; zLeOGBY)3!=}Fmn$v(4LI~ zFNIJpD@=K!pK8mT3B4Y&HF%)|57~kbd7K7`)+&;XXH*B?h{-?=`(JX)R4A^}QZVis zMjPU!G~$iAJ`=$xl9xD{K-pg&nOzKj)vjWaUKT=ydlGTjQhl2@?l(W!oF4+> z?(~*>w=ajFI4sMwSwS){Mjy(x`DBUh$vAzbZ^=0X7$Uhttk^OeNR^M0Yvio-y8g;v z;_(@G2$G}$*yNP5gUSi>$arpcB4 zwq?f@P48boFK^_sJSp&3m@Jdy3h-t|-|JVQFO-79avHgEesHx6N00}_guq+&E4*2O z0#f+Jfas6mecknNgkOrd`pte0VgBd2o@4u_ZuNx@zFb7}qa0C&1&Zp^z~l23*Y$2Z zsecTzPsbwQ_U@F=iQc7gRlj`Ic>oBW$|sssH-rc61j5*bOMV3!FRLd&m*q7xi^o8m zNP-jlRdpe}i#B^40RLiU-5BCZUsJ2m3Mhql2cxIo4pcl;UXoXt(aY^*9XJALf%uEe z2X25SNHtQx`_MBx_X;{B?V=Ad!@q@KS;?Q^l?dngl&AgL=;2kB1?~gkYJbZW&gBuh zVZa}u<0998x{Qx7?m#)L4+f9(S555a*Am58k!h#+L*%Y_Dc-1Kq%zvFDS4|Zr5Uam z+5w(CkLfKJch4^0l38j~tC%9)Gn;>7GNuOt-p5@%ScHi8^U9&{>yI#*SC8bOuw~i{ zZ-ZXmbu+3+fqq_*--pj!Qvt!GSdtxUvkE2WkQk&N?@+o*4aLuhE?k1wsBol4D+^&k zJ}>Z_aau2n;8br4GJw~RJoe9vuO@eMp=^RLb6c1cQGLefzA9&m&ygy%-j#I~Wgb)H z<*WDJlC#(8t4K566LKA2-YD|eb_)B*lrFuNKFg?$F9d1Mw z49lb+@RZZ4cRn)VGYa%Jj&3Np*lNR%dUJMQ;&KD4e)FXD=>2#(z~YcMCBE+_f=BJ6 zPa>R9!)^WWXLhFj)a*LrcKMCHvGDIMm9W-4RA8i{M&X+)>&BnoSGO6~mx}$SsxIqW z9@}Mic5&L91Wb!qSwkaq_|)|(kU|Rf!fnDUlH-(_-KLoGMZBrKs@Dmw_lN^(JzAr1 z<>~BK{Y7K{$+I5Z&A6ujR7htnezz@9-IhC)?GnT$qe%fCQ1DX4y_qg46BhMIJtT3Y ztQY4^>Wey+leIez_;ayi#Q8Ei6nkS|?J1N|cqX|^eW^o&{Up@P0RGN}CC>f3eH=bB zp4>glXXu3T_LMagOZv{Qxn(PA_1;!={dnii;pl87U2kkpUF}H2ymhr~pMOnE1mj_Q z6Yr~;6w({Fjvzmc{s*yLftNW$FWI+ZMqWvU`=xFqyabEF@-%tf)VtWf#%c!)$l<03f#uW=C>$N zd;PlzKO^H}Tk5~mOR7OUgTy;!3jU3kDRBcoi365xr|639)+!r#^&UVMH>;B!c4c|%!b^E(k~N0Vv6KLkR!Zw5-L*OjP&(m#1 zO!#%|^yn?I-17LZp)qR!faI8pyqqrL9k{(bMf@Q5f6Jn7g}5sj3S43w)A~H2p2+zw z@!f|Ie4U?@8Hd%GY30Cm<DtW(dR)Q$ySAYL2! zJOykqwd#ZeFAb+E2W?@y{Wp}k6HJREymMwN4IV-VYfO5^hKLp$W|?eR7qOTK^In5{ zgFe2$dhX?q@7b!4q0C44%a7pM4-7EXmgKzuhM7GV3f>y{mwO7f!$erJaz1LIELG*! z;e*{9$B>PUoB&Jx$JRg>uFvxXq@GdX{Ru~GlVi^qSD34C0bcFEy?wnBgv@O>hxV_I zHWSfz7fPgLgB+4brtYimZ_LB1ZgQ>>I}b0QtRUdhnBe1v<8sFqI`EUT`enbP5{+Nn z=Z`kVFQy5l>|gJ;U!OmBxziF;jj^2`Q8@58Xt!~2UF%Ue2UbSjAqYvjEryceo6$H`-*v7Q%zq1_Jf!kp=UtqMSe62ii@+GjM{<1RZxFTp2J^2f{U z0z#q2r-Cx4nr4f|3cYeJ-6J^7KTJ6ZJFHz5 zWb1~V_;w6h3{+>TIZx?}BUz&) z{RYOC-0qSYn>!hIU)C!@P>?2SkUhgrp?m;T3*bkB1>O`?4Z0@=1)QD!zw*BNFUoIg zdnhSs2_+>4ky1(;!~jVZ=~TK)xshskiyka75zBWGF0gRKxJ3L`p#`f720 zlI}fS{hy%PkY?8Yjy1g);fzn>3=gnM22C%?%S!G)aF(-LU~d!8L9(c3UWZQVqSHK! z0OpgW0_t$QMqrRdDj7?uSc6c_^I%h@2v;k1EmrY!?znH}frX@gxVG&rYs=iYE(Oi3 z0{T(m#10-+l=QEEh2}(bz1Qkq33d13BW47_xJm7b7RvHH@+XzNJ9OTAgni4YK6L!< zL%usGCD}@O6Y7{BP^pgp)Smt}y8d$8uiAsjgN3vB>Y&mj9acxWBPw#k$VWWsu3fK4 z8zA8sQji(&SZdfMe3wfu>rAW#6QoKq%Ru(ZdzEosvrwK`Yo62#ho($^f?}xb%CgCe zE`_Gnk#LWPeID3ExbbVFd@?e9SE9Ke;4U~Uu?Q==swAdYb7>MkOS?+qz$7h}o+B~= zu9lJVP4R*Sto0mSeqMe`zQq7WHdeRw)mPN-o-+wOTg!g&1Wq@igP>8Qd436=y7Y&} zc*A_d5dZLq5<8#^>g+|?_yqg9km2pf5pp`s(!VRo#ukcQ%AvPhP*v5+v8~b4NwKwr2QH224DSEQa=gQz*9?#K5Q>Fe4 zw2D5~<^wMlm;6d4_&C}|eq>x7cW-t!)>w*ZF<;+gTw;!yUm9)^7`4=gmHVcGC7(`a?0SK&&NSUF#f?Nvj-jlte7pX+l+n1)ipvLfN> zwGq$(lRsbj2uy!Y7Ts`gRqkMp#HtpBU2Vu2s1~1?ZIk%P#pM2R7pPR|Xj*<4@c`{N z2fJrj&L8WWU&rOfrP`)3!s^|olSKp-n4P!UaSw-LcfV)f(=!`&p=3B%*^Ob-f^SA` z_En14L)B?3ec|+FG`1GYFPtG(?=eH%tT|-6c=443O57CZb~fEIms!Q^9Fo>UvJFPp zAepu>1`r)@!o?LBE={)Y@dR1-N#x2|K^+1Ou*&?I{JY@lF{kyajY0%k9%j)5o3Y9b z&j?1(E+eQbAk2rISA-aCyz6BldS{ENgJXY|Fes4dAsRl|z(Zi0RF>3AFNRN96xK%X z2aHi9fh(Cm*2`~?L_qltsbHAJJz@}?gkw0yXKM~+GI9iu*luPC(6a4>6Ok1fuZGU?%bjzKj>WgyH2$t2Nm z4Lkfwc}$}9hAXPy5to=Wug50!oBGq~h%PAi(B?b;wO@)4oUkI$*jw#DEv*v9S0TJ0 zNe$Z^kHJd&^)klcG&3Ctk?k(891dwewhs=GkE&#XJK9FUiZ^x_p9SW$ZyH3>GrX?8 zQqPm|0SjppL>37uQlggrLjJy4;N)H(Aj%i z@NQg^ciZfe<@u@VI5({ppkx~O%C~7OAC~6!K97KV>nGk!$OqUj8;se?%+tjW%d5kz zurhjcxy7GM*{p`?|K?Os#g)H!ecKqT)Rs9-fd0rg%BLp$D3<&!GC-YVD3F{(gMo$0 zs=ckkoEnyVIC zWaNC(%Euz~K639Ll%*64QF`1rz)r*~Z1waDE?#E%rD4q3vU@UxbjcH!U&QhnoxL2J z-7nvf@ey{425As4Wr(T9;OALVJdB>~n&Nf>EftC!HX@uq01f!h5s#YfwyhMd7$%qXzMTIeJxLpMo<%`;GEWp6Ms$q+ zMX5vhEX03`g#BxtXWD+fD1d3$w`U?zPcQQL>Z*(`=2k~tdkJiPy59O{g)?b#D`XFB zH{}0D8#6Y|to*_}5kA>n(;Rx$lT8`X@%ky)=Z{mEVNUR9v1zsydB*;!67Nfw9Ap9T2lL7dSkuW?hf%BEOJN>k7i$2uMX`u^xeX!v8Fg`B+l%|fs(Fw>Y-Dq z&rwTc2hUr78`5xY@-TiWcVfA~&Dl62E?grcl0(n&hFbrOGE4EmqH%FK>&=F!2cD=r}RrjJ1 zGb%S~Hg4uWK2^c*Gap`@z&BQ#D)H<9+iW_Kyf?a7+hwIW;y>LsPM((ig-%Bc%Ddl! zCLL1?f%T=MC3N7TgB}GLkvF4z zQs*$BeV!))QC*$rh3fp6iqOME+c44Z5gk0^P#G2m1(1MkrSKk0?V-Uo8cfpEz^#G$ z6PWX{+=3B@dh_TnAr+&Fe%~df+6X^(;~dTUw`g5WR5Ett!b4Mj5+$kY)3-$_e2GS%wfj8NbIgvbd9=v}+>AsueTNk)yj|Rgj&j z$ZBBywz|)-n6PI;)+XZl+-7I~`2t4mI%z3-@>gCqquEstNJIYy368k|^`*Je3X6b| zkN~N2y^((1!{3wJ2Ff0#k%fnY;mfmN@D@lb``*R#bk%w41=7*$%BZ?@A??|$2enb? z$Xo%JsGN9E8P9nGmmRLkJu+cs(aZ0j2Ke8(#^6!yN){dU7!G0ipDER+f0D{M!J%># zl~Sn+-j#AQMsBk9QssS;OUAVNrG8I9u3y*&>kahaLVen4D3iWooM+K?0Y?6UxZ(y7 zf)z01M|RU{ene~O_1$ z=rN`#{VDy;=)oO-koRx zX^VJ)w+@t;re*YKmcXdRdGT)U8(sw)rZ|iw0n(Jt*p~*m4@0|*-Ev|>(=uzr@E`|6Ri?rEPklc>tTeedHS%8P)$Z?+>41PP zSuL`wH>a;C%ItJ}oX4m%^TQ3TlS~WHb~F(>kOW~Yna;x7pg51H>N7t+4kENs&4jn~ z25Az(??2s;f2Bg(V9wBNwIDI=6DI%RVaSd4NJv~0;W?Kj!Ds#mB~xko(wg$|dLgCW zmu>xw2E_bcSt-r=MiyFKH-vL&RuFz1*QJPm7lT3Md$*wLG+PWO z3Dk0Hph&EhSdSoVun5klV?~K?#`KD!nXP7q@L&@%p?MTkg?D)LcX*5$vWlR~d8}0- zs2y5l+@sFaSPHPgTO|T+k4r%=8aSxe2qvGmAO`JcS`Os4VqqODEcxjok|MDN6SYx$ zandg3)OsAuRk7?bLapWsPD>SUIC(vgt5}ug1ULeWmqjOm*oy>y5!K-D%b%VLG?vXW zI0sE&lYLc3RD?E~nAh;zB6rC@c}{B=mZgXxU4>8a(nTPQ+}BS_6+w$eGR=Zy%_*iEt{>pDOp;};3@1zc zS8W}fCP}-WnbE#$6QVCB%wrYLJPQJTp{mH1s^4&Z8^^*-QE z_N8X@LiR!zsxed@wMJM2l63Ld%>L zkE>4VIn@kJOVr5F)ndU!$CGK0KcZ9+u)9|!;fh>R;Z}%$NJF1c{ef}eMRBNb>2-Iw zWc#38lSavc_}(lFYpT;gbQ=$Z)Hd#3AKqATRB;z-8=Wf39pk!_s5>?^avtLZE-fqi z(rlsEG*8qK2-^FEhb-S{>edN$#48~NiZ^Jl{WTNMwFB-}L8>Pw6 z7^O2yL9hHRp|SDl(WF51im{5&<^n4u)af-fu)_Mu;e4NMrPWKb%E?C1s*$@VrJ>0~ zt?hRw=j(C!$mnPzqe>qYv!+V1g=zPbCEBSJhMceQlqt#;^Nim8=Uy+vyoE z*xcrVvh|zFQw@+O}TXf zfww$VqTF@2AoZW_n>l1bIMe~O=>7;%Y>2{p&_Pcd=T$w}DvIPKsQk$fudT>=uLD0F zW@-$*T24a)TVOWDF3OhQ)`Foe^^ig;iBHJZM#wfbC+HQIMbZ`#e0y)H+UJecY7%a} zHR<}$-hoRD&5^0aO@%lo| zdJ)E)7u^tmcxlTNAK-BT;N^>fyNrB83=RFlF%f|nan{*FPVXtzByGkSi51dZv#!|_ zLIvjK{x}`fF+8Wvo=zR$G7iowj$Pm&V&_JcZx}zSSobuWQ!O>fYQgCxsKHC0{|c?Q zHyCy@TRvGV(q!ds4qj*Ud#%BWIo|s%{&2|ci~qwUvrQnlwAbJ-oZb{}b?&3+XWa@j zrW>BCW1`vp?iJ2l8_{4uSqALSfK!QRK14Lz|Cz3e$X*s^Bl}vk$I7}?u}Qi-3Uf0h zo$sdgEh!{9nbyz+0~%*SC{K3d=~e$~iK7FJCUFryJ$EUpZT~>8Dt)FV7b`S!# z!=IV ze~BY|y^-_Cju^A;j?KOS?`hFPVg$C6Qs6Xj{UUtACzBA{`-P>wfs*3ex*(+_3g2(4 zPZT(#cB_n7VI;4?Sc>npQcU!#$|Ne@X40`Tsuj>DcJTwMKwTK=# z-elD=n`u?K*sM!^Q6@NDAMd@`=YP#H9BI=K43mUTQ63XuKAmkqB;h0tAjF*}l;$w! zy!Dy)?L7dG!hZvzfS@PLMh5QqM^=B@);H+;1leZ(ur_sV`rGfFNHm@?R0*XYnxg(o z)ThF60S*;l7}<)=F^a8B_yS!jEcEe)s~4pOYEyoOF{2q)<%Jd*{+fy7PEfky>DZ*T zva3)i;t4AP9pUOFU#Me9sQSvrR7Bip{H85?I?h6Yze2zHD!$DmDp_7JR~0p<8h6e4 z(EzH`SZ%+k@iTQ-kufC&bBo#Y4cRDF-4MdgbRi~SiMP4YSYnetG^>g?>1t4WIjy&QtkY zUR~q<7wlRdb>*Cre?Wb>^BTsN>ZAN7G%_hx#!hqJ@%>4V(u$3E8J(xHJd`{;^;50*r;&vzEB@&R zY20ysRdfwTDlu&;4V4-fDuyn)N8xcxm+Eh)>`OMI&L>xsi&?A;q`1SshVhvY;O0#Z zn(ve;HrkW@bRum*x^|{_sMtq`>zZfMP`Vq(9aJGpdQb+BXDc?Lcq41B>TU7-oAojq zk|n++@}eP<%IO+!N+}d%k6^)haKM-XA-;cFB}_?ncTzW%$w?MwW3Y5gYmrra#+v5x z*;USU-^BJ@y12b*J2WK?JPsVv8`vV^jGTEs%=>ly5Is)T{InKs<6&Sn?J?sqfiex@ z`wG9{z~h?$&w_6StS&jS>P~~9`?SbK~zSiIkbC5cA8hd+1BiAr3 zJB4jdOBx0W=R4UCQvx>wzvSF+Wyj5-9+w_s*ag@jdGW2|2PqbDxI z4uP(S!N?Ul;sb|`1s-g2$Ap`HhC?pHP2D|O_@4ZZWQ4*4*F~53G#m$v89mZ=U>rS!G!14wr{)4Mp}H#~Jq%M~euW&E06GozLjwja;?R#(@h30+c|V=PSn%W=>eE$2)g$ z&$IT$d$qgKI%AjuXkwCRrUb^CqMXyS>o3p_cEHb`Wot1`hB6a`B~rj5n7L`6=>-(N zBf``W=SY!i2fqh(MuD=I=bZ&NRaA#&o6t9}=lSa2*q=BdVs zp}@1JrNmH%Tm5Ll#01NFKG=Gq@Z;Rj4`F5NJ!$uqMlgUOV9xXvxI|elX7O!mjYc3y z#Mk)vjjqM}F{_JZ9>?tp$zpZ<9Va$>S6mCWQF&xM+sU}t$QAoOho)=xvLoK&pU?=H zG~d!}Y_UJ*{w7`P8uN$iTup9$Wf*<*3TvK%?dSgMPUXwxk_WXM#>yh=GwG;uIb>SC z2P%Q4HjpOarIR!(1+A{X*~ir^rogGTWV0yo)0UGue9K?0V_SRmUD1!H51+o4qfxVH zkkxQ@ElB`{_N(W8Pt<+HB5~nn^n&X?${}6;a&)^^l9>Jbx9=QQ>|z9EG)Cy`?16l( z|ENx2yKgSNqO!3Z7@Mmb?WNhIYR&x9OP?T52X6J?AM~|7l;vqZOGe@5!XI)v8m(8e|OuWcs*<*+foNe>`V7> zMnvO-V{)7(yV!Q6*6SM7CVewFbGBZwJS64YAADQt8rs~a-g8kyXPV0Mp=hlIA2XpI}h5HaeNs%gwhIto5_Axc1Y(6xzNks`h^c>Z3 zgDmNf(+OLix-Xu?<-YCygxc99z8=(bcC2XZ_3ibw*Z@vpVZ6n0P%9{Tx*kf`n(`96ObvCPi5NtHv7PKBpCwx9+k}A5 zBT-wCs^-ZLc$*zsEq2f6&~77!EWB=N54ycU?A3BZxz=IB1d; zAC-|A-?V2`C7+Y0Lq=C*48TvBJ&d|}nHq0Ad$DPq)8-HPwnrf);l)s^?!*9G5=Gd17qbM`VHzt?N3Upg-eSJ`j|& zhvpGqnIJEh_x8<}`e>{&A|EIkz z1vcS5nAt9vSI)-k>=*q)<;8rw&T72d>l|r z)xIgEtQXto#1M(-oiN%&A-imlU7bKShLh&G{!aT6h4h=5be+2Wi$cmDfnHmIUU`eb z+&81#`_PTeJA+<>ECN#XsWHlqt>gG+CSBT1@7*Y@|LwWfhe5~UORu!kHd`NWgqruc z8nn`iB>A#&Q>>HnfKIMp7LVUOb4s=R(EAoqa}wsAqH%NSb$HpQVu)Q0P3qG_%uY>q zar*79&5_X*E?b8#MS*U+#V^X%%ev_b&ezj#_Ds0F&!oNiba`5q7_o8u12_t3G;MBc zyb(1vWfvL~YY={025_r?R-ujOiL>{m^F&VJuwyiVWO249uBvdaG0oIR;AIh8p`)A^ zP20@qD>=Tm@bp{P=-6#V)CxChg?TqW-H^O?pe~_TsTrQ!0$)o#^w{i;yqS(nHRMFZ zs7LL1FitC#zna_7ykr3#Fef25rpvs8k8GpIAu+$#Y2DR|n}9h6!QMevR*lG(K2(pi zxPA1#t5SK)Qq>ece&Z5NP_*Xhkj~jjC7vAtdb>)f3+gnz#iKXg;~jasHn&2MkOQEb z;xt#M61S(L_1Bs?hxAvz^iMdE-?%-NPgL~IRXokhLUh0f>uO%>YUbEN6wK~D4*18x zEy#85CGFj2?%NY?3393$A=dys-Qy574yT-7*?1cj%JaXK93XLxJXDtA0cnW&O;zJp zTK?jm>qr41+gcjIY0)d=#7^n8`*Snqz%97afVL#@6J9)&_{f#G0UvcYQ)}~)aXO}# zFYT#6C_^9D-5ed;%b)D%SemQ6C^J9;6F@II@;?mJ#OPLz*<<&oE{nro7wH^jQ8sOj zuim5#!Bbc$GL^eGB71#bZ9rNvkZ-C%v9DSz)QW)BHRkY^TSUt%FNzAQDTuFH%k>0Q z>1JFhR?;-)f#%LGuuV;$5H@PCWsP*mSd4Rv4KU? z^8tQ7_+)5l)?!|8xhm|Nf+ZN@ePTz2y@Y65YUub@QTjB?xp9@&s1v?MetNqGS$2TI z&`^vVl{J@3sQo4DrZF?+ue185C}XkS4$8tLa6vF2*tFaj` zx(^BJPu12m?OTyQ9;mdb5;FTLkWp9ilVQ@0=xi>BzXj3I(%>=W>^T+fGf5h9V9~-o zR)TpGM=kxygQv5raym55Zf2KA%HQOkn1zrg`<&-ojpv*ZLhvw| zX1W(`HW608bbGVJZEH}ki_mpm^!UwcdN7Fw^ZSu6oq%S@K^LRw0K?*TX1iqH^ntB8x3MQ&3iNUx`#K1;7x`0uD zHkBBlKmhdLQeX$uKY?X~J&~*Q5>@S!6ZHazX#l_ftGECPibFt$fZuxEOnKidiwDn~ z{bbWP5xc}m#V?^)yn5aCtbHdf=B=0b;Hul!8%#` zRmX{=-lZdyT@T)eVLD;i_E(rmfX^^Rlh^``BoCqXhT;I|_khm;gt0C(0KDQ10^J+& ze>I^U1~h1QA(S2op=^XEl ztbsF-nf$v`SZqShTJ=Qa4sCq@G0{861q#ptH`kE+;%Qt><2ol)>Gn*CTMyZ(=ix2l zN#u5MQ3I@ig9- z8fidEtMyH>1Jm;Ldg{nB@!`A?R%Bx@bu=4c>2J9nrE(V59B@ zNbNH`?GT>M!ksyVrD(idn8nsPv{>Z4c*mGlYq@M z_4;l~+`^%VrCU^Q&|Q}RyEbh5d+QTYVYpmkO!JXfQl)SIU-_U&?jKctc_qIuN_2H1Z_$sfs z0d&UIbinuSOC8;-HcGDQwq~~LJ0sG3HyRRMqd;^Ic4@R{5GhtIdfY*V&_%&Yl9Bb( z#0kiHs+NDq-^Oh$Ij$jrhI?|fz4rRJn_jQ^T&u+qzEgtQX*V7Aty!Txbm=~EO}})7 zI(u{jZ&t~1Pc^bp=tbC^7XK9jFrR&AivC`r6?T7x9)l5oCsF9J)gu4D#Rq81@LiBJ z9BCq8{U1Pu+24I9)I8Ae7w*8=gm*|nbbZ|4^F4?dEv>)e+`M877tizE~dE-zm=9KiRhyxk8g#9NkTb}zLo_8s2&V9Ed=hF#oYh>0iVM>JA~e?BrSk{IcI z0i^#v>~TZqJJMMC_UT{MeK0@bc8o%l5}v)(tTJcWOm$A1w}*0;+pqV1;sGZ>h;SnhndSh=6Y#qqQRaLi-}#V+TMCYn7FdB5Hj(7W(k&#RxDocy+!DlYfF zMOE$#HlrZr<`Yl1hco?8&8JHxdH!@C|+lGr|8?RMN6 z&YZ2bntOmZS7W1w=+#UWaW0-E&^)Z)<=S6ta+ht$!w>1hrzx&o!J`#%DlFEj8PpRL zur?Y<;@2$u@zF7&`!u9}QMW>iLtK z_LNBM?1#P)Ma{=gy0CJ?Uedaq`~pl~Bc#{Wj7d7}3+-k+oodU(md%ZgzIUvt%udP* z%aG&PNW;@~mGsbZisCX)-l*>< z+X>8{2(N3Ie@c6Gse7iv#KfG=%=LrI8#|BALC743s%Ew2L&6O;UDwX=k58_gjoQBx z_*K7bmB^T>vWTY<_YfrRTxrT{6tJG(`c7*(S82wiQlNTHEX~2d@+A130sLeg%i2WC zs;|-Y^tW1xE-oi3&)#F5iA}9Ih1yC!3lN0j^Ab(eV8HB3XQ)xnkH@H+Z=ubAdf~dS zDle271fK2Bsp+~be+!dc)dhO_F@5MuCi&VL>xD;_T^mhL8bM4UtL#qq#O*#sKIDDd z&+^3o9)auw_XX`dlwP7At-3}=2R|Xv0{$u%*9PfNK-jn|Sz_)NKhZGm!AjLBT%JD@ zYBPUOcCprWtnvdV>_cxf3sx7(pGce--{yg(zLw+8c+}_i@4??B3{gn<$x0|Up7o*~ zyO`VA4{^_HnVE7U-uyJt=3(O7=bRCjC0R%4=`s-|f*rb}+k$Rqrd6?VNm}3qYg!@u z-`xnmRHt@T1Z5aq4UwUpn*>RTA%#}8I`D2N$nEc{L+4a6yEbH-%j0nq5wfd{7 zA#M0}CYAVjUoL|r3{)s`Pk<}**4nK~#ZvQ`G6>9&81k`FM3hLF(ulgezdqlkES`RD zY(7=Y$*x)`)4^i;!sQ5t{&M5k+e8YVN& zEq=*M`xe6CCZ`_ElT=wx9!ohpGoZ@ZwW&+kU}n+av_G20+b31{dvD{B8`t&aVF{(K zOJ+g;acuJ@r%ii$Gh}fc8ei}bK3q|4I>K1xQciCgS-7)X(sJOK{67ENz}L%zg=X3* ziSS4KPk+2g@7di*wwaWs_xxnV_#1#a6?YExfghdZvAa4{B~K!_gSBiVkVoz2ywg9Y z0K^+NZZ&5gB<{UBUak7{=c8tmo8z~JdoQ+XV)uH{^{ zLO-?Z%6H3U%q+fUQ#sWABBkY@Ns{(tblY;SSj zB`<^m0NQP8!K5s|-OhJ-0L8z(YQF@eo#uWZB%uUoks$yf-_lMKP12&;1BobS!LFNo zGZpL6a$N5yZnO8X>0BfM6S|C#SbxbZiJo{z(#Gv#%tsQerJe{UA-A(UBnokOGO1%_*wK{U^DHXG8wGzjek@ZndkolTwcZVvpUb)-7(WtIKm{|bK_)9f`D4! z=Zi%brU$REdya$-ni19y#oQ{>@yS8}mI&DQ!wYl#Qp+&i)xi{lYI4842qylzH(OPp zAKfa2&a995^BblN>{bz?x)f59{!pdpeWu?mo^B{PdyerLvZ6ovh0?%qe%+v_`&v)b z?C*DERlzT(|Qgv-}(n~chmB9V7_WRXdrvop{ zm(gx%1Tx=4@sEb|y965}Trs}_KpnFC!eWx6u`A=H|2UX_+3EcZ;C01u>1YD_{7cXT z;5PDlDb#+=l3T?M;&Qk2bNhHBTC*Y()6u)qM(Mgf~e zjs#4YQNeu1ABYc_QcI;FS@g&4^==8ef3mtQfi_teUu_q63v#9S`l(8O%F(0NI^rqm zFjUj!VhpuS<4EUfcVzsBUvFJyK*eXQX)bN7WUbFz8(g1bF5^95M$Pms?S9*~p(B3T z1FF0CY_B?tDDu=F9*Frx*5{sh4y{sEMJOl?akf3?SrwrqZZ^tnHl7z{0yF3owOe`p zB225sIyRBlByZ_xleSW$It~(W}nNtQ2Tg5F+neuJd`77gre>7s4N`T z8$1Ai@v~_2vzwBJ1K}Yx$m`8!#6e7qlDN=*oif*3G~CxIk|@&0Dm}R?f8n=>kv|Vx2P(3q3Ok;3FT*b0&aI(O@zb^XQ&SJkwNpjVicBU~Sue~Q>3QE& zL9Oez!7tJ2RL5j?@Y9^T9e>5c?|fT`wx$Ti*MEh)cLhYlORUv>ZN{xp9%Yb_(F6s zl796ahag?#6xMeUJG~D(E|o+FPV#g-!U!pKR~!0agrNmUoE5F+x?BgynrZdW+`y|KT#OYFeH~D^Y3AKI?(>&@-hEk zx_toDXOnsI_i&FD2!oz?|%S6I(z;A literal 0 HcmV?d00001 diff --git a/vignettes-raw/mixed_response.Rmd b/vignettes-raw/mixed_response.Rmd index e4ee0a96..61765d8d 100644 --- a/vignettes-raw/mixed_response.Rmd +++ b/vignettes-raw/mixed_response.Rmd @@ -65,7 +65,7 @@ We define the loading matrix as follows, where the value `1` indicates that the (loading_matrix <- matrix(c(1, NA), ncol = 1)) ``` -We set `load.var = "itemgroup"` because all rows of the data with the same value of `itemgroup` will receive the same factor loading. In `formula`, we state `(0 + level | id)` to specify that each subject, identified by `id`, has a given level. `level` is not an element of the `mresp` data, but is instead the factor onto which the loading matrix loads. We identify this with the argument `factor = list("level")`. We could have chosen any other name for `"level"`, except for names that are already columns in `mresp`. +We set `load.var = "itemgroup"` because all rows of the data with the same value of `itemgroup` will receive the same factor loading. In `formula`, we state `(0 + level | id)` to specify that each subject, identified by `id`, has a given level. `level` is not an element of the `mresp` data, but is instead the factor onto which the loading matrix loads. We identify this with the argument `factor = "level"`. We could have chosen any other name for `"level"`, except for names that are already columns in `mresp`. We also need to define the response families, with the vector diff --git a/vignettes/lmm_factor.Rmd b/vignettes/lmm_factor.Rmd index c00f2277..287118cd 100644 --- a/vignettes/lmm_factor.Rmd +++ b/vignettes/lmm_factor.Rmd @@ -32,13 +32,13 @@ This example comes from Section 3.1 in @jeonProfileLikelihoodApproachEstimating2 ```r head(KYPSsim) -#> mid hid sid time esteem time12 time34 -#> 1 1 1 1 1 2.759234 1 0 -#> 2 1 1 1 2 2.980368 1 0 -#> 3 1 1 1 3 3.130784 0 1 -#> 4 1 1 1 4 3.310306 0 1 -#> 5 2 1 2 1 2.924520 1 0 -#> 6 2 1 2 2 2.997440 1 0 +#> mid hid sid time esteem +#> 1 1 1 1 1 2.759234 +#> 2 1 1 1 2 2.980368 +#> 3 1 1 1 3 3.130784 +#> 4 1 1 1 4 3.310306 +#> 5 2 1 2 1 2.924520 +#> 6 2 1 2 2 2.997440 ``` Student self esteem (variable `esteem`) was assessed at four timepoints, the first two while the student attended middle school, and the second two while the student attended high school. The variables `mid` and `hid` represent the middle school and high school which a given student attended, and `sid` is the student identifier. The variable `time` indicates the given timepoint. @@ -97,7 +97,7 @@ $$ where $N_{3}(a, b)$ denotes a trivariate normal distribution with mean $a$ and covariance $b$. -In order to fit the model with galamm, we use the same syntax as PLmixed, and start by defining the loading matrix. The first column contains $\boldsymbol{\lambda}_{m}$ and the second column contains $\boldsymbol{\lambda}_{h}$. Numerical values in this matrix means that the entry is fixed to the given value, whereas `NA` means that the value is unknown, and should be estimated. +In order to fit the model with galamm, we use syntax similar to PLmixed, and start by defining the loading matrix. The first column contains $\boldsymbol{\lambda}_{m}$ and the second column contains $\boldsymbol{\lambda}_{h}$. Numerical values in this matrix means that the entry is fixed to the given value, whereas `NA` means that the value is unknown, and should be estimated. @@ -153,16 +153,17 @@ mod <- galamm( ) ``` -The model could be fit with `PLmixed` using the following call with exactly the same arguments as to `galamm`. The reader is encouraged to try, and confirm that the results are essentially equivalent, but we won't run it in this vignette as it takes 5-10 minutes. +The model could be fit with `PLmixed` using the following call, which differs from the call to `galamm` in that `factor` and `lambda` must be put inside lists. The reader is encouraged to try, and confirm that the results are essentially equivalent, but we won't run it in this vignette as it takes 5-10 minutes. + ```r kyps_plmixed <- PLmixed( formula = form, data = KYPSsim, - factor = factors, + factor = list(factors), load.var = load.var, - lambda = loading_matrix + lambda = list(loading_matrix) ) ``` @@ -534,13 +535,13 @@ Using `PLmixed`, we could have estimated the model as follows, and doing it woul judge_plmixed <- PLmixed( formula = form, data = JUDGEsim, - lambda = loading_matrix, + lambda = list(loading_matrix), load.var = "item", - factor = factors + factor = list(factors) ) ``` -We get identical results using `galamm` in less than five minutes. +We get identical results using `galamm`, and it takes less than five minutes. ```r From c89991fe96a39d1d572b406cc4c5fb31fc8bbd94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98ystein=20S=C3=B8rensen?= Date: Thu, 19 Oct 2023 22:12:22 +0200 Subject: [PATCH 7/9] rebuilt vignettes --- vignettes-raw/lmm_factor_diagnostic_plot-1.png | Bin 98464 -> 0 bytes vignettes/glmm_factor.Rmd | 2 +- vignettes/latent_observed_interaction.Rmd | 2 +- vignettes/mixed_response.Rmd | 2 +- vignettes/semiparametric.Rmd | 3 ++- 5 files changed, 5 insertions(+), 4 deletions(-) delete mode 100644 vignettes-raw/lmm_factor_diagnostic_plot-1.png diff --git a/vignettes-raw/lmm_factor_diagnostic_plot-1.png b/vignettes-raw/lmm_factor_diagnostic_plot-1.png deleted file mode 100644 index d1b7c4c4eaf3cd6396819a835439f0cee72dc337..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 98464 zcmeFZWm8;T(>6RaxFk4)puydO1RWp{0t5>LcPF^JLy*BeA-KD{O$bh4aCaxTJG{er zUH5svKj5i)zC0hMYS-F(_v+QF`{<*4?V2z}d8rrZuh9VjzzgY*5}yG8;Pvwl6&XH~ z&+z0603g1!c>i9}^1al1TPs@!Wjg~S6DboL69)^U&r;$5z`MxEFFNL=U+_gzn`)S8 zh65+^^W$DV@+8z+eI5;(+j%spv0h+_x2hG3>EB;Wv!HvsexGZ>D1tAB{i|>#e&tE) z7yX;BB?l1J>{`Wa#6X7Ykz1*(F(S0RrnfaM@r{;kg9|rXKaYI2mmCk5Fk%{6w33!+ zK`gJ!SvSYmYI?W2x&)kld5^sC9${sUN@Pl;1Cp$FcFY|dQT8lNUp8DlneCM;N&!O` zgJ(+L4N$F_03Hdtt9ASk#qRphrVX#rx&*C$Qm*0tt^=sOO2;~`hJBCa3e}TjVJMDx z?3xcn*`~hKL_~2so{tjZDJ)~88kFqkE%_l&J>C|0=UXqiXxTL%s1RMl;f=qH(0G~5 zKQXtk$N?*BOsth2SJdQ{au!UXgOO_HyuGKP*Nt0* zrXcS_Rx=BM=@I@s({M4CL&*9mj%tBOms(MJviO4@YXg1{t?)P9pO{X49M1O_w=QaF z-%;1%_=zxHN6@mX)}4s=`Kr~?=VI&CJNZVhE%eFF#}5EAIx=2wD#la9A83no5u`-+ zi~b67-zVVE$CHSm#Io?x($6VCf4yU#wUcL_bv?PVzud`q^9X2A`TE$mw=>Eip8rIB zJW||wWd4P^71PJvyu^6~&D4&1f>WAZIxg^ z?mE$!z5dGT)HgSg<^X%@)Z3n})Z6CM#C$~YLGaz1S8d?g^W=MUVa6jU!N(jl&OV5j21RvaK7f8GeX;<1mnQ+*x9(=hLVUbo__v3H)@BWQ- zL-YzRCPK_jXePs6^G*xtX#UH(Wb;ixr6)aL{8%fc z2qSmqgS>ub>upDXQjvVO%g=7~U53k`cawp?f2k?FhLuq4U1+)4&@KUHyxMf1?jAh6 z&k!b-0gahY2v4+b7~MuwgK&U&XQD1`DkleEf?uNofFTwDMEDgD{(BAo0RW&((Ep48 zuQL(;_xkxJqP)dl06-KVEg`1j2HamnYrW9p0Uz7uNd)3YzW&4YWlQD>`OTOp7fpv= z(3tT@+5{>SrbzMYynaQc*XB?Kgz(Qyaok~w#M$N}Hx*Z70++(DrmH5HBdl$E!ExHB z?eHP}jO?=VQsk~?%B-xdgD6Gvhd>>oCWzHPNmMqIw(<*ZTu9Ba)KE$067%JU#M^y<~W&qE2}FZf^x)}>DTD+KwP9O1uHjNs0Q z!(Xs9C~%4Ycis`eveEy@*92)X_#^xSY;u;UTLUQZjt_XQggo}&j0BDJSl5}D8fCV%U8al|1L@sz9wPhfAy&h z#x?g_?vy{E`(NV#+k2l)^#6?T|BUc|-4jIiX~I?*72F5T{jt1OXn30D=l&aOmI+W3 zcF)<}4}8s|nqTewHFE0`yt;x#E{YV=$nRHB6t>en4{?$gy8r9gQ0YYIZ;Hp`-7p2O zHK!p<-LpPPJ3|(?q&xoYpKljeU%I|?c;o8--#D`4qCIY&ZnL>vK>;@=^)z7luo@nT zYyA&=*fqk#2CRMKa`X+4ZXkL9`>n4%s!Um)d2@Lt(8Sntsu|5fYoXx?PC zQ%E_qkM{foeS{C*o>ztlTkXv+y|98K)8+FkOQ*Xftp1TnP+8s6xLwX@_dibG2enOg zVRws1KI0JG^X!|e$7fLa`3Vk-8Gn3^RBgi$**r!e^0vNr^RBg9X-8h$PFWZ_=Dkgi zW6uu?JwMngaztnwi)dpH(fY*ha{?(S5I(TD=_zh#-DDkyi@v&_&o*^YepbCEMpSAv zBY5OCHVK;lab=CD!|istV>h*(5n-Rrr3xbF+P0@tu%~;~ocg)DTnZlFm2~haDx{Bf ztYr1!DPbayjYoJ-1E%@KB~9V^HZ87sxR0zrb?WH9Tc`v_&>Zet$NxnKiRU~q7P}bn zZ=Q&Ny9Y(yXvV(|^DM}3IWB77^nbzq8JZa)9~b_W%o+>r0ktkRi5&Z5S%syv5dZ63 z?=;b@5TO~4!~ElxdUtbHH+oMOdJ24PyK)80dN%{(o9wYF5}W7gZJS#`2P2~l<&FJcZw&bR+vZ!MxcwSOegZR&P@>9T$o z(!K+sv;AFsUaZ#kwRx(p{EOb*%sIZD-c{di5h>phdq(*xj1h|S0M6&R@#yA_&=f2 ze${3xb56(C)PLT?*KU8i4}08SXI?FLhi|MM70q!>h>-E^O~%vBu0wLmg3~BO*Qxm| z?MO&x4L3PO_c(y)XXvFXHJ<{AJ0v|MBdcB&hGhv@dOn?{Of-PcNT^b#lIkyN;*3bBvB-3@M#NwhG!9crYCK z|A%0xQu-@g2PvfZ!0qMfF_A;g1A8I)q>*u-?EUR_frPigs1rA2; z32-X>4~2meORwy8Jnj|H3-3zDPlCOUFE6j-!8h>{rQn>1+pQ#MI87xEz*cZhO5`-Q zY3ad`sAYyx?rtXfVMclQBJ7{-sANM^pSNkdgXrC_?NW=ZQPXjXz!7fpZQm82&pKaz z$NgZ(PxW(nhz%BC=L#jV7dI)EeMt|?!O#)rJrw^Olj>?D$VI%R+2`Ui$ZcH>1XZsGoYpS$Y2W#(f_ zHo42C1!|GeHv&99gI+m_^ZtK0=?An>nU23-Dg<=41*ZG0%bws~ZsoDKr^_dNSrtuY zD;NjkowyCJxDF>Xap#med(Vxh*H?30rs9<7cz>urxqjGVUVWT>E;x{`7uoXd1GnV@KGlZF)w=h4$Sa&!Q>f0Fcr83M0@d%Rr92>V?

x^GT$wkoV!Xk4?JKubdu*Wc4{+kP-dhpzdY$xU27#EB(i` z!=|?K4C|( zmqNx?k7#n?YzX0IjQuWFW!YXjCM$X0Y3@0#+M0GamBDu?8GtcWlKgZX{q)X%*YgeOjeFW?}A~9KwSY(|TmO}n`9Ac(F2gkS!g-bJEIB$qcvR$mn$Z1BnoNnFX zL1VddkE}Im@akT^@?K7s^cvpwlGi+`k4CyRi|dOtgnqGm&K;k~g)grR<@X0%>q>N3 zlabIFaye$3qEDwR-qPBcemys>&$66^!~gz!5Y_Na*x=v$4TZ;z-Dgn1nsnWFhVgaW z3^@j4iV_qek-}WU=B2zRq)ccYe4uF`84w$qC(B$uZo_S)o4#y+%B|-EPg+jZn1^sk zwLN5gqSZU4#Vc{a!!zA_P9x<*PgN9mRl=@li?$E_*qI&sChW3E+m;=dmNl4MI3Xi& zrQmz@{b8)`D_W^GDCtL5bMN^m#lvVw^9C9vsyNBu#kY_1jpUNzyrwOXts?I?m^N#;35I;rnQu^Fs=JJ9@_i z=LP*zei?o4<;YNr3ir$oi(Pu(Q~H7sk@Ku^IKz$q@_TH?;wahoCfV`(s@R^a!Ru-Uk<1mU?#JK=4m6$7>D>}=r$if4vKx<)*Fg{PdP%D8soZOC#=A`P(e)+2XDs2g# ztPcAIzHyj)Ox)kW4Zc7l8AA%bx7mHhtS+^Vlxdr*BjP(NL7Qb4k$sb}Zm%WxZ8;WL0lY%^hc@mG!$Y)M>&p9O%5$#1Yd@l} zt<4%FJG2W4gWWtQJudQ7ytaxbURLildY`rP4SFztAY+M*HfBnMJiP*(^wC$qTDmQh zphr)SM+&a3>tqFR%_tNGN3?qVHtH(852%a2*R9`afPI<=)E(%df8Al8x*tBz%Ot_b zoL?9-W$l*XfpxN9secRp-2TixANr#2KO#@e@*fVuvyQ^mb(VI4oABK%Oy1;oKHL4F z-(nN9_A0uW&KAk?bF7yr`6g}oyT6s!xEIu{gdby&p3#?*LCHErq)AgM+gDxGdrjsIe(qr6L|zzyg;zv=MS3y$Tt z8fsF@`qY2DPq@aOR0-|mN~!GMEU>xE?ZLC&KmCt);%Mr<56`?QS)0HPh^v&3Tqta15viGWRjC8CTI%d+ z?hZC^Mu+-}IqZFMS#CM7wrz=H0=U>?FK{Kcf*;jEhWv&Wkc`i`>^6yyqJ>aVsdU z{V8T1e$}*guGL!%H~8!|n5sNJQ*&+Khn9}Jmfh5+H26kgZW-0hVQ)Uyz3H?%4}aBQ z`odQR%3bXD>II-LHb9lX)XfJVFfM+V-1&`7_ zPb*qzhn4jAO~dRo4eQH`$EJ+CrheS|Ksv1=51_1n%YA8B`$d_p_eg27h;MxA0!{Lq z0VM+)Xcy7lfa*F0DK{(aCRuXytSEymW<>L$;~LVD!g95isvA6LXt6o%gez!30pJ$C znI+Mqq%f&ameq$R0YAMu3BiseC@$Ll8vCQ64cBif$svrTYW8Nn)H0@iSNpcJXNIYK zKUcz}z(JZ(o9A*_Ej6tbmH%>ySR{3*y6~B56KfnTjRRXz7(kEpoCZ9NZ#j6iu>$bR z$luAg{0f6)2F=!A{eWk%smn>;hz{gy>z!_!-ydZCx=r5`3l{Td4{6kA?{wUjbljGD zPeCF-SkG5Q;2M;*Sj(1Cga7z3@-J!R# zHCbjkfjfha1`z$_e0f|S^rzf5VA<-Y;n3AAavm#suZN>*b)=)>+5H{V`wP$zvN`F> z9$jS-)#shmFk~=tMlEOnnSL%_FZ$Y}>w`8y*iBJnnWP%^bd|R8agmVgi8gEN3h%?@ zGJI&Y3UrgM&O$?HKdwOU!rz!UeKU4JZhoMdCH}WP|1`i?+oO2TorQL#u8?KcQhWV% z6EP+;IqKOjO@zo|y-!nt7%K5LE;XrX)fYQ!59@G6Rw3X4#1*6{3+h39%K0<$%+&k3 z_&nRTd!Kj$fD0rC+Z`btAd)0RXC~AkMb7>^UNB$(&Y)A&h@j`ujlFXCve3F@2xhOlMttDGC1M z%u8cD?QVG76m(2&yv{{i2=Lkpwf$6_(8pt2qS)4<_&^(Yx(Tm!-X39&?t#}lD8Yz90R|f^Q4K2Sv8i~@3%pD*xyPPVcJVaF2BMv{*&()#a z8&aXjMC*&s-<%pLgRTti$xmZKJ{5Y~e#^Ozl{*luOnEZ!El@Z3R{Q1Dh2I&Mrcgcr zwSPFSSDu8@A2o;{htWJwObPu2F$fI^Q!oFlu{zbLZ#C4iI7jF=kxl!Y@%cIu%FIPMn%&Vt zy$MB*G+J-iYfyb3SnxwM8WDzh2N8q|z-x6?HK?_z!IlPu%w(oGo}3sXFfSvDAdN;# zQyf=^Oo#g2RmEG=s^BxtALkukXOJ0Q>jWo3eQ5L}`5-SR!*C_XZ5+h4Gy`pn;tPZ} z8Jo1yct#*c4AaP?+n~*!JOi)#`{ty`GJz_@CR7&7nww$WT_kolF`LZwr7zYc&80$~ zr#W5+8N;}Q3Fc=c%l$EV5f9ifzhXUYo=O(jTYjlw2G)YuLAD=L;<63ynNtB)UyBV+7ag-yJ(SamXiAu41Fz&!yZvPOen(iXG<9>6{ zM);WDU-QVVSCETBl)5mg6|qVK2XMdmyY5Xu>XB<=7(`4^iWY-a2N(-6na%AZx!7N! zS5mYg@{ep$x`n_p4h4}EZj8{C?OqnH-_$&m4(a;PV2^noU43Sl>}En6$Py;&lZ?@) zWkczZGxF{T3o!1_WQ`UqQwLeDzL-)pS3bAIz72;thU8D_?2701qVj`ya!z zpEp-+B0*T*gqeJTPxohL2Hr=`mb#GXnc+@>pWW3}8Y~y7-KQl9Qi%IQ;*38e@vm5* z-j~YY3uS4oiXZvgxNkycPF;1U6&p>TyA@_tYX&|x_s}xzRE(j3FF%5vg)#PgTfH)2 zJuUS_NaRVL1Jntb2%B11gKCR`CdJCTx}ho2xvSgst~b~f=R>zUMqCg;i_RkPV0GxL z$k{R*=MoKuS2ZLHE66p9DW)eP0VWOP^NxGDJA!>S-RR0@u5P%QyEP8I#u;}0>U%P! z6bV)Us7w-C)g@9sno6Ad`x4zdYgsLl(HX{aT+>x5qNb1DX%vftRCS0IaSRrzX)JUq z^`Wj;;pkT)SI?bHi_IoJy5rm?4eu7(eZNd`VAukQDkZ$jag{O=s4X##v30uL&7G8A z##NPaR_YdL5rYMjU%8nd^1}o%CG5@Re!zGwZuSohDJ0&u|rJgg2bA*nSVM zhSC5iB`iZGlj6(FbrZ7DT*&%!quY@sye0}azF4USAYUDx{FceC0>+^=i7rBPS_jiw ze!ElJ9EhlaI#@(2A}u=y$LaaG6$yCw4`nIKGMHx*pNhfUSSOOILv*rs0_eZ z5LTEn80CXz6mhsVbpU;cex2_Q#rsJkiXIc65rY^lD=MQ_0)_#6+0jFLP_ZB2m`MKuiXy@>?HO&&&57W+ z!iJ`36dA2=>?$l4%kp{f$wgK)&0&?n1qi5I0W!pNixgsEn_8!{pP7kaYl)chP; zx=A3+jPTABQpv7ee7tN>AXZQBy()zrtno7Gy+RbToeDG(^HY}iUV)!EkuheXof!A! zLTE=x@IbuxsoXm5Mexg0Rp`T15eTLCjxlb}c%!G;>i70P90hLzS$`N;0+^~kU6=Fy z0tT%r{LEdjLOs#EIL#~ky$e_wX{25Zn1mQ2CZna}ZrZk8#;2d>{=BY22jJ)%$r19s zHLxITv=rR@)x-%HbRWH4_a0`YtCql0=JNYd=uX-D#h<6BmO}Qsq5dC5RO&-YF>Olf z?ko`5q>?T)*fMi!AHg=0HAPv+kA)UMIcKQe$q*Yq*6wgR zrKwWG4@4&WsvP;n(NYQ@%HCG75f#Zz5n#U?p!rJtv!XMa z0J@$Z!e@n(uEK~`^pe<_-Dj)$!xJK-6W)BIo?0>tVIEAV}a~Mxnt(BX&TF{PR&) z2nu%2H@D+qXh@hsFm9@lic~|VX&~GT$VOVI`u{K10GFdE$Bvctt$PkP5J6OE91-(c z9SIv$V-mKeAHr7(ti%uM8mlEMZO)9{G*WOQT=hXX8_8nfjc?^56)q9>^C9M7EujE6 zH3JorN@x`bypZmJabcu`_QU+!8&h=ybrX>RYudE3`RY9`=LlUan$=VC13=2~kB|Jv zla7N?_*L1PIw-p8=uU!uQj5xTei)e@%MXXiQ!J{1LVhDD6G(+>pg##ERhR=9rf6<8 z^*48IiaqTwj12jE^WUA(#w;M?kItxyJ(M7m#rdi%3FGBn@g+wTtkmwfE6++hX(*&{lVa>lpdLhV*F09V9L?MAs zb`D~YRCZuG+ZaO7djy+9K3;lY8_+W31oQTNib8TB%o(&=vqs%wV=Y zSAmlt?WGuq(_BH~uk$f|c4{*3SpE0akfAd9GUa-?eV-}g!9UICDaVeU6PX_r`oT~u zLe;zW`mNdvi|f~pY0m0KH7Tu*FRJRMr+84Huuk!`StjRCRT1Wd0HO3B!O7tU<_Rw7?La2yd0~zpJ&5W`)A$wCltHUNysf0};3A%K-W5bs{ z!)h0>sK_0!F7I0mUPHoxji~u^i!QRs`jne5p!T*D9UoLz4V@Hz9KuiK0K6rGDdrW|wp zBMWR(?1Us(byFToVnm47T9a}w1r77juQ>p*@-+L=UqUncGiagV!Y>S`HHi)+V} zPP5Eux}!a&$yJ5;{F9@v$9VQUb5Kg_ESGmIiu?%S}AhiROC;N=iK{ zm`sFg%9-_Ts+K_0>J#@10|v1e0Y+Jljxj9&b|>K{!6ZKjz&Sb=r@${3JE7ol0UZ){ z!T2Qe_s=9JooaED^`{vGv7pPBl|5uSwx5`=0<)qgkvxrxvQH;TnAZ-xZY9W%;o;~D z#UF7E3MWKqARZ5~x?UMlt9QS82}4pJf=u$30`^3BE&J2<)qeuLnv`__K1u6&EI?IR zS9T21Kgs73ETUT^+*d+Rx7b=c`TOeUV)HOirWx>>KO^W=r6J9M#x^OjYd}rUcQqpe zDGh8C=4m0*g}(u@od}+!@Jn4*=;x4cL{Y|0?f6sfQdcs>ffM&Q&WF>LylnK~9YwwQ ziby>SaOqVQ*@QhWQIF#odu2p0A=%M2R=se+%H9QL??lTpA{>xSf>mQ(DLon#Cta8b zw-o2tM;oACNe;;0WT{s$-5F)T!WvdAR#`|1@m^@?Lw1y3zp;ckJJ21)J;of4h2YTu zeXK;RRH-pSf`^lttJ5yH^HVra+fNp%VJv;4T(vgI>?s-UpQa!fxa-UWr6#WI{_nlk z5+|7H9&Ud~O?g-~{lG*RRPrNV7ReCF&m2Z$`7MGfx>mDYaq&aG0|$0SThrP0?i<#3 zf}zjk@s6d2QW4+C#wt;g+GiWT1&4pv?>-~KxUC^AjpOx5|3oVbLwt41_DaFnRq?=m z$jlxz$E^40K2;AEF@sfI((bKxe_+vCE?b_ci)*%UY}H{?*p+I@tg@Pnwi*_`T8NN? zau=dNk|PM?9gwaxB=y1Wj?s@sCTE^CP>pUN)Zz=I!EV?&ss2bEb^RsD`skyK!D?0j53y<(17%&kj;V5#yr)2 zAn5q116YRK~qSS_zt`U>P(&z=t$|KllCK@Q~IUBB>L%Q3UA;nv74{R^le zwp6diYOn6?a@7sfYi5y3q8sGSuewjWO{-dX2o87)3B9#Sy{wX_zGlBt#hRA7jX@|i zHN;M$y<6_BubOSR48bB2>p|03q$;#09KESQ<^7~#AZO#4I{yb$_?CIwh}AWHenYz^l-qZ(QHR^g@kL=RA8``ieq z+TnKLBoP4KaIfNy$QwLe-8NOKTvI10rEMQNwTDQ&WV)z*QJ>$M^tozI!VqG&^;Ft5 zW?B^;J2@p*lX>%*dG<{d>_^8;KJ!VALP_XmFAR3XlAU&xOaMcvi2BPUuIG>@`ARft z>J22$gojSHP^j0n^pNG9DiI{kH!UNbz@q58CkIBU#Z;5S{%~)x8F9P;?ZuRjYWb-~ z&QzokW1lq1nhCXqeKk1@i%!fZDY^=@2!q*ps*AWyrFn!C63s?&#;E^FK^k1);AAoN zF2PKy7P6`}>bM5Xo~IgdsF%;+5!QA!*GmDOoHx^mzWLT^M^!9lI6uxraE5h+S_pV= z4AgY`k#0s3bM%zjD58jXDP1I?|9wA*FzFj{-pdl!yv%z8>AV(ezxXeDt-j2;4X8MOf*#J3(o387xfKejnucy$fp zTH*|5lDgk4B907orX@Ditf&Xi_;oh|+fPtm{~-VX(HWCd8pomsKoCHMhTrBw0KR)v z5o+bY{Q~4g3>ARm2&<*<eG5cRHVY zOLQ-aYfy8426LreC{6?5MvP$P3@IM3PQfeVPprBP@m~7&MA}DCo6EpctAm# zjRe<9E7fO3GNzXoc&gB-uro>mYd>)4Iq#ju3f^OnJ1tOGUFB{3kP?l*oKF`%DmqY- z=aL$nXFkC!crF`G--qTju)jzhnyZ*9lb6 zJfJ?XB2z+zkCvKcpV;-=@*7$)hjUFue?xrqU%?541MfK0h-^TjY<(}hS=s%9UZSjt zYI0>26U6|W@FQh>o=&d=vD%o3LV>u}t2Roez&uM#M4Q)mymLZSR=j}dLq zDSk@VHTqj)Vuu@peP0sI=(510jb@Bvc^E%jaBbHXB$;-FuAl2<=U7N2kj^skg;<+v z1DDWaV8iKxhdkm0CzJu}??*=52nKL?H&;wj2UPe$kgkJT#h2DeC|QQqL^EHi=!Gh+ zSbTsqXJi|w`iW80i2Pz-iJY|s)5Sz8_IBvV_EUl$TLmYvG6<~peG2WEf-umE8aEuH zt}X=;mbt`2&98&2LIpL}z|C}^8w^R>9Np$s1_cZvQlNQ__41mD@kfanhG;mR^Ub}Z z>&u-(3i?LtAro6@E@oqdtNnFfRvQBtCw-IS0RIArgml2O0YBc&25C<~lcXC=S9>rS z@RwkRQ1o3eRZO*5lLOxjY7ZAXP{-WO8QYj%PoY!TQ==k zV}$wb&$1Spun||0`slpN(p&OUy53?Bo$_ClI@E&wM0PI~>BfeBZ&JRCk`)JM|7y~X z;Kb)>Kq!9A3PTd7b1ARMvu3rx-_IABD|*D~jTu}}+}*G~BR`CtLw0TOAi;t-61%>* z74l=b!(^p+yd_l_JjJ>0wdSU?K}v9@Ew9kWsAj*2r&MJlT#sn!(uQO=B(SgX(N(We zX}O{sarRECV*!hKxnIYh)P*u<*$wq>yB$?Mw93eBa-159p~lTAh!2evCO`DAf8qtq zD!6;Ywm$k1gn)>Se^V`V1+AIwR%%*%1c*n38TEb^y4Z@t)vrt5Y%ldz=TM!0#qe#m zQhR_WwN{@{-K10ozm}4}mN{HwWg$hqINj`5jxEw&6+Tj0l@QtQX#tFwRQ5Aw4{k$r zE?aI+$d9~UGJiI+6SQ`W%unIzc_gBo>#8^(`{FP=F8@?(+}Ood&L}OJTWrRrZj%b{ z2@H_xM88<6h9d*tLHNgu(wtTf6ce4h@|RKC6s2kzNCYxb?M9*-)H_(-Y<^r`x5TZX zqZ~(>owQd$o$>4X+91-EpDdUSsM7;B{aiA#psF+@E>-nHuGyod2lu#7m7tEX<9Nt30AClRERq^5j6m^-!|^%-O??qmLFy@E!1safL9>zuPNFpX&Q2O(Xuay=fA zyYLg>(5D=PC*+q!^Sw)|d(M@Tes1Am$BXhsseh#-lr~^w3upC-}LQUU@D zXm$hAss?78FM*Wx!#-?^Dw2&j`M7m1c`|6A&H7y-IFNx#(|2y4$*NlY~b%h0OH zF!Cp6coFU%HrGexG^^`~R-KZJz|$L*w@f&zg z4M~U!^N^aj|E_`^3HeK~7>yhqMl<~`=ev@B_Wr?3q7~PHX6MZ==WU)bg`#XnmMs$9H@Xnp5@h}VF7!R%zmZAxOb=E;Nl53~@V+IUOf9s}MQ|zDFkYo? z5;Y6k7+)wnjYVP$^0i?weNWXlw7j9(PfOA>4^ZZ%Q+-RA$FAo*uOQwOUvy%2fpGPy z@~vlcAd{XADl1Hv68sdyusp%zu{?1ZJl#=Y=;emsd=n=*2Wq&PLnX7+qt6l zIa37?VJS@?c3X5@5~6>Tf)1i^RkOruG7{{wWWN#mLT}vVtT1Rw@htrpa%HQA$-uh9 ztN6?60iEBfF=&>ldF&}zd4p?oB>UgE`o0rbeF5f+-AO!Q_^1$#p3h}Rve+gFsv&xi zQ&Yy`-Tsh8)`_aZF*$N)BQg3T^X8!TZ)Tji+ttL(2zw6>b9(CgYS6F7Ifb=OD!e3? zWK2uFPHa^4g`-!!J^H8+;pq)C&EkzDrQGnu;|x6wR2fyJITMUr`g z(;Jux02)z*P{2nTen8wvIgj*bCPF+gCDXhEGoG;iCIzynA8O%=bRGVmK)8wtHBzAH6^P3P)GVxBf92NtL$AC{8l8=jA8!c>s7zycmW1 zl4GDaYecCTs7tI=L3$J}MV8Y;J5l`6UTNHbfNqh3(CNc_M}(lx*!iWn;9JB>xs#+u zA^HK5VD?jO?gj=A=QCm?g%2W-nB~f_xnKNEW=dTuW}l`?gSoR)vrt_I%jn<&Rr=(I3-}4>fMyHm zy85WoL!J*=4H+FQYSrG4c@;L~Wc4TKg(^v-LQ@>7(;;qcU2{%!(|d9Mk9^`BWbPEM ze!Q=#wK-K8LO)~a4>a2!*%1&^-wyGVpq3Jj$eTn-FIyC}HsYPP5n#@l#gn52TSx4k zh1!4Ac*pL`{5fLXgz?g-??rzET3E@4aP`Pi*CzDS4V=gI$ErAkPgyft@5o?jT`>4( z?k+W4I5!wF`!tvD8*Z>^p&@@11nHp2#)R?G6t4gwnfi`#$e9qlzU!Z1Z%Y>t^|$3_ z=+s0Fa1%cjidp3QzT!BeMIaXurNmd&WMfAVg(g?bdG;~B5$)=NVPT~B@n<6B<5*{` zKz$ibBoXBAl9g!yL0+u~k>IhhqD;>kFMR|4FX~b8{aW5Y4(M9)1*u{+DSF|tz4E)S z?l`_2F?ARzCN`&)37FR(Hx8RPV?$`I6R>584@k|VcpnV1^EUwUqIv8LA}-D~gRcO% zB)^i&$#8ZOeN?N_n7J#M2Yq(%*)my99F>z6@K`=Fv5G2#=lRk=AFJLFt>m@ceiUX9 z+YqZ-D=BD2BuP2QZG1)4gAS#4gvExn*7nY1y^G12K@025j_wv|~#lqwuxN_-v|g3QO4qoGK@A|Fc|JR)QYx)Mmqfw)f*H;L zvD=&`f+X4ru%0)-hdgm8nIzu%Qg-xYj_m~Er~votsK3!Z^zoH++agvDqz|(T=#%8B zXeSl%VW%7Z%`VZ4(;I?^aYW%_=@=!vIfqI!yq&8cG`r!MV8xy4j*+dbppA#Y9vKD# zMqd-VK)ag4_W-xcs*|1!7NnBz-eSYQ0Drvym|wi6H)QRR$R}V{+8*86^cj&_qm3Zp zZm}m9^W|?ZQ?2|Xw+QOnI}vj5w3Gdqy>0!qE73oFJC37gpdf>f>;z15T-ZQ{&Fa3P zi%z=}YFXuJ4?jQW>qO~jF;{iX1Nt~u3q|pNkN5Kxko4~p()ngVOzj@*uE0L8F*^EoD|?1|>`mJ`rx1$yE=)vpMfj&< zRY3Pm#g{A=c(t^{&{G~L>W)8_{w3zJCDPt^C++R4Bq=g))g(WtKB+_R+YBV9el-Bz zaU#~SD;V6~Y#ss?1zHUQl)Od=*FEHDemquqoqI+h}K2`AkNc%#CX)jB6)7c62e zVyA`c*`Bm%QQ+6oy{YPOG@Po@4~Z;AfQTJvPitfhqCoMDW_iD==dlD}*v^fz|blzUoV z?!EmzGS$%ol+4?3Q{POJZr?+^j?COKI(C77L|%+I5|*_~KN-2SQQ)MVG%~ky0O+~d zOz9eD7XWj5t=LKuJMx?W=YI`xu_iEmeMK9on)o5Z_fuaJ3q~U3Qew}Ojcz1&7(pdI z8_Q4nhlWp~XZu$bmcQv(-`TlY_PiFEHAy5P0$YTlSEmSPzj`F%+ubJG1>(Xsoq)Wv zys}r}E>hAtdy7UzDQ1dog^D-DZ^Kg~sPtKr|70~Zy=0-U`Gi)+oDqjYL@FTY_a?bD ztDKe9k;c&(%~HU^^I)w&HA=wVHZv!&pDdgFZ-Gua5j}PZbsYLOb3@JRE5A+4!v;*u z?oyE~e40t?u@_aEW%~(0TFYnS+=TqziJE&Y!QZJk$>=on$*4Ja=5}L0J>VB#qdO_=I=^WM#0j z_q=f;Siv-UH(y(KQx${a(uuI3(d^&e0GRi1Wn`O)dRqIvoy>^W2Nc1l%R#wO+Hcbc zY3%5J^x3yJuwJh(cuHwq9a9$&^c2S`-ksF&n>a>Ssg}M{6&V=!IlPlTHS3gnF3nq zj;TrIhXC@S%&hgNd)C55S|U3$>0q8_0@Qn%UYUt^2zVBR1I9dW#!axM}(R8gyn zCX2zC7_RCP@75b4f6uBd@17dc-Uw$UjpgG$F!ob0RN2Awp%|~e(-RS~FtYzhtTN~1y?jp@*Zo#+ zkoQ==q8}G_Y#uurcUQs=o`1G61o$q(7*i8XA1NsF{d!pd306+49PA8Kn=4nb!St6t zfN+v_pke$gjo;&2AmkO{D8%prfzQDDJ?I+%ADh_=JyuAtYjd&Vh*JeFT-omSP&{;5^oU` z;Mcz6Efc^siA72BUK?kib_0VWwX}}CbR!}DQSs#!Fou@;qKwvq!F(D>M2p|IqaaFi zs0o+Mz75Eb*8C|BV!8NkpUobCHp8hcF|dbV zJuP1jU?Z;1=YCgAkEGy^1B-znlmya*2RGY0OW5hwM^}qitqhF#4rHnWJRVkWtBv!5 zIJ4LzvpDVh83IpxoQ>VnecqM_W_@h3%n@13<}8ma`a*X~?OXD;kt_j5&}p50G?i%R zn5Qx}m2GZ?@xr0mgjBl8i(&}Pm_<6hGBOfu?u_>xD}I023G}8 zCWY*JfMeb#=nQp@%2lpwF)`**03F(0M7G_@1b+oE<<4bxwphdfM~cB7)JBfl^2N3%(=A za!qKDK19Q#!pDRL=k^eEN!B*IAb<}wi|D5_rK20-Vlhu}g%d*o{sg*?wB$y;00Tb<>j&-ELZMJN+HvQrPM5>}nK zTLeCiN!eC5tTQ!5AHTLv$4de%IrJr~q>=!in6T>WksPht6)eSiKxMO14m+_71XS54 z9JPwpMui6`&e;orRje?UBFdZPSOIE_^-%F{W)e9qq>tWDM%28U%QaVwy=S-b9MUAzBkLD32}6!^ytVJ*5f@alm2#XJZg zV|7N#I?rHsUHDD29r@N$vxj_WHvGmdU2$neumUUan{mQ{= z=yU9jF|6-m=fMm@RWE&EeD0MnqnH}&=rI%eo~2J#ta+}aF{vNf9W-IO^c5Ll2^@`+ zF>&n}yC*iSQ?ohBvfF}Rt+WM{RS;C=Y%Oh2boKR6jxlk}!VQqyY_eztFA3g>i93Ue zSSE*^Skd9P-BFC7rT~QbIYCpFht+Z&>+7A1$!&g*4=jJOWO-mkumLPRmTV>M3~-#U zId>j&!XgfeHLcvTUIk|f5M$-Xq?Y#>`(^R7jxAr4pX;pDpr+Fb7yKpQOfXh}Lk7jM zc-_aUcMQPJyanZHPk?W3yIXbDKbB*u59`}AEMP}LSFK5PM!havv48Ix2v_Ey?ZTl#FPjQ6l8}5 zVZlrKjqCy-lci99R<|+W$l~@pu$)d%WA(4~=$M57;H)$DLx3E4vv$LWoxy$r)huNT z5Y4yQ=^N|t{gwh;vu}*;#Uw9QKek2^5LmJ?rt}|cT3{T8$M3T0W<2Q=?>Mj&b4=I> z!HoJ=->Z*6%L=d?@2$`F4udK_`#OZ`o1%01^-m>tTM2&m$40 zAJ8e#6sa%zUHbvF2^FQyjW8y_XRS3wAuPBAX=Q4Qi5`urZR(4rS5B3I<_E$+)E`P6 zK$D}f=DBh`$g`9fYq|lrbDnVo{OVuIgJ*#n*9S$xJ?f*5&dH0i;yl?{4m^RfsCmj}y??>xIaV7`QYggA^%5J;drsMv0JBvBqw?+<%IaQ;l0@OK ziIUY4#;p4I9a#B5cFNOsdnz0>*(+k)kjTmtiY0kdtjNvAOBAbhrv~^V=z#|K)eG4q z=m%v|F&UXy4l5}O0F0o@vReEB13SLs!$>AdAjoG4oG3H^P2GTP1Y6CE<(pniz;m?x zfdcEj5=$D``QxiKu`9zd0{(L1=d)&s0S3SZpp;d>1`zcJS$WPMOAuhzch>-`_kf$= zEh)3S&xSrgyY6FUo3sI=Bg>WrEI)tlqfm^Ad|0-wvnbWebAZZptYY)S~Qa1Wn4fS|CruC0KavH`#WwZ3sLzy{3ZgtY*0&I202?s-6l0ig)k@Wplg zS#b;60W6F$&$z#o5$FAp4L}B(EJ#ZN0S$2XetG>RsRwqY5-Ym;ijt&V{L(CO_l{Pl zKH={VO1d6TfEEW(iJv_v>RdWW3DFPQs=nlE++hp&$t~ma+tDW!2{N#xjwKuck!kr? zzIDWI?3DnlexlgOSN`EHNQKdJ;qsYbB6izcYOlPPT_cah1k}YpggYu~rxGz1VIm!O9y1Y|?CrF57|8@KKng(FNj}7og&;t$dMmD z65l3U73D<hj0T%$XM2rs=csTD5z>$g1KtVkzT9Oz8Wl{ud z{Kx^wQCd*`WXUpiR8Re`EOi2u#+rQmsfR!E=9gyu`mU_9)YXa4n9@W+P%lf`04c>x z9r(&w(v(X8O|erRWs>^>;A@jQQG$#w%!V;a2~i%!mX)skz+PF#mNA57lEfR3C#8Vv zfsECS61@oG*aK{VxP#b|IG?Y7+O&*Huj>arQrfwBxKOI@2hH;^o?j=xUobklQ%hju zB@mNzE=#iuR{%^d!3k?Am!{RS9=ozFOE&ZfT5|qAZ~K#5eiqhqE`wTpCMMM>KrWa1 z#N?Mtt8Ca2lXY$oV@Xqh⪼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 diff --git a/vignettes/glmm_factor.Rmd b/vignettes/glmm_factor.Rmd index 738376be..0c117a6e 100644 --- a/vignettes/glmm_factor.Rmd +++ b/vignettes/glmm_factor.Rmd @@ -104,7 +104,7 @@ mod <- galamm( ) ``` -A couple of things in the model formula are worth pointing out. First the part `(0 + ability | school / sid)` in the model formula specifies that student ability varies between students within schools. It corresponds to the term $\mathbf{x}_{ijk}^{T}\boldsymbol{\lambda} (\eta_{j} + \eta_{jk})$ in the mathematical specification of the model. The variable `ability` is not part of the `IRTsim` dataframe, but is instead specified in the argument `factor = list("ability")`. The argument `load.var = "item"` specifies that all rows in the dataframe with the same value of "item" should get the same element of $\boldsymbol{\lambda}$, and hence it defines the dummy variable $\mathbf{x}_{ijk}$. Finally, `lambda = list(loading_matrix)` provides the matrix of factor loadings. Note that we must explicitly add a zero in `(0 + ability | school / sid)` to avoid having a random intercept estimated in addition to the effect for each value of "item"; such a model would not be identified. The fixed effect part of the model formula, which is simply `item`, specifies the term $\mathbf{x}_{ijk}^{T} \boldsymbol{\beta}$. +A couple of things in the model formula are worth pointing out. First the part `(0 + ability | school / sid)` in the model formula specifies that student ability varies between students within schools. It corresponds to the term $\mathbf{x}_{ijk}^{T}\boldsymbol{\lambda} (\eta_{j} + \eta_{jk})$ in the mathematical specification of the model. The variable `ability` is not part of the `IRTsim` dataframe, but is instead specified in the argument `factor = "ability"`. The argument `load.var = "item"` specifies that all rows in the dataframe with the same value of "item" should get the same element of $\boldsymbol{\lambda}$, and hence it defines the dummy variable $\mathbf{x}_{ijk}$. Finally, `lambda = loading_matrix` provides the matrix of factor loadings. Note that we must explicitly add a zero in `(0 + ability | school / sid)` to avoid having a random intercept estimated in addition to the effect for each value of "item"; such a model would not be identified. The fixed effect part of the model formula, which is simply `item`, specifies the term $\mathbf{x}_{ijk}^{T} \boldsymbol{\beta}$. We can start by inspecting the fitted model: diff --git a/vignettes/latent_observed_interaction.Rmd b/vignettes/latent_observed_interaction.Rmd index d1db785d..69098028 100644 --- a/vignettes/latent_observed_interaction.Rmd +++ b/vignettes/latent_observed_interaction.Rmd @@ -165,7 +165,7 @@ $$ \end{pmatrix}. $$ -We specify the factor interactions with a list of lists. The reason for this notation is that we need one list to hold the regression terms for each loading variable specified in `load.var`. +We specify the factor interactions with a list, one for each row of `lambda`: ```r diff --git a/vignettes/mixed_response.Rmd b/vignettes/mixed_response.Rmd index 07f2e0ac..167deb28 100644 --- a/vignettes/mixed_response.Rmd +++ b/vignettes/mixed_response.Rmd @@ -71,7 +71,7 @@ We define the loading matrix as follows, where the value `1` indicates that the #> [2,] NA ``` -We set `load.var = "itemgroup"` because all rows of the data with the same value of `itemgroup` will receive the same factor loading. In `formula`, we state `(0 + level | id)` to specify that each subject, identified by `id`, has a given level. `level` is not an element of the `mresp` data, but is instead the factor onto which the loading matrix loads. We identify this with the argument `factor = list("level")`. We could have chosen any other name for `"level"`, except for names that are already columns in `mresp`. +We set `load.var = "itemgroup"` because all rows of the data with the same value of `itemgroup` will receive the same factor loading. In `formula`, we state `(0 + level | id)` to specify that each subject, identified by `id`, has a given level. `level` is not an element of the `mresp` data, but is instead the factor onto which the loading matrix loads. We identify this with the argument `factor = "level"`. We could have chosen any other name for `"level"`, except for names that are already columns in `mresp`. We also need to define the response families, with the vector diff --git a/vignettes/semiparametric.Rmd b/vignettes/semiparametric.Rmd index d0de4536..a204a471 100644 --- a/vignettes/semiparametric.Rmd +++ b/vignettes/semiparametric.Rmd @@ -771,7 +771,8 @@ We can look at the model summary: ```r summary(mod_byvar_mixed) #> GALAMM fit by maximum marginal likelihood. -#> Formula: y ~ domain + sl(x, by = domain, factor = c("ability1", "ability2")) + (0 + domain1:ability1 + domain2:ability2 | id) +#> Formula: y ~ domain + sl(x, by = domain, factor = c("ability1", "ability2")) + +#> (0 + domain1:ability1 + domain2:ability2 | id) #> Data: dat #> Control: galamm_control(optim_control = list(factr = 1e+09, trace = 3, REPORT = 30, maxit = 1000)) #> From c62b7a70ce13b6bedbb9490937ab86852d2218a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98ystein=20S=C3=B8rensen?= Date: Thu, 19 Oct 2023 22:12:59 +0200 Subject: [PATCH 8/9] updated codemeta --- codemeta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codemeta.json b/codemeta.json index 37ffdef7..613c1b2d 100644 --- a/codemeta.json +++ b/codemeta.json @@ -254,7 +254,7 @@ }, "SystemRequirements": "C++17" }, - "fileSize": "26486.883KB", + "fileSize": "26559.084KB", "citation": [ { "@type": "ScholarlyArticle", From 2b585ae42091c818c7d590305e8afb9707833247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98ystein=20S=C3=B8rensen?= Date: Thu, 19 Oct 2023 22:16:48 +0200 Subject: [PATCH 9/9] importing formula --- NAMESPACE | 1 + R/galamm-package.R | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index e96d64f8..0e1998b3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -47,6 +47,7 @@ importFrom(stats,coef) importFrom(stats,deviance) importFrom(stats,family) importFrom(stats,fitted) +importFrom(stats,formula) importFrom(stats,gaussian) importFrom(stats,logLik) importFrom(stats,nobs) diff --git a/R/galamm-package.R b/R/galamm-package.R index 28875cf1..b15605e5 100644 --- a/R/galamm-package.R +++ b/R/galamm-package.R @@ -14,8 +14,8 @@ NULL ## usethis namespace: start #' @importFrom Rcpp sourceCpp -#' @importFrom stats anova coef deviance family fitted gaussian logLik nobs -#' predict residuals sigma vcov +#' @importFrom stats anova coef deviance family fitted formula gaussian logLik +#' nobs predict residuals sigma vcov #' @importFrom Rdpack reprompt ## usethis namespace: end NULL