ML Foundations
Avoid split label counts with itertools.groupby
itertools.groupby groups consecutive matching keys. Sort by the same key first when a global label summary is intended.
Evidence and testing
Review record: Tensor Almanac automated evidence gate (no independent human review). Example versions, recorded outputs and limitations appear below.
groupby follows runs in the input
`itertools.groupby` can look like a compact way to count records by label, but it does not collect every equal key from the entire input in one pass. It starts a new group whenever the key changes, so the same label can appear in multiple groups when its records are separated. Python's documentation describes this as grouping consecutive elements and notes that the input generally needs to be sorted by the same key when a global grouping is wanted. [1][2]
For example, a label sequence `cat, dog, cat` produces three runs. Treating those runs as three complete class summaries would split the two cat records and could make a small audit table misleading. That consequence is an application of the documented iterator behavior, not a claim that a model's training labels have been changed.
Sort by the grouping key for a global summary
If the goal is one count per label, sort the records by the same key function before passing them to `groupby`. The example contains four synthetic rows: the unsorted input produces two separate cat runs, while sorting first produces one cat group with three rows and one dog group with one row.
Sorting is appropriate when it fits the data and ordering requirements. It changes row order, so keep stable identifiers alongside the records and do not confuse a grouped summary with the original training order. If the stream must remain unsorted or cannot be reordered, use an aggregation method designed to accumulate keys across the whole input instead. Those are workflow choices built around the documented behavior.
Executed with Python 3.12.14 standard library only on 2026-10-25; four synthetic rows, no files, network or model framework.
"""Show that itertools.groupby combines adjacent labels, not all labels."""
from itertools import groupby
records = [
("cat", "row-0"),
("dog", "row-1"),
("cat", "row-2"),
("cat", "row-3"),
]
def label_counts(rows):
return [(label, sum(1 for _ in group)) for label, group in groupby(rows, key=lambda row: row[0])]
print("input label runs:", label_counts(records))
print("after sorting by label:", label_counts(sorted(records, key=lambda row: row[0])))
Recorded output
input label runs: [('cat', 1), ('dog', 1), ('cat', 2)]
after sorting by label: [('cat', 3), ('dog', 1)]Consume a group before advancing
The groups returned by `groupby` are iterators that share the underlying source. If an application needs to keep a group for later, it should materialize that group before asking `groupby` for the next one; otherwise the earlier group is no longer available as the shared iterator advances. [1][2]
This example consumes each group immediately while computing its count, which is why it does not store a list of every record in every class. It demonstrates run counting and sorting only. It does not benchmark memory, validate data labels, or determine whether reordering is safe for a particular model pipeline.