-
Notifications
You must be signed in to change notification settings - Fork 101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add Series.__contains__
#1480
Changes from 4 commits
e36d4ed
e6d9419
9a90632
3aa5b75
c653e48
3f66045
c9f0b36
b2b005a
38c5833
634c262
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -851,6 +851,11 @@ def rolling_mean( | |
def __iter__(self: Self) -> Iterator[Any]: | ||
yield from self._native_series.__iter__() | ||
|
||
def __contains__(self: Self, other: Any) -> bool: | ||
if other is None: | ||
return self._native_series.isna().any() # type: ignore[no-any-return] | ||
FBruzzesi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return other in self._native_series | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I fell for the example contained in our why section! There are two ways of doing this:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. May I ask a general, possibly silly, question, just for the sake of better understanding? π I see the point in implementing Should we implement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Damn that's true... but now I don't know why, and I wonder what is being called. The following returns false: 1 in pa.chunked_array([[1,2,3]])
False
I am addressing it in the PR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Me neither, had issues in trying to figure it out
yeah, I see it; was just asking to confirm my understanding as I couldn't indeed see where this was coming from, without the explicit reference to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, since we implement class A:
pass
class B:
def __iter__(self):
yield from [1,2,3]
0 in B(), 1 in B()
> (False, True)
1 in A()
> TypeError: argument of type 'A' is not iterable When I wrote the comment, the result was false, and now it returns true due to the changes in #1471, lines. In fact before such change, iteration was returning arrow scalars There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice, thanks for the clarification!:) |
||
|
||
def is_finite(self: Self) -> Self: | ||
s = self._native_series | ||
return self._from_native_series((s > float("-inf")) & (s < float("inf"))) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
|
||
import pytest | ||
|
||
import narwhals.stable.v1 as nw | ||
|
||
if TYPE_CHECKING: | ||
from tests.utils import ConstructorEager | ||
|
||
data = [1, 2, None] | ||
|
||
|
||
@pytest.mark.parametrize(("other", "expected"), [(1, True), (None, True), (3, False)]) | ||
def test_contains( | ||
constructor_eager: ConstructorEager, | ||
other: int | None, | ||
expected: bool, # noqa: FBT001 | ||
) -> None: | ||
s = nw.from_native(constructor_eager({"a": data}), eager_only=True)["a"] | ||
|
||
assert (other in s) == expected |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ | |
"value_counts", | ||
"zip_with", | ||
"__iter__", | ||
"__contains__", | ||
} | ||
BASE_DTYPES = { | ||
"NumericType", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if
other
is of a type not convertible tonative_series.type
, will this raise? I'm wondering if we should returnFalse
in such a case?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like polars would also raise here:
probably ok to just follow them here then
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still, probably good to have a test to check it raises? we can leave the expression unification for a separate topic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will add a test and make sure that same exception is raised π
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok this is definitly wrong behaviour for arrow as the casting would convert from float to int, leading to the following:
On the Exception side, I would not be sure how to catch the error for pandas as:
In a way, I find polars behaviour more surprising
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tbh we could just dispatch to
(s._compliant_series == other).any()
for all backends π that would still be better than the status quo