Getting Started¶
This tutorial demonstrates how to quickly get started with the HSSM package. We will cover the following steps:
- How to create a model
- How to create some simple simulated data
- How to specify parameters
- How to specify parameters with regressions
- How to use ArviZ to summarize and visualize the traces.
Colab Instructions¶
If you would like to run this tutorial on Google colab, please click this link.
Once you are in the colab, follow the installation instructions below and then restart your runtime.
Just uncomment the code in the next code cell and run it!
NOTE:
You may want to switch your runtime to have a GPU or TPU. To do so, go to Runtime > Change runtime type and select the desired hardware accelerator.
Note that if you switch your runtime you have to follow the installation instructions again.
# !pip install hssm
Import Modules¶
import numpy as np
import hssm
%matplotlib inline
%config InlineBackend.figure_format='retina'
Setting the global float type¶
Note: Using the analytical DDM (Drift Diffusion Model) likelihood in PyMC without setting the float type in PyTensor
may result in warning messages during sampling, which is a known bug in PyMC v5.6.0 and earlier versions. To avoid these warnings, we provide a convenience function:
hssm.set_floatX("float32")
Setting PyTensor floatX type to float32. Setting "jax_enable_x64" to False. If this is not intended, please set `jax` to False.
Simulating a dataset¶
The hssm.simulate_data()
function generates data for most SSM types. Here we simulate some data from a Drift Diffusion Model (DDM) with known true parameter values.
v_true, a_true, z_true, t_true = [0.5, 1.5, 0.5, 0.5]
dataset = hssm.simulate_data(
model="ddm",
theta=[v_true, a_true, z_true, t_true],
size=1000,
)
dataset
rt | response | |
---|---|---|
0 | 4.782969 | -1.0 |
1 | 3.615409 | 1.0 |
2 | 0.733378 | 1.0 |
3 | 7.904665 | 1.0 |
4 | 2.653816 | -1.0 |
... | ... | ... |
995 | 1.566862 | 1.0 |
996 | 2.054777 | 1.0 |
997 | 2.213145 | -1.0 |
998 | 1.103277 | 1.0 |
999 | 4.738029 | 1.0 |
1000 rows × 2 columns
Model specification¶
1. DDM using defaults¶
We begin with a simple example. The only information required to create a model in HSSM
is a dataset.
A dataset in HSSM
is typically a pandas
DataFrame
with at least rt
and response
columns, which indicates response time and choices respectively. Right now, response
only accepts values of 1
and -1
.
If none of the optional parameters is provided, HSSM will assume that we are modeling a classical DDM model with v
, a
, z
, and t
as its parameters. HSSM also provides a default analytical likelihood function and some uninformative priors. These can all be overriden by user inputs.
Note
From HSSM v0.1.2 on, lapse distributions will be enabled by default, with `p_outlier` fixed to 0.05. You can set `p_outlier` to 0 or `None` to disable lapse distributions.
simple_ddm_model = hssm.HSSM(data=dataset)
simple_ddm_model
Hierarchical Sequential Sampling Model Model: ddm Response variable: rt,response Likelihood: analytical Observations: 1000 Parameters: v: Prior: Normal(mu: 0.0, sigma: 2.0) Explicit bounds: (-inf, inf) a: Prior: HalfNormal(sigma: 2.0) Explicit bounds: (0.0, inf) z: Prior: Uniform(lower: 0.0, upper: 1.0) Explicit bounds: (0.0, 1.0) t: Prior: HalfNormal(sigma: 2.0, initval: 0.10000000149011612) Explicit bounds: (0.0, inf) Lapse probability: 0.05 Lapse distribution: Uniform(lower: 0.0, upper: 10.0)
Visualizing the model¶
If you have graphviz
installed on your machine, you will also be able to visualize the model. Please uncomment the code and run it if you have graphviz
installed.
# Uncomment if you have graphviz installed
# simple_ddm_model.graph()
Performing MCMC sampling¶
Similar to PyMC
, HSSM provides the sample()
method once the model is created to perform MCMC sampling. By default, it uses PyMC
's NUTS
sampler. We can use other samplers, which we will cover soon.
simple_ddm_model.sample()
Auto-assigning NUTS sampler... Initializing NUTS using jitter+adapt_diag... Multiprocess sampling (4 chains in 4 jobs) NUTS: [z, t, a, v]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 13 seconds.
-
<xarray.Dataset> Dimensions: (chain: 4, draw: 1000) Coordinates: * chain (chain) int64 0 1 2 3 * draw (draw) int64 0 1 2 3 4 5 6 7 8 ... 992 993 994 995 996 997 998 999 Data variables: v (chain, draw) float32 0.4881 0.5779 0.61 ... 0.6006 0.5699 0.6188 z (chain, draw) float32 0.501 0.4659 0.4684 ... 0.4648 0.4799 0.4705 t (chain, draw) float32 0.538 0.5098 0.5079 ... 0.5072 0.5275 0.5301 a (chain, draw) float32 1.489 1.487 1.508 1.484 ... 1.461 1.468 1.482 Attributes: created_at: 2023-10-20T16:01:05.808666 arviz_version: 0.14.0 inference_library: pymc inference_library_version: 5.9.0 sampling_time: 13.109592199325562 tuning_steps: 1000 modeling_interface: bambi modeling_interface_version: 0.12.0
-
<xarray.Dataset> Dimensions: (chain: 4, draw: 1000) Coordinates: * chain (chain) int64 0 1 2 3 * draw (draw) int64 0 1 2 3 4 5 ... 994 995 996 997 998 999 Data variables: (12/17) max_energy_error (chain, draw) float64 -0.3492 -0.3537 ... 0.4069 reached_max_treedepth (chain, draw) bool False False False ... False False tree_depth (chain, draw) int64 3 3 2 3 2 3 3 3 ... 3 3 3 3 3 3 2 step_size_bar (chain, draw) float64 0.6181 0.6181 ... 0.652 0.652 acceptance_rate (chain, draw) float64 0.9678 0.9922 ... 0.4169 0.7194 step_size (chain, draw) float64 0.4296 0.4296 ... 0.6385 0.6385 ... ... energy_error (chain, draw) float64 0.031 -0.3537 ... 0.3076 smallest_eigval (chain, draw) float64 nan nan nan nan ... nan nan nan energy (chain, draw) float64 2.011e+03 ... 2.009e+03 lp (chain, draw) float64 -2.009e+03 ... -2.009e+03 largest_eigval (chain, draw) float64 nan nan nan nan ... nan nan nan index_in_trajectory (chain, draw) int64 4 -5 3 -6 -3 -4 ... 2 -2 4 2 2 -2 Attributes: created_at: 2023-10-20T16:01:05.815190 arviz_version: 0.14.0 inference_library: pymc inference_library_version: 5.9.0 sampling_time: 13.109592199325562 tuning_steps: 1000 modeling_interface: bambi modeling_interface_version: 0.12.0
-
<xarray.Dataset> Dimensions: (rt,response_obs: 1000, rt,response_extra_dim_0: 2) Coordinates: * rt,response_obs (rt,response_obs) int64 0 1 2 3 ... 996 997 998 999 * rt,response_extra_dim_0 (rt,response_extra_dim_0) int64 0 1 Data variables: rt,response (rt,response_obs, rt,response_extra_dim_0) float32 ... Attributes: created_at: 2023-10-20T16:01:05.817200 arviz_version: 0.14.0 inference_library: pymc inference_library_version: 5.9.0 modeling_interface: bambi modeling_interface_version: 0.12.0
Visualizing the traces with ArviZ
¶
Like that of pm.sample()
, the result of model.sample()
is also an az.InferenceData
object, which can be used with the ArviZ
package. The last sample the model has performed is stored in the model.traces
property for eazy access. Here we use the az.summary()
and az.plot_trace()
functions to generate a summary table and diagnostic plots for the samples.
simple_ddm_model.summary()
mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
---|---|---|---|---|---|---|---|---|---|
v | 0.567 | 0.036 | 0.498 | 0.632 | 0.001 | 0.0 | 2731.0 | 2657.0 | 1.0 |
z | 0.482 | 0.014 | 0.456 | 0.509 | 0.000 | 0.0 | 2470.0 | 2508.0 | 1.0 |
t | 0.509 | 0.024 | 0.465 | 0.555 | 0.000 | 0.0 | 2484.0 | 2613.0 | 1.0 |
a | 1.497 | 0.029 | 1.446 | 1.555 | 0.001 | 0.0 | 2593.0 | 2738.0 | 1.0 |
simple_ddm_model.plot_trace();
Congratulations! You have just created and sampled from your first model in HSSM! Parameter recovery seems to be pretty successful.
2. Specifying different model types¶
The default model in HSSM is the classic DDM, but HSSM supports many other model types. Below is a full list of supported models at the moment. We will add new models, and HSSM users can also contribute models. Please click here for our contribution guidelines.
- ddm
- ddm_sdv
- full_ddm
- angle
- levy
- ornstein
- weibull
- race_no_bias_angle_4
- ddm_seq2_no_bias
For more information about these models, please see here.
To model a different model type, please use the model
parameter of the HSSM class constructor.
If model
provided is one of the supported models above, HSSM also has default specifications for these models. No other specifications is necessary.
If model
provided is not one of the above, then HSSM considers that you are using a custom model. We will cover custom models in more detail in a separate tutorial.
Below is an example of specifying the angle
model type.
# Simulate data for an angle model
angle_data = hssm.simulate_data(
model="angle",
theta=[0.5, 1.5, 0.5, 0.5, 0.3], # true values
size=1000,
)
angle_model = hssm.HSSM(data=angle_data, model="angle")
angle_model
Hierarchical Sequential Sampling Model Model: angle Response variable: rt,response Likelihood: approx_differentiable Observations: 1000 Parameters: v: Prior: Uniform(lower: -3.0, upper: 3.0) Explicit bounds: (-3.0, 3.0) a: Prior: Uniform(lower: 0.30000001192092896, upper: 3.0) Explicit bounds: (0.3, 3.0) z: Prior: Uniform(lower: 0.10000000149011612, upper: 0.8999999761581421) Explicit bounds: (0.1, 0.9) t: Prior: Uniform(lower: 0.0010000000474974513, upper: 2.0) Explicit bounds: (0.001, 2.0) theta: Prior: Uniform(lower: -0.10000000149011612, upper: 1.2999999523162842) Explicit bounds: (-0.1, 1.3) Lapse probability: 0.05 Lapse distribution: Uniform(lower: 0.0, upper: 10.0)
angle
models, by default, use an approximate differentiable likelihood function that relies on JAX for likelihood computation. At the moment, due to the different ways JAX
and Jupyter
handle parallelism, parallel sampling is not available for this type of likelihood computations. There are ways to get around this, but for now, let's just perform sequential sampling with just one core.
Note: the sample()
method internally calls bambi
's fit()
fit method, which internally calls pymc
's sample()
function. This means that HSSM's sample()
method will accept most parameters accepted by the two other functions. You could pretty much use it just like the pm.sample()
function.
# Unless otherwise specified, we default to the `nuts_numpyro` sampler
# for "approx_differentiable" likelihoods.
angle_model.sample()
Compiling... Compilation time = 0:00:01.653871 Sampling...
/Users/yxu150/Library/Caches/pypoetry/virtualenvs/hssm-dbG0tPWc-py3.11/lib/python3.11/site-packages/jax/_src/numpy/array_methods.py:733: UserWarning: Explicitly requested dtype float64 requested in astype is not available, and will be truncated to dtype float32. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more. return getattr(self.aval, name).fun(self, *args, **kwargs)
0%| | 0/2000 [00:00<?, ?it/s]
0%| | 0/2000 [00:00<?, ?it/s]
0%| | 0/2000 [00:00<?, ?it/s]
0%| | 0/2000 [00:00<?, ?it/s]
Sampling time = 0:00:42.134979 Transforming variables... Transformation time = 0:00:00.091019
-
<xarray.Dataset> Dimensions: (chain: 4, draw: 1000) Coordinates: * chain (chain) int64 0 1 2 3 * draw (draw) int64 0 1 2 3 4 5 6 7 8 ... 992 993 994 995 996 997 998 999 Data variables: t (chain, draw) float32 0.4991 0.5069 0.519 ... 0.51 0.5225 0.5266 z (chain, draw) float32 0.5082 0.5297 0.5165 ... 0.5097 0.5257 0.5374 theta (chain, draw) float32 0.3435 0.3218 0.3255 ... 0.3051 0.3265 0.2962 a (chain, draw) float32 1.54 1.53 1.462 1.485 ... 1.479 1.479 1.427 v (chain, draw) float32 0.5175 0.4828 0.4441 ... 0.4988 0.5235 0.4166 Attributes: created_at: 2023-10-20T16:01:51.398276 arviz_version: 0.14.0 modeling_interface: bambi modeling_interface_version: 0.12.0
-
<xarray.Dataset> Dimensions: (chain: 4, draw: 1000) Coordinates: * chain (chain) int64 0 1 2 3 * draw (draw) int64 0 1 2 3 4 5 6 ... 993 994 995 996 997 998 999 Data variables: acceptance_rate (chain, draw) float32 1.0 0.7703 0.985 ... 0.8695 0.979 step_size (chain, draw) float32 0.2364 0.2364 0.2364 ... 0.183 0.183 diverging (chain, draw) bool False False False ... False False False energy (chain, draw) float32 1.41e+03 1.411e+03 ... 1.415e+03 n_steps (chain, draw) int32 7 15 31 7 15 15 7 ... 15 31 23 31 15 15 tree_depth (chain, draw) int64 3 4 5 3 4 4 3 5 3 ... 4 4 4 4 5 5 5 4 4 lp (chain, draw) float32 1.407e+03 1.41e+03 ... 1.413e+03 Attributes: created_at: 2023-10-20T16:01:51.400377 arviz_version: 0.14.0 modeling_interface: bambi modeling_interface_version: 0.12.0
-
<xarray.Dataset> Dimensions: (rt,response_obs: 1000, rt,response_extra_dim_0: 2) Coordinates: * rt,response_obs (rt,response_obs) int64 0 1 2 3 ... 996 997 998 999 * rt,response_extra_dim_0 (rt,response_extra_dim_0) int64 0 1 Data variables: rt,response (rt,response_obs, rt,response_extra_dim_0) float32 ... Attributes: created_at: 2023-10-20T16:01:51.401244 arviz_version: 0.14.0 inference_library: numpyro inference_library_version: 0.12.1 sampling_time: 42.134979 modeling_interface: bambi modeling_interface_version: 0.12.0
angle_model.summary()
mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
---|---|---|---|---|---|---|---|---|---|
t | 0.483 | 0.029 | 0.432 | 0.540 | 0.001 | 0.001 | 1222.0 | 1584.0 | 1.0 |
z | 0.500 | 0.014 | 0.474 | 0.525 | 0.000 | 0.000 | 1608.0 | 1710.0 | 1.0 |
theta | 0.332 | 0.032 | 0.272 | 0.392 | 0.001 | 0.001 | 1277.0 | 1878.0 | 1.0 |
a | 1.535 | 0.066 | 1.406 | 1.650 | 0.002 | 0.001 | 1198.0 | 1702.0 | 1.0 |
v | 0.548 | 0.046 | 0.464 | 0.634 | 0.001 | 0.001 | 1845.0 | 1830.0 | 1.0 |
angle_model.plot_trace();
3. Specifying priors: the non-regression case¶
Next, let's take a look at how to specify priors in the non-regression case. In HSSM, parameter specification can be done in two ways:
- through the
include
parameter, or - through a shortcut
3.1 Specifying priors through the include
parameter¶
The include
parameter accept a list of dictionaries or hssm.Param
objects. Both dictionaries and hssm.Param
objects are equivalent, since the content of the dictionary will be passed as parameters to hssm.Param
class during model creation, so it is more of a matter of preference. We recommend the hssm.Param
object because some IDEs will be able to provide prompts for possible options of parameters. In the non-regression case, each dictionary typically looks like this:
{
"name": "v",
"prior": {
"name": "Uniform",
"lower": -5.0,
"upper": 5.0,
},
"bounds": (-10.0, 10.0)
}
This is the equivalent of writing:
hssm.Param(
"v",
prior=dict(name="Uniform", upper=-5.0, lower=5.0),
bounds=(-10.0, 10.0)
)
The name
field corresponds with the name of the parameter being specified.
The prior
field specifies the distribution of the prior. There are two ways to achieve this:
- A dictionary with the
name
of the distribution (typically captalized) and the parameters of the distribution that you would typically set if you were specifying a distribution inPyMC
. For example, if you would like to specifypm.Normal(mu=0.0, sigma=1.0)
as the prior, then inHSSM
, this prior dictionary would be:
{
"name": "Normal", ## Note it is capitalized
"mu": 0.0,
"sigma": 1.0,
}
or, using the dict
function:
dict(name="Normal", mu=0.0, sigma=1.0)
- A
hssm.Prior
object. This is exactly how you would specify priors usingbambi
(In fact,hssm.Prior
is a subclass ofbmb.Prior
and for the most part can be used interchangeably withbmb.Prior
). Then to specify the same normal prior as above, you would write:
hssm.Prior("Normal", mu=0.0, sigma=1.0)
The bounds
field accepts a tuple of floats, indicating the lower and upper bounds for the parameter.
Fixing parameters: sometimes you might want to fix the values of a parameter. You can easily do so by specifying that value to the prior
field of the dictionary. In the following example, the paramter v
is fixed to 0.5
.
{
"name": "v",
"prior": 0.5,
}
Now let's make this concrete with an example:
# A Normal prior for `v` without explicit bounds
param_v = {
"name": "v",
"prior": {
"name": "Normal",
"mu": 0.0,
"sigma": 2.0,
},
}
# A Uniform prior for `a`. Using the `dict` function
param_a = hssm.Param(
"a",
prior=dict(
name="Uniform",
lower=0.01,
upper=5,
),
bounds=(0, np.inf),
)
# A Uniform prior for `z` over (0, 1) set using hssm.Prior.
# bounds are not set, existing default bounds will be used
param_z = {"name": "z", "prior": hssm.Prior("Uniform", lower=0.0, upper=1.0)}
# A fixed value for t
param_t = {"name": "t", "prior": 0.5}
example_ddm_model = hssm.HSSM(
data=dataset,
model="ddm",
include=[
param_v,
param_a,
param_z,
param_t,
],
)
example_ddm_model
Hierarchical Sequential Sampling Model Model: ddm Response variable: rt,response Likelihood: analytical Observations: 1000 Parameters: v: Prior: Normal(mu: 0.0, sigma: 2.0) Explicit bounds: (-inf, inf) a: Prior: Uniform(lower: 0.009999999776482582, upper: 5.0) Explicit bounds: (0, inf) z: Prior: Uniform(lower: 0.0, upper: 1.0) Explicit bounds: (0.0, 1.0) t: Prior: 0.5 Explicit bounds: (0.0, inf) Lapse probability: 0.05 Lapse distribution: Uniform(lower: 0.0, upper: 10.0)
example_ddm_model.sample()
Auto-assigning NUTS sampler... Initializing NUTS using jitter+adapt_diag... Multiprocess sampling (4 chains in 4 jobs) NUTS: [z, a, v]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 9 seconds.
-
<xarray.Dataset> Dimensions: (chain: 4, draw: 1000) Coordinates: * chain (chain) int64 0 1 2 3 * draw (draw) int64 0 1 2 3 4 5 6 7 8 ... 992 993 994 995 996 997 998 999 Data variables: v (chain, draw) float32 0.5911 0.5641 0.5253 ... 0.5174 0.5096 0.5369 z (chain, draw) float32 0.4682 0.4809 0.4967 ... 0.5004 0.5013 0.4962 a (chain, draw) float32 1.479 1.512 1.49 1.503 ... 1.494 1.497 1.533 Attributes: created_at: 2023-10-20T16:02:22.465776 arviz_version: 0.14.0 inference_library: pymc inference_library_version: 5.9.0 sampling_time: 9.108352899551392 tuning_steps: 1000 modeling_interface: bambi modeling_interface_version: 0.12.0
-
<xarray.Dataset> Dimensions: (chain: 4, draw: 1000) Coordinates: * chain (chain) int64 0 1 2 3 * draw (draw) int64 0 1 2 3 4 5 ... 994 995 996 997 998 999 Data variables: (12/17) max_energy_error (chain, draw) float64 -0.9647 -0.1088 ... -0.1673 reached_max_treedepth (chain, draw) bool False False False ... False False tree_depth (chain, draw) int64 2 2 2 2 2 3 3 3 ... 2 2 3 3 3 1 3 step_size_bar (chain, draw) float64 0.7821 0.7821 ... 0.8129 0.8129 acceptance_rate (chain, draw) float64 1.0 0.9777 ... 1.0 0.9674 step_size (chain, draw) float64 0.4749 0.4749 ... 0.7902 0.7902 ... ... energy_error (chain, draw) float64 -0.9647 -0.1088 ... -0.0922 smallest_eigval (chain, draw) float64 nan nan nan nan ... nan nan nan energy (chain, draw) float64 2.009e+03 ... 2.008e+03 lp (chain, draw) float64 -2.006e+03 ... -2.007e+03 largest_eigval (chain, draw) float64 nan nan nan nan ... nan nan nan index_in_trajectory (chain, draw) int64 -3 -2 2 -3 -1 -3 ... 5 -5 -4 -1 2 Attributes: created_at: 2023-10-20T16:02:22.473744 arviz_version: 0.14.0 inference_library: pymc inference_library_version: 5.9.0 sampling_time: 9.108352899551392 tuning_steps: 1000 modeling_interface: bambi modeling_interface_version: 0.12.0
-
<xarray.Dataset> Dimensions: (rt,response_obs: 1000, rt,response_extra_dim_0: 2) Coordinates: * rt,response_obs (rt,response_obs) int64 0 1 2 3 ... 996 997 998 999 * rt,response_extra_dim_0 (rt,response_extra_dim_0) int64 0 1 Data variables: rt,response (rt,response_obs, rt,response_extra_dim_0) float32 ... Attributes: created_at: 2023-10-20T16:02:22.475950 arviz_version: 0.14.0 inference_library: pymc inference_library_version: 5.9.0 modeling_interface: bambi modeling_interface_version: 0.12.0
example_ddm_model.summary()
mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
---|---|---|---|---|---|---|---|---|---|
v | 0.571 | 0.033 | 0.508 | 0.631 | 0.001 | 0.0 | 2716.0 | 2809.0 | 1.0 |
z | 0.479 | 0.012 | 0.457 | 0.502 | 0.000 | 0.0 | 2553.0 | 2962.0 | 1.0 |
a | 1.503 | 0.023 | 1.461 | 1.546 | 0.000 | 0.0 | 3170.0 | 2837.0 | 1.0 |
3.2 Specifying priors using the shortcut¶
HSSM also supports a syntax very similar to PyMC
: You can directly specify priors by passing the prior to the name of the parameter in hssm.HSSM
. This is convenient if the prior is simple. Below is an example almost equivalent to the above example:
# All ways to specify priors mentioned above are supported in the shortcut syntax
shortcut_ddm_model = hssm.HSSM(
data=dataset,
model="ddm",
v={"name": "Normal", "mu": 0.0, "sigma": 2.0},
a=dict(name="Uniform", lower=0.01, upper=5),
z=hssm.Prior("Uniform", lower=0.01, upper=1.0),
t=0.5,
)
shortcut_ddm_model
Hierarchical Sequential Sampling Model Model: ddm Response variable: rt,response Likelihood: analytical Observations: 1000 Parameters: v: Prior: Normal(mu: 0.0, sigma: 2.0) Explicit bounds: (-inf, inf) a: Prior: Uniform(lower: 0.009999999776482582, upper: 5.0) Explicit bounds: (0.0, inf) z: Prior: Uniform(lower: 0.009999999776482582, upper: 1.0) Explicit bounds: (0.0, 1.0) t: Prior: 0.5 Explicit bounds: (0.0, inf) Lapse probability: 0.05 Lapse distribution: Uniform(lower: 0.0, upper: 10.0)
Note that the shortcut syntax also supports specifying bounds. It will be more polished in a future update. We will skip this step for now.
4. Specifying priors: the regression case¶
Built on top of bambi
, HSSM uses an lmer
-like syntax that makes it extremely straight-forward to specify regressions.
Parameters that are targets of regressions are also specified using dictionaries in include
. Below is an example for such dictionaries.
{
"name": "v",
"formula": "v ~ 1 + x + y",
"prior": {
# All ways to specify priors in the non-regression case
# work the same way here.
"Intercept": {"name": "Uniform", "lower": -10.0, "upper": 10.0},
"x": dict(name="Normal", mu=0, sigma=1),
"y": hssm.Prior("HalfNormal", sigma=0.5),
"z": 1.0
}
"link": "identity",
"bounds": (-10.0, 10.0)
}
We see that in the regression case, name
and bounds
are specified the exact same way as in the non-regression case. The regression formula is specified in a way that's very similar to the lmer
package in R. Users that have experience with R formulas should be very familar with this syntax. In this case, the formula means that the parameter v
is regressed on variables x
and y
, which can be found in the dataframe passed to hssm.HSSM
. The 1
explicitly specifies an intercept for the regression.
In addition to the formula
, the users typically needs to specify priors for the regression coefficients. This is done in the prior
field of the dictionary. Instead of specifying priors for the parameter, the priors are now specified for the corresponding regression coefficients. If not specified, HSSM will use default priors generated in Bambi.
The users might also want to specify a link
function for generalized linear models. If left unspecified, the identity
link function will be used.
Now let's see an example of a regression:
# Generate some fake simulation data
intercept = 1.5
x = np.random.uniform(-5.0, 5.0, size=1000)
y = np.random.uniform(-5.0, 5.0, size=1000)
v = intercept + 0.8 * x + 0.3 * y
true_values = np.column_stack([v, np.repeat([[1.5, 0.5, 0.5]], axis=0, repeats=1000)])
true_values.shape
(1000, 4)
dataset_reg_v = hssm.simulate_data(
model="ddm",
theta=true_values,
size=1, # Generate one data point for each of the 1000 set of true values
)
dataset_reg_v["x"] = x
dataset_reg_v["y"] = y
dataset_reg_v
rt | response | x | y | |
---|---|---|---|---|
0 | 0.694455 | 1.0 | 4.293505 | 1.462886 |
1 | 0.685728 | 1.0 | 4.523834 | 4.172541 |
2 | 0.817345 | 1.0 | 4.907615 | -3.951474 |
3 | 1.122517 | -1.0 | -4.882512 | 1.316306 |
4 | 0.917183 | 1.0 | 0.386110 | -0.028240 |
... | ... | ... | ... | ... |
995 | 1.438341 | 1.0 | 1.074268 | -1.578958 |
996 | 1.276988 | 1.0 | -1.546509 | -0.707855 |
997 | 0.748494 | 1.0 | 2.516730 | 3.707871 |
998 | 0.672862 | 1.0 | 4.995486 | -1.544320 |
999 | 1.252353 | -1.0 | -4.960653 | -4.543217 |
1000 rows × 4 columns
model_reg_v = hssm.HSSM(
data=dataset_reg_v,
include=[
{
"name": "v",
"formula": "v ~ 1 + x + y",
"prior": {
"Intercept": {"name": "Uniform", "lower": 0.0, "upper": 0.5},
"x": dict(name="Uniform", lower=0.0, upper=1.0),
"y": hssm.Prior("Uniform", lower=0.0, upper=1.0),
},
"link": "identity",
}
],
)
model_reg_v
Hierarchical Sequential Sampling Model Model: ddm Response variable: rt,response Likelihood: analytical Observations: 1000 Parameters: v: Formula: v ~ 1 + x + y Priors: v_Intercept ~ Uniform(lower: 0.0, upper: 0.5) v_x ~ Uniform(lower: 0.0, upper: 1.0) v_y ~ Uniform(lower: 0.0, upper: 1.0) Link: identity Explicit bounds: (-inf, inf) a: Prior: HalfNormal(sigma: 2.0) Explicit bounds: (0.0, inf) z: Prior: Uniform(lower: 0.0, upper: 1.0) Explicit bounds: (0.0, 1.0) t: Prior: HalfNormal(sigma: 2.0, initval: 0.10000000149011612) Explicit bounds: (0.0, inf) Lapse probability: 0.05 Lapse distribution: Uniform(lower: 0.0, upper: 10.0)
# Uncomment to see model graph if you have graphviz installed
# model_reg_v.graph()
trace_reg_v = model_reg_v.sample()
Auto-assigning NUTS sampler... Initializing NUTS using jitter+adapt_diag... Multiprocess sampling (4 chains in 4 jobs) NUTS: [z, t, a, v_Intercept, v_x, v_y]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 21 seconds.
# Looks like parameter recovery was successful
model_reg_v.summary()
mean | sd | hdi_3% | hdi_97% | mcse_mean | mcse_sd | ess_bulk | ess_tail | r_hat | |
---|---|---|---|---|---|---|---|---|---|
z | 0.633 | 0.009 | 0.616 | 0.649 | 0.000 | 0.0 | 3879.0 | 3277.0 | 1.0 |
t | 0.543 | 0.009 | 0.526 | 0.559 | 0.000 | 0.0 | 3704.0 | 3180.0 | 1.0 |
a | 1.358 | 0.033 | 1.293 | 1.418 | 0.001 | 0.0 | 3601.0 | 3037.0 | 1.0 |
v_Intercept | 0.471 | 0.004 | 0.464 | 0.475 | 0.000 | 0.0 | 3400.0 | 2531.0 | 1.0 |
v_x | 0.631 | 0.019 | 0.594 | 0.665 | 0.000 | 0.0 | 3474.0 | 3027.0 | 1.0 |
v_y | 0.240 | 0.014 | 0.214 | 0.267 | 0.000 | 0.0 | 4755.0 | 3082.0 | 1.0 |
model_reg_v.plot_trace();