Repository: pola-rs/polars (language: python)

Issue:
`GatherExpr` returns incorrect statistics in groupby context
### Checks

- [X] I have checked that this issue has not already been reported.
- [X] I have confirmed this bug exists on the [latest version](https://crates.io/crates/polars) of Polars.

### Reproducible example

```rust
use polars::df;
use polars::prelude::*;

fn main() -> Result<(), PolarsError> {
    // Initialize the Polars DataFrame
    std::env::set_var("POLARS_FMT_TABLE_CELL_LIST_LEN", "100");
    std::env::set_var("POLARS_FMT_STR_LEN", "100");
    let df = df![
        "foo" => ["baz",],
    ]?;

    let expr = int_range(1.into(), 6.into(), 1, DataType::UInt64).gather(int_range(
        0.into(),
        4.into(),
        1,
        DataType::UInt64,
    ));

    // Perform lazy operations
    let result: DataFrame = df
        .lazy()
        .group_by([col("foo")])
        .agg(&[
            expr.clone().median().alias("median"),
            expr.clone().len().alias("len"),
            expr.clone().alias("list"),
        ])
        .collect()?;

    // Print the result DataFrame
    println!("{:?}", result);

    Ok(())
}
```



### Log output

```shell
shape: (1, 4)
┌─────┬────────┬─────┬──────────────┐
│ foo ┆ median ┆ len ┆ list         │
│ --- ┆ ---    ┆ --- ┆ ---          │
│ str ┆ f64    ┆ u32 ┆ list[u64]    │
╞═════╪════════╪═════╪══════════════╡
│ baz ┆ 1.0    ┆ 1   ┆ [1, 2, 3, 4] │
└─────┴────────┴─────┴──────────────┘
```


### Issue description

Hello Polars maintainers,

I find the behaviour of gather-related Exprs very confusing. I am not sure if this is a bug or an undocumented feature.
We use polars in data-preprocessing pipelines.
When in groupby context, the statistics, len, and the evaluated expr itself are obviously inconsistent.
In the minimal example above, the length of the expression should be 4, but the `expr.len()` evaluates to 1.

Since "foo" column only has one value, We expect `groupby().agg(...)` behaviour to be consistent with `.select(...)`, which returns 4 as length.



Below is a slightly more complex example
Input
```rust
fn main() -> Result<(), PolarsError> {
    // Initialize the Polars DataFrame
    std::env::set_var("POLARS_FMT_TABLE_CELL_LIST_LEN", "100");
    std::env::set_var("POLARS_FMT_STR_LEN", "100");
    let df = df![
        "foo" => ["baz", "baz", "baz", "baz", "baz"],
        "bar" => [0, 1, 2, 3, 4]
    ]?;

    let expr = int_range(1.into(), 5.into(), 1, DataType::UInt64)
        .repeat_by(int_range(1.into(), 5.into(), 1, DataType::UInt64))
        .explode();

    // Perform lazy operations
    let result: DataFrame = df
        .lazy()
        .group_by([col("foo")])
        .agg(&[
            col("bar").gather(expr.clone()).median().alias("median"),
            col("bar").gather(expr.clone()).len().alias("len"),
            col("bar").gather(expr.clone()).alias("list"),
        ])
        .collect()?;

    // Print the result DataFrame
    println!("{:?}", result);

    Ok(())
}
```
stdout
```
shape: (1, 4)
┌─────┬────────┬─────┬────────────────────────────────┐
│ foo ┆ median ┆ len ┆ list                           │
│ --- ┆ ---    ┆ --- ┆ ---                            │
│ str ┆ f64    ┆ u32 ┆ list[i32]                      │
╞═════╪════════╪═════╪════════════════════════════════╡
│ baz ┆ 2.0    ┆ 5   ┆ [1, 2, 2, 3, 3, 3, 4, 4, 4, 4] │
└─────┴────────┴─────┴────────────────────────────────┘
```

### Expected behavior

`expr.len()` to be equal to length of collected `expr`

### Installed versions

<details>
```
polars = { version = "0.44.1", features = ["lazy", "range", "array_to_struct", "repeat_by"]}
```
</details>



Discussion / hints:
Python repro.

```python
import polars as pl

pl.Config(
    fmt_table_cell_list_len=10, 
    fmt_str_lengths=100
)

N = 4

df = pl.DataFrame({
    "foo": ["baz"] * N,
    "bar": range(N)
})

idxs = pl.int_range(1, N).repeat_by(pl.int_range(1, N)).flatten()
gather = pl.col.bar.gather(idxs)

df.group_by("foo").agg(gather, len1 = idxs.len(), len2 = gather.len())

# shape: (1, 4)
# ┌─────┬────────────────────┬──────┬──────┐
# │ foo ┆ bar                ┆ len1 ┆ len2 │
# │ --- ┆ ---                ┆ ---  ┆ ---  │
# │ str ┆ list[i64]          ┆ u32  ┆ u32  │
# ╞═════╪════════════════════╪══════╪══════╡
# │ baz ┆ [1, 2, 2, 3, 3, 3] ┆ 6    ┆ 4    │
# └─────┴────────────────────┴──────┴──────┘
```


Fix patch:
diff --git a/crates/polars-expr/src/expressions/gather.rs b/crates/polars-expr/src/expressions/gather.rs
index bd602a15cd34..a6450bcb531b 100644
--- a/crates/polars-expr/src/expressions/gather.rs
+++ b/crates/polars-expr/src/expressions/gather.rs
@@ -84,7 +84,7 @@ impl PhysicalExpr for GatherExpr {
         };
 
         ac.with_series(taken.into_series(), true, Some(&self.expr))?;
-        ac.with_update_groups(UpdateGroups::WithGroupsLen);
+        ac.with_update_groups(UpdateGroups::WithSeriesLen);
         Ok(ac)
     }
 


Test patch:
diff --git a/py-polars/tests/unit/operations/test_gather.py b/py-polars/tests/unit/operations/test_gather.py
index 4c74f60a7ff9..1d7cc1a85fef 100644
--- a/py-polars/tests/unit/operations/test_gather.py
+++ b/py-polars/tests/unit/operations/test_gather.py
@@ -201,3 +201,15 @@ def test_gather_array_outer_validity_19482() -> None:
     expect = pl.Series([[1], None], dtype=pl.Array(pl.Int64, 1))
     assert_series_equal(s, expect)
     assert_series_equal(s.gather([0, 1]), expect)
+
+
+def test_gather_len_19561() -> None:
+    N = 4
+    df = pl.DataFrame({"foo": ["baz"] * N, "bar": range(N)})
+    idxs = pl.int_range(1, N).repeat_by(pl.int_range(1, N)).flatten()
+    gather = pl.col.bar.gather(idxs).alias("gather")
+
+    assert df.group_by("foo").agg(gather.len()).to_dict(as_series=False) == {
+        "foo": ["baz"],
+        "gather": [6],
+    }