Repository navigation
feat: Implement list.contains for compatible types - #143
jonasdedden wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
Thanks for the contribution @jonasdedden
Although I understand the request, for me we should reject implementations that require custom apply and UDF's. Those are very likely a performance killer, and we never want to be the bottleneck for a user.
I would instead consider opening a feature request in daft for a more generic list_contains support
|
Thanks for the review @FBruzzesi ! A general question as I've never contributed to narwhals: what's preferred, support for as many expressions first, and focus on performance as a second step? Or instead only support those expressions that are very likely to give optimal/native performance and live with suboptimal expression support? I'm not at home currently, but maybe something's possible to try to use Daft's native list_contains, and only fall back on a Python UDF on type mismatches? |
No worries, that's something we might want to document better. I personally think we should stick to that. We have been opening feature requests (and bugs) upstream in many many cases, asking for a feature to be supported natively, so this would not be a first time doing so.
That's definitely possible, but I would rather just wrap |
list.contains for compatible types
|
The UDF is gone. For now I replaced it with a type-checking and then wrapping solution. The list's element dtype is resolved against the frame schema at plan time (a new |
list.contains for compatible typeslist.contains for compatible types
Daft's native list_contains requires exact type match (Int64 item vs Int32 list errors), so use expr.apply with Python 'in', which handles all dtypes and propagates nulls. Unblocks test_contains_expr.
Daft requires the searched item to have exactly the list's element type, so resolve that type against the frame's schema at plan time and cast the literal to it when the cast is lossless (integral, in range). Values that can never be an element (e.g. 1.5 for an Int8 list) yield False, and `contains(None)` uses the null count. This replaces the row-wise Python UDF.
9b6fdfa to
b55c655
Compare
|
thanks! could we just keep it simple and let Daft be as strict as it wants to be? (and/or raise a request with them to make it more relaxed?) i'd rather that than add a complex workaround here |
Let Daft reject any other item/element dtype mismatch, such as a microsecond datetime on a nanosecond list, matching Polars.
For sure, but just so to check again, I had a look to what actually would be required to have full test compliance even with the not-yet-merged PRs narwhals-dev/narwhals#4001 and narwhals-dev/narwhals#3915 (which both add a few tests for There were only 3 tiny commits (b296810 + 69b247d + c7f9dea, now called "fixes") required to have full compliance:
=> @MarcoGorelli I know you already pointed out you'd like a simple wrapper solution, but could you confirm once again that you want this (even with test compliance shortcomings)? 🙏 Sorry for the trouble!!! It's just that IMHO the logic in this PR is still relatively simple and we still could gradually remove it again once upstream issues are fixed. |
Daft's native
list_containsrequires exact type match (Int64item vsInt32list errors), so this usesexpr.applywith Pythonin, which handles all dtypes and propagates nulls.Requires #138 (narwhals pin update) first. The newly added exclusions in
run_tests.py(categorical coverage etc.) come from the pin update, not from this change. Upstream added tests since the old pin that fail onmaineither way.Verified green on the new pin. Ruff and pyright clean.