Using the do-operator to simulate data¶
This tutorial illustrates how we can use the underlying PyMC model of a given HSSM model, to forward simulate datasets that you may use for numerical studies.
import pymc as pm
import hssm
Simulate Data¶
We will simulate a simple dataset that contains two conditions.
# Simple model
data = hssm.simulate_data(model="ddm", theta=dict(v=0.5, a=1.5, z=0.5, t=0.1), size=500)
Specify HSSM Model¶
We will fit two separate models to the data.
# Model 1
hssm_model = hssm.HSSM(model="ddm", data=data)
Model initialized successfully.
Underlying PyMC model¶
pm.model_to_graphviz(hssm_model.pymc_model)
Using the do-operator¶
synth_idata, synth_model = hssm_model.sample_do(
params={"v": 0, "a": 1.5, "z": 0.5, "t": 0.1}, draws=100, return_model=True
)
/Users/yxu150/HSSM/.venv/lib/python3.13/site-packages/pytensor/link/numba/dispatch/basic.py:214: UserWarning: Numba will use object mode to run ddm_RV_rv{"(),(),(),(),()->(2)"}'s perform method. Set `pytensor.config.compiler_verbose = True` to see more details.
warnings.warn(
Sampling: [rt,response]
Note that the process defines an new PyMC model, which sets the parameters we passed in our dictionary to Data objects.
pm.model_to_graphviz(synth_model)
synth_idata
<xarray.DataTree>
Group: /
├── Group: /prior
│ Dimensions: (chain: 1, draw: 100, v_mean_dim_0: 1)
│ Coordinates:
│ * chain (chain) int64 8B 0
│ * draw (draw) int64 800B 0 1 2 3 4 5 6 7 ... 92 93 94 95 96 97 98 99
│ * v_mean_dim_0 (v_mean_dim_0) int64 8B 0
│ Data variables:
│ v_mean (chain, draw, v_mean_dim_0) float64 800B 0.0 0.0 ... 0.0 0.0
│ Attributes:
│ created_at: 2026-06-29T18:40:21.224201+00:00
│ creation_library: ArviZ
│ creation_library_version: 1.1.0
│ creation_library_language: Python
│ inference_library: pymc
│ inference_library_version: 6.0.1
│ sample_dims: ['chain', 'draw']
├── Group: /prior_predictive
│ Dimensions: (chain: 1, draw: 100, __obs__: 500, rt,response_dim: 2)
│ Coordinates:
│ * chain (chain) int64 8B 0
│ * draw (draw) int64 800B 0 1 2 3 4 5 6 7 ... 93 94 95 96 97 98 99
│ * __obs__ (__obs__) int64 4kB 0 1 2 3 4 5 ... 494 495 496 497 498 499
│ * rt,response_dim (rt,response_dim) int64 16B 0 1
│ Data variables:
│ rt,response (chain, draw, __obs__, rt,response_dim) float64 800kB 0....
│ Attributes:
│ created_at: 2026-06-29T18:40:21.225297+00:00
│ creation_library: ArviZ
│ creation_library_version: 1.1.0
│ creation_library_language: Python
│ inference_library: pymc
│ inference_library_version: 6.0.1
│ sample_dims: ['chain', 'draw']
├── Group: /observed_data
│ Dimensions: (__obs__: 500, rt,response_extra_dim_0: 2)
│ Coordinates:
│ * __obs__ (__obs__) int64 4kB 0 1 2 3 4 ... 496 497 498 499
│ * rt,response_extra_dim_0 (rt,response_extra_dim_0) int64 16B 0 1
│ Data variables:
│ rt,response (__obs__, rt,response_extra_dim_0) float64 8kB 3...
│ Attributes:
│ created_at: 2026-06-29T18:40:21.226032+00:00
│ creation_library: ArviZ
│ creation_library_version: 1.1.0
│ creation_library_language: Python
│ inference_library: pymc
│ inference_library_version: 6.0.1
│ sample_dims: []
└── Group: /constant_data
Dimensions: ()
Data variables:
v float64 8B 0.0
t float64 8B 0.1
z float64 8B 0.5
a float64 8B 1.5
Attributes:
created_at: 2026-06-29T18:40:21.226577+00:00
creation_library: ArviZ
creation_library_version: 1.1.0
creation_library_language: Python
inference_library: pymc
inference_library_version: 6.0.1
sample_dims: []Observe two things:
- A closer look at the
priorgroup reveals that no parameters were actually sampled (it collects on the deterministicv_meanwhich, arguably, we can skip too) - The
prior_predictivegroup is where we collected our synthetic data
Our synthetic data sits in the prior_predictive group, since, under the hood, were de facto called the sample_prior_predictive() method, on our new do-model.
If you prefer to get your outputs as a dataframe, you can use the predictive_dt_to_dataframe method from the hssm.utils module.
synth_df = hssm.utils.predictive_dt_to_dataframe(
synth_idata, predictive_group="prior_predictive"
)
synth_df
| chain | draw | __obs__ | rt | response | |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0.954976 | 1.0 |
| 1 | 0 | 0 | 1 | 1.817664 | 1.0 |
| 2 | 0 | 0 | 2 | 0.541369 | 1.0 |
| 3 | 0 | 0 | 3 | 3.444736 | 1.0 |
| 4 | 0 | 0 | 4 | 3.674135 | 1.0 |
| ... | ... | ... | ... | ... | ... |
| 49995 | 0 | 99 | 495 | 1.368595 | 1.0 |
| 49996 | 0 | 99 | 496 | 5.492402 | 1.0 |
| 49997 | 0 | 99 | 497 | 3.048027 | 1.0 |
| 49998 | 0 | 99 | 498 | 4.248541 | -1.0 |
| 49999 | 0 | 99 | 499 | 8.824156 | -1.0 |
50000 rows × 5 columns
From here you may re-attach the original data used to define the hssm_model (e.g.to re-attach covariates that you want as part of the model you are testing on your synthetic data).
The sample_do() function can be extremely helpful in setting up numerical experiments to test e.g. for parameter identifiability in scenarios that you wish to control precisely.