Skip to content

Estimators

estimator

FlagGAMClassifier and FlagGAMRegressor: sklearn-compatible estimators for rule-basis GAMs.

FlagGAMClassifier

FlagGAMClassifier(task='auto', quantile_low=(0.05, 0.45), quantile_high=(0.55, 0.95), quantile_step=0.05, min_support='auto', fdr_alpha=0.05, effect_size='risk_difference', representation='full', feature_weighting=None, head='additive', flexible_estimator=None, C=1.0, missing='no_evidence', monotonic_constraints=None, categorical_features=None, random_state=None)

Bases: ClassifierMixin, _BaseFlagGAM

Rule-basis generalized additive model classifier (sklearn contract).

Source code in src/flaggam/estimator.py
def __init__(
    self,
    task="auto",
    quantile_low=(0.05, 0.45),
    quantile_high=(0.55, 0.95),
    quantile_step=0.05,
    min_support="auto",
    fdr_alpha=0.05,
    effect_size="risk_difference",
    representation="full",
    feature_weighting=None,
    head="additive",
    flexible_estimator=None,
    C=1.0,
    missing="no_evidence",
    monotonic_constraints=None,
    categorical_features=None,
    random_state=None,
) -> None:
    self.task = task
    self.quantile_low = quantile_low
    self.quantile_high = quantile_high
    self.quantile_step = quantile_step
    self.min_support = min_support
    self.fdr_alpha = fdr_alpha
    self.effect_size = effect_size
    self.representation = representation
    self.feature_weighting = feature_weighting
    self.head = head
    self.flexible_estimator = flexible_estimator
    self.C = C
    self.missing = missing
    self.monotonic_constraints = monotonic_constraints
    self.categorical_features = categorical_features
    self.random_state = random_state

fit

fit(X, y)

Discover flag bases, fit head on Z(X).

Source code in src/flaggam/estimator.py
def fit(self, X, y):
    """Discover flag bases, fit head on Z(X)."""
    if y is None:
        raise ValueError("requires y to be passed, but the target y is None")
    if self.task not in {"auto", "binary", "multiclass"}:
        raise ValueError(
            f"task={self.task!r} is not recognised; "
            "valid values are 'auto', 'binary', 'multiclass'"
        )
    if self.representation not in {"full", "compact"}:
        raise ValueError(
            f"representation={self.representation!r} is not recognised; "
            "valid values are 'full', 'compact'"
        )
    if self.missing not in {"no_evidence", "indicator"}:
        raise ValueError(
            f"missing={self.missing!r} is not recognised; "
            "valid values are 'no_evidence', 'indicator'"
        )
    df = self._to_frame(X, reset=True)
    y = np.asarray(y)
    if len(df) != len(y):
        raise ValueError(
            f"Found input variables with inconsistent numbers of samples: "
            f"[{len(df)}, {len(y)}]"
        )
    check_classification_targets(y)
    self.label_encoder_ = LabelEncoder().fit(y)
    self.classes_ = self.label_encoder_.classes_
    y_enc = self.label_encoder_.transform(y)

    task = self.task
    if task == "auto":
        task = "binary" if len(self.classes_) == 2 else "multiclass"

    if self.monotonic_constraints is not None:
        _validate_constraints(self.monotonic_constraints, list(df.columns))
        if self.head != "additive":
            raise ValueError("monotonic_constraints require head='additive'")
        if self.representation == "compact":
            raise ValueError(
                "monotonic_constraints are incompatible with representation='compact'"
            )
        if task == "multiclass":
            raise NotImplementedError(
                "monotonic constraints support binary classification only"
            )
    if self.head == "flexible" and self.flexible_estimator is None:
        raise ValueError("head='flexible' requires flexible_estimator")

    self.core_ = self._build_core(task).fit(df, y_enc)
    Z = self.core_.transform(df)

    self.feature_weights_ = None
    H = Z
    if self.representation == "compact":
        if self.feature_weighting == "auto":
            self.feature_weights_ = feature_weights(
                df, y_enc, task, self.core_.numerical_features_
            )
        H = compact_scores(
            Z,
            self.core_.bases_,
            np.arange(len(self.classes_)),
            self.feature_weights_,
        )

    if self.head == "additive":
        if self.monotonic_constraints is not None:
            from .monotonic import MonotonicAdditiveHead, bounds_for_bases

            C = 1.0 if isinstance(self.C, (list, tuple)) else self.C
            bounds = bounds_for_bases(self.core_.bases_, dict(self.monotonic_constraints))
            self.head_ = MonotonicAdditiveHead(task, bounds, C=C)
        else:
            self.head_ = AdditiveHead(task, C=self.C, random_state=self.random_state)
    else:
        self.head_ = FlexibleHead(self.flexible_estimator, task, random_state=self.random_state)
    self.head_.fit(H, y_enc)
    return self

