From 222709deb41c411e68fdfdbdb48172ee236d91c3 Mon Sep 17 00:00:00 2001 From: akramz Date: Sat, 31 Oct 2026 12:19:30 +0300 Subject: [PATCH] Reject a model whose class codes would be scored against label names A model fitted on 0/1 (e.g. is_crop) audited against labels "crop"/"noncrop" had its codes taken as positions in the sorted names, so crop was read as class 0 and the model was scored with its classes swapped (accuracy 0.99 printed as 0.01, no error). Codes now stand for labels only when the labels are numbers. Co-Authored-By: Claude Opus 5.5 --- pappi/core/pipeline.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pappi/core/pipeline.py b/pappi/core/pipeline.py index 00a554a..77ad91e 100644 --- a/pappi/core/pipeline.py +++ b/pappi/core/pipeline.py @@ -284,6 +284,11 @@ def _as_model(m, data: Dataset) -> Fitted: if not hasattr(m, "classes_") and not np.isnan(num).any() and num != sorted(num): raise ValueError(f"numeric labels sort as strings here {list(data.classes)}; give the " "model a classes_ attribute listing its column order") + ## Codes 0..k-1 stand for the labels only when the labels are numbers: a model fit on + ## is_crop 0/1 against labels "crop"/"noncrop" would be scored with its classes swapped. + if names == codes and names != list(data.classes) and np.isnan(num).any(): + raise ValueError(f"model classes {names} are codes but the labels are names " + f"{list(data.classes)}; fit the model on the label names") if names != list(data.classes) and (names != codes or misordered): raise ValueError(f"model classes {names} != label classes {list(data.classes)}") cols = getattr(m, "feature_names_in_", None)