Source code for pytyche.analysis._functions
"""Polymorphic ``pt.analyze`` — the one-shot fit-and-analyze entry point.
The five primitive function forms (``fit_policy_tree``,
``thompson_allocation``, ``apply_calibration``, ``recommendation_summary``,
``evaluate_against_truth``) are the impl functions themselves, re-exported
through ``pytyche.analysis`` — the posterior methods are lazy delegates to
the same objects, so function form and method form share one code path by
construction. Only ``analyze`` needs a wrapper, because its function form
accepts MORE types than the method: raw ``ObservedExperimentData`` is fit
first (``pt.fit`` with defaults), then analyzed.
The other function forms are deliberately NOT polymorphic — implicit
re-fit on every call would be a footgun for the sweep-over-alternatives
workflow (spec: "pt.analyze is polymorphic over data type").
"""
from __future__ import annotations
from pytyche.bcf.config import (
BinaryBCFResult,
ContinuousBCFResult,
HurdleBCFResult,
)
from pytyche.contracts import AnalysisResult, ObservedExperimentData
[docs]
def analyze(
data: ObservedExperimentData
| HurdleBCFResult
| ContinuousBCFResult
| BinaryBCFResult,
*,
max_depth: int = 3,
min_segment_share: float = 0.10,
n_bootstrap: int = 50,
bootstrap_seed: int = 0,
) -> AnalysisResult:
"""Fit-if-needed, then assemble the canonical analysis output.
Dispatches on the type of *data*:
- ``ObservedExperimentData`` — the one-shot path: ``pt.fit(data)``
with fit defaults (customize the fit by calling :func:`~pytyche.bcf.dispatch.fit`
explicitly), then ``.analyze(**kwargs)`` on the fitted posterior.
- A posterior result type — delegates to ``data.analyze(**kwargs)``
directly.
Keyword arguments are the embedded policy tree's hyperparameters and
are forwarded to ``.analyze`` in both branches; none reach the fit.
Args:
data: Observed experiment data, or one of the three posterior
result types.
max_depth: Embedded policy tree depth (forwarded).
min_segment_share: Minimum per-leaf population share (forwarded).
n_bootstrap: Stability bootstrap count (forwarded; ``0`` skips
stability with a ``UserWarning``).
bootstrap_seed: Stability bootstrap seed (forwarded).
Returns:
:class:`~pytyche.contracts.AnalysisResult` — the SUMMARY surface;
anything needing posterior samples goes through
``analysis.posterior``.
Raises:
TypeError: When *data* is neither observed data nor a posterior
result type.
ValueError: When a posterior *data* carries no observed data.
"""
if isinstance(data, ObservedExperimentData):
# Heavy import (jax via the fit stack) — only the one-shot path
# pays it; `import pytyche.analysis` stays light.
from pytyche.bcf.dispatch import fit
data = fit(data)
elif not isinstance(
data, (HurdleBCFResult, ContinuousBCFResult, BinaryBCFResult)
):
raise TypeError(
"pt.analyze accepts ObservedExperimentData (fit-then-analyze) "
"or a fitted posterior (HurdleBCFResult, ContinuousBCFResult, "
f"or BinaryBCFResult), got {type(data).__name__}."
)
return data.analyze(
max_depth=max_depth,
min_segment_share=min_segment_share,
n_bootstrap=n_bootstrap,
bootstrap_seed=bootstrap_seed,
)