If the data don't need to be processed in batches by category, and if the category is derivable from an data item alone, I don't really see the benefit you're proposing.
Even worse, by splitting one state (and one derivable category from that state) into two separate arguments for do_thing, something can be off rather badly. I'd then feel the need to design an assertion of the relation of the arguments in order to make things bearable again:
def do_thing(category, data):
# to avoid shadowing lets rename your function `category` to `category_from_item`
assert all(category == category_from_item(item) for item in data)
...
But that would add a third loop to your two loops, and would duplicate the computation of a category.Instead, if category would be a property of data item:
class DataItem:
@property
def category(self):
...
...and the do_thing function would work on a single data item, then it'd just become a simple matter of one for loop and one match/case: def do_thing(item):
match item.category:
case ...:
...