Skip to content

Byrow-aware literal handling for @subset / @rsubset (#259)#420

Open
kimjune01 wants to merge 4 commits into
JuliaData:masterfrom
kimjune01:fix-subset-literal-259
Open

Byrow-aware literal handling for @subset / @rsubset (#259)#420
kimjune01 wants to merge 4 commits into
JuliaData:masterfrom
kimjune01:fix-subset-literal-259

Conversation

@kimjune01

@kimjune01 kimjune01 commented May 12, 2026

Copy link
Copy Markdown

Summary

@subset(df, true) previously threw an opaque MethodError (no fun_to_vec dispatch for literals), which is the bug in #259.

This handles literals in a way that mirrors the underlying DataFrames.subset exactly, keyed on the @byrow flag:

  • @subset(df, true) (not byrow): a bare literal is a scalar predicate, which subset rejects just as subset(df, [] => Returns(true)) does. It now raises a clear ArgumentError in place of the MethodError.
  • @rsubset(df, true) (byrow): a per-row literal is well defined and matches subset(df, [] => ByRow(Returns(true))), which works, so it returns the expected rows.

ByRow is applied only when the @byrow flag is set. Applying it unconditionally (the first approach here) made @subset accept what the function rejects, which is the divergence raised in review.

Test plan

  • @subset(df, true) / false / grouped / @subset! raise at macro expansion (asserted via @test_throws LoadError @eval ..., matching the suite's convention for invalid expansions).
  • @rsubset(df, true) returns all rows, @rsubset(df, false) returns none, including the grouped case.
  • Real column predicates (@subset(df, :A .> 1)) unaffected.

There was no test coverage for literal predicates in either mode before, so this adds it for both.

kimjune01 added 2 commits May 11, 2026 21:49
Add support for literal boolean values (and other literals) in @subset
by implementing a catch-all fun_to_vec method that wraps literals in
Returns. This allows expressions like @subset(df, true) or
@subset(df, false) to work correctly.

The fix uses Returns to create a constant function from literal values,
which is only allowed in no_dest contexts (@subset, @rsubset, @with).

Tests added to verify:
- @subset(df, true) returns all rows
- @subset(df, false) returns empty dataframe
- Literals combine correctly with other conditions
The original fix used `[] => Returns(true)` which returns a scalar Bool.
DataFrames.subset wraps non-ByRow functions with assert_bool_vec, which
throws ArgumentError when the function returns a non-vector value.

Fix: use `[] => ByRow(Returns(ex))` instead. ByRow bypasses the
assert_bool_vec check (assert_bool_vec(::ByRow) = identity) and
DataFrames' _empty_selector_helper calls the inner function once per
row, producing a proper boolean vector.

Also use $(Base.Returns) interpolation to avoid depending on caller
scope resolution.

Adds tests for @subset!, grouped DataFrames with literals.
@pdeffebach

Copy link
Copy Markdown
Collaborator

Thanks! But I'm not 100% sure this is right. @subset(df, true) should error, as subset(df, [] => Returns(true)) errors, right?

A bare literal like `@subset(df, true)` lowers to a scalar predicate,
which `DataFrames.subset` rejects exactly as `subset(df, [] => Returns(true))`
does. Accepting it (via `ByRow(Returns(...))`) made the macro diverge from
the function and would mean different things across macros (`@select(df, 1)`
vs `@subset(df, true)`).

Replace the prior MethodError from JuliaData#259 with a clear ArgumentError pointing
at the column-expression form. Tests assert the expansion-time error.
@kimjune01 kimjune01 changed the title Fix @subset to accept literal boolean values Raise a clear error for literal subset predicates (#259) May 22, 2026
@kimjune01

Copy link
Copy Markdown
Author

Good call. Reworked it to raise a clear ArgumentError instead of broadcasting, so the macro stays consistent with subset(df, [] => Returns(true)); if you want literals as a real feature later, that's the scoped allow_literals / ^(true) path from #259.

Comment thread src/parsing.jl
outer_flags::Union{NamedTuple, Nothing}=nothing,
allow_multicol::Bool = false)
if no_dest
return :([] => $ByRow($(Base.Returns)($ex)))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you need to have ByRow here only if byrow = true?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like my fix was overly permissive. I was hoping tests passing would be good enough, but I was wrong. Added tests for both modes so to increase coverage.

ByRow only applies under the @byrow flag, so behaviour mirrors the
underlying subset in both modes:
  @subset(df, true)  -> errors, like subset(df, [] => Returns(true))
  @rsubset(df, true) -> all rows, like subset(df, [] => ByRow(Returns(true)))

Applying ByRow unconditionally (prior approach) made @subset accept what
the function rejects. Tests now cover both modes; master had none for
literal predicates either way.
@kimjune01 kimjune01 changed the title Raise a clear error for literal subset predicates (#259) Byrow-aware literal handling for @subset / @rsubset (#259) May 22, 2026
@pdeffebach

Copy link
Copy Markdown
Collaborator

Can we allow literals for non-by-row subsetting as well?

julia> df = DataFrame(x = [1, 2, 3])
3×1 DataFrame
 Row │ x     
     │ Int64 
─────┼───────
   1 │     1
   2 │     2
   3 │     3

julia> b = [true, false, true]
3-element Vector{Bool}:
 1
 0
 1

julia> @subset df b
ERROR: LoadError: ArgumentError: literal value `b` is not a valid column expression; pass a column reference or a vectorised predicate (e.g. `:x .> 0`), or use `@rsubset` for a per-row literal

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants