Skip to content

Post-hoc sensitivity

sensitivity_from_table() fits a surrogate over completed experiments and estimates Morris or Sobol sensitivity cheaply through that model. Inspect its cross-validated accuracy before interpreting importance: this measures the surrogate's estimated response, not a new set of simulator evaluations. Use sobol_indices() when you need first-order and total-order indices together; ST - S1 reveals effects attributable to interactions.

trade_study.TableSensitivity(importance, surrogate_cv_r2) dataclass

Post-hoc sensitivity indices computed via a table-fit surrogate.

Attributes:

Name Type Description
importance dict[str, NDArray[floating[Any]]]

Mapping from observable name to an array of factor importances (mu_star for Morris, S1 for Sobol), one value per continuous factor, in the same order as :func:trade_study.screen reports (i.e. continuous factors only, in the order they appear in the factors argument).

surrogate_cv_r2 dict[str, float]

Per-observable cross-validated R^2 of the surrogate the indices were computed from (#114). A low value means the sensitivity indices reflect a poorly learned response surface, not necessarily the true system -- treat such observables' indices as unreliable.

trade_study.sensitivity_from_table(results, factors, *, method='sobol', surrogate_method='rf', n_trajectories=100, seed=42, n_estimators=200, warn_below_r2=0.0)

Compute post-hoc Sobol/Morris sensitivity from a collected table.

Only continuous factors are screened, matching screen()'s own contract; the surrogate is fit on that same continuous subset, so any non-continuous keys present in results.configs (categorical factors, bookkeeping fields, etc.) are simply ignored rather than causing an encoding mismatch.

Parameters:

Name Type Description Default
results ResultsTable

A :class:~trade_study.protocols.ResultsTable from a previous run_grid/Study/etc. call.

required
factors list[Factor]

Factor definitions to screen. Non-continuous factors are dropped (as in screen()); at least one continuous factor must remain.

required
method str

"sobol" or "morris", forwarded to screen().

'sobol'
surrogate_method str

"rf" or "gp", forwarded to :func:trade_study.fit_surrogate.

'rf'
n_trajectories int

Forwarded to screen() (Morris trajectory count, or Sobol base sample size).

100
seed int

Random seed for both the surrogate fit and screen().

42
n_estimators int

Forwarded to :func:trade_study.fit_surrogate (rf only).

200
warn_below_r2 float | None

Forwarded to :func:trade_study.fit_surrogate; warns if any observable's cross-validated R^2 is too low to trust its sensitivity indices. Pass None to disable.

0.0

Returns:

Name Type Description
A TableSensitivity

class:TableSensitivity with importance indices and the

TableSensitivity

surrogate's cross-validated accuracy per observable.

Raises:

Type Description
ValueError

If factors has no continuous entries, or propagated from fit_surrogate() (e.g. an empty table).

Source code in src/trade_study/sensitivity.py
def sensitivity_from_table(  # ruff: ignore[too-many-arguments]
    results: ResultsTable,
    factors: list[Factor],
    *,
    method: str = "sobol",
    surrogate_method: str = "rf",
    n_trajectories: int = 100,
    seed: int = 42,
    n_estimators: int = 200,
    warn_below_r2: float | None = 0.0,
) -> TableSensitivity:
    """Compute post-hoc Sobol/Morris sensitivity from a collected table.

    Only continuous factors are screened, matching ``screen()``'s own
    contract; the surrogate is fit on that same continuous subset, so any
    non-continuous keys present in ``results.configs`` (categorical
    factors, bookkeeping fields, etc.) are simply ignored rather than
    causing an encoding mismatch.

    Args:
        results: A :class:`~trade_study.protocols.ResultsTable` from a
            previous ``run_grid``/``Study``/etc. call.
        factors: Factor definitions to screen. Non-continuous factors are
            dropped (as in ``screen()``); at least one continuous factor
            must remain.
        method: ``"sobol"`` or ``"morris"``, forwarded to ``screen()``.
        surrogate_method: ``"rf"`` or ``"gp"``, forwarded to
            :func:`trade_study.fit_surrogate`.
        n_trajectories: Forwarded to ``screen()`` (Morris trajectory
            count, or Sobol base sample size).
        seed: Random seed for both the surrogate fit and ``screen()``.
        n_estimators: Forwarded to :func:`trade_study.fit_surrogate`
            (``rf`` only).
        warn_below_r2: Forwarded to :func:`trade_study.fit_surrogate`;
            warns if any observable's cross-validated R^2 is too low to
            trust its sensitivity indices. Pass ``None`` to disable.

    Returns:
        A :class:`TableSensitivity` with importance indices and the
        surrogate's cross-validated accuracy per observable.

    Raises:
        ValueError: If ``factors`` has no continuous entries, or
            propagated from ``fit_surrogate()`` (e.g. an empty table).
    """
    continuous = [f for f in factors if f.factor_type == FactorType.CONTINUOUS]
    if not continuous:
        msg = "Screening requires at least one continuous factor"
        raise ValueError(msg)

    surrogate = fit_surrogate(
        results,
        continuous,
        method=surrogate_method,
        seed=seed,
        n_estimators=n_estimators,
        warn_below_r2=warn_below_r2,
    )

    def run_fn(cfg: dict[str, Any]) -> dict[str, float]:
        return surrogate.predict(cfg)

    importance = screen(
        run_fn,
        continuous,
        method=method,
        n_trajectories=n_trajectories,
        seed=seed,
    )
    return TableSensitivity(importance=importance, surrogate_cv_r2=surrogate.cv_r2)