Printing and plotting
Summary
A fitted CALM is its own explanation: score = baseline + one curve value per
feature, the curve being the one of the region the row falls in. Five
methods show it. The ones named print_… print, the ones named plot_…
draw.
| method | answers |
|---|---|
print_summary() |
what is this model: size, features by importance, partitions; with data, where it stands between a GAM and the black box |
plot_curves() |
what does each feature do, and where: one panel per feature, one curve per region |
plot_importance() |
which features carry the model |
print_prediction(x) |
why this number, for one row |
plot_prediction(x) |
the same row as a waterfall |
Three more return the numbers behind them:
curves(),
contributions(X) and
importances().
CALMClassifier has the same methods.
Usage
calm.print_summary() # the model card
calm.print_summary(X_test, y_test) # … with GAM / CALM / black box on that data
calm.plot_curves() # the most important features
calm.plot_curves("hour") # one feature, large
calm.plot_importance()
row = X_test.iloc[0]
calm.print_prediction(row) # the row's ledger
calm.plot_prediction(row) # … as a waterfall
calm.plot_curves(at=row) # … marked on the curves
fig, axes = calm.plot_curves(show_plot=False) # to save or edit
What the views say
Everything is read from the fitted model. Curves, contributions and
importances come from the curves fit produced, not from the teacher, and
they are exact: for every row,
calm.contributions(X).sum(axis=1) + calm.baseline_ # == the model's score
The score is the prediction for regression. For a classifier it is the log-odds of the positive class, the scale the model is additive on; the ledger's last lines map it to the probability and the class.
A curve is a feature's whole contribution inside its region, centred so that the feature's contributions average zero over the training rows. The baseline is therefore the average score. Curves of the same feature sit at different heights when their regions differ in level.
Curves are drawn where the data is. Each curve covers only the values its region has training rows for, and the strip under a panel shows how many (one row of bars per region). Categorical features are drawn as one mark per level, with the level names on the axis.
Importance is the mean |contribution| of a feature over the training rows, in the units of the score. The bar of a feature with a partition is cut into one segment per region.
Where it stands. print_summary(X, y) scores three models on the data you
pass: a GAM (the same curve fitter, with no partitions), this CALM, and the
teacher. The GAM is fitted on the training data the first time it is needed
and kept as calm.gam_. Pass held-out data: on its own training rows the
teacher looks better than it is.
Why the model has this structure
These views describe the model. Why a partition was kept or rejected is
the selection ledger, calm.chain_.show(), and what the teacher showed
for a feature is calm.effect_.plot("hour"); both are
effector objects.
The look
Figures use effector's active theme: effector.set_theme("dark") or
"paper" changes their colours. A schema's scale_x_list / scale_y
is applied to the views (not to contributions, importances and
baseline_, which stay in the model's units).
API
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 |
10
|
max_partitions
|
Optional[int]
|
the most features that may get a partition;
selection stops once it is reached. Default |
None
|
max_partition_depth
|
int
|
(d_max in the paper) depth of each feature's
partition tree, so a feature gets at most
|
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'
|
|
effect
|
Union[str, object]
|
how the teacher is read to detect interactions — |
'pdp'
|
partitioner
|
Union[str, object]
|
what proposes a partition tree per feature — |
'best'
|
partition_features
|
Union[str, list]
|
which features may get a partition —
|
'heterogeneous'
|
conditioning_features
|
Union[str, list]
|
which features may appear in the region
rules — |
'all'
|
selection
|
str
|
which proposed partitions are kept — |
'r2_gain'
|
curve_fitter
|
what is fitted inside the regions — |
'ebm'
|
Other Parameters:
| Name | Type | Description |
|---|---|---|
selection_target |
str
|
what the R² of |
min_relative_r2_gain |
Optional[float]
|
optional relaxed gate for nearly saturated
models — the threshold becomes
|
teacher_params |
Optional[dict]
|
keyword arguments for a teacher given by name or
class, e.g. |
effect_params |
Optional[dict]
|
effector's settings for an effect given by name —
constructor or |
partitioner_params |
Optional[dict]
|
effector's settings for a partitioner given by
name, e.g. |
curve_fitter_params |
Optional[dict]
|
keyword arguments for a fitter given by name,
e.g. |
schema |
feature names and types for a numpy |
|
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 |
Attributes:
| Name | Type | Description |
|---|---|---|
chain_ |
the decision ledger ( |
|
partitions_ |
|
|
model_ |
the fitted additive model ( |
|
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; |
|
interactions_info_ |
regions per feature and their conditioning features. |
|
n_features_in_ |
number of features seen in |
|
feature_names_in_ |
their names — the DataFrame's columns, the
schema's |
|
baseline_ |
float
|
the average score over the training rows; a prediction
is this plus the row's |
gam_ |
float
|
the GAM reference (the same curve fitter, no partitions) —
set by the first |
Methods:
| Name | Description |
|---|---|
print_summary |
Print the model card: what this model is, on one screen. |
plot_curves |
Draw the model: one panel per feature, one curve per region. |
plot_importance |
Draw the features by importance, one bar each. |
print_prediction |
Print why the model predicts what it does for one row. |
plot_prediction |
Draw one prediction as a waterfall: from the baseline, one bar per |
curves |
The fitted curves as arrays, grouped by feature — what |
contributions |
What each feature adds to the baseline, row by row. |
importances |
Each feature's importance: its mean |contribution| over the |
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 | |
baseline_
property
The average score over the training rows; every prediction is this plus the row's contributions.
print_summary(X=None, y=None, *, max_features=20)
Print the model card: what this model is, on one screen.
calm.print_summary() # size, features by importance, partitions
calm.print_summary(X_test, y_test) # … and where it stands on that data
The card lists the model's size (curves, partitions, conditional interactions), every feature with its importance and the features its curves depend on, and each partition as a tree. Importance is the mean |contribution| of the feature over the training rows, read from the fitted curves.
With data, a where it stands block scores three models on it: a
GAM (the same curve fitter with no partitions), this CALM, and the
black-box teacher — and says how much of the GAM → black-box
distance CALM covers. The GAM is fitted on the training data the
first time it is needed and kept as gam_.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
|
None
|
|
y
|
|
None
|
|
max_features
|
int
|
rows of the feature table; the rest are summed up. |
20
|
Source code in calm_additive/engine.py
982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 | |
plot_curves(features=None, *, at=None, ncols=3, share_y=False, max_features=12, show_plot=True)
Draw the model: one panel per feature, one curve per region.
calm.plot_curves() # the most important features
calm.plot_curves("hour") # one feature, large
calm.plot_curves(at=X_test.iloc[0]) # … with one row marked on its curves
A curve is the feature's contribution to the prediction inside its region (the legend gives the region's rule and its number of training rows) and is drawn only where that region has data; the strip under a panel shows where the rows are. A prediction is the baseline plus one curve value per feature. Categorical features are drawn as one mark per level.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
features
|
a name or index (one large panel), a list of them, or
|
None
|
|
at
|
one row (as in |
None
|
|
ncols
|
int
|
panels per row. |
3
|
share_y
|
bool
|
if |
False
|
max_features
|
int
|
how many features |
12
|
show_plot
|
bool
|
if |
True
|
Source code in calm_additive/engine.py
1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 | |
plot_importance(*, max_features=15, show_plot=True)
Draw the features by importance, one bar each.
Importance is the mean |contribution| of the feature over the training rows, in the units of the prediction (log-odds for a classifier). The bar of a feature with a partition is cut into one segment per region: how much of its importance each region carries.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_features
|
int
|
bars drawn, most important first. |
15
|
show_plot
|
bool
|
if |
True
|
Source code in calm_additive/engine.py
1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 | |
print_prediction(x, *, max_features=10)
Print why the model predicts what it does for one row.
calm.print_prediction(X_test.iloc[0])
A ledger with one line per feature: its value, the rule that
selected its curve (for a feature with a partition), and what that
curve adds to the baseline. The lines add up to the prediction
exactly: baseline + Σ contributions. A classifier's lines are in
log-odds, the scale it is additive on, and the last line maps their
sum to the probability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
one row — a |
required | |
max_features
|
int
|
lines shown, largest |contribution| first; the rest are summed in one line. |
10
|
Source code in calm_additive/engine.py
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 | |
plot_prediction(x, *, max_features=10, show_plot=True)
Draw one prediction as a waterfall: from the baseline, one bar per feature, to the prediction.
The figure of print_prediction: the same numbers, largest
|contribution| first, each bar labelled with the feature's value and
the rule that selected its curve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
one row — a |
required | |
max_features
|
int
|
bars drawn; the rest are summed in one bar. |
10
|
show_plot
|
bool
|
if |
True
|
Source code in calm_additive/engine.py
1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 | |
curves()
The fitted curves as arrays, grouped by feature — what plot_curves draws.
Returns:
| Type | Description |
|---|---|
dict
|
|
Source code in calm_additive/engine.py
1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 | |
contributions(X)
What each feature adds to the baseline, row by row.
c = calm.contributions(X_test)
c.sum(axis=1) + calm.baseline_ # the model's score for every row
The score is the prediction for regression and the log-odds of the
positive class for a classifier (curve_fitter="ebm").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
|
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in calm_additive/engine.py
1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 | |
importances()
Each feature's importance: its mean |contribution| over the training rows, read from the fitted curves.
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in calm_additive/engine.py
1185 1186 1187 1188 1189 1190 1191 1192 | |