Skip to content

Estimators

Summary

CALMRegressor and CALMClassifier are the two estimators. One fit call trains the teacher, finds and selects the regions, and fits one curve per (feature, region). Both follow the scikit-learn convention: constructor arguments configure, fit learns, fitted attributes end with _. Every argument is keyword-only and has a default; the parameters page lists them by importance.


Usage

from calm_additive import CALMRegressor, CALMClassifier

calm = CALMRegressor(max_partition_depth=2)
calm.fit(X_train, y_train)      # numpy array or pandas DataFrame
calm.predict(X_test)

clf = CALMClassifier().fit(X_train, y_train)   # y in {0, 1}
clf.predict(X_test)             # labels
clf.predict_proba(X_test)       # (N, 2)

After fit:

access what it is
calm.print_summary(), calm.plot_curves(), … the model printed and drawn: see printing and plotting
calm.chain_.show() the ledger: accepted and rejected partitions, and why
calm.n_conditional_interactions_ number of (feature, conditioning feature) pairs; x1 \| x2 and x2 \| x1 are two
calm.partitions_ {feature index: Partition}; part.leaves, part.label(leaf.idx)
calm.interactions_info_ regions per feature, conditioning features
calm.teacher_ the fitted teacher
calm.effect_ the effector effect used for detection
calm.model_ the fitted additive model
calm.gam_ the GAM reference, once print_summary(X, y) has fitted it

chain_, partitions_ and effect_ are effector objects and are documented there.

Binary classification and regression only

Multi-class targets are not supported.

API

calm_additive.engine.CALMRegressor(*, max_conditional_interactions=10, max_partitions=None, max_partition_depth=2, min_heterogeneity_drop=0.05, min_r2_gain=0.01, teacher='xgb', effect='pdp', partitioner='best', partition_features='heterogeneous', conditioning_features='all', selection='r2_gain', curve_fitter='ebm', selection_target='teacher', min_relative_r2_gain=None, teacher_params=None, effect_params=None, partitioner_params=None, curve_fitter_params=None, schema=None, random_state=42, verbose=0)

Bases: _CALMBase

CALM for regression.

calm = CALMRegressor(max_partition_depth=2)
calm.fit(X_train, y_train)              # numpy array or pandas DataFrame
calm.predict(X_test)
calm.print_summary(X_test, y_test)      # the model card, and where it stands
calm.plot_curves()                      # one panel per feature, one curve per region
calm.print_prediction(X_test.iloc[0])   # why this prediction
calm.chain_.show()                      # why each partition was kept or rejected

One fit runs the whole pipeline: fit the teacher, read it with a feature-effect method, propose and select the partitions, then fit one curve per (feature, region). Every argument is keyword-only and has a default.

Parameters:

Name Type Description Default
max_conditional_interactions Optional[int]

(K in the paper) the most conditional interactions the model may have — one per (feature, conditioning feature) pair, so x1 | x2 and x2 | x1 are two. A feature's partition is kept whole or not at all, so selection stops before the partition that would exceed the limit. Default 10; None for no limit. Needs selection="r2_gain".

10
max_partitions Optional[int]

the most features that may get a partition; selection stops once it is reached. Default None, no limit. Needs selection="r2_gain".

None
max_partition_depth int

(d_max in the paper) depth of each feature's partition tree, so a feature gets at most 2**max_partition_depth curves and a region's rule has at most that many conditions. Default 2.

2
min_heterogeneity_drop float

(ε in the paper) inside one partition tree, the fraction by which a split must reduce the feature's heterogeneity to be made. Default 0.05. Higher means fewer splits.

0.05
min_r2_gain float

