Skip to contents

estimate_dm() is the main function to fit a drift diffusion model (DDM) in dRiftDM. Several ways of fitting a model are supported: fitting a single participant, fitting multiple participants separately or aggregated, and fitting a (hierarchical) Bayesian model. The particular way is controlled via the approach argument.

Usage

estimate_dm(
  drift_dm_obj,
  obs_data = NULL,
  approach = NULL,
  optimizer = NULL,
  control = list(),
  n_cores = 1,
  parallelization_strategy = NULL,
  lower = NULL,
  upper = NULL,
  start_vals = NULL,
  means = NULL,
  sds = NULL,
  shapes = NULL,
  rates = NULL,
  n_chains = 40,
  burn_in = 500,
  samples = 1000,
  prob_migration = 0.1,
  prob_re_eval = 1,
  messaging = TRUE,
  seed = NULL,
  ...
)

# S3 method for class 'fits_agg_dm'
print(x, ...)

# S3 method for class 'fits_ids_dm'
print(x, ...)

# S3 method for class 'mcmc_dm'
print(x, ..., round_digits = drift_dm_default_rounding())

Arguments

drift_dm_obj

a drift_dm object containing the model to be fitted.

obs_data

an optional data.frame (see also obs_data). If no ID column is present, a single-individual setup is assumed. If an ID column is present, the model is fitted separately for each individual.

approach

an optional character string, specifying the approach to fitting the model. Options are "sep_c", "agg_c", "sep_b", "hier_b" (see the Details).

optimizer

a character string. For classical optimization, one of "nmkb", "Nelder-Mead", "BFGS", "L-BFGS-B", "DEoptim". For the Bayesian framework, only "DE-MCMC" is currently supported. If NULL and if a classical optimization approach is used, defaults to "DEoptim" or "Nelder-Mead", depending on whether lower/upper are provided or not. If NULL and if a Bayesian framework is used, defaults to "DE-MCMC. Note that "BFGS" and "L-BFGS-B" are often unstable.

control

a list of control parameters passed to the optimizer (see dfoptim::nmkb, DEoptim::DEoptim, stats::optim). Per default, we set the trace control argument for DEoptim::DEoptim to FALSE. Also, we set the parscale control argument for "Nelder-Mead" via stats::optim to pmax(x0, 1e-6).

n_cores

