feat: accept column names for aggregate function inputs - #1783
richacode007-byte wants to merge 4 commits into
Conversation
|
Thank you for the PR. The description reads like it's got quite a bit of AI slop in it. Could you make this more human facing? We've also got some conflicts that need to be resolved. |
|
@timsaucer Issue are resolved , Pls review them |
timsaucer
left a comment
There was a problem hiding this comment.
Looks pretty good. A thing my agent picked up on:
- python/datafusion/functions/__init__.py:5755 (low): PR missed grouping().
Signature still expression: Expr, body still calls expression.expr
(line 5811). So F.grouping("a") fails with AttributeError: 'str' object has
no attribute 'expr', not the clear TypeError PR promises. Also contradicts
new note in .ai/skills/make-pythonic/SKILL.md saying aggregate column inputs
take Expr | str. Fix: type it Expr | str, call _to_raw_expr(expression).
| **IMPORTANT:** In `functions.py`, string arguments almost never mean column names. Functions operate on expressions, and column references should use `col()`. Category C applies mainly to DataFrame methods and context APIs, not to scalar/aggregate/window functions. Do NOT convert string arguments to column expressions in `functions.py` unless there is a very clear reason to do so. | ||
|
|
||
| The documented exception is the column inputs of aggregate functions (`sum`, `avg`, `count`, `corr`, the `regr_*` family, and so on). There a string can only mean a column, so they accept `Expr | str` via `_to_raw_expr()`. Their literal arguments (for example `string_agg`'s `delimiter` or `nth_value`'s `n`) are unaffected. | ||
|
|
There was a problem hiding this comment.
It sounds like we've got a fairly large list of "exceptions" to the rule here, so maybe the rule needs revisiting instead. Maybe we need to rewrite this to explain when it is appropriate to turn bare strings into literals and when to treat it as column names.
| def test_aggregate_rejects_non_column_input(build) -> None: | ||
| with pytest.raises(TypeError, match="Expected Expr or column name"): | ||
| build() |
There was a problem hiding this comment.
What do you think is the value added of this kind of test?
| def test_string_agg_accepts_column_name() -> None: | ||
| ctx = SessionContext() | ||
| df = ctx.from_pydict({"a": ["one", "two", "three"], "b": [2, 0, 1]}) | ||
|
|
||
| result = df.aggregate([], [f.string_agg("a", ",", order_by="b").alias("v")]) | ||
|
|
||
| assert result.collect()[0].to_pydict() == {"v": ["two,three,one"]} |
There was a problem hiding this comment.
Could this not have been one of the variants in the previous test?
| args = [_to_raw_expr(arg) for arg in expressions] | ||
| else: | ||
| args = [expressions.expr] | ||
| args = [_to_raw_expr(expressions)] |
There was a problem hiding this comment.
My agent picked up on something I didn't. It's review:
The one to decide: count("*"). SQL users will write this. With the PR, "*"
becomes a column named * and fails at execution with Schema error: No field
named "*". Before, it failed right away with AttributeError. It's not a
regression, but the PR invites the call. Two options:
- Treat "*" as count() (same as count(lit(1))).
- Reject it at build time with a TypeError pointing to count().
I'd pick the first: it matches SQL and costs one line. Either way, add a test.
Which issue does this PR close?
Closes #1757.
Rationale for this change
Right now you have to write
F.sum(col("a"))to sum a column, even though DataFrame methods likeselectandaggregatealready accept plain column names. This PR lets aggregate functions take a column name too, soF.sum("a")just works.I kept this to aggregate inputs only. The make-pythonic skill warns against treating strings as columns in
functions.py, since for things likeconcatorreplacea string is usually a literal value. For an aggregate's input, a string can only mean a column, so there's no ambiguity.What changes are included in this PR?
sum,avg,min,max,count,corr,regr_*,first_value,bit_and,string_agg, etc.) now accept a column name wherever they took anExprfor a column. Strings go through the existing_to_raw_exprhelper, and anything that isn't anExpror a string still raisesTypeError.countalso accepts a list of names, and the percentile functions accept a name forsort_expressionandweight.filter=,string_agg's delimiter,nth_value'sn, andcount_star.col(...), and that bad inputs likeF.sum(1)still raise.main. The newany_valuealso accepts a column name, and the newdistinct=options are kept.All tests, doctests and ruff checks pass locally.
Side note: on
main,F.count([col("a"), col("b")])already fails because the Rust binding only takes one expression. I didn't touch that here, but I'm happy to open a separate issue.Are there any user-facing changes?
Yes, but nothing breaks. You can now write
F.sum("a")orF.corr("a", "b"), and existing code that passesExprworks exactly as before.