Split refuses, class missing from eval¶
Symptoms¶
typedecide split (or group_split) exits with status 2 and:
error: A split at eval_fraction=0.2 with seed=0 would leave 2 class(es) with zero examples in eval, so the score would say nothing about them:
queue/legal: 3 row(s) across 3 group(s)
severity/critical: 4 row(s) across 1 group(s) -- one group, so no group split can ever put it on both sides
Fix: raise eval_fraction, try another seed, collect more examples of those classes, or -- if a class genuinely lives in one group -- merge or drop it before splitting.
Each line is <criterion key>/<answer id>, its labelled row count, and how many
groups those rows span.
Related refusals from the same function:
All N rows share the group '...': there is only one group.eval_fraction=... must be strictly between 0 and 1.Cannot split an empty dataset.
Diagnosis¶
The refusal is deliberate: a class with no eval examples is a class the score says
nothing about, and balanced_accuracy would silently average over fewer classes.
Read the group count on each line. It decides the fix.
| Line says | Meaning |
|---|---|
across 1 group(s) -- one group... |
Structural. No seed or fraction can help |
| A few rows across a few groups | The class is rare. A different seed may work; more data is the real answer |
| Many rows across many groups, and the dataset is small | eval_fraction is too small for this many classes |
validate gives the same information earlier, as rare_class and unused_option
warnings:
Fix¶
In order of preference:
-
Collect more examples of the rare classes. A class with three examples cannot be both trained on and measured, whatever the split does.
-
Merge or drop a class that cannot be supported. If
legalhas three rows, fold it intootherat the source, or remove those rows and the option. Remember that every row sharing a criterion key must list the same option set. -
Fix the grouping if it is too coarse. A class living in one group often means
group_idis a customer or batch, not a ticket. Group by the thing that must not leak, which is usually the state. -
Raise
eval_fraction. -
Try other seeds, as a last resort, and record the one you used:
from typedecide import group_split, load_decisions from typedecide.errors import DataError decisions = load_decisions("tickets.csv") for seed in range(50): try: train, held = group_split(decisions, eval_fraction=0.2, seed=seed) except DataError: continue print("seed", seed, len(train), len(held)) break else: raise SystemExit("no seed in 0..49 covers every class; the data needs fixing")
Seed-hunting selects a split
Picking the first seed that passes is mild. Picking the seed that gives the best score is not. Choose the seed before you look at any model output, and keep it.
Do not work around the refusal with a row-level split
Splitting rows yourself puts the same state on both sides whenever rows share evidence, and the resulting eval score measures memorisation.
Verify¶
typedecide split tickets.csv --out data/ --eval-fraction 0.3 --seed 0
typedecide validate data/eval.jsonl
- The split prints row counts for both files and exits
0. - In the eval report's
label balance, every class you care about has a non-zero count. -
No group appears on both sides: