Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion narwhals/_arrow/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ def is_sorted(self, *, descending: bool) -> bool:
result = pc.all(pc.less_equal(self.native[:-1], self.native[1:]))
return maybe_extract_py_scalar(result, return_py_scalar=True)

def unique(self, *, maintain_order: bool) -> Self:
def unique(self, *, maintain_order: bool = True) -> Self:
# TODO(marco): `pc.unique` seems to always maintain order, is that guaranteed?
return self._with_native(self.native.unique())

Expand Down
211 changes: 211 additions & 0 deletions narwhals/_compliant/column.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Protocol

if TYPE_CHECKING:
from collections.abc import Mapping, Sequence

from typing_extensions import Self

from narwhals._compliant.any_namespace import (
CatNamespace,
DateTimeNamespace,
ListNamespace,
StringNamespace,
StructNamespace,
)
from narwhals._compliant.namespace import CompliantNamespace
from narwhals._utils import Version
from narwhals.typing import (
ClosedInterval,
FillNullStrategy,
IntoDType,
NonNestedLiteral,
NumericLiteral,
RankMethod,
TemporalLiteral,
)

__all__ = ["CompliantColumn"]


class CompliantColumn(Protocol):
"""Common parts of `Expr`, `Series`."""

_version: Version

def __add__(self, other: Any) -> Self: ...
def __and__(self, other: Any) -> Self: ...
def __eq__(self, other: object) -> Self: ... # type: ignore[override]
def __floordiv__(self, other: Any) -> Self: ...
def __ge__(self, other: Any) -> Self: ...
def __gt__(self, other: Any) -> Self: ...
def __invert__(self) -> Self: ...
def __le__(self, other: Any) -> Self: ...
def __lt__(self, other: Any) -> Self: ...
def __mod__(self, other: Any) -> Self: ...
def __mul__(self, other: Any) -> Self: ...
def __ne__(self, other: object) -> Self: ... # type: ignore[override]
def __or__(self, other: Any) -> Self: ...
def __pow__(self, other: Any) -> Self: ...
def __rfloordiv__(self, other: Any) -> Self: ...
def __rmod__(self, other: Any) -> Self: ...
def __rpow__(self, other: Any) -> Self: ...
def __rsub__(self, other: Any) -> Self: ...
def __rtruediv__(self, other: Any) -> Self: ...
def __sub__(self, other: Any) -> Self: ...
def __truediv__(self, other: Any) -> Self: ...

def __narwhals_namespace__(self) -> CompliantNamespace[Any, Any]: ...

def abs(self) -> Self: ...
def alias(self, name: str) -> Self: ...
def cast(self, dtype: IntoDType) -> Self: ...
def clip(
self,
lower_bound: Self | NumericLiteral | TemporalLiteral | None,
upper_bound: Self | NumericLiteral | TemporalLiteral | None,
) -> Self: ...
def cum_count(self, *, reverse: bool) -> Self: ...
def cum_max(self, *, reverse: bool) -> Self: ...
def cum_min(self, *, reverse: bool) -> Self: ...
def cum_prod(self, *, reverse: bool) -> Self: ...
def cum_sum(self, *, reverse: bool) -> Self: ...
def diff(self) -> Self: ...
def drop_nulls(self) -> Self: ...
def ewm_mean(
self,
*,
com: float | None,
span: float | None,
half_life: float | None,
alpha: float | None,
adjust: bool,
min_samples: int,
ignore_nulls: bool,
) -> Self: ...
def exp(self) -> Self: ...
def sqrt(self) -> Self: ...
def fill_null(
self,
value: Self | NonNestedLiteral,
strategy: FillNullStrategy | None,
limit: int | None,
) -> Self: ...
def is_between(
self, lower_bound: Self, upper_bound: Self, closed: ClosedInterval
) -> Self:
if closed == "left":
return (self >= lower_bound) & (self < upper_bound)
if closed == "right":
return (self > lower_bound) & (self <= upper_bound)
if closed == "none":
return (self > lower_bound) & (self < upper_bound)
return (self >= lower_bound) & (self <= upper_bound)

