## TL;DR
This error means the function you passed to `.agg` or `.apply` returned a Series or DataFrame instead of a single scalar per group. Fix it by making your function return one scalar value (a float, int, string) per group. It happens because pandas expected a reduction (many rows to one value) and got something else back.

```text
ValueError: Function does not reduce
```

## Use this when
- `df.groupby('col').agg(my_func)` raises `ValueError: Function does not reduce`
- A lambda in `.agg` or `.apply` returns a Series instead of a scalar
- Groupby aggregation returns an unexpected shape or errors after a pandas upgrade

## Not for
- Basic groupby how-to, use the pandas groupby docs
- `.transform` usage (returns one value per row, not per group)
- Groupby performance problems, different topic

## Steps

1. Find the offending function. Reproduce on one group:

```python
df.groupby('col').get_group(df['col'].iloc[0]).pipe(my_func)
```
Expected output: you will see it returns a Series/DataFrame, not a scalar.

2. Rewrite the function to return a single scalar:

```python
def my_func(s):
    return float(s.max() - s.min())
```
Expected output: calling `my_func` on one group's column returns one number.

3. Rerun the aggregation:

```python
df.groupby('col').agg(my_func)
```
Expected output: one row per group, one column per aggregation.

4. If you actually wanted a Series back per group, you meant `.apply`, not `.agg`:

```python
df.groupby('col').apply(my_func)
```
Expected output: a result indexed by group, with the Series your function returns.

5. Check the pandas version if old code broke:

```python
import pandas as pd; pd.__version__
```
Expected output: the version. In pandas 2.x, `.agg` is stricter about what counts as a reduction than in 1.x.

## Variant phrasings

### groupby agg ValueError function does not reduce
The canonical case. Your function returned non-scalar output to `.agg`.

### function does not reduce after upgrading pandas
Pandas 2.x tightened the rules: functions that happened to work in 1.x because of lenient coercion now raise. Wrap or rewrite to return a scalar explicitly.

### custom aggregation function groupby error
If the function works outside groupby but fails inside it, the mismatch is the group Series it receives. Test it standalone on a slice (step 1) to see.

## Why it happens
`.agg` is a reduction: it feeds each group to your function and expects exactly one scalar back per group. If your function returns a Series, pandas cannot collapse that into the single-row-per-group result, so it raises. `.apply` is the flexible sibling that accepts any return shape.

## Edge cases
- String-returning functions are fine as long as it is one string per group.
- Returning `np.nan` or None counts as scalar, no error.
- Mixed `agg` dicts like `{'a': 'sum', 'b': my_func}`: only the custom function needs to reduce.
- If you need multiple scalars per group, return a dict or use named aggregation: `df.groupby('col').agg(total=('x','sum'), spread=('x', my_func))`.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_AOQ-OdCIQPXj3TYZmcHbJg
