From f69dc9bd3ad17f28a5d48bf4655d3d6989f4b0d3 Mon Sep 17 00:00:00 2001 From: Tanuj Date: Sat, 10 Oct 2026 11:42:00 +0530 Subject: [PATCH] Fix #14: k_fold covers every row in exactly one test fold Spread the len % k remainder over the first folds instead of dropping it, and add coverage tests for n % k != 0 and the shipped dataset. Co-Authored-By: Claude Sonnet 5.5 --- mess_mood/evaluate.py | 9 ++++++--- tests/test_mess_mood.py | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/mess_mood/evaluate.py b/mess_mood/evaluate.py index 4dc9ead..9c84482 100644 --- a/mess_mood/evaluate.py +++ b/mess_mood/evaluate.py @@ -16,11 +16,14 @@ def k_fold(rows, k=5, seed=42): """Yields (train, test) k times. Every row is in exactly one test set.""" rows = rows[:] random.Random(seed).shuffle(rows) - fold_size = len(rows) // k + fold_size, extra = divmod(len(rows), k) + start = 0 for i in range(k): - test = rows[i * fold_size:(i + 1) * fold_size] - train = rows[:i * fold_size] + rows[(i + 1) * fold_size:] + end = start + fold_size + (i < extra) + test = rows[start:end] + train = rows[:start] + rows[end:] yield train, test + start = end def fit(rows): diff --git a/tests/test_mess_mood.py b/tests/test_mess_mood.py index 5c9dbc6..ba0a6a8 100644 --- a/tests/test_mess_mood.py +++ b/tests/test_mess_mood.py @@ -68,6 +68,29 @@ def test_k_fold_test_sets_dont_overlap(): assert all(not (a & b) for i, a in enumerate(tests) for b in tests[i + 1:]) +def test_k_fold_every_row_in_exactly_one_test_set(): + from collections import Counter + + for n in (23, 24, 21, 7): + rows = [(str(i), "x") for i in range(n)] + folds = list(k_fold(rows, k=5)) + assert len(folds) == 5 + assert Counter(r for _, test in folds for r in test) == Counter(rows) + for train, test in folds: + assert sorted(train + test) == sorted(rows) + assert not set(train) & set(test) + + +def test_k_fold_covers_every_shipped_review(): + from collections import Counter + + rows = load_reviews() + folds = list(k_fold(rows, k=5)) + assert Counter(r for _, test in folds for r in test) == Counter(rows) + sizes = [len(test) for _, test in folds] + assert max(sizes) - min(sizes) <= 1 + + def test_model_save_and_load(tmp_path): from mess_mood.model import NaiveBayes