Repository: ibis-project/ibis

Issue:
bug: polars backend doesn't make default non-column names when making new columns
### What happened?

When using DuckDB as a back-end, my experience is that I could write the following code, with the provided CSV, and obtain an output table with one column named `min(col)` and another named `max(col)`:

```
t = con.read_csv("small.csv")
aggs = []
aggs.append(t.col.min())
aggs.append(t.col.max())
t.aggregate(aggs).execute()
```
The CSV: 
```
col
1,
2,
```


With Polars, the system attempts to yield two columns with the name `col`, which leads to an error. Instead of this, I'd expect it to handle basic automatic naming without my intervention.

### What version of ibis are you using?

4.1.0

### What backend(s) are you using, if any?

Polars

### Relevant log output

```sh
---------------------------------------------------------------------------
DuplicateError                            Traceback (most recent call last)
Cell In[11], line 1
----> 1 t.aggregate(aggs).execute()

File ~/mambaforge/envs/mamba_ibis/lib/python3.10/site-packages/ibis/expr/types/core.py:300, in Expr.execute(self, limit, timecontext, params, **kwargs)
    273 def execute(
    274     self,
    275     limit: int | str | None = 'default',
   (...)
    278     **kwargs: Any,
    279 ):
    280     """Execute an expression against its backend if one exists.
    281 
    282     Parameters
   (...)
    298         Keyword arguments
    299     """
--> 300     return self._find_backend(use_default=True).execute(
    301         self, limit=limit, timecontext=timecontext, params=params, **kwargs
    302     )

File ~/mambaforge/envs/mamba_ibis/lib/python3.10/site-packages/ibis/backends/polars/__init__.py:279, in Backend.execute(self, expr, params, limit, **kwargs)
    272 def execute(
    273     self,
    274     expr: ir.Expr,
   (...)
    277     **kwargs: Any,
    278 ):
--> 279     df = self.compile(expr, params=params).collect()
    280     if isinstance(expr, ir.Table):
    281         return df.to_pandas()

File ~/mambaforge/envs/mamba_ibis/lib/python3.10/site-packages/polars/utils.py:391, in deprecated_alias.<locals>.deco.<locals>.wrapper(*args, **kwargs)
    388 @functools.wraps(fn)
    389 def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
    390     _rename_kwargs(fn.__name__, kwargs, aliases)
--> 391     return fn(*args, **kwargs)

File ~/mambaforge/envs/mamba_ibis/lib/python3.10/site-packages/polars/internals/lazyframe/frame.py:1165, in LazyFrame.collect(self, type_coercion, predicate_pushdown, projection_pushdown, simplify_expression, no_optimization, slice_pushdown, common_subplan_elimination, streaming)
   1154     common_subplan_elimination = False
   1156 ldf = self._ldf.optimization_toggle(
   1157     type_coercion,
   1158     predicate_pushdown,
   (...)
   1163     streaming,
   1164 )
-> 1165 return pli.wrap_df(ldf.collect())

DuplicateError: Column with name: 'col' has more than one occurrences
```


### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:

Resolving patch:
diff --git a/ibis/backends/polars/compiler.py b/ibis/backends/polars/compiler.py
--- a/ibis/backends/polars/compiler.py
+++ b/ibis/backends/polars/compiler.py
@@ -212,7 +212,7 @@ def aggregation(op):
         lf = lf.select
 
     if op.metrics:
-        metrics = [translate(arg) for arg in op.metrics]
+        metrics = [translate(arg).alias(arg.name) for arg in op.metrics]
         lf = lf(metrics)
 
     return lf


Test patch:
diff --git a/ibis/backends/tests/test_aggregation.py b/ibis/backends/tests/test_aggregation.py
--- a/ibis/backends/tests/test_aggregation.py
+++ b/ibis/backends/tests/test_aggregation.py
@@ -958,9 +958,6 @@ def test_agg_sort(alltypes):
 @pytest.mark.xfail_version(
     polars=["polars==0.14.31"], reason="projection of scalars is broken"
 )
-@pytest.mark.xfail_version(
-    polars=["polars>=0.15"], reason="projection of scalars is broken"
-)
 def test_filter(backend, alltypes, df):
     expr = (
         alltypes[_.string_col == "1"]
@@ -988,3 +985,10 @@ def test_column_summary(alltypes):
     expr = alltypes.aggregate(bool_col_summary)
     result = expr.execute()
     assert result.shape == (1, 7)
+
+
+def test_agg_name_in_output_column(alltypes):
+    query = alltypes.aggregate([alltypes.int_col.min(), alltypes.int_col.max()])
+    df = query.execute()
+    assert "min" in df.columns[0].lower()
+    assert "max" in df.columns[1].lower()