Skip to content

Commit b77747d

Browse files
committed
More relevant documentation examples for Model
1 parent 5a26eaf commit b77747d

File tree

2 files changed

+111
-58
lines changed

2 files changed

+111
-58
lines changed

pymc/model/core.py

+85-58
Original file line numberDiff line numberDiff line change
@@ -392,76 +392,103 @@ class Model(WithMemoization, metaclass=ContextMeta):
392392
name : str
393393
name that will be used as prefix for names of all random
394394
variables defined within model
395+
coords : dict
396+
Xarray-like coordinate keys and values. These coordinates can be used
397+
to specify the shape of random variables and to label (but not specify)
398+
the shape of Determinsitic, Potential and Data objects.
399+
Other than specifying the shape of random variables, coordinates have no
400+
effect on the model. They can't be used for label-based broadcasting or indexing.
401+
You must use numpy-like operations for those behaviors.
395402
check_bounds : bool
396403
Ensure that input parameters to distributions are in a valid
397404
range. If your model is built in a way where you know your
398405
parameters can only take on valid values you can set this to
399406
False for increased speed. This should not be used if your model
400407
contains discrete variables.
408+
model : PyMC model, optional
409+
A parent model that this model belongs to. If not specified and the current model
410+
is created inside another model's context, the parent model will be set to that model.
411+
If `None` the model will not have a parent.
401412
402413
Examples
403414
--------
404-
How to define a custom model
415+
Use context manager to define model and respective variables
405416
406417
.. code-block:: python
407418
408-
class CustomModel(Model):
409-
# 1) override init
410-
def __init__(self, mean=0, sigma=1, name=''):
411-
# 2) call super's init first, passing model and name
412-
# to it name will be prefix for all variables here if
413-
# no name specified for model there will be no prefix
414-
super().__init__(name, model)
415-
# now you are in the context of instance,
416-
# `modelcontext` will return self you can define
417-
# variables in several ways note, that all variables
418-
# will get model's name prefix
419-
420-
# 3) you can create variables with the register_rv method
421-
self.register_rv(Normal.dist(mu=mean, sigma=sigma), 'v1', initval=1)
422-
# this will create variable named like '{name::}v1'
423-
# and assign attribute 'v1' to instance created
424-
# variable can be accessed with self.v1 or self['v1']
425-
426-
# 4) this syntax will also work as we are in the
427-
# context of instance itself, names are given as usual
428-
Normal('v2', mu=mean, sigma=sigma)
429-
430-
# something more complex is allowed, too
431-
half_cauchy = HalfCauchy('sigma', beta=10, initval=1.)
432-
Normal('v3', mu=mean, sigma=half_cauchy)
433-
434-
# Deterministic variables can be used in usual way
435-
Deterministic('v3_sq', self.v3 ** 2)
436-
437-
# Potentials too
438-
Potential('p1', pt.constant(1))
439-
440-
# After defining a class CustomModel you can use it in several
441-
# ways
442-
443-
# I:
444-
# state the model within a context
445-
with Model() as model:
446-
CustomModel()
447-
# arbitrary actions
448-
449-
# II:
450-
# use new class as entering point in context
451-
with CustomModel() as model:
452-
Normal('new_normal_var', mu=1, sigma=0)
453-
454-
# III:
455-
# just get model instance with all that was defined in it
456-
model = CustomModel()
457-
458-
# IV:
459-
# use many custom models within one context
460-
with Model() as model:
461-
CustomModel(mean=1, name='first')
462-
CustomModel(mean=2, name='second')
463-
464-
# variables inside both scopes will be named like `first::*`, `second::*`
419+
import pymc as pm
420+
421+
with pm.Model() as model:
422+
x = pm.Normal("x")
423+
424+
425+
Use object API to define model and respective variables
426+
427+
.. code-block:: python
428+
429+
import pymc as pm
430+
431+
model = pm.Model()
432+
x = pm.Normal("x", model=model)
433+
434+
435+
Use coords for defining the shape of random variables and labeling other model variables
436+
437+
.. code-block:: python
438+
439+
import pymc as pm
440+
import numpy as np
441+
442+
coords = {
443+
"feature", ["A", "B", "C"],
444+
"trial", [1, 2, 3, 4, 5],
445+
}
446+
447+
with pm.Model(coords=coords) as model:
448+
intercept = pm.Normal("intercept", shape=(3,)) # Variable will have default dim label `intercept__dim_0`
449+
beta = pm.Normal("beta", dims=("feature",)) # Variable will have shape (3,) and dim label `feature`
450+
451+
# Dims below are only used for labeling, they have no effect on shape
452+
idx = pm.Data("idx", np.array([0, 1, 1, 2, 2])) # Variable will have default dim label `idx__dim_0`
453+
x = pm.Data("x", np.random.normal(size=(5, 3)), dims=("trial", "feature"))
454+
mu = pm.Deterministic("mu", intercept[idx] + beta @ x, dims="trial") # single dim can be passed as string
455+
456+
# Dims controls the shape of the variable
457+
# If not specified, it would be inferred from the shape of the observations
458+
y = pm.Normal("y", mu=mu, observed=[-1, 0, 0, 1, 1], dims=("trial",))
459+
460+
461+
Define nested models, and provide name for variable name prefixing
462+
463+
.. code-block:: python
464+
465+
import pymc as pm
466+
467+
with pm.Model(name="root") as root:
468+
x = pm.Normal("x") # Variable wil be named "root::x"
469+
470+
with pm.Model(name='first') as first:
471+
# Variable will belong to root and first
472+
y = pm.Normal("y", mu=x) # Variable wil be named "root::first::y"
473+
474+
# Can pass parent model explicitly
475+
with pm.Model(name='second', model=root) as second:
476+
# Variable will belong to root and second
477+
z = pm.Normal("z", mu=y) # Variable wil be named "root::second::z"
478+
479+
480+
Set `check_bounds` to False for models with only continuous variables and default transformers
481+
PyMC will remove the bounds check from the model logp which can speed up sampling
482+
483+
.. code-block:: python
484+
485+
import pymc as pm
486+
487+
with pm.Model(check_bounds=False) as model:
488+
sigma = pm.HalfNormal("sigma")
489+
x = pm.Normal("x", sigma=sigma) # No bounds check will be performed on `sigma`
490+
491+
465492
"""
466493

467494
if TYPE_CHECKING:

tests/model/test_core.py

+26
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,27 @@ def test_context_passes_vars_to_parent_model(self):
130130
assert m["d"] is model["one_more::d"]
131131
assert m["one_more::d"] is model["one_more::d"]
132132

133+
def test_docstring_example(self):
134+
with pm.Model(name="root") as root:
135+
x = pm.Normal("x") # Variable wil be named "root::x"
136+
137+
with pm.Model(name="first") as first:
138+
# Variable will belong to root and first
139+
y = pm.Normal("y", mu=x) # Variable wil be named "root::first::y"
140+
141+
# Can pass parent model explicitly
142+
with pm.Model(name="second", model=root) as second:
143+
# Variable will belong to root and second
144+
z = pm.Normal("z", mu=y) # Variable wil be named "root::second::z"
145+
146+
assert x.name == "root::x"
147+
assert y.name == "root::first::y"
148+
assert z.name == "root::second::z"
149+
150+
assert set(root.basic_RVs) == {x, y, z}
151+
assert set(first.basic_RVs) == {y}
152+
assert set(second.basic_RVs) == {z}
153+
133154

134155
class TestNested:
135156
def test_nest_context_works(self):
@@ -1084,7 +1105,12 @@ def test_model_parent_set_programmatically():
10841105
with pm.Model(model=model):
10851106
y = pm.Normal("y")
10861107

1108+
with model:
1109+
with pm.Model(model=None):
1110+
z = pm.Normal("z")
1111+
10871112
assert "y" in model.named_vars
1113+
assert "z" in model.named_vars
10881114

10891115

10901116
class TestModelContext:

0 commit comments

Comments
 (0)