--- title: "Working with Mixtures" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Working with Mixtures} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- Mixture components describe different sets of active accumulators. Two choices determine how a mixture is used: - The model controls how mixture weights are defined. Use `set_mixture(mode = "fixed")` when the weights are known in advance, and `set_mixture(mode = "sample")` when mixture weights should be estimated. - The data control whether the component is observed on a trial. If a `component` column is present, likelihood evaluation conditions on that component. If `component` is omitted or set to `NA`, the likelihood marginalizes over components. `response_probabilities()` always averages over components using their probabilities; it takes no trial data. ```{r} library(AccumulatR) ``` ## A common architecture Both sections use the same fast-versus-slow architecture. Response `R1` comes from a target route and response `R2` comes from a competitor. The components differ only in whether the target route is fast or slow on a given trial. ## Fixed mixture weights with observed components In this first example, the mixture weights are fixed and the component label is observed on each trial. ```{r} model_fixed <- race_spec() |> add_accumulator("target_fast", "lognormal") |> add_accumulator("target_slow", "lognormal") |> add_accumulator("competitor", "lognormal") |> add_pool("TARGET", c("target_fast", "target_slow")) |> add_outcome("R1", "TARGET") |> add_outcome("R2", "competitor") |> add_component("fast", members = c("target_fast", "competitor")) |> add_component("slow", members = c("target_slow", "competitor")) |> set_parameters(separate = list(m = TRUE, s = TRUE)) |> set_mixture(mode = "fixed", weights = c(fast = 0.4, slow = 0.6)) |> finalize_model() true_params_fixed <- c( target_fast.m = log(0.25), target_fast.s = 0.15, target_slow.m = log(0.45), target_slow.s = 0.20, competitor.m = log(0.35), competitor.s = 0.18 ) ``` We now create a trial table that says which component each trial belongs to. ```{r} set.seed(123456) n_trials_fixed <- 1200 trial_component <- sample( c("fast", "slow"), n_trials_fixed, replace = TRUE, prob = c(0.4, 0.6) ) trial_conditions <- data.frame( trials = rep(seq_len(n_trials_fixed), each = 3L), racer = rep(c("target_fast", "target_slow", "competitor"), n_trials_fixed), component = rep(trial_component, each = 3L), stringsAsFactors = FALSE ) params_df_fixed <- build_param_matrix( model_fixed, true_params_fixed, n_trials = n_trials_fixed ) sim_fixed <- simulate( model_fixed, params_df_fixed, trial_df = trial_conditions ) data_fixed <- sim_fixed[, c("trials", "R", "rt", "component")] table(data_fixed$component, data_fixed$R) ``` Fixed mixtures include the selected component label in simulation output by default. For real data, retain a `component` column when trial type is observed. Omit it to evaluate the data as a latent mixture. ```{r} prepared_fixed <- prepare_data(model_fixed, data_fixed) ctx_fixed <- make_context(model_fixed) params_df_fixed_true <- build_param_matrix( model_fixed, true_params_fixed, n_trials = n_trials_fixed ) ll_fixed <- as.numeric(log_likelihood(ctx_fixed, prepared_fixed, params_df_fixed_true)) ll_fixed ``` ## Sampled mixture weights with latent components Now we use the same architecture as a latent mixture. The component is not observed per trial. Instead, the model samples a hidden component for each trial, with `p.fast` controlling the proportion of fast trials. ```{r} model_sampled <- race_spec() |> add_accumulator("target_fast", "lognormal") |> add_accumulator("target_slow", "lognormal") |> add_accumulator("competitor", "lognormal") |> add_pool("TARGET", c("target_fast", "target_slow")) |> add_outcome("R1", "TARGET") |> add_outcome("R2", "competitor") |> add_component("fast", members = c("target_fast", "competitor")) |> add_component("slow", members = c("target_slow", "competitor")) |> set_parameters(separate = list(m = TRUE, s = TRUE)) |> set_mixture(mode = "sample", reference = "slow") |> finalize_model() true_params_sampled <- c( target_fast.m = log(0.25), target_fast.s = 0.15, target_slow.m = log(0.45), target_slow.s = 0.20, competitor.m = log(0.35), competitor.s = 0.18, p.fast = 0.35 ) ``` By default, sampled mixtures do not expose the component label in the output, because that label is latent. ```{r} set.seed(123456) n_trials_sampled <- 1500 params_df_sampled <- build_param_matrix( model_sampled, true_params_sampled, n_trials = n_trials_sampled ) sim_sampled <- simulate(model_sampled, params_df_sampled) names(sim_sampled) ``` If you want to inspect the hidden generating component, you can ask for it explicitly. ```{r} sim_sampled_with_component <- simulate( model_sampled, params_df_sampled, keep_component = TRUE ) table(sim_sampled_with_component$component) ``` For likelihood evaluation, we keep the sampled-mixture data in the same form as real latent-mixture data: only the observed response `R` and response time `rt`. Because `component` is omitted here, the likelihood marginalizes over the possible components. If you do supply a `component` column, that component is treated as observed on those trials. ```{r} data_sampled <- sim_sampled[, c("trials", "R", "rt")] prepared_sampled <- prepare_data(model_sampled, data_sampled) ctx_sampled <- make_context(model_sampled) params_df_sampled_true <- build_param_matrix( model_sampled, true_params_sampled, n_trials = n_trials_sampled ) ll_sampled_true <- as.numeric(log_likelihood(ctx_sampled, prepared_sampled, params_df_sampled_true)) ll_sampled_true ``` ## Estimate a sampled-mixture weight Latent weights can also be estimated. To keep the example simple, we estimate only the latent mixture weight `p.fast`, while holding the accumulator timing parameters fixed. ```{r eval=FALSE} neg_loglik <- function(theta) { est <- true_params_sampled est["p.fast"] <- plogis(theta[["logit_p_fast"]]) params_df <- build_param_matrix( model_sampled, est, n_trials = n_trials_sampled ) ll <- log_likelihood(ctx_sampled, prepared_sampled, params_df) -as.numeric(ll) } start <- c(logit_p_fast = qlogis(0.60)) fit <- optim(start, neg_loglik, method = "BFGS") fit_params <- c(p.fast = plogis(fit$par[["logit_p_fast"]])) target <- c(p.fast = true_params_sampled[["p.fast"]]) data.frame(true = target, recovered = fit_params, miss = abs(target - fit_params)) ``` Use `mode = "fixed"` when mixture weights are known and `mode = "sample"` when mixture weights should be estimated. Independently of that choice, include a `component` column only when the component is observed on a trial. Otherwise omit it, or set it to `NA`, so the likelihood marginalizes over components.