Skip to content

Commit 580aef6

Browse files
FrancescAltedclaude
andcommitted
Address Copilot review comments on UDF aggregations (Gap D2)
- _final_rows() was wrapping the UDF's return value in np.asarray() before passing it to _python_scalar(). Since _python_scalar() only unwraps np.generic (NumPy scalar types), a 0-D ndarray produced by that wrapping was left as-is instead of becoming a plain Python/ NumPy scalar. Pass the UDF result through directly instead. - _infer_udf_spec() silently inferred float64 from an empty results list (e.g. an empty table, or every group all-null so the UDF was never called), even though there's nothing to infer from. Now raises a clear error pointing at the explicit-dtype escape hatch, matching the existing inconsistent-types error. Added regression tests for both. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 76b7616 commit 580aef6

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

src/blosc2/groupby.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1800,7 +1800,7 @@ def _final_rows( # noqa: C901
18001800
else:
18011801
group_values = np.concatenate(chunks)
18021802
try:
1803-
result = _python_scalar(np.asarray(spec.udf(group_values)))
1803+
result = _python_scalar(spec.udf(group_values))
18041804
except Exception as exc:
18051805
raise RuntimeError(
18061806
f"UDF aggregation {spec.output_col!r} raised for group "
@@ -1835,6 +1835,16 @@ def _infer_udf_spec(results: list, name: str) -> SchemaSpec:
18351835
another) is caught here with a clear error, rather than surfacing as
18361836
an opaque failure while building the result table.
18371837
"""
1838+
if not results:
1839+
# No group ever produced a value (e.g. an empty table, or every
1840+
# group is all-null so the UDF was never called) -- there is
1841+
# nothing to infer a dtype from.
1842+
raise ValueError(
1843+
f"Cannot infer a CTable dtype for UDF aggregation {name!r}: it was never "
1844+
f"called (empty table, or every group had no non-null values). Pass an "
1845+
f"explicit dtype in the named-agg tuple, e.g. "
1846+
f"g.agg({name}=(col, fn, blosc2.float64()))."
1847+
)
18381848
try:
18391849
arr = np.asarray(results)
18401850
except ValueError as exc:

tests/ctable/test_groupby.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,34 @@ def test_agg_udf_unsupported_result_dtype_raises_clear_error():
906906
g.agg(x=("sales", lambda a: "always-a-string"))
907907

908908

909+
def test_agg_udf_never_called_raises_clear_error():
910+
# Every group has zero non-null "sales" values, so the UDF is never
911+
# called for any of them -- there is nothing to infer a dtype from.
912+
t = CTable(SalesRow, new_data=[("Paris", 1, np.nan, 0), ("Rome", 1, np.nan, 0)])
913+
g = t.group_by("city")
914+
915+
with pytest.raises(ValueError, match="it was never called"):
916+
g.agg(x=("sales", lambda a: a.max() - a.min()))
917+
918+
919+
def test_agg_udf_result_not_wrapped_in_zero_d_array(monkeypatch):
920+
# Regression test: the UDF result must reach _python_scalar() directly,
921+
# not pre-wrapped in np.asarray() -- a plain Python/NumPy scalar wrapped
922+
# in np.asarray() becomes a 0-D ndarray, which _python_scalar() (only
923+
# unwraps np.generic) then fails to turn back into a plain scalar.
924+
import blosc2.groupby as gb_module
925+
926+
seen_types = []
927+
original = gb_module._python_scalar
928+
monkeypatch.setattr(gb_module, "_python_scalar", lambda v: (seen_types.append(type(v)), original(v))[1])
929+
930+
t = CTable(SalesRow, new_data=[("Paris", 1, 10.0, 0), ("Rome", 1, 20.0, 0)])
931+
g = t.group_by("city")
932+
g.agg(x=("sales", lambda a: float(a.sum())))
933+
934+
assert np.ndarray not in seen_types
935+
936+
909937
def test_agg_udf_error_names_the_group_key():
910938
t = CTable(SalesRow, new_data=DATA)
911939
g = t.group_by("city")

0 commit comments

Comments
 (0)