Utilities¶
Helpers shared across the package. Public in the sense that they are importable and documented, but most callers will not need them.
thresher.utils ¶
Helpers shared across the package: label handling, option lookup and output.
as_sequence ¶
Give the solvers something they can measure, iterate twice and index - cheaply.
The solvers need three things from their input: len(), more than one pass over it,
and integer indexing. A list is the obvious way to guarantee all three, and building
one was what this used to do for anything that was not already a Sequence. That
quietly included the two types this library is built around: neither numpy.ndarray
nor pandas.Series is registered as a Sequence - they have no index or count -
so both were copied on the way in, at O(n), which is the allocation 0.5.3 removed
from optimize_threshold in the first place. hist holds a few kilobytes of counters
however large the data is, and was still paying 12 MB to receive 200,000 rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
Iterable[T]
|
the scores or the classes, however the caller holds them. |
required |
Returns:
| Type | Description |
|---|---|
Sequence[T]
|
The caller's own container where it already does what the solvers need, and a list |
Sequence[T]
|
built from it where it does not - a generator, a set, a dict view. A |
Sequence[T]
|
object is handed over as its underlying array, which is a view rather than a copy |
Sequence[T]
|
for the numeric dtypes a score column has. |
Source code in src/thresher/utils.py
validate_lengths ¶
Check that every score has a class to go with it.
The solvers pair the two with zip, which stops at the shorter sequence, so a
mismatch used to be absorbed in silence: six scores against four classes simply
discarded two scores and returned a threshold computed from the rest. That is a wrong
answer rather than a partial one, and nothing in the result hints at it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scores
|
Sequence[float]
|
the values being split. |
required |
actual_classes
|
Sequence[int]
|
the matching ground-truth classes. |
required |
Returns:
| Type | Description |
|---|---|
None
|
None. This is a guard - it either passes silently or raises. |
Raises:
| Type | Description |
|---|---|
LengthMismatchError
|
if the two differ in length. It is a |
Source code in src/thresher/utils.py
validate_scores ¶
Check that every score is a number a threshold can be placed against.
NaN is the case that matters. Every comparison against it is false, so score > t is
false for any threshold, and a NaN that reaches a solver is not merely ignored - it
propagates. exact sorted it into place and handed the NaN back as the answer, a
"threshold" that classifies the whole dataset negative; hist failed instead, but
with a bare ValueError out of its bin arithmetic. One NaN in a predict_proba
column is an ordinary upstream accident, so it is worth one pass to catch here.
None is treated the same way: it is the shape a blank takes in a plain Python list,
where pandas would have produced NaN, and it used to reach the sort and fail there
with a bare TypeError. The Spark interface refuses a null score for the same reason.
Infinities are left alone. They order correctly against everything else, so a
threshold can be placed relative to them, and exact handles them already.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scores
|
Sequence[float]
|
the values being split. |
required |
Returns:
| Type | Description |
|---|---|
None
|
None. This is a guard - it either passes silently or raises. |
Raises:
| Type | Description |
|---|---|
UndefinedScoresError
|
if any score is NaN. It is a |
Source code in src/thresher/utils.py
validate_actual_classes ¶
Check that the labels are usable before any algorithm runs.
This was previously a bare assert set(actual_classes) == {-1, 1}, which said nothing
about what was wrong - and, being an assertion, vanished entirely under python -O,
letting malformed input reach the solvers instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
actual_classes
|
Sequence[int]
|
the ground-truth classes, already normalized to -1 and 1. |
required |
Returns:
| Type | Description |
|---|---|
None
|
None. This is a guard - it either passes silently or raises. |
Raises:
| Type | Description |
|---|---|
(EmptyInputError, MissingLabelsError, UnexpectedLabelsError, SingleClassError)
|
for each of those cases in turn. All are |
Source code in src/thresher/utils.py
validate_label_mapping ¶
Check that a labels option is a usable mapping before anything relies on it.
Shared between Thresher.__init__, which validates the option the moment it is
given, and map_labels, which is what a caller mutating the live options dict
afterwards still runs into.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mapping
|
Any
|
the value of the |
required |
Returns:
| Type | Description |
|---|---|
list[Any] | tuple[Any, ...]
|
The mapping itself, now known to be an indexable two-item pair. |
Raises:
| Type | Description |
|---|---|
LabelMappingError
|
if |
Source code in src/thresher/utils.py
map_labels ¶
Translate caller-supplied labels into the internal -1 / 1 pair.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
labels
|
Iterable[Any]
|
the ground-truth classes as the caller provided them. |
required |
mapping
|
Iterable[Any]
|
a two-item list or tuple, negative label first, positive second -
the value of the |
required |
Yields:
| Type | Description |
|---|---|
int
|
-1 for each label matching |
Raises:
| Type | Description |
|---|---|
LabelMappingError
|
if |
Source code in src/thresher/utils.py
get_or_default ¶
Read one algorithm parameter, falling back to its default.
This does not report an unknown key - it cannot tell one from a key meant for a
different solver. dispatch.validate_algorithm_params is what rejects those, when
the Thresher is built and the algorithm is known.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
options
|
Mapping[str, Any]
|
the user-supplied |
required |
key
|
str
|
the parameter name to read. |
required |
default
|
T
|
the value to use when the key is absent. |
required |
Returns:
| Type | Description |
|---|---|
T
|
The value stored under |
Source code in src/thresher/utils.py
pairwise ¶
Yield consecutive overlapping pairs from an iterable.
pairwise([1, 2, 3]) yields (1, 2) then (2, 3).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
iterable
|
Iterable[T]
|
the values to pair up. |
required |
Returns:
| Type | Description |
|---|---|
Iterator[tuple[T, T]]
|
An iterator of adjacent pairs. It is empty for inputs shorter than two items, |
Iterator[tuple[T, T]]
|
which is why callers have to handle "no candidate found". |
Source code in src/thresher/utils.py
print_progress_bar ¶
print_progress_bar(
iteration: int,
total: int,
prefix: str = "",
suffix: str = "",
decimals: int = 1,
length: int = 100,
fill: str = "#",
) -> None
Draw one frame of the built-in progress bar. Moved to thresher.progress.
Kept here because it has been importable from this module since the first release.
Everything inside the package now goes through thresher.progress.make_progress,
which picks between this bar and tqdm.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
iteration
|
int
|
current iteration. |
required |
total
|
int
|
total number of iterations. The line is ended once the two match. |
required |
prefix
|
str
|
string printed before the bar. |
''
|
suffix
|
str
|
string printed after the bar. |
''
|
decimals
|
int
|
number of decimals in the percentage. |
1
|
length
|
int
|
character length of the bar. |
100
|
fill
|
str
|
bar fill character. |
'#'
|
Returns:
| Type | Description |
|---|---|
None
|
None. The bar is written to stderr - it was stdout until 0.8.0, which is the |
None
|
stream the command line prints its answer on. |