Skip to content

Commit 26efdcb

Browse files
authored
Add section to lookup.rst about configuring source via component (#694)
* Add section to lookup.rst about configuring source via component * add blank line * adjustments for feedback * add nitpick exceptions * update references and backticks * update method references * remove nitpick exceptions * fix model spec file link * add backticks for listeners, add logger ref * fix ref to artifactmanager * add ref to pop view * add note about data sources in config defaults * changelog * update class reference * add links * fix link formatting * Update CHANGELOG date for version 3.6.4
1 parent 2a048b5 commit 26efdcb

File tree

4 files changed

+348
-131
lines changed

4 files changed

+348
-131
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
**3.6.4 - 12/16/25**
2+
3+
- Add documentation for Component LookupTable configuration
4+
15
**3.6.3 - 11/20/25**
26

37
- Raise ValueError if null values are present in probabilities in RandomnessStream.filter_for_probability()

docs/nitpick-exceptions

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,19 @@ py:class vivarium.framework.utilities.T
5151

5252
# layered_config_tree
5353
py:class layered_config_tree.main.LayeredConfigTree
54-
py:class LayeredConfigTree
5554
py:exc layered_config_tree.exceptions.ConfigurationError
5655

56+
# Special methods that Sphinx can't resolve
57+
py:meth __init__
58+
py:meth __repr__
59+
60+
# vivarium internal references that may not be in the docs
61+
py:class vivarium.framework.population.PopulationView
62+
py:class vivarium.framework.lookup.LookupTable
63+
py:class vivarium.framework.artifact.ArtifactManager
64+
py:meth vivarium.framework.artifact.ArtifactInterface.load
65+
py:class Component
66+
5767
# TODO: Need to revisit this. Nitpicking here to avoid failing builds on 3.9
5868
py:class Logger
5969
py:class Path

docs/source/concepts/lookup.rst

Lines changed: 190 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,200 @@ Female 40 60 30
142142
Female 60 100 27
143143
====== ========= ======= ======
144144

145+
Constructing Lookup Tables from a Component
146+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
147+
148+
Components can register lookup tables to be built by specifying
149+
a ``data_sources`` block in their :attr:`~vivarium.component.Component.configuration_defaults` property.
150+
As a basic example, DiseaseModel in ``vivarium_public_health`` has the following
151+
``data_sources`` configuration:
152+
153+
.. code-block:: python
154+
155+
@property
156+
def configuration_defaults(self) -> dict[str, Any]:
157+
return {
158+
f"{self.name}": {
159+
"data_sources": {
160+
"cause_specific_mortality_rate": self.load_cause_specific_mortality_rate,
161+
},
162+
},
163+
}
164+
165+
which specifies a single lookup table named
166+
``cause_specific_mortality_rate`` whose data is provided by the component's
167+
``load_cause_specific_mortality_rate`` method.
168+
169+
Each entry in ``data_sources`` maps a table name to a data source from one of
170+
several supported types (see `Data Source Types`_). Barring edge cases (see
171+
`Limitations and When to Override`_), one should specify all of a component's
172+
lookup tables via ``data_sources``, instead of accessing the builder's lookup
173+
interface directly.
174+
175+
When a component configures ``data_sources``, the base
176+
:class:`~vivarium.component.Component` class automatically builds
177+
the lookup tables before the component's :meth:`~vivarium.component.Component.setup` method is called. The
178+
resulting tables are stored in the component's :attr:`~vivarium.component.Component.lookup_tables` dictionary,
179+
keyed by the name specified in ``data_sources``.
180+
181+
This approach separates the *what* (which tables to build and where to get data) from the
182+
*how* (the mechanics of table construction), making components easier to
183+
write and configure. It also allows users to override data sources in model specification files
184+
without modifying component code. Following the example above, a model specification could adjust the
185+
``cause_specific_mortality_rate`` data source to point to different data or a scalar value:
186+
187+
.. code-block:: yaml
188+
189+
configuration:
190+
disease_model:
191+
data_sources:
192+
cause_specific_mortality_rate: 0.02
193+
194+
Data Source Types
195+
^^^^^^^^^^^^^^^^^
196+
197+
Each entry in ``data_sources`` maps a table name to a data source. The
198+
following data source types are supported:
199+
200+
**Artifact key (string without** ``::`` **):**
201+
A string path to data in the artifact, e.g.,
202+
``"cause.all_causes.cause_specific_mortality_rate"``. The data is loaded
203+
via :meth:`builder.data.load() <vivarium.framework.artifact.interface.ArtifactInterface.load>`. Strings with ``::`` are reserved for method
204+
or function references (see below).
205+
206+
**Callable:**
207+
Any callable (function, lambda, or bound method) that accepts a ``builder``
208+
argument and returns the data.
209+
210+
**Scalar value:**
211+
A numeric value (``int``, ``float``), ``datetime``, or ``timedelta`` that
212+
will be broadcast over the population index when the table is called.
213+
214+
**Method reference (string with** ``self::`` **):**
215+
A string of the form ``"self::method_name"`` that references a method on
216+
the component itself. The method should accept a ``builder`` argument and
217+
return the data. This is primarily for use in
218+
:ref:`model specification YAML files <model_specification_concept>` where
219+
direct method references are not possible.
220+
221+
**External function reference (string with** ``module.path::`` **):**
222+
A string of the form ``"module.path::function_name"`` that references a
223+
function in another module. The function should accept a ``builder``
224+
argument and return the data. This is primarily for use in
225+
:ref:`model specification YAML files <model_specification_concept>` where
226+
direct method references are not possible.
227+
228+
229+
230+
Column Detection
231+
^^^^^^^^^^^^^^^^
232+
233+
When building a lookup table from a :class:`pandas.DataFrame` using ``data_sources``,
234+
the component automatically determines key columns, parameter columns, and value columns
235+
based on the data structure:
236+
237+
- **Value columns** are assumed by the structure of the artifact to be ``["value"]``. In principle,
238+
this could be configured by implementing a custom :class:`~vivarium.framework.artifact.manager.ArtifactManager`.
239+
- **Parameter columns** are detected by finding columns ending in ``_start``
240+
that have corresponding ``_end`` columns (e.g., ``age_start``/``age_end``).
241+
- **Key columns** are all remaining columns that are neither value columns
242+
nor parameter bin edge columns.
243+
244+
See the `Construction Parameters`_ section for definitions of these
245+
column types.
246+
247+
Example: Writing a Component with Data Sources
248+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
249+
250+
A more complete example is reproduced from the `Mortality <https://vivarium.readthedocs.io/projects/vivarium-public-health/en/latest/api_reference/population/mortality.html>`_ component in `vivarium_public_health <https://vivarium.readthedocs.io/projects/vivarium-public-health/en/latest/>`_:
251+
252+
.. code-block:: python
253+
254+
from vivarium import Component
255+
256+
class Mortality(Component):
257+
258+
@property
259+
def configuration_defaults(self) -> dict[str, Any]:
260+
return {
261+
"mortality": {
262+
"data_sources": {
263+
# Artifact key - loaded via builder.data.load()
264+
"all_cause_mortality_rate": "cause.all_causes.cause_specific_mortality_rate",
265+
# Method reference - calls self.load_unmodeled_csmr(builder)
266+
"unmodeled_cause_specific_mortality_rate": self.load_unmodeled_csmr,
267+
# Another artifact key
268+
"life_expectancy": "population.theoretical_minimum_risk_life_expectancy",
269+
},
270+
"unmodeled_causes": [],
271+
},
272+
}
273+
274+
Example: Configuring Data Sources as a User
275+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
276+
277+
Users can override the default data sources in a :ref:`model specification <model_specification_concept>`
278+
file. This allows changing where data comes from without modifying component
279+
code:
280+
281+
.. code-block:: yaml
282+
283+
configuration:
284+
mortality:
285+
data_sources:
286+
# Override with a scalar value instead of artifact data
287+
all_cause_mortality_rate: 0.01
288+
# point to a module function
289+
unmodeled_cause_specific_mortality_rate: "my_module.data::load_unmodeled_csmr"
290+
# Or point to different artifact data
291+
life_expectancy: "alternative.life_expectancy.data"
292+
293+
Limitations and When to Override
294+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
295+
296+
The automatic ``data_sources`` mechanism works well for straightforward cases,
297+
but some situations require overriding the :meth:`~vivarium.component.Component.build_all_lookup_tables` method:
298+
299+
**Non-standard value columns:**
300+
The component defaults to ``["value"]`` as the value column name. If your
301+
data has differently named value columns or multiple value columns, you
302+
must call :meth:`~vivarium.component.Component.build_lookup_table` directly with explicit
303+
``value_columns``.
304+
305+
**Complex data transformations:**
306+
When data requires transformation before building tables (e.g., pivoting,
307+
computing derived parameters, combining multiple data sources), override
308+
:meth:`~vivarium.component.Component.build_all_lookup_tables` to perform the transformation first.
309+
310+
**Delegation to sub-components:**
311+
When lookup tables should be built by sub-components rather than the
312+
parent component, override :meth:`~vivarium.component.Component.build_all_lookup_tables` to skip the
313+
default behavior.
314+
315+
Examples of these patterns can be found in `vivarium_public_health <https://vivarium.readthedocs.io/projects/vivarium-public-health/en/latest/>`_:
316+
317+
- `RateTransition <https://vivarium.readthedocs.io/projects/vivarium-public-health/en/latest/api_reference/disease/transition.html>`_ and `DiseaseState <https://vivarium.readthedocs.io/projects/vivarium-public-health/en/latest/api_reference/disease/state.html>`_ in `vivarium_public_health.disease <https://vivarium.readthedocs.io/projects/vivarium-public-health/en/latest/api_reference/disease/>`_
318+
demonstrate the basic ``data_sources`` pattern with various data source types.
319+
- ``Risk`` in ``vivarium_public_health.risks`` overrides :meth:`~vivarium.component.Component.build_all_lookup_tables`
320+
to delegate table construction to its exposure distribution sub-component.
321+
322+
Using the Lookup Interface Directly
323+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
324+
325+
For cases not covered by ``data_sources``, or when working in an interactive
326+
context, you can build lookup tables directly using the builder's lookup
327+
interface.
328+
145329
Example Usage
146330
~~~~~~~~~~~~~
147331

148332
The following is an example of creating and calling a lookup table in an
149-
:ref:`interactive setting <interactive_tutorial>` using the data above. The
150-
interface and process are the same when integrating a lookup table into a
151-
:term:`component <Component>`, which is primarily how they are used. Assuming
152-
you have a valid simulation object named ``sim`` and the data from the above
153-
table in a :class:`pandas.DataFrame` named ``data``, you can construct a
154-
lookup table in the following way, using the interface from the builder.
333+
:ref:`interactive setting <interactive_tutorial>` using the data from
334+
`Construction Parameters`_ above. The interface and process are the same when
335+
integrating a lookup table into a :term:`component <Component>`, which is primarily
336+
how they are used. Assuming you have a valid simulation object named ``sim`` and
337+
the data from the above table in a :class:`pandas.DataFrame` named ``data``, you
338+
can construct a lookup table in the following way, using the interface from the builder.
155339

156340
.. code-block:: python
157341

0 commit comments

Comments
 (0)