effector.proposers
The proposer protocol
Inside the built-in finders, candidate enumeration is its own seam: a proposer is any object exposing
propose(ctx: SearchContext, foc: int) -> list[CandidateSplit]
where each CandidateSplit is an ordered tuple of disjoint, jointly-covering
Conditions on the conditioning feature foc — a split expressed as
parent rule → child conditions. Child i of a parent region is
parent_rule.refine(conditions[i]) with mask
parent_mask & conditions[i].contains(data); candidates are
parent-independent (a level-wise finder applies one candidate to every node
of a level) and k-way by construction (len(conditions) >= 2).
The defaults reproduce the classic search — ContinuousThreshold (binary
x < t / x >= t on an interior grid) and CategoricalOneVsRest
({v} vs the explicit complement over the observed levels). The richer
built-ins are selected by name from a finder's constructor:
effector.space_partitioning.Best(
categorical_proposer="ordered", # "one_vs_rest" | "subsets" | "ordered" | "multiway"
continuous_proposer="quantiles", # "threshold" | "quantiles"
)
Both kwargs also accept a proposer instance (for non-default parameters,
e.g. CategoricalOrdered(order=[2.0, 0.0, 1.0])), and any custom object
exposing propose. The raw seam underneath is the finder's
proposer_factory attribute (feature type -> proposer), which
make_proposer_factory builds from the two kwargs.
API
effector.proposers.SearchContext(data, axis_limits, feature_types, numerical_grid_size)
dataclass
Everything a proposer may read; built once per split search.
effector.proposers.CandidateSplit(conditions)
dataclass
An ordered tuple of disjoint, jointly-covering conditions on one feature — the children of any parent split by this candidate.
effector.proposers.ContinuousThreshold
Binary threshold splits — the continuous default ("threshold").
One candidate (x < t, x >= t) per interior position t of a uniform
grid over the axis limits (numerical_features_grid_size segments, so
grid_size - 1 candidates).
Methods:
| Name | Description |
|---|---|
propose |
|
propose(ctx, foc)
Source code in effector/proposers.py
90 91 92 93 94 95 96 97 98 99 100 101 | |
effector.proposers.CategoricalOneVsRest
One level vs all the others — the categorical default ("one_vs_rest").
One binary candidate ({v}, universe - {v}) per observed level v,
ascending. The complement is materialized as an explicit LevelSet over
the observed universe (this proposer owns the != semantics).
Methods:
| Name | Description |
|---|---|
propose |
|
propose(ctx, foc)
Source code in effector/proposers.py
118 119 120 121 122 123 124 125 126 127 128 | |
effector.proposers.CategoricalSubsets(max_levels=8)
Every binary subset-vs-complement split over the observed levels ("subsets").
Each unordered {S, complement} pair appears exactly once: the smallest
level is pinned to the first child, subsets enumerate size-ascending (ties
in the finder's argmin therefore prefer simpler splits), lexicographic
within a size — so the first candidates are exactly the one-vs-rest
singletons.
Exponential in the level count
2^(K-1) - 1 candidates for K levels: above max_levels the
proposal degrades to the one-vs-rest list with a UserWarning — the
feature stays searchable instead of silently vanishing from the
candidate set.
Initialize the proposer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_levels
|
int
|
Above this many observed levels, fall back to the one-vs-rest candidates (with a warning). Must be >= 2. |
8
|
Methods:
| Name | Description |
|---|---|
propose |
|
Source code in effector/proposers.py
161 162 163 164 165 166 167 168 169 170 | |
propose(ctx, foc)
Source code in effector/proposers.py
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | |
effector.proposers.CategoricalOrdered(order='auto')
Contiguous cuts after ordering the levels ("ordered").
K - 1 binary prefix/suffix splits for K observed levels — both
sides explicit LevelSets. Linear in the level count, so it scales where
CategoricalSubsets explodes; the price is that only order-contiguous
groupings are reachable.
Initialize the proposer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order
|
How to order the levels, resolved per feature at propose time:
|
'auto'
|
Methods:
| Name | Description |
|---|---|
propose |
|
Source code in effector/proposers.py
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | |
propose(ctx, foc)
Source code in effector/proposers.py
256 257 258 259 260 261 262 263 264 265 266 267 268 269 | |
effector.proposers.CategoricalMultiway
A single k-way candidate: one child per observed level ("multiway").
One LevelSet({v}) child per observed level, ascending. Fewer than 2
observed levels proposes nothing (a 1-condition candidate is not a split).
Methods:
| Name | Description |
|---|---|
propose |
|
propose(ctx, foc)
Source code in effector/proposers.py
138 139 140 141 142 | |
effector.proposers.ContinuousQuantiles(max_children=5)
K-way splits at the marginal quantiles ("quantiles").
One k-way candidate per child count k in 2..max_children, ascending
(ties in the finder's argmin therefore prefer fewer children): the children
cut the conditioning column at its quantiles i/k — a jointly-covering
chain of half-open Intervals over (-inf, inf).
Edges are data-driven (x-only): duplicate quantiles collapse, so a
candidate may end up with fewer than k children; edges at the column
minimum are dropped (they would bound an empty first child — a constant
column therefore proposes nothing); identical edge chains produced by
different k are proposed once. Both ctx.numerical_grid_size (the
threshold-grid knob) and ctx.axis_limits are ignored —
max_children is this proposer's own knob and the unbounded chain
covers any axis.
Initialize the proposer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_children
|
int
|
Largest child count to propose (one candidate per
|
5
|
Methods:
| Name | Description |
|---|---|
propose |
|
Source code in effector/proposers.py
290 291 292 293 294 295 296 297 298 299 | |
propose(ctx, foc)
Source code in effector/proposers.py
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | |
effector.proposers.default_proposer(feature_type)
The finder default: one-vs-rest for categorical conditioning features, binary threshold for continuous ones.
Source code in effector/proposers.py
322 323 324 325 326 327 | |
effector.proposers.make_proposer_factory(categorical='one_vs_rest', continuous='threshold')
Build a finder's feature type -> proposer factory from one
categorical and one continuous proposer spec (names or instances).
Source code in effector/proposers.py
361 362 363 364 365 366 367 368 369 370 371 372 | |