def is_close(
self,
other: Self | NumericLiteral,
*,
abs_tol: float,
rel_tol: float,
nans_equal: bool,
) -> Self:
from decimal import Decimal

other_abs: Self | NumericLiteral
other_is_nan: Self | bool
other_is_inf: Self | bool
other_is_not_inf: Self | bool

if isinstance(other, (float, int, Decimal)):
from math import isinf, isnan

# NOTE: See https://discuss.python.org/t/inferred-type-of-function-that-calls-dunder-abs-abs/101447
other_abs = other.__abs__()
other_is_nan = isnan(other)
other_is_inf = isinf(other)

# Define the other_is_not_inf variable to prevent triggering the following warning:
# > DeprecationWarning: Bitwise inversion '~' on bool is deprecated and will be
# > removed in Python 3.16.
other_is_not_inf = not other_is_inf

else:
other_abs, other_is_nan = other.abs(), other.is_nan()
other_is_not_inf = other.is_finite() | other_is_nan
other_is_inf = ~other_is_not_inf

rel_threshold = self.abs().clip(lower_bound=other_abs, upper_bound=None) * rel_tol
tolerance = rel_threshold.clip(lower_bound=abs_tol, upper_bound=None)

self_is_nan = self.is_nan()
self_is_not_inf = self.is_finite() | self_is_nan

# Values are close if abs_diff <= tolerance, and both finite
is_close = (
((self - other).abs() <= tolerance) & self_is_not_inf & other_is_not_inf
)

# Handle infinity cases: infinities are close/equal if they have the same sign
self_sign, other_sign = self > 0, other > 0
is_same_inf = (~self_is_not_inf) & other_is_inf & (self_sign == other_sign)

# Handle nan cases:
# * If any value is NaN, then False (via `& ~either_nan`)
# * However, if `nans_equals = True` and if _both_ values are NaN, then True
either_nan = self_is_nan | other_is_nan
result = (is_close | is_same_inf) & ~either_nan

if nans_equal:
both_nan = self_is_nan & other_is_nan
result = result | both_nan

return result

def is_duplicated(self) -> Self:
return ~self.is_unique()

def is_finite(self) -> Self: ...
def is_first_distinct(self) -> Self: ...
def is_in(self, other: Any) -> Self: ...
def is_last_distinct(self) -> Self: ...
def is_nan(self) -> Self: ...
def is_null(self) -> Self: ...
def is_unique(self) -> Self: ...
def log(self, base: float) -> Self: ...
def mode(self) -> Self: ...
def rank(self, method: RankMethod, *, descending: bool) -> Self: ...
def replace_strict(
self,
old: Sequence[Any] | Mapping[Any, Any],
new: Sequence[Any],
*,
return_dtype: IntoDType | None,
) -> Self: ...
def rolling_mean(
self, window_size: int, *, min_samples: int, center: bool
) -> Self: ...
def rolling_std(
self, window_size: int, *, min_samples: int, center: bool, ddof: int
) -> Self: ...
def rolling_sum(
self, window_size: int, *, min_samples: int, center: bool
) -> Self: ...
def rolling_var(
self, window_size: int, *, min_samples: int, center: bool, ddof: int
) -> Self: ...
def round(self, decimals: int) -> Self: ...
def shift(self, n: int) -> Self: ...
def unique(self) -> Self: ...

@property
def str(self) -> StringNamespace[Self]: ...
@property
def dt(self) -> DateTimeNamespace[Self]: ...
@property
def cat(self) -> CatNamespace[Self]: ...
@property
def list(self) -> ListNamespace[Self]: ...
@property
def struct(self) -> StructNamespace[Self]: ...
Loading
Loading