Source code for mmm_framework.config.roles

"""Generalized variable-role taxonomy for the flexible :class:`Dataset` type.

:class:`DatasetRole` is a **superset** of :class:`~mmm_framework.config.enums.VariableRole`
(``config/enums.py``): it keeps the four MMM roles (target / predictor / control /
auxiliary) and adds the roles a non-MMM family needs — ``INDICATOR`` (a CFA/LCA
manifest variable), ``GROUP`` (a hierarchy key such as geography or segment),
``TIME``, ``OFFSET`` (a known exposure), ``WEIGHT`` (a per-observation regression
weight) and ``TRIALS`` (a binomial denominator).

It is a **new** enum rather than an edit to ``VariableRole`` because ``VariableRole``
is embedded in :class:`~mmm_framework.config.mff.MFFConfig`'s ``extra="forbid"``
Pydantic graph and is serialized into saved configs; changing its members risks
existing artifacts. The bidirectional bridge below keeps the MFF ↔ Dataset
adapters lossless for the four roles the two enums share.
"""

from __future__ import annotations

from enum import Enum

from .enums import VariableRole


[docs] class DatasetRole(str, Enum): """Role of a column in a :class:`~mmm_framework.dataset.Dataset`. The first four members map 1:1 to :class:`VariableRole`; the rest generalize the container to non-MMM families. ``str``-valued so a role serializes as its plain string (``"indicator"``) in JSON specs. """ TARGET = "target" # the modeled outcome (MMM: kpi) PREDICTOR = "predictor" # a regressor with a causal role (MMM: media) CONTROL = "control" # a nuisance regressor (MMM: control) INDICATOR = "indicator" # a measured manifest variable (CFA/LCA item) GROUP = "group" # a grouping / hierarchy key (geo, product, segment) TIME = "time" # the time axis OFFSET = "offset" # a known additive / exposure offset (e.g. log-exposure) WEIGHT = "weight" # a per-observation regression weight TRIALS = "trials" # a binomial denominator (n per observation) AUXILIARY = "auxiliary" # allocation weights, etc. (mirrors VariableRole.AUXILIARY)
#: The substantive *measured* roles — what a family treats as observed data #: (everything except structural columns like TIME / GROUP / WEIGHT / OFFSET / #: TRIALS / AUXILIARY). A CFA/LCA reads exactly these as its indicator matrix. OBSERVED_ROLES: tuple[DatasetRole, ...] = ( DatasetRole.TARGET, DatasetRole.PREDICTOR, DatasetRole.CONTROL, DatasetRole.INDICATOR, ) #: Lossless bridge for the four roles MFF and Dataset share. ``INDICATOR`` / #: ``GROUP`` / ``TIME`` / ``OFFSET`` / ``WEIGHT`` / ``TRIALS`` have no MFF #: equivalent (they only appear in genuinely non-MMM datasets). MFF_ROLE_TO_DATASET: dict[VariableRole, DatasetRole] = { VariableRole.KPI: DatasetRole.TARGET, VariableRole.MEDIA: DatasetRole.PREDICTOR, VariableRole.CONTROL: DatasetRole.CONTROL, VariableRole.AUXILIARY: DatasetRole.AUXILIARY, } DATASET_ROLE_TO_MFF: dict[DatasetRole, VariableRole] = { v: k for k, v in MFF_ROLE_TO_DATASET.items() } __all__ = [ "DatasetRole", "OBSERVED_ROLES", "MFF_ROLE_TO_DATASET", "DATASET_ROLE_TO_MFF", ]