an integer > 0, indicating the number of CPU cores/threads to use (at the moment, this doesn't have an effect when fitting a single individual within the Bayesian framework).

parallelization_strategy

an integer, controlling how parallelization is performed when fitting multiple individuals with the classical approach. If 1, parallelization is across individuals. If 2, parallelization is within individuals (currently only supported for "DEoptim"). Defaults to 1.

lower, upper

numeric vectors or lists, specifying the lower and upper bounds on each parameter to be optimized (see Details).

start_vals

optional starting values for classical single-subject fits and when using an optimizer that requires a starting value. Can be a numeric vector of model parameters when fitting a single individual, or a data.frame with columns for each model parameter. In the latter case, enables multi-start (one row per start). For 'approach = "separately"', a data.frame with an ID column is required.

means, sds, shapes, rates

optional numeric vectors for prior specification (when using the Bayesian framework, see Details).

n_chains

an integer, providing the number of MCMC chains (Bayesian framework).

burn_in

an integer, number of burn-in iterations (Bayesian framework).

samples

an integer, number of post-burn-in samples per chain ( Bayesian framework).

prob_migration

a numeric in [0,1], controlling the migration probability of the DE-MCMC algorithm (Bayesian framework).

prob_re_eval

a numeric in [0,1], probability to re-evaluate the model at current group-level parameters during sampling (Bayesian framework; only relevant for the hierarchical case).

messaging

a logical, if TRUE progress/info messages are printed

seed

an optional integer to set the RNG seed for reproducibility.

...

additional arguments forwarded to lower-level routines. Options are: progress/verbose (integers, for controlling progress bars and verbosity of estimation infos), round_digits (for controlling the number of digits for rounding when printing individual model evaluations; if verbose = 2), return_runs (when fitting a single individual and starting the estimation routine with multiple starting points; if TRUE, then a list of all routines is returned), probs/n_bins (the quantile levels and the number of CAF bins when fitting aggregated data using the RMSE cost function).

x

an object of type fits_agg_dm, fits_ids_dm, or mcmc_dm

round_digits

integer, specifying the number of decimal places for rounding in the printed summary. Default is 3.

Value

  • If fitting a single individual: either a drift_dm object with fitted parameters and additional fit information (for the classical optimization framework) or an object of type mcmc_dm (for the Bayesian framework)

  • If fitting multiple individuals separately: a fits_ids_dm object or a list of mcmc_dm objects, containing all the individual model fits.

  • If fitting aggregated data: a fits_agg_dm object containing the model itself and the raw data.

  • If fitting multiple individuals hierarchically: an object of type mcmc_dm.

Details

Fitting Approaches

The function supports different "approaches" to fitting data.

  • "sep_c": This means that data is always considered separately for each participant (if there are multiple participants) and that a classical approach to parameter optimization is used. This means that a standard cost_function is minimized (e.g., the negative log-likelihood). If users provide only a single participant or a data set without an ID column, then the model is fitted just once to that data set.

  • "agg_c": This fits the model to aggregated data. For each individual in a data set, summary statistics (e.g., quantiles, accuracies) are calculated, and the model is fitted once to the average of these summary statistics.

  • "sep_b": Similar to sep_b", although a Bayesian approach is used to sample from the posterior distribution.

  • "hier_b": A hierarchical approach to parameter estimation. In this case all participants are considered simultaneously and samples are drawn both at the individual-level and group-level.

The optimizers "nmkb", "L-BFGS-B", and "DEoptim" (for classical parameter optimization) require the specification of the lower/upper arguments.

Fitting to Aggregated Data

For aggregated fits, aggregated statistics are set to the model and the cost function is switched to "rmse". If incompatible settings are requested, the function switches to a compatible configuration and informs the user with messages (these messages can be suppressed via the messaging argument).

Specifying lower/upper for Classical optimization

the function estimate_model_dm() provides a flexible way of specifying the optimization space; this is identical to specifying the parameter simulation space in simulate_data.drift_dm().

Users have three options to specify the search space (see also the examples below):

  • Plain numeric vectors (not very much recommended). In this case, lower/upper must be sorted in accordance with the parameters in the underlying flex_prms object of drift_dm_obj that vary for at least one condition (call print(drift_dm_obj) and have a look at the columns of the Parameter Settings output; for each column that has a number > 0, specify an entry in lower/upper).

  • Named numeric vectors. In this case lower/upper have to provide labels in accordance with the parameters that are considered "free" at least once across conditions (call coef(drift_dm_obj) and provide one named entry for each parameter; dRiftDM will try to recycle parameter values across conditions).

  • The most precise way is when lower/upper are lists. In this case, the list requires an entry called "default_values" which specifies the named or plain numeric vectors as above. If the list only contains this entry, then the behavior is as if lower/upper were already numeric vectors. However, the lower/upper lists can also provide entries labeled as specific conditions, which contain named (!) numeric vectors with parameter labels. This will modify the value for the upper/lower parameter space with respect to the specified parameters in the respective condition.

Specifying Priors for Bayesian Estimation

(Default) Prior settings in the non-hierarchical case:

Let \(\theta^{(j)}\) indicate parameter \(j\) of a model (e.g., the drift rate). The prior on \(\theta^{(j)}\) is a truncated normal distribution: $$ \theta^{(j)} \sim NT(\mu^{(j)}, \sigma^{(j)}, l^{(j)}, u^{(j)}) $$ With \(\mu^{(j)}\) and \(\sigma^{(j)}\) representing the mean and standard deviation of parameter \(j\). \(l^{(j)}\) and \(u^{(j)}\) represent the lower and upper boundary. \(\mu^{(j)}\) is taken from the mean argument or the currently set model parameters (i.e., from coef(drift_dm_obj)) when calling the function. \(\sigma^{(j)}\) is, per default, equal to \(\mu^{(j)}\). This can be changed by passing the sd argument. The lower and upper boundaries of the truncated normal are -Inf and Inf per default. This can be altered by passing the arguments lower and upper.

(Default) Prior settings in the hierarchical case:

Let \(\theta_i^{(j)}\) indicate parameter \(j\) for participant \(i\) (e.g., the drift rate estimated for individual \(i\)). The prior on \(\theta_i^{(j)}\) is a truncated normal distribution: $$ \theta_i^{(j)} \sim NT(\mu^{(j)}, \sigma^{(j)}, l^{(j)}, u^{(j)}) $$ With \(\mu^{(j)}\) and \(\sigma^{(j)}\) representing the mean and standard deviation of parameter \(j\) at the group level. \(l^{(j)}\) and \(u^{(j)}\) represent the lower and upper boundary. The lower and upper boundaries of the truncated normal are -Inf and Inf per default. This can be altered by passing the arguments lower and upper.

For a group-level mean parameter, \(\mu^{(j)}\), the prior is also a truncated normal distributions: $$ \mu^{(j)} \sim NT(M^{(j)}, SD^{(j)}, l^{(j)}, u^{(j)}) $$ With \(M^{(j)}\) specified by the mean argument or the currently set model parameters. \(SD^{(j)}\) is, per default, equal to \(M^{(j)}\). This can be changed by passing the sd argument.

For a group-level standard deviation parameter, \(\sigma^{(j)}\), the prior is a gamma distribution: $$ \sigma^{(j)} \sim \Gamma(shape^{(j)},rate^{(j)}) $$ With \(shape^{(j)}\) and \(rate^{(j)}\) being 1 by default. This can be changed by passing the arguments shape and rate.

Specifying Prior Settings/Arguments

Argument specification for mean, sd, lower, upper, shape and rate is conceptually identical to specifying lower/upper for the classical optimization approach (see the subsection above and the examples below).

Note

estimate_dm dispatches to underlying estimation routines that are not exported:

Examples

##########
# Note: The following examples were trimmed for speed to ensure they run
# within seconds. They do not always provide realistic settings.
##########

####
# Setup

# get a model for the examples (DMC with just two free parameters)
model <- dmc_dm(
  t_max = 1.5, dx = .01, dt = .01,
  instr = '
   b <!>
   non_dec <!>
   sd_non_dec <!>
   tau <!>
   alpha <!>
   '
)

# get some data (the first two participants in the data set of Ulrich et al.)
data <- ulrich_flanker_data[ulrich_flanker_data$ID %in% 1:2,]



####
# Fit a single individual (using unbounded Nelder-Mead)
fit <- estimate_dm(
  drift_dm_obj = model,
  obs_data = data[data$ID == 1,],
  optimizer = "Nelder-Mead"
)
#> Using the data supplied via the 'obs_data' argument.
#> Using optimizer 'Nelder-Mead'.
#> Fitting a single data set/participant (cost function: 'neg_log_like'). The returned object will be the model itself.
#> Starting optimizer 'Nelder-Mead' with the following starting values:
#> muc=4, A=0.1
#> Optimization routine exited after 89 function evaluations
#> Final Parameters
#> muc = 4.627
#> A = 0.084
#> ==> gave a neg_log_like of -369.167
print(fit)
#> Class(es) dmc_dm, drift_dm
#> Optimizer: Nelder-Mead
#> Convergence: TRUE
#> 
#> Parameter Values:
#>          muc   b non_dec sd_non_dec  tau a      A alpha
#> comp   4.627 0.6     0.3       0.02 0.04 2  0.084     4
#> incomp 4.627 0.6     0.3       0.02 0.04 2 -0.084     4
#> 
#> Parameter Settings:
#>        muc b non_dec sd_non_dec tau a A alpha
#> comp   1   0 0       0          0   0 2 0    
#> incomp 1   0 0       0          0   0 d 0    
#> 
#> Special Dependencies:
#> A ~ incomp == -(A ~ comp)
#> 
#> Custom Parameters:
#>        peak_l
#> comp     0.04
#> incomp   0.04
#> 
#> Deriving PDFS:
#>   solver: kfe
#>   values: sigma=1, t_max=1.5, dt=0.01, dx=0.01, nt=150, nx=200
#> 
#> Cost Function: neg_log_like
#> 
#> Observed Data: 168 trials comp; 168 trials incomp
#> 


####
# Fit a single individual (using bounded Nelder-Mead and custom starting
# values)
l_u <- get_lower_upper(model)
fit <- estimate_dm(
  drift_dm_obj = model,
  obs_data = data[data$ID == 1,],
  optimizer = "nmkb",
  lower = l_u$lower, upper = l_u$upper,
  start_vals = c(muc = 4, A = 0.06)
)
#> Using the data supplied via the 'obs_data' argument.
#> Using optimizer 'nmkb'.
#> Fitting a single data set/participant (cost function: 'neg_log_like'). The returned object will be the model itself.
#> Starting optimizer 'nmkb' with the following starting values:
#> muc=4, A=0.06
#> Optimization routine exited after 116 function evaluations
#> Final Parameters
#> muc = 4.627
#> A = 0.084
#> ==> gave a neg_log_like of -369.167
print(fit)
#> Class(es) dmc_dm, drift_dm
#> Optimizer: nmkb
#> Convergence: TRUE
#> 
#> Parameter Values:
#>          muc   b non_dec sd_non_dec  tau a      A alpha
#> comp   4.627 0.6     0.3       0.02 0.04 2  0.084     4
#> incomp 4.627 0.6     0.3       0.02 0.04 2 -0.084     4
#> 
#> Parameter Settings:
#>        muc b non_dec sd_non_dec tau a A alpha
#> comp   1   0 0       0          0   0 2 0    
#> incomp 1   0 0       0          0   0 d 0    
#> 
#> Special Dependencies:
#> A ~ incomp == -(A ~ comp)
#> 
#> Custom Parameters:
#>        peak_l
#> comp     0.04
#> incomp   0.04
#> 
#> Deriving PDFS:
#>   solver: kfe
#>   values: sigma=1, t_max=1.5, dt=0.01, dx=0.01, nt=150, nx=200
#> 
#> Cost Function: neg_log_like
#> 
#> Observed Data: 168 trials comp; 168 trials incomp
#> 


####
# Fit a single individual (using DEoptim)
# Note: DEoptim always runs for 200 iterations per default; which is not
# necessary here -> in this simple example, we stop it after 10 iterations
# without improvement
l_u <- get_lower_upper(model)
set.seed(2)
fit <- estimate_dm(
  drift_dm_obj = model,
  obs_data = data[data$ID == 1,],
  optimizer = "DEoptim",
  lower = l_u$lower, upper = l_u$upper,
  control = list(steptol = 10)
)
#> Using the data supplied via the 'obs_data' argument.
#> Using optimizer 'DEoptim'.
#> Fitting a single data set/participant (cost function: 'neg_log_like'). The returned object will be the model itself.
#> Starting optimizer 'DEoptim' 
#> Optimization routine exited after 29 iterations.
#> Final Parameters
#> muc = 4.627
#> A = 0.084
#> ==> gave a neg_log_like of -369.167
print(fit)
#> Class(es) dmc_dm, drift_dm
#> Optimizer: DEoptim
#> Convergence: TRUE
#> 
#> Parameter Values:
#>          muc   b non_dec sd_non_dec  tau a      A alpha
#> comp   4.627 0.6     0.3       0.02 0.04 2  0.084     4
#> incomp 4.627 0.6     0.3       0.02 0.04 2 -0.084     4
#> 
#> Parameter Settings:
#>        muc b non_dec sd_non_dec tau a A alpha
#> comp   1   0 0       0          0   0 2 0    
#> incomp 1   0 0       0          0   0 d 0    
#> 
#> Special Dependencies:
#> A ~ incomp == -(A ~ comp)
#> 
#> Custom Parameters:
#>        peak_l
#> comp     0.04
#> incomp   0.04
#> 
#> Deriving PDFS:
#>   solver: kfe
#>   values: sigma=1, t_max=1.5, dt=0.01, dx=0.01, nt=150, nx=200
#> 
#> Cost Function: neg_log_like
#> 
#> Observed Data: 168 trials comp; 168 trials incomp
#> 


####
# Fit multiple individuals (separately; using bounded Nelder-Mead)
l_u <- get_lower_upper(model)
fit <- estimate_dm(
  drift_dm_obj = model,
  obs_data = data, # contains the data for two individuals
  optimizer = "nmkb",
  lower = l_u$lower, upper = l_u$upper,
)
#> Using the data supplied via the 'obs_data' argument.
#> Using optimizer 'nmkb'.
#> Fitting the model separately to multiple participants (cost function:'neg_log_like'). The result will be a fit object of type 'fits_ids_dm'.
print(fit)
#> Fit approach: separately - classical
#> Fitted model type: dmc_dm, drift_dm
#> Optimizer: nmkb 
#> Convergence: TRUE 
#> N Individuals: 2 
#> Average Trial Numbers:
#>  168 trials comp; 168 trials incomp
#> Cost Function: neg_log_like
coef(fit)
#> Object Type: coefs_dm
#> 
#>   ID   muc     A
#> 1  1 4.627 0.084
#> 2  2 6.934 0.114
#> 
#> (access the data.frame's columns/rows as usual)


###
# Fit to aggregated data (using unbounded Nelder-Mead)
fit <- estimate_dm(
  drift_dm_obj = model,
  obs_data = data, # contains data for two individuals
  optimizer = "Nelder-Mead",
  approach = "agg_c"
)
#> Using the data supplied via the 'obs_data' argument.
#> Using optimizer 'Nelder-Mead'.
#> Changing the 'cost_function' to 'rmse'.
#> Aggregated data has been set to the model.
#> Fitting the model to aggregated data across participants. The returned object will of type 'fits_agg_dm'.
#> Starting optimizer 'Nelder-Mead' with the following starting values:
#> muc=4, A=0.1
#> Optimization routine exited after 129 function evaluations
#> Final Parameters
#> muc = 5.77
#> A = 0.131
#> ==> gave a rmse of 0.024
print(fit)
#> Fit approach: aggregated - classical
#> Fitted model type: dmc_dm, drift_dm
#> Optimizer: Nelder-Mead
#> Convergence: TRUE
#> N Individuals: 2 
#> Average Trial Numbers:
#>  168 trials comp; 168 trials incomp
coef(fit)
#>       muc         A 
#> 5.7702593 0.1305267 


###
# EXPERIMENTAL
# Fit a single individual (using DE-MCMC; Bayesian; custom priors)
fit <- estimate_dm(
  drift_dm_obj = model,
  obs_data = data[data$ID == 1,],
  approach = "sep_b",
  burn_in = 2, # this is usually way higher
  samples = 2, # this too
  n_chains = 10, # this too
  mean = c(muc = 3, A = 0.9),
  sd = c(muc = 2, A = 0.8),
)
#> Using the data supplied via the 'obs_data' argument.
#> Using optimizer 'DE-MCMC'.
#> Fitting a single data set/participant using the Bayesian framework. The result will be a fit object of type 'mcmc_dm'.
#> Finding starting values...
#> Starting the sampling procedure
print(fit)
#> Sampler: DE-MCMC 
#> Hierarchical: FALSE 
#> No. Parameters: 2 
#> No. Chains: 10 
#> Iterations Per Chain: 2 
coef(fit)
#>       muc         A 
#> 3.5762989 0.1580784 


###
# EXPERIMENTAL
# Fit multiple individuals (using DE-MCMC; hierarchical Bayesian)
fit <- estimate_dm(
  drift_dm_obj = model,
  approach = "hier_b",
  obs_data = data, # contains data for two individuals
  burn_in = 2, # this is usually way higher
  samples = 2, # this too
  n_chains = 10 # this too
)
#> Using the data supplied via the 'obs_data' argument.
#> Using optimizer 'DE-MCMC'.
#> Fitting the model hierarchically using a Bayesian framework. The result will be a fit object of type 'mcmc_dm'
#> Finding starting values...
#> Starting the sampling procedure
print(fit)
#> Sampler: DE-MCMC 
#> Hierarchical: TRUE 
#> No. Group-Level Parameters: 4 
#> No. Chains: 10 
#> Iterations Per Chain: 2 
coef(fit)
#>     M-muc     S-muc       M-A       S-A 
#> 2.2452057 2.7738100 0.1012086 0.5637372