Source code for qca.mlqca.backend
"""Backend contracts and result objects for mlQCA predictors."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Protocol, runtime_checkable
import pandas as pd
from qca.mlqca.validation import ValidatedMLQCAInput
[docs]
@dataclass(frozen=True)
class PredictorFitResult:
"""Result returned by an mlQCA predictive backend."""
backend: str
model: Any
feature_importance: pd.DataFrame
cutoff_candidates: pd.DataFrame
predictions: pd.DataFrame
metrics: dict[str, float] = field(default_factory=dict)
settings: dict[str, Any] = field(default_factory=dict)
def __post_init__(self) -> None:
backend = str(self.backend).strip()
if not backend:
raise ValueError("backend must be a non-empty string.")
object.__setattr__(self, "backend", backend)
for name in ("feature_importance", "cutoff_candidates", "predictions"):
value = getattr(self, name)
if not isinstance(value, pd.DataFrame):
raise TypeError(f"{name} must be a pandas DataFrame.")
object.__setattr__(self, name, value.copy())
object.__setattr__(
self,
"metrics",
{str(key): float(value) for key, value in self.metrics.items()},
)
object.__setattr__(self, "settings", dict(self.settings))
@property
def condition_ranking(self) -> pd.DataFrame:
"""Return feature importance ordered for condition selection."""
return self.feature_importance.copy()
@property
def used_features(self) -> tuple[str, ...]:
"""Return features used by at least one tree split."""
if "used" not in self.feature_importance:
return ()
return tuple(
self.feature_importance.loc[
self.feature_importance["used"].astype(bool),
"feature",
].astype(str)
)
@runtime_checkable
class PredictorBackend(Protocol):
"""Protocol implemented by mlQCA predictive backends."""
name: str
def fit(self, validated: ValidatedMLQCAInput) -> PredictorFitResult:
"""Fit a predictor and return analysis-ready backend output."""
__all__ = ["PredictorBackend", "PredictorFitResult"]