smallest R² gain (fraction of the target's variance, default 0.01 = 1 pt) a feature's partition must add, on top of the partitions already kept, to be kept. Higher means fewer partitions.

0.01
teacher

the black-box model the partitions are read from. A registered name ("xgb", default), an unfitted model or class (fitted here), a fitted model (used as is), or a plain callable X -> scores.

'xgb'
effect Union[str, object]

how the teacher is read to detect interactions — "pdp" (default), "ale", "shap", "rhale", or a fitted effector effect.

'pdp'
partitioner Union[str, object]

what proposes a partition tree per feature — "best" (default), "best_level_wise", or an effector finder object.

'best'
partition_features Union[str, list]

which features may get a partition — "heterogeneous" (default; heterogeneity at or above the median), "all", or a list.

'heterogeneous'
conditioning_features Union[str, list]

which features may appear in the region rules — "all" (default), "numeric", or a list.

'all'
selection str

which proposed partitions are kept — "r2_gain" (default) keeps only those that make an additive surrogate explain the target noticeably better (see min_r2_gain), up to max_conditional_interactions; "all" keeps every one and takes no limit.

'r2_gain'
curve_fitter

what is fitted inside the regions — "ebm" (default; an EBM without interactions, refit on the labels), "surrogate" (no refit: the teacher's regional curves are frozen into a model), or a MaskedFitter.

'ebm'

Other Parameters:

Name Type Description
selection_target str

what the R² of selection is measured against — the teacher's predictions ("teacher", default) or the labels ("y").

min_relative_r2_gain Optional[float]

optional relaxed gate for nearly saturated models — the threshold becomes min(min_r2_gain, min_relative_r2_gain * (1 - R²)).

teacher_params Optional[dict]

keyword arguments for a teacher given by name or class, e.g. {"n_estimators": 1000} for "xgb".

effect_params Optional[dict]

effector's settings for an effect given by name — constructor or fit arguments, e.g. {"nof_instances": 10_000} (CALM's default is all rows) or {"model_jac": f} for "rhale".

partitioner_params Optional[dict]

effector's settings for a partitioner given by name, e.g. {"min_samples_leaf": 50}. The depth and the heterogeneity drop are set by the two arguments above.

curve_fitter_params Optional[dict]

keyword arguments for a fitter given by name, e.g. {"max_bins": 64} for "ebm". The EBM's interactions is always 0.

schema

feature names and types for a numpy X. A DataFrame brings its own.

random_state Optional[int]

one seed, passed to every component built by name. Default 42.

verbose int

0 (default) is silent; 1 shows a progress bar over the stages of fit (teacher, effect, propose, select, curves) with the time each took.

Attributes:

Name Type Description
chain_

the decision ledger (CalmSequence); None when selection="all".

partitions_

{feature index: Partition} for the split features.

model_

the fitted additive model (ExecutableCALM).

teacher_

the fitted teacher.

effect_

the effector effect the partitions were read from.

n_conditional_interactions_

number of (feature, conditioning feature) pairs in the model; x1 | x2 and x2 | x1 are two.

interactions_info_

regions per feature and their conditioning features.

n_features_in_

number of features seen in fit.

feature_names_in_

their names — the DataFrame's columns, the schema's feature_names, or x_0, x_1, ….

baseline_ float

the average score over the training rows; a prediction is this plus the row's contributions.

gam_ float

the GAM reference (the same curve fitter, no partitions) — set by the first print_summary(X, y).

Methods:

Name Description
fit

Fit the teacher, propose and select the partitions, fit the curves.

predict

Predict: values for regression, 0/1 labels for classification.

predict_score

The raw score: the prediction for regression, the positive-class

paper

The apples-to-apples preset: old-CALM behavior on the new engine.

Source code in calm_additive/engine.py
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
def __init__(
    self,
    *,
    # hyperparameters
    max_conditional_interactions: Optional[int] = 10,
    max_partitions: Optional[int] = None,
    max_partition_depth: int = 2,
    min_heterogeneity_drop: float = 0.05,
    min_r2_gain: float = 0.01,
    # choices
    teacher="xgb",
    effect: Union[str, object] = "pdp",
    partitioner: Union[str, object] = "best",
    partition_features: Union[str, list] = "heterogeneous",
    conditioning_features: Union[str, list] = "all",
    selection: str = "r2_gain",
    curve_fitter="ebm",
    # advanced
    selection_target: str = "teacher",
    min_relative_r2_gain: Optional[float] = None,
    teacher_params: Optional[dict] = None,
    effect_params: Optional[dict] = None,
    partitioner_params: Optional[dict] = None,
    curve_fitter_params: Optional[dict] = None,
    schema=None,
    random_state: Optional[int] = 42,
    verbose: int = 0,
):
    self.max_conditional_interactions = max_conditional_interactions
    self.max_partitions = max_partitions
    self.max_partition_depth = max_partition_depth
    self.min_heterogeneity_drop = min_heterogeneity_drop
    self.min_r2_gain = min_r2_gain
    self.teacher = teacher
    self.effect = effect
    self.partitioner = partitioner
    self.partition_features = partition_features
    self.conditioning_features = conditioning_features
    self.selection = selection
    self.curve_fitter = curve_fitter
    self.selection_target = selection_target
    self.min_relative_r2_gain = min_relative_r2_gain
    self.teacher_params = teacher_params
    self.effect_params = effect_params
    self.partitioner_params = partitioner_params
    self.curve_fitter_params = curve_fitter_params
    self.schema = schema
    self.random_state = random_state
    self.verbose = verbose

fit(X, y)

Fit the teacher, propose and select the partitions, fit the curves.

Parameters:

Name Type Description Default
X

(N, D) numpy array or pandas DataFrame. A DataFrame brings its column names and categorical columns along.

required
y

(N,) target — values for regression, 0/1 for classification.

required

Returns:

Type Description

self, fitted.

Source code in calm_additive/engine.py
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
def fit(self, X, y):
    """Fit the teacher, propose and select the partitions, fit the curves.

    Args:
        X: `(N, D)` numpy array or pandas DataFrame. A DataFrame brings
            its column names and categorical columns along.
        y: `(N,)` target — values for regression, 0/1 for classification.

    Returns:
        `self`, fitted.
    """
    self._validate_params()
    X, schema = self._ingest(X)
    ingestion.check_finite(X, None if schema is None else schema.feature_names)
    self.n_features_in_ = X.shape[1]
    schema = self._name_target(schema, y)
    y = self._check_y(y, X.shape[0])
    # kept for the views: curves are drawn where the training rows are,
    # and the GAM reference of `print_summary(X, y)` is fitted on the same data
    self._X_fit, self._y_fit = X, y
    self._view_builder_cache = None
    self.__dict__.pop("gam_", None)

    selecting = self.selection == "r2_gain"
    progress = _Progress(5 if selecting else 4, enabled=self.verbose >= 1)
    done = False
    try:
        with progress.stage(f"teacher ({self._slot_name('teacher')})"):
            self.teacher_ = self._resolve_teacher(X, y)
        forward = _as_forward(self.teacher_, self._task)
        with progress.stage(f"effect ({self._slot_name('effect')})"):
            self.effect_ = self._make_effect(X, forward, schema)
        self.feature_names_in_ = np.array(
            self.effect_.feature_names, dtype=object
        )

        conditioning = self._resolve_conditioning(self.effect_)
        partitioner = self._make_partitioner()
        with progress.stage("propose partitions"):
            parts = self.effect_.find_regions(
                features=self.partition_features,
                finder=partitioner,
                candidate_conditioning_features=conditioning,
            )
        if selecting:
            target = y if self.selection_target == "y" else None
            with progress.stage("select partitions"):
                self.chain_ = self.effect_.select_regions(
                    parts,
                    min_r2_gain=self.min_r2_gain,
                    target=target,
                    relative_gain=self.min_relative_r2_gain,
                    max_conditional_interactions=self.max_conditional_interactions,
                    max_partitions=self.max_partitions,
                )
            self.partitions_ = dict(self.chain_.final.partitions)
        else:
            self.partitions_ = {
                self.effect_._resolve_feature(k): p
                for k, p in parts.items()
                if len(p.leaves) > 1
            }
            self.chain_ = None

        with progress.stage(f"curves ({self._slot_name('curve_fitter')})"):
            self.model_ = fit_partitions(
                self.effect_,
                self.partitions_,
                task=self._task,
                curve_fitter=self.curve_fitter,
                curve_fitter_params=self.curve_fitter_params,
                X=X,
                y=y,
                target=self.selection_target,
                random_state=self.random_state,
                chain=self.chain_,
                calm=self.chain_.final if self.chain_ is not None else None,
            )
        done = True
    finally:
        progress.close(done)
    self.interactions_info_ = self.model_.interactions_info
    self.n_conditional_interactions_ = self.model_.n_conditional_interactions
    return self

predict(X)

Predict: values for regression, 0/1 labels for classification.

Parameters:

Name Type Description Default
X

(N, D) array or DataFrame, with the columns fit saw.

required

Returns:

Type Description

(N,) predictions.

Source code in calm_additive/engine.py
958
959
960
961
962
963
964
965
966
967
def predict(self, X):
    """Predict: values for regression, 0/1 labels for classification.

    Args:
        X: `(N, D)` array or DataFrame, with the columns `fit` saw.

    Returns:
        `(N,)` predictions.
    """
    return self.model_.predict(self._encode(X))

predict_score(X)

The raw score: the prediction for regression, the positive-class probability for classification.

Parameters:

Name Type Description Default
X

(N, D) array or DataFrame, with the columns fit saw.

required

Returns:

Type Description

(N,) scores.

Source code in calm_additive/engine.py
969
970
971
972
973
974
975
976
977
978
979
def predict_score(self, X):
    """The raw score: the prediction for regression, the positive-class
    probability for classification.

    Args:
        X: `(N, D)` array or DataFrame, with the columns `fit` saw.

    Returns:
        `(N,)` scores.
    """
    return self.model_.predict_score(self._encode(X))

paper(**overrides) classmethod

The apples-to-apples preset: old-CALM behavior on the new engine.

Every proposed partition is applied (no R² selection, no cap on the conditional interactions), Best partitioner, threshold proposer, numeric-only conditioning (categorical splits off), 20 candidate split points, depth 2. The drop threshold translates the paper's variance-scale ε=0.2 to the std scale: 1 − √0.8 ≈ 0.106. Keyword arguments override the preset.

Source code in calm_additive/engine.py
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
@classmethod
def paper(cls, **overrides):
    """The apples-to-apples preset: old-CALM behavior on the new engine.

    Every proposed partition is applied (no R² selection, no cap on the
    conditional interactions), `Best` partitioner, threshold proposer, numeric-only conditioning
    (categorical splits off), 20 candidate split points, depth 2. The
    drop threshold translates the paper's variance-scale ε=0.2 to the std
    scale: ``1 − √0.8 ≈ 0.106``. Keyword arguments override the preset.
    """
    preset = dict(
        max_conditional_interactions=None,
        selection="all",
        partitioner="best",
        max_partition_depth=2,
        min_heterogeneity_drop=1.0 - math.sqrt(0.8),
        conditioning_features="numeric",
        partitioner_params={
            "numerical_features_grid_size": 20,
            "continuous_proposer": "threshold",
            "search_partitions_when_categorical": False,
        },
    )
    return cls(**{**preset, **overrides})

calm_additive.engine.CALMClassifier(*, max_conditional_interactions=10, max_partitions=None, max_partition_depth=2, min_heterogeneity_drop=0.05, min_r2_gain=0.01, teacher='xgb', effect='pdp', partitioner='best', partition_features='heterogeneous', conditioning_features='all', selection='r2_gain', curve_fitter='ebm', selection_target='teacher', min_relative_r2_gain=None, teacher_params=None, effect_params=None, partitioner_params=None, curve_fitter_params=None, schema=None, random_state=42, verbose=0)

Bases: _CALMBase

CALM for binary classification.

calm = CALMClassifier(max_partition_depth=2)
calm.fit(X_train, y_train)              # numpy array or pandas DataFrame
calm.predict(X_test)
calm.print_summary(X_test, y_test)      # the model card, and where it stands
calm.plot_curves()                      # one panel per feature, one curve per region
calm.print_prediction(X_test.iloc[0])   # why this prediction
calm.chain_.show()                      # why each partition was kept or rejected

One fit runs the whole pipeline: fit the teacher, read it with a feature-effect method, propose and select the partitions, then fit one curve per (feature, region). Every argument is keyword-only and has a default.

The target must be 0/1. The teacher is read through its positive-class probability; predict thresholds the score at 0.5.

Parameters:

Name Type Description Default
max_conditional_interactions Optional[int]

(K in the paper) the most conditional interactions the model may have — one per (feature, conditioning feature) pair, so x1 | x2 and x2 | x1 are two. A feature's partition is kept whole or not at all, so selection stops before the partition that would exceed the limit. Default 10; None for no limit. Needs selection="r2_gain".

10
max_partitions Optional[int]

the most features that may get a partition; selection stops once it is reached. Default None, no limit. Needs selection="r2_gain".

None
max_partition_depth int

(d_max in the paper) depth of each feature's partition tree, so a feature gets at most 2**max_partition_depth curves and a region's rule has at most that many conditions. Default 2.

2
min_heterogeneity_drop float

(ε in the paper) inside one partition tree, the fraction by which a split must reduce the feature's heterogeneity to be made. Default 0.05. Higher means fewer splits.

0.05
min_r2_gain float

smallest R² gain (fraction of the target's variance, default 0.01 = 1 pt) a feature's partition must add, on top of the partitions already kept, to be kept. Higher means fewer partitions.

0.01
teacher

the black-box model the partitions are read from. A registered name ("xgb", default), an unfitted model or class (fitted here), a fitted model (used as is), or a plain callable X -> scores.

'xgb'
effect Union[str, object]

how the teacher is read to detect interactions — "pdp" (default), "ale", "shap", "rhale", or a fitted effector effect.

'pdp'
partitioner Union[str, object]

what proposes a partition tree per feature — "best" (default), "best_level_wise", or an effector finder object.

'best'
partition_features Union[str, list]

which features may get a partition — "heterogeneous" (default; heterogeneity at or above the median), "all", or a list.

'heterogeneous'
conditioning_features Union[str, list]

which features may appear in the region rules — "all" (default), "numeric", or a list.

'all'
selection str

which proposed partitions are kept — "r2_gain" (default) keeps only those that make an additive surrogate explain the target noticeably better (see min_r2_gain), up to max_conditional_interactions; "all" keeps every one and takes no limit.

'r2_gain'
curve_fitter

what is fitted inside the regions — "ebm" (default; an EBM without interactions, refit on the labels), "surrogate" (no refit: the teacher's regional curves are frozen into a model), or a MaskedFitter.

'ebm'

Other Parameters:

Name Type Description
selection_target str

what the R² of selection is measured against — the teacher's predictions ("teacher", default) or the labels ("y").

min_relative_r2_gain Optional[float]

optional relaxed gate for nearly saturated models — the threshold becomes min(min_r2_gain, min_relative_r2_gain * (1 - R²)).

teacher_params Optional[dict]

keyword arguments for a teacher given by name or class, e.g. {"n_estimators": 1000} for "xgb".

effect_params Optional[dict]

effector's settings for an effect given by name — constructor or fit arguments, e.g. {"nof_instances": 10_000} (CALM's default is all rows) or {"model_jac": f} for "rhale".

partitioner_params Optional[dict]

effector's settings for a partitioner given by name, e.g. {"min_samples_leaf": 50}. The depth and the heterogeneity drop are set by the two arguments above.

curve_fitter_params Optional[dict]

keyword arguments for a fitter given by name, e.g. {"max_bins": 64} for "ebm". The EBM's interactions is always 0.

schema

feature names and types for a numpy X. A DataFrame brings its own.

random_state Optional[int]

one seed, passed to every component built by name. Default 42.

verbose int

0 (default) is silent; 1 shows a progress bar over the stages of fit (teacher, effect, propose, select, curves) with the time each took.

Attributes:

Name Type Description
chain_

the decision ledger (CalmSequence); None when selection="all".

partitions_

{feature index: Partition} for the split features.

model_

the fitted additive model (ExecutableCALM).

teacher_

the fitted teacher.

effect_

the effector effect the partitions were read from.

n_conditional_interactions_

number of (feature, conditioning feature) pairs in the model; x1 | x2 and x2 | x1 are two.

interactions_info_

regions per feature and their conditioning features.

n_features_in_

number of features seen in fit.

feature_names_in_

their names — the DataFrame's columns, the schema's feature_names, or x_0, x_1, ….

baseline_ float

the average score over the training rows; a prediction is this plus the row's contributions.

gam_ float

the GAM reference (the same curve fitter, no partitions) — set by the first print_summary(X, y).

Methods:

Name Description
fit

Fit the teacher, propose and select the partitions, fit the curves.

predict

Predict: values for regression, 0/1 labels for classification.

predict_proba

Class probabilities, in scikit-learn's two-column shape.

predict_score

The raw score: the prediction for regression, the positive-class

paper

The apples-to-apples preset: old-CALM behavior on the new engine.

Source code in calm_additive/engine.py
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
def __init__(
    self,
    *,
    # hyperparameters
    max_conditional_interactions: Optional[int] = 10,
    max_partitions: Optional[int] = None,
    max_partition_depth: int = 2,
    min_heterogeneity_drop: float = 0.05,
    min_r2_gain: float = 0.01,
    # choices
    teacher="xgb",
    effect: Union[str, object] = "pdp",
    partitioner: Union[str, object] = "best",
    partition_features: Union[str, list] = "heterogeneous",
    conditioning_features: Union[str, list] = "all",
    selection: str = "r2_gain",
    curve_fitter="ebm",
    # advanced
    selection_target: str = "teacher",
    min_relative_r2_gain: Optional[float] = None,
    teacher_params: Optional[dict] = None,
    effect_params: Optional[dict] = None,
    partitioner_params: Optional[dict] = None,
    curve_fitter_params: Optional[dict] = None,
    schema=None,
    random_state: Optional[int] = 42,
    verbose: int = 0,
):
    self.max_conditional_interactions = max_conditional_interactions
    self.max_partitions = max_partitions
    self.max_partition_depth = max_partition_depth
    self.min_heterogeneity_drop = min_heterogeneity_drop
    self.min_r2_gain = min_r2_gain
    self.teacher = teacher
    self.effect = effect
    self.partitioner = partitioner
    self.partition_features = partition_features
    self.conditioning_features = conditioning_features
    self.selection = selection
    self.curve_fitter = curve_fitter
    self.selection_target = selection_target
    self.min_relative_r2_gain = min_relative_r2_gain
    self.teacher_params = teacher_params
    self.effect_params = effect_params
    self.partitioner_params = partitioner_params
    self.curve_fitter_params = curve_fitter_params
    self.schema = schema
    self.random_state = random_state
    self.verbose = verbose

fit(X, y)

Fit the teacher, propose and select the partitions, fit the curves.

Parameters:

Name Type Description Default
X

(N, D) numpy array or pandas DataFrame. A DataFrame brings its column names and categorical columns along.

required
y

(N,) target — values for regression, 0/1 for classification.

required

Returns:

Type Description

self, fitted.

Source code in calm_additive/engine.py
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
def fit(self, X, y):
    """Fit the teacher, propose and select the partitions, fit the curves.

    Args:
        X: `(N, D)` numpy array or pandas DataFrame. A DataFrame brings
            its column names and categorical columns along.
        y: `(N,)` target — values for regression, 0/1 for classification.

    Returns:
        `self`, fitted.
    """
    self._validate_params()
    X, schema = self._ingest(X)
    ingestion.check_finite(X, None if schema is None else schema.feature_names)
    self.n_features_in_ = X.shape[1]
    schema = self._name_target(schema, y)
    y = self._check_y(y, X.shape[0])
    # kept for the views: curves are drawn where the training rows are,
    # and the GAM reference of `print_summary(X, y)` is fitted on the same data
    self._X_fit, self._y_fit = X, y
    self._view_builder_cache = None
    self.__dict__.pop("gam_", None)

    selecting = self.selection == "r2_gain"
    progress = _Progress(5 if selecting else 4, enabled=self.verbose >= 1)
    done = False
    try:
        with progress.stage(f"teacher ({self._slot_name('teacher')})"):
            self.teacher_ = self._resolve_teacher(X, y)
        forward = _as_forward(self.teacher_, self._task)
        with progress.stage(f"effect ({self._slot_name('effect')})"):
            self.effect_ = self._make_effect(X, forward, schema)
        self.feature_names_in_ = np.array(
            self.effect_.feature_names, dtype=object
        )

        conditioning = self._resolve_conditioning(self.effect_)
        partitioner = self._make_partitioner()
        with progress.stage("propose partitions"):
            parts = self.effect_.find_regions(
                features=self.partition_features,
                finder=partitioner,
                candidate_conditioning_features=conditioning,
            )
        if selecting:
            target = y if self.selection_target == "y" else None
            with progress.stage("select partitions"):
                self.chain_ = self.effect_.select_regions(
                    parts,
                    min_r2_gain=self.min_r2_gain,
                    target=target,
                    relative_gain=self.min_relative_r2_gain,
                    max_conditional_interactions=self.max_conditional_interactions,
                    max_partitions=self.max_partitions,
                )
            self.partitions_ = dict(self.chain_.final.partitions)
        else:
            self.partitions_ = {
                self.effect_._resolve_feature(k): p
                for k, p in parts.items()
                if len(p.leaves) > 1
            }
            self.chain_ = None

        with progress.stage(f"curves ({self._slot_name('curve_fitter')})"):
            self.model_ = fit_partitions(
                self.effect_,
                self.partitions_,
                task=self._task,
                curve_fitter=self.curve_fitter,
                curve_fitter_params=self.curve_fitter_params,
                X=X,
                y=y,
                target=self.selection_target,
                random_state=self.random_state,
                chain=self.chain_,
                calm=self.chain_.final if self.chain_ is not None else None,
            )
        done = True
    finally:
        progress.close(done)
    self.interactions_info_ = self.model_.interactions_info
    self.n_conditional_interactions_ = self.model_.n_conditional_interactions
    return self

predict(X)

Predict: values for regression, 0/1 labels for classification.

Parameters:

Name Type Description Default
X

(N, D) array or DataFrame, with the columns fit saw.

required

Returns:

Type Description

(N,) predictions.

Source code in calm_additive/engine.py
958
959
960
961
962
963
964
965
966
967
def predict(self, X):
    """Predict: values for regression, 0/1 labels for classification.

    Args:
        X: `(N, D)` array or DataFrame, with the columns `fit` saw.

    Returns:
        `(N,)` predictions.
    """
    return self.model_.predict(self._encode(X))

predict_proba(X)

Class probabilities, in scikit-learn's two-column shape.

Parameters:

Name Type Description Default
X

(N, D) array or DataFrame, with the columns fit saw.

required

Returns:

Type Description

(N, 2) array: [P(y=0), P(y=1)].

Source code in calm_additive/engine.py
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
def predict_proba(self, X):
    """Class probabilities, in scikit-learn's two-column shape.

    Args:
        X: `(N, D)` array or DataFrame, with the columns `fit` saw.

    Returns:
        `(N, 2)` array: `[P(y=0), P(y=1)]`.
    """
    p = np.clip(self.predict_score(X), 0.0, 1.0)
    return np.column_stack([1.0 - p, p])

predict_score(X)

The raw score: the prediction for regression, the positive-class probability for classification.

Parameters:

Name Type Description Default
X

(N, D) array or DataFrame, with the columns fit saw.

required

Returns:

Type Description

(N,) scores.

Source code in calm_additive/engine.py
969
970
971
972
973
974
975
976
977
978
979
def predict_score(self, X):
    """The raw score: the prediction for regression, the positive-class
    probability for classification.

    Args:
        X: `(N, D)` array or DataFrame, with the columns `fit` saw.

    Returns:
        `(N,)` scores.
    """
    return self.model_.predict_score(self._encode(X))

paper(**overrides) classmethod

The apples-to-apples preset: old-CALM behavior on the new engine.

Every proposed partition is applied (no R² selection, no cap on the conditional interactions), Best partitioner, threshold proposer, numeric-only conditioning (categorical splits off), 20 candidate split points, depth 2. The drop threshold translates the paper's variance-scale ε=0.2 to the std scale: 1 − √0.8 ≈ 0.106. Keyword arguments override the preset.

Source code in calm_additive/engine.py
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
@classmethod
def paper(cls, **overrides):
    """The apples-to-apples preset: old-CALM behavior on the new engine.

    Every proposed partition is applied (no R² selection, no cap on the
    conditional interactions), `Best` partitioner, threshold proposer, numeric-only conditioning
    (categorical splits off), 20 candidate split points, depth 2. The
    drop threshold translates the paper's variance-scale ε=0.2 to the std
    scale: ``1 − √0.8 ≈ 0.106``. Keyword arguments override the preset.
    """
    preset = dict(
        max_conditional_interactions=None,
        selection="all",
        partitioner="best",
        max_partition_depth=2,
        min_heterogeneity_drop=1.0 - math.sqrt(0.8),
        conditioning_features="numeric",
        partitioner_params={
            "numerical_features_grid_size": 20,
            "continuous_proposer": "threshold",
            "search_partitions_when_categorical": False,
        },
    )
    return cls(**{**preset, **overrides})