FlagGAMRegressor

FlagGAMRegressor(quantile_low=(0.05, 0.45), quantile_high=(0.55, 0.95), quantile_step=0.05, min_support='auto', fdr_alpha=0.05, head='additive', flexible_estimator=None, alpha=1.0, missing='no_evidence', monotonic_constraints=None, categorical_features=None, random_state=None)

Bases: RegressorMixin, _BaseFlagGAM

Rule-basis generalized additive model regressor (sklearn contract).

Source code in src/flaggam/estimator.py
def __init__(
    self,
    quantile_low=(0.05, 0.45),
    quantile_high=(0.55, 0.95),
    quantile_step=0.05,
    min_support="auto",
    fdr_alpha=0.05,
    head="additive",
    flexible_estimator=None,
    alpha=1.0,
    missing="no_evidence",
    monotonic_constraints=None,
    categorical_features=None,
    random_state=None,
) -> None:
    self.quantile_low = quantile_low
    self.quantile_high = quantile_high
    self.quantile_step = quantile_step
    self.min_support = min_support
    self.fdr_alpha = fdr_alpha
    self.head = head
    self.flexible_estimator = flexible_estimator
    self.alpha = alpha
    self.missing = missing
    self.monotonic_constraints = monotonic_constraints
    self.categorical_features = categorical_features
    self.random_state = random_state

fit

fit(X, y)

Discover flag bases, fit regression head on Z(X).

Source code in src/flaggam/estimator.py
def fit(self, X, y):
    """Discover flag bases, fit regression head on Z(X)."""
    if y is None:
        raise ValueError("requires y to be passed, but the target y is None")
    if self.missing not in {"no_evidence", "indicator"}:
        raise ValueError(
            f"missing={self.missing!r} is not recognised; "
            "valid values are 'no_evidence', 'indicator'"
        )
    df = self._to_frame(X, reset=True)
    y = np.asarray(y)
    if y.dtype.kind == "c":
        raise ValueError(f"Complex data not supported\n{y}\n")
    if y.ndim == 2 and y.shape[1] == 1:
        warnings.warn(
            "A column-vector y was passed when a 1d array was expected. "
            "Please change the shape of y to (n_samples,), for example using ravel().",
            DataConversionWarning,
            stacklevel=2,
        )
    y = y.astype(float).ravel()
    if len(df) != len(y):
        raise ValueError(
            f"Found input variables with inconsistent numbers of samples: "
            f"[{len(df)}, {len(y)}]"
        )
    if self.monotonic_constraints is not None:
        _validate_constraints(self.monotonic_constraints, list(df.columns))
        if self.head != "additive":
            raise ValueError("monotonic_constraints require head='additive'")
    if self.head == "flexible" and self.flexible_estimator is None:
        raise ValueError("head='flexible' requires flexible_estimator")
    self.core_ = self._build_core("regression").fit(df, y)
    Z = self.core_.transform(df)
    if self.head == "additive":
        if self.monotonic_constraints is not None:
            from .monotonic import MonotonicAdditiveHead, bounds_for_bases

            alpha = 1.0 if isinstance(self.alpha, (list, tuple)) else self.alpha
            bounds = bounds_for_bases(self.core_.bases_, dict(self.monotonic_constraints))
            self.head_ = MonotonicAdditiveHead("regression", bounds, alpha=alpha)
        else:
            self.head_ = AdditiveHead(
                "regression", alpha=self.alpha, random_state=self.random_state
            )
    else:
        self.head_ = FlexibleHead(
            self.flexible_estimator, "regression", random_state=self.random_state
        )
    self.head_.fit(Z, y)
    return self