From 2f6056931a93b1b8ac33f749eae352ec7b924df1 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 25 Mar 2025 16:32:26 +0000 Subject: [PATCH 1/7] refactor: simplify `extract_native` --- narwhals/_polars/expr.py | 6 +++++- narwhals/_polars/utils.py | 8 ++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/narwhals/_polars/expr.py b/narwhals/_polars/expr.py index e00d7c8f75..f8c2fe41d2 100644 --- a/narwhals/_polars/expr.py +++ b/narwhals/_polars/expr.py @@ -32,6 +32,10 @@ def __init__( self._backend_version = backend_version self._metadata: ExprMetadata | None = None + @property + def native(self) -> pl.Expr: + return self._native_expr + def __repr__(self: Self) -> str: # pragma: no cover return "PolarsExpr" @@ -43,7 +47,7 @@ def _from_native_expr(self: Self, expr: pl.Expr) -> Self: @classmethod def _from_series(cls, series: Any) -> Self: return cls( - series._native_series, + series.native, version=series._version, backend_version=series._backend_version, ) diff --git a/narwhals/_polars/utils.py b/narwhals/_polars/utils.py index 1f44368bc1..e51852f532 100644 --- a/narwhals/_polars/utils.py +++ b/narwhals/_polars/utils.py @@ -57,12 +57,8 @@ def extract_native( from narwhals._polars.expr import PolarsExpr from narwhals._polars.series import PolarsSeries - if isinstance(obj, (PolarsDataFrame, PolarsLazyFrame)): - return obj._native_frame - if isinstance(obj, PolarsSeries): - return obj._native_series - if isinstance(obj, PolarsExpr): - return obj._native_expr + if isinstance(obj, (PolarsDataFrame, PolarsLazyFrame, PolarsSeries, PolarsExpr)): + return obj.native return obj From 2967996f5c6b41f30005735facad990273d64d77 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 25 Mar 2025 16:55:07 +0000 Subject: [PATCH 2/7] chore: Update `_polars.series` --- narwhals/_polars/series.py | 157 +++++++++++++++---------------------- 1 file changed, 63 insertions(+), 94 deletions(-) diff --git a/narwhals/_polars/series.py b/narwhals/_polars/series.py index e5f06ddaf3..cfe8131924 100644 --- a/narwhals/_polars/series.py +++ b/narwhals/_polars/series.py @@ -71,7 +71,7 @@ def __native_namespace__(self: Self) -> ModuleType: def _change_version(self: Self, version: Version) -> Self: return self.__class__( - self._native_series, backend_version=self._backend_version, version=version + self.native, backend_version=self._backend_version, version=version ) @classmethod @@ -119,23 +119,21 @@ def __getattr__(self: Self, attr: str) -> Any: def func(*args: Any, **kwargs: Any) -> Any: args, kwargs = extract_args_kwargs(args, kwargs) # type: ignore[assignment] - return self._from_native_object( - getattr(self._native_series, attr)(*args, **kwargs) - ) + return self._from_native_object(getattr(self.native, attr)(*args, **kwargs)) return func def __len__(self: Self) -> int: - return len(self._native_series) + return len(self.native) @property def name(self: Self) -> str: - return self._native_series.name + return self.native.name @property def dtype(self: Self) -> DType: return native_to_narwhals_dtype( - self._native_series.dtype, self._version, self._backend_version + self.native.dtype, self._version, self._backend_version ) @property @@ -143,7 +141,7 @@ def native(self) -> pl.Series: return self._native_series def alias(self, name: str) -> Self: - return self._from_native_object(self._native_series.alias(name)) + return self._from_native_object(self.native.alias(name)) @overload def __getitem__(self: Self, item: int) -> Any: ... @@ -154,7 +152,7 @@ def __getitem__(self: Self, item: slice | Sequence[int] | pl.Series) -> Self: .. def __getitem__( self: Self, item: int | slice | Sequence[int] | pl.Series ) -> Any | Self: - return self._from_native_object(self._native_series.__getitem__(item)) + return self._from_native_object(self.native.__getitem__(item)) def cast(self: Self, dtype: DType) -> Self: dtype_pl = narwhals_to_native_dtype(dtype, self._version, self._backend_version) @@ -163,7 +161,7 @@ def cast(self: Self, dtype: DType) -> Self: def replace_strict( self: Self, old: Sequence[Any], new: Sequence[Any], *, return_dtype: DType | None ) -> Self: - ser = self._native_series + ser = self.native dtype = ( narwhals_to_native_dtype(return_dtype, self._version, self._backend_version) if return_dtype @@ -179,90 +177,72 @@ def to_numpy(self, dtype: Any = None, *, copy: bool | None = None) -> _1DArray: def __array__(self: Self, dtype: Any, *, copy: bool | None) -> _1DArray: if self._backend_version < (0, 20, 29): - return self._native_series.__array__(dtype=dtype) - return self._native_series.__array__(dtype=dtype, copy=copy) + return self.native.__array__(dtype=dtype) + return self.native.__array__(dtype=dtype, copy=copy) def __eq__(self: Self, other: object) -> Self: # type: ignore[override] - return self._from_native_series(self._native_series.__eq__(extract_native(other))) + return self._from_native_series(self.native.__eq__(extract_native(other))) def __ne__(self: Self, other: object) -> Self: # type: ignore[override] - return self._from_native_series(self._native_series.__ne__(extract_native(other))) + return self._from_native_series(self.native.__ne__(extract_native(other))) def __ge__(self: Self, other: Any) -> Self: - return self._from_native_series(self._native_series.__ge__(extract_native(other))) + return self._from_native_series(self.native.__ge__(extract_native(other))) def __gt__(self: Self, other: Any) -> Self: - return self._from_native_series(self._native_series.__gt__(extract_native(other))) + return self._from_native_series(self.native.__gt__(extract_native(other))) def __le__(self: Self, other: Any) -> Self: - return self._from_native_series(self._native_series.__le__(extract_native(other))) + return self._from_native_series(self.native.__le__(extract_native(other))) def __lt__(self: Self, other: Any) -> Self: - return self._from_native_series(self._native_series.__lt__(extract_native(other))) + return self._from_native_series(self.native.__lt__(extract_native(other))) def __and__(self: Self, other: PolarsSeries | bool | Any) -> Self: - return self._from_native_series( - self._native_series.__and__(extract_native(other)) - ) + return self._from_native_series(self.native.__and__(extract_native(other))) def __or__(self: Self, other: PolarsSeries | bool | Any) -> Self: - return self._from_native_series(self._native_series.__or__(extract_native(other))) + return self._from_native_series(self.native.__or__(extract_native(other))) def __add__(self: Self, other: PolarsSeries | Any) -> Self: - return self._from_native_series( - self._native_series.__add__(extract_native(other)) - ) + return self._from_native_series(self.native.__add__(extract_native(other))) def __radd__(self: Self, other: PolarsSeries | Any) -> Self: - return self._from_native_series( - self._native_series.__radd__(extract_native(other)) - ) + return self._from_native_series(self.native.__radd__(extract_native(other))) def __sub__(self: Self, other: PolarsSeries | Any) -> Self: - return self._from_native_series( - self._native_series.__sub__(extract_native(other)) - ) + return self._from_native_series(self.native.__sub__(extract_native(other))) def __rsub__(self: Self, other: PolarsSeries | Any) -> Self: - return self._from_native_series( - self._native_series.__rsub__(extract_native(other)) - ) + return self._from_native_series(self.native.__rsub__(extract_native(other))) def __mul__(self: Self, other: PolarsSeries | Any) -> Self: - return self._from_native_series( - self._native_series.__mul__(extract_native(other)) - ) + return self._from_native_series(self.native.__mul__(extract_native(other))) def __rmul__(self: Self, other: PolarsSeries | Any) -> Self: - return self._from_native_series( - self._native_series.__rmul__(extract_native(other)) - ) + return self._from_native_series(self.native.__rmul__(extract_native(other))) def __pow__(self: Self, other: PolarsSeries | Any) -> Self: - return self._from_native_series( - self._native_series.__pow__(extract_native(other)) - ) + return self._from_native_series(self.native.__pow__(extract_native(other))) def __rpow__(self: Self, other: PolarsSeries | Any) -> Self: - result = self._native_series.__rpow__(extract_native(other)) + result = self.native.__rpow__(extract_native(other)) if self._backend_version < (1, 16, 1): # Explicitly set alias to work around https://github.com/pola-rs/polars/issues/20071 result = result.alias(self.name) return self._from_native_series(result) def __invert__(self: Self) -> Self: - return self._from_native_series(self._native_series.__invert__()) + return self._from_native_series(self.native.__invert__()) def is_nan(self: Self) -> Self: - native = self._native_series try: - native_is_nan = native.is_nan() + native_is_nan = self.native.is_nan() except Exception as e: # noqa: BLE001 raise catch_polars_exception(e, self._backend_version) from None if self._backend_version < (1, 18): # pragma: no cover - return self._from_native_series( - pl.select(pl.when(native.is_not_null()).then(native_is_nan))[native.name] - ) + select = pl.when(self.native.is_not_null()).then(native_is_nan) + return self._from_native_series(pl.select(select)[self.name]) return self._from_native_series(native_is_nan) def median(self: Self) -> Any: @@ -272,23 +252,21 @@ def median(self: Self) -> Any: msg = "`median` operation not supported for non-numeric input type." raise InvalidOperationError(msg) - return self._native_series.median() + return self.native.median() def to_dummies(self: Self, *, separator: str, drop_first: bool) -> PolarsDataFrame: from narwhals._polars.dataframe import PolarsDataFrame if self._backend_version < (0, 20, 15): - has_nulls = self._native_series.is_null().any() - result = self._native_series.to_dummies(separator=separator) + has_nulls = self.native.is_null().any() + result = self.native.to_dummies(separator=separator) output_columns = result.columns if drop_first: _ = output_columns.pop(int(has_nulls)) result = result.select(output_columns) else: - result = self._native_series.to_dummies( - separator=separator, drop_first=drop_first - ) + result = self.native.to_dummies(separator=separator, drop_first=drop_first) result = result.with_columns(pl.all().cast(pl.Int8)) return PolarsDataFrame( result, backend_version=self._backend_version, version=self._version @@ -305,15 +283,13 @@ def ewm_mean( min_samples: int, ignore_nulls: bool, ) -> Self: - native_series = self._native_series - extra_kwargs = ( {"min_periods": min_samples} if self._backend_version < (1, 21, 0) else {"min_samples": min_samples} ) - native_result = native_series.ewm_mean( + native_result = self.native.ewm_mean( com=com, span=span, half_life=half_life, @@ -325,8 +301,8 @@ def ewm_mean( if self._backend_version < (1,): # pragma: no cover return self._from_native_series( pl.select( - pl.when(~native_series.is_null()).then(native_result).otherwise(None) - )[native_series.name] + pl.when(~self.native.is_null()).then(native_result).otherwise(None) + )[self.native.name] ) return self._from_native_series(native_result) @@ -350,7 +326,7 @@ def rolling_var( ) return self._from_native_series( - self._native_series.rolling_var( + self.native.rolling_var( window_size=window_size, center=center, ddof=ddof, @@ -377,7 +353,7 @@ def rolling_std( ) return self._from_native_series( - self._native_series.rolling_std( + self.native.rolling_std( window_size=window_size, center=center, ddof=ddof, @@ -399,7 +375,7 @@ def rolling_sum( ) return self._from_native_series( - self._native_series.rolling_sum( + self.native.rolling_sum( window_size=window_size, center=center, **extra_kwargs, # type: ignore[arg-type] @@ -420,7 +396,7 @@ def rolling_mean( ) return self._from_native_series( - self._native_series.rolling_mean( + self.native.rolling_mean( window_size=window_size, center=center, **extra_kwargs, # type: ignore[arg-type] @@ -429,22 +405,18 @@ def rolling_mean( def sort(self: Self, *, descending: bool, nulls_last: bool) -> Self: if self._backend_version < (0, 20, 6): - result = self._native_series.sort(descending=descending) + result = self.native.sort(descending=descending) if nulls_last: is_null = result.is_null() result = pl.concat([result.filter(~is_null), result.filter(is_null)]) else: - result = self._native_series.sort( - descending=descending, nulls_last=nulls_last - ) + result = self.native.sort(descending=descending, nulls_last=nulls_last) return self._from_native_series(result) def scatter(self: Self, indices: int | Sequence[int], values: Any) -> Self: - values = extract_native(values) - s = self._native_series.clone() - s.scatter(indices, values) + s = self.native.clone().scatter(indices, extract_native(values)) return self._from_native_series(s) def value_counts( @@ -460,10 +432,9 @@ def value_counts( if self._backend_version < (1, 0, 0): value_name_ = name or ("proportion" if normalize else "count") - result = self._native_series.value_counts(sort=sort, parallel=parallel) - result = result.select( + result = self.native.value_counts(sort=sort, parallel=parallel).select( **{ - (self._native_series.name): pl.col(self._native_series.name), + (self.native.name): pl.col(self.native.name), value_name_: pl.col("count") / pl.sum("count") if normalize else pl.col("count"), @@ -471,7 +442,7 @@ def value_counts( ) else: - result = self._native_series.value_counts( + result = self.native.value_counts( sort=sort, parallel=parallel, name=name, normalize=normalize ) @@ -481,16 +452,16 @@ def value_counts( def cum_count(self: Self, *, reverse: bool) -> Self: if self._backend_version < (0, 20, 4): - not_null_series = ~self._native_series.is_null() + not_null_series = ~self.native.is_null() result = not_null_series.cum_sum(reverse=reverse) else: - result = self._native_series.cum_count(reverse=reverse) + result = self.native.cum_count(reverse=reverse) return self._from_native_series(result) def __contains__(self: Self, other: Any) -> bool: try: - return self._native_series.__contains__(other) + return self.native.__contains__(other) except Exception as e: # noqa: BLE001 raise catch_polars_exception(e, self._backend_version) from None @@ -513,7 +484,7 @@ def hist( backend_version=self._backend_version, version=self._version, ) - elif (self._backend_version < (1, 15)) and self._native_series.count() < 1: + elif (self._backend_version < (1, 15)) and self.native.count() < 1: data_dict: dict[str, Sequence[Any] | pl.Series] if bins is not None: data_dict = { @@ -542,10 +513,10 @@ def hist( if ( (self._backend_version < (1, 15)) and (bin_count is not None) - and (self._native_series.count() > 0) + and (self.native.count() > 0) ): # pragma: no cover - lower = cast("float", self._native_series.min()) - upper = cast("float", self._native_series.max()) + lower = cast("float", self.native.min()) + upper = cast("float", self.native.max()) pad_lowest_bin = False if lower == upper: width = 1 / bin_count @@ -562,12 +533,12 @@ def hist( # Polars inconsistently handles NaN values when computing histograms # against predefined bins: https://github.com/pola-rs/polars/issues/21082 - series = self._native_series + series = self.native if self._backend_version < (1, 15) or bins is not None: series = series.set(series.is_nan(), None) df = series.hist( - bins=bins, + bins, bin_count=bin_count, include_category=False, include_breakpoint=include_breakpoint, @@ -588,7 +559,7 @@ def hist( ) def to_polars(self: Self) -> pl.Series: - return self._native_series + return self.native @property def dt(self: Self) -> PolarsSeriesDateTimeNamespace: @@ -619,7 +590,7 @@ def __getattr__(self: Self, attr: str) -> Any: def func(*args: Any, **kwargs: Any) -> Any: args, kwargs = extract_args_kwargs(args, kwargs) # type: ignore[assignment] return self._compliant_series._from_native_series( - getattr(self._compliant_series._native_series.dt, attr)(*args, **kwargs) + getattr(self._compliant_series.native.dt, attr)(*args, **kwargs) ) return func @@ -633,7 +604,7 @@ def __getattr__(self: Self, attr: str) -> Any: def func(*args: Any, **kwargs: Any) -> Any: args, kwargs = extract_args_kwargs(args, kwargs) # type: ignore[assignment] return self._compliant_series._from_native_series( - getattr(self._compliant_series._native_series.str, attr)(*args, **kwargs) + getattr(self._compliant_series.native.str, attr)(*args, **kwargs) ) return func @@ -647,7 +618,7 @@ def __getattr__(self: Self, attr: str) -> Any: def func(*args: Any, **kwargs: Any) -> Any: args, kwargs = extract_args_kwargs(args, kwargs) # type: ignore[assignment] return self._compliant_series._from_native_series( - getattr(self._compliant_series._native_series.cat, attr)(*args, **kwargs) + getattr(self._compliant_series.native.cat, attr)(*args, **kwargs) ) return func @@ -658,7 +629,7 @@ def __init__(self: Self, series: PolarsSeries) -> None: self._series = series def len(self: Self) -> PolarsSeries: - native_series = self._series._native_series + native_series = self._series.native native_result = native_series.list.len() if self._series._backend_version < (1, 16): # pragma: no cover @@ -676,7 +647,7 @@ def __getattr__(self: Self, attr: str) -> Any: # pragma: no cover def func(*args: Any, **kwargs: Any) -> Any: args, kwargs = extract_args_kwargs(args, kwargs) # type: ignore[assignment] return self._series._from_native_series( - getattr(self._series._native_series.list, attr)(*args, **kwargs) + getattr(self._series.native.list, attr)(*args, **kwargs) ) return func @@ -690,9 +661,7 @@ def __getattr__(self: Self, attr: str) -> Any: def func(*args: Any, **kwargs: Any) -> Any: args, kwargs = extract_args_kwargs(args, kwargs) # type: ignore[assignment] return self._compliant_series._from_native_series( - getattr(self._compliant_series._native_series.struct, attr)( - *args, **kwargs - ) + getattr(self._compliant_series.native.struct, attr)(*args, **kwargs) ) return func From c73ddf3ac5ff34b6e05daee9a72ae4a318553b50 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 25 Mar 2025 17:01:26 +0000 Subject: [PATCH 3/7] chore: Update `_pandas_like.utils` --- narwhals/_pandas_like/utils.py | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/narwhals/_pandas_like/utils.py b/narwhals/_pandas_like/utils.py index b15544a453..fbeeca1d78 100644 --- a/narwhals/_pandas_like/utils.py +++ b/narwhals/_pandas_like/utils.py @@ -99,36 +99,34 @@ def align_and_extract_native( from narwhals._pandas_like.dataframe import PandasLikeDataFrame from narwhals._pandas_like.series import PandasLikeSeries - lhs_index = lhs._native_series.index + lhs_index = lhs.native.index if isinstance(rhs, PandasLikeDataFrame): return NotImplemented if lhs._broadcast and isinstance(rhs, PandasLikeSeries) and not rhs._broadcast: - return lhs._native_series.iloc[0], rhs._native_series + return lhs.native.iloc[0], rhs.native - lhs_native = lhs._native_series if isinstance(rhs, PandasLikeSeries): if rhs._broadcast: - return (lhs_native, rhs._native_series.iloc[0]) - rhs_native = rhs._native_series - if rhs_native.index is not lhs_index: + return (lhs.native, rhs.native.iloc[0]) + if rhs.native.index is not lhs_index: return ( - lhs_native, + lhs.native, set_index( - rhs_native, + rhs.native, lhs_index, implementation=rhs._implementation, backend_version=rhs._backend_version, ), ) - return (lhs_native, rhs_native) + return (lhs.native, rhs.native) if isinstance(rhs, list): msg = "Expected Series or scalar, got list." raise TypeError(msg) # `rhs` must be scalar, so just leave it as-is - return lhs_native, rhs + return lhs.native, rhs def horizontal_concat( @@ -611,28 +609,27 @@ def align_series_full_broadcast( lengths = [len(s) for s in series] max_length = max(lengths) - idx = series[lengths.index(max_length)]._native_series.index + idx = series[lengths.index(max_length)].native.index reindexed = [] max_length_gt_1 = max_length > 1 for s, length in zip(series, lengths): - s_native = s._native_series if max_length_gt_1 and length == 1: reindexed.append( s._from_native_series( native_namespace.Series( - [s_native.iloc[0]] * max_length, + [s.native.iloc[0]] * max_length, index=idx, - name=s_native.name, - dtype=s_native.dtype, + name=s.name, + dtype=s.native.dtype, ) ) ) - elif s_native.index is not idx: + elif s.native.index is not idx: reindexed.append( s._from_native_series( set_index( - s_native, + s.native, idx, implementation=s._implementation, backend_version=s._backend_version, From 3f4fcb27f3ef581e97bb67a3c3c04d8ae851ed3f Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 25 Mar 2025 17:25:01 +0000 Subject: [PATCH 4/7] chore: Update `_pandas_like.series` --- narwhals/_pandas_like/series.py | 342 ++++++++++++-------------------- 1 file changed, 128 insertions(+), 214 deletions(-) diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py index 1f75f0647c..e5a237be1c 100644 --- a/narwhals/_pandas_like/series.py +++ b/narwhals/_pandas_like/series.py @@ -126,11 +126,7 @@ def native(self) -> Any: return self._native_series def __native_namespace__(self: Self) -> ModuleType: - if self._implementation in { - Implementation.PANDAS, - Implementation.MODIN, - Implementation.CUDF, - }: + if self._implementation.is_pandas_like(): return self._implementation.to_native_namespace() msg = f"Expected pandas/modin/cudf, got: {type(self._implementation)}" # pragma: no cover @@ -151,8 +147,8 @@ def __getitem__(self: Self, idx: slice | Sequence[int]) -> Self: ... def __getitem__(self: Self, idx: int | slice | Sequence[int]) -> Any | Self: if isinstance(idx, int) or is_numpy_scalar(idx): - return self._native_series.iloc[idx] - return self._from_native_series(self._native_series.iloc[idx]) + return self.native.iloc[idx] + return self._from_native_series(self.native.iloc[idx]) def _change_version(self: Self, version: Version) -> Self: return self.__class__( @@ -228,7 +224,7 @@ def ewm_mean( min_samples: int, ignore_nulls: bool, ) -> PandasLikeSeries: - ser = self._native_series + ser = self.native mask_na = ser.isna() if self._implementation is Implementation.CUDF: if (min_samples == 0 and not ignore_nulls) or (not mask_na.any()): @@ -251,12 +247,12 @@ def ewm_mean( def scatter(self: Self, indices: int | Sequence[int], values: Any) -> Self: if isinstance(values, self.__class__): values = set_index( - values._native_series, - self._native_series.index[indices], + values.native, + self.native.index[indices], implementation=self._implementation, backend_version=self._backend_version, ) - s = self._native_series.copy(deep=True) + s = self.native.copy(deep=True) s.iloc[indices] = values s.name = self.name return self._from_native_series(s) @@ -264,8 +260,8 @@ def scatter(self: Self, indices: int | Sequence[int], values: Any) -> Self: def _scatter_in_place(self: Self, indices: Self, values: Self) -> None: # Scatter, modifying original Series. Use with care! values_native = set_index( - values._native_series, - self._native_series.index[indices._native_series], + values.native, + self.native.index[indices.native], implementation=self._implementation, backend_version=self._backend_version, ) @@ -276,23 +272,19 @@ def _scatter_in_place(self: Self, indices: Self, values: Self) -> None: self._implementation is Implementation.PANDAS and self._backend_version < min_pd_version ): - self._native_series.iloc[indices._native_series.values] = values_native # noqa: PD011 + self.native.iloc[indices.native.values] = values_native # noqa: PD011 else: - self._native_series.iloc[indices._native_series] = values_native + self.native.iloc[indices.native] = values_native def cast(self: Self, dtype: DType | type[DType]) -> Self: - ser = self._native_series - dtype_backend = get_dtype_backend( - dtype=ser.dtype, implementation=self._implementation - ) pd_dtype = narwhals_to_native_dtype( dtype, - dtype_backend=dtype_backend, + dtype_backend=get_dtype_backend(self.native.dtype, self._implementation), implementation=self._implementation, backend_version=self._backend_version, version=self._version, ) - return self._from_native_series(ser.astype(pd_dtype)) + return self._from_native_series(self.native.astype(pd_dtype)) def item(self: Self, index: int | None) -> Any: # cuDF doesn't have Series.item(). @@ -303,14 +295,14 @@ def item(self: Self, index: int | None) -> Any: f" or an explicit index is provided (Series is of length {len(self)})" ) raise ValueError(msg) - return self._native_series.iloc[0] - return self._native_series.iloc[index] + return self.native.iloc[0] + return self.native.iloc[index] def to_frame(self: Self) -> PandasLikeDataFrame: from narwhals._pandas_like.dataframe import PandasLikeDataFrame return PandasLikeDataFrame( - self._native_series.to_frame(), + self.native.to_frame(), implementation=self._implementation, backend_version=self._backend_version, version=self._version, @@ -318,9 +310,8 @@ def to_frame(self: Self) -> PandasLikeDataFrame: ) def to_list(self: Self) -> list[Any]: - if self._implementation is Implementation.CUDF: - return self._native_series.to_arrow().to_pylist() - return self._native_series.to_list() + is_cudf = self._implementation.is_cudf() + return self.native.to_arrow().to_pylist() if is_cudf else self.native.to_list() def is_between( self: Self, @@ -328,7 +319,7 @@ def is_between( upper_bound: Any, closed: Literal["left", "right", "none", "both"], ) -> PandasLikeSeries: - ser = self._native_series + ser = self.native _, lower_bound = align_and_extract_native(self, lower_bound) _, upper_bound = align_and_extract_native(self, upper_bound) if closed == "left": @@ -344,23 +335,20 @@ def is_between( return self._from_native_series(res).alias(ser.name) def is_in(self: Self, other: Any) -> PandasLikeSeries: - ser = self._native_series - res = ser.isin(other) - return self._from_native_series(res) + return self._from_native_series(self.native.isin(other)) def arg_true(self: Self) -> PandasLikeSeries: - ser = self._native_series + ser = self.native result = ser.__class__(range(len(ser)), name=ser.name, index=ser.index).loc[ser] return self._from_native_series(result) def arg_min(self: Self) -> int: - ser = self._native_series if self._implementation is Implementation.PANDAS and self._backend_version < (1,): - return ser.to_numpy().argmin() - return ser.argmin() + return self.native.to_numpy().argmin() + return self.native.argmin() def arg_max(self: Self) -> int: - ser = self._native_series + ser = self.native if self._implementation is Implementation.PANDAS and self._backend_version < (1,): return ser.to_numpy().argmax() return ser.argmax() @@ -374,9 +362,7 @@ def filter(self: Self, predicate: Any) -> PandasLikeSeries: _, other_native = align_and_extract_native(self, predicate) else: other_native = predicate - return self._from_native_series(self._native_series.loc[other_native]).alias( - self.name - ) + return self._from_native_series(self.native.loc[other_native]).alias(self.name) def __eq__(self: Self, other: object) -> PandasLikeSeries: # type: ignore[override] ser, other = align_and_extract_native(self, other) @@ -431,7 +417,7 @@ def __add__(self: Self, other: Any) -> PandasLikeSeries: def __radd__(self: Self, other: Any) -> PandasLikeSeries: _, other_native = align_and_extract_native(self, other) return self._from_native_series( - self._native_series.__radd__(other_native), + self.native.__radd__(other_native), ).alias(self.name) def __sub__(self: Self, other: Any) -> PandasLikeSeries: @@ -441,7 +427,7 @@ def __sub__(self: Self, other: Any) -> PandasLikeSeries: def __rsub__(self: Self, other: Any) -> PandasLikeSeries: _, other_native = align_and_extract_native(self, other) return self._from_native_series( - self._native_series.__rsub__(other_native), + self.native.__rsub__(other_native), ).alias(self.name) def __mul__(self: Self, other: Any) -> PandasLikeSeries: @@ -451,7 +437,7 @@ def __mul__(self: Self, other: Any) -> PandasLikeSeries: def __rmul__(self: Self, other: Any) -> PandasLikeSeries: _, other_native = align_and_extract_native(self, other) return self._from_native_series( - self._native_series.__rmul__(other_native), + self.native.__rmul__(other_native), ).alias(self.name) def __truediv__(self: Self, other: Any) -> PandasLikeSeries: @@ -461,7 +447,7 @@ def __truediv__(self: Self, other: Any) -> PandasLikeSeries: def __rtruediv__(self: Self, other: Any) -> PandasLikeSeries: _, other_native = align_and_extract_native(self, other) return self._from_native_series( - self._native_series.__rtruediv__(other_native), + self.native.__rtruediv__(other_native), ).alias(self.name) def __floordiv__(self: Self, other: Any) -> PandasLikeSeries: @@ -471,7 +457,7 @@ def __floordiv__(self: Self, other: Any) -> PandasLikeSeries: def __rfloordiv__(self: Self, other: Any) -> PandasLikeSeries: _, other_native = align_and_extract_native(self, other) return self._from_native_series( - self._native_series.__rfloordiv__(other_native), + self.native.__rfloordiv__(other_native), ).alias(self.name) def __pow__(self: Self, other: Any) -> PandasLikeSeries: @@ -481,7 +467,7 @@ def __pow__(self: Self, other: Any) -> PandasLikeSeries: def __rpow__(self: Self, other: Any) -> PandasLikeSeries: _, other_native = align_and_extract_native(self, other) return self._from_native_series( - self._native_series.__rpow__(other_native), + self.native.__rpow__(other_native), ).alias(self.name) def __mod__(self: Self, other: Any) -> PandasLikeSeries: @@ -491,52 +477,51 @@ def __mod__(self: Self, other: Any) -> PandasLikeSeries: def __rmod__(self: Self, other: Any) -> PandasLikeSeries: _, other_native = align_and_extract_native(self, other) return self._from_native_series( - self._native_series.__rmod__(other_native), + self.native.__rmod__(other_native), ).alias(self.name) # Unary def __invert__(self: PandasLikeSeries) -> PandasLikeSeries: - ser = self._native_series - return self._from_native_series(~ser) + return self._from_native_series(~self.native) # Reductions def any(self: Self) -> bool: - return self._native_series.any() + return self.native.any() def all(self: Self) -> bool: - return self._native_series.all() + return self.native.all() def min(self: Self) -> Any: - return self._native_series.min() + return self.native.min() def max(self: Self) -> Any: - return self._native_series.max() + return self.native.max() def sum(self: Self) -> float: - return self._native_series.sum() + return self.native.sum() def count(self: Self) -> int: - return self._native_series.count() + return self.native.count() def mean(self: Self) -> float: - return self._native_series.mean() + return self.native.mean() def median(self: Self) -> float: if not self.dtype.is_numeric(): msg = "`median` operation not supported for non-numeric input type." raise InvalidOperationError(msg) - return self._native_series.median() + return self.native.median() def std(self: Self, *, ddof: int) -> float: - return self._native_series.std(ddof=ddof) + return self.native.std(ddof=ddof) def var(self: Self, *, ddof: int) -> float: - return self._native_series.var(ddof=ddof) + return self.native.var(ddof=ddof) def skew(self: Self) -> float | None: - ser_not_null = self._native_series.dropna() + ser_not_null = self.native.dropna() if len(ser_not_null) == 0: return None elif len(ser_not_null) == 1: @@ -550,15 +535,15 @@ def skew(self: Self) -> float | None: return m3 / (m2**1.5) if m2 != 0 else float("nan") def len(self: Self) -> int: - return len(self._native_series) + return len(self.native) # Transformations def is_null(self: Self) -> PandasLikeSeries: - return self._from_native_series(self._native_series.isna()) + return self._from_native_series(self.native.isna()) def is_nan(self: Self) -> PandasLikeSeries: - ser = self._native_series + ser = self.native if self.dtype.is_numeric(): return self._from_native_series(ser != ser) # noqa: PLR0124 msg = f"`.is_nan` only supported for numeric dtype and not {self.dtype}, did you mean `.is_null`?" @@ -570,7 +555,7 @@ def fill_null( strategy: Literal["forward", "backward"] | None, limit: int | None, ) -> Self: - ser = self._native_series + ser = self.native if value is not None: _, value = align_and_extract_native(self, value) res_ser = self._from_native_series(ser.fillna(value=value)) @@ -584,10 +569,10 @@ def fill_null( return res_ser def drop_nulls(self: Self) -> PandasLikeSeries: - return self._from_native_series(self._native_series.dropna()) + return self._from_native_series(self.native.dropna()) def n_unique(self: Self) -> int: - return self._native_series.nunique(dropna=False) + return self.native.nunique(dropna=False) def sample( self: Self, @@ -598,20 +583,19 @@ def sample( seed: int | None, ) -> Self: return self._from_native_series( - self._native_series.sample( + self.native.sample( n=n, frac=fraction, replace=with_replacement, random_state=seed ) ) def abs(self: Self) -> PandasLikeSeries: - return self._from_native_series(self._native_series.abs()) + return self._from_native_series(self.native.abs()) def cum_sum(self: Self, *, reverse: bool) -> Self: - native_series = self._native_series result = ( - native_series.cumsum(skipna=True) + self.native.cumsum(skipna=True) if not reverse - else native_series[::-1].cumsum(skipna=True)[::-1] + else self.native[::-1].cumsum(skipna=True)[::-1] ) return self._from_native_series(result) @@ -619,16 +603,14 @@ def unique(self: Self, *, maintain_order: bool) -> PandasLikeSeries: # pandas always maintains order, as per its docstring: # "Uniques are returned in order of appearance" # noqa: ERA001 return self._from_native_series( - self._native_series.__class__( - self._native_series.unique(), name=self._native_series.name - ) + self.native.__class__(self.native.unique(), name=self.name) ) def diff(self: Self) -> PandasLikeSeries: - return self._from_native_series(self._native_series.diff()) + return self._from_native_series(self.native.diff()) def shift(self: Self, n: int) -> PandasLikeSeries: - return self._from_native_series(self._native_series.shift(n)) + return self._from_native_series(self.native.shift(n)) def replace_strict( self: Self, @@ -638,9 +620,7 @@ def replace_strict( return_dtype: DType | type[DType] | None, ) -> PandasLikeSeries: tmp_name = f"{self.name}_tmp" - dtype_backend = get_dtype_backend( - dtype=self._native_series.dtype, implementation=self._implementation - ) + dtype_backend = get_dtype_backend(self.native.dtype, self._implementation) dtype = ( narwhals_to_native_dtype( return_dtype, @@ -652,16 +632,12 @@ def replace_strict( if return_dtype else None ) - other = self.__native_namespace__().DataFrame( - { - self.name: old, - tmp_name: self.__native_namespace__().Series(new, dtype=dtype), - } + namespace = self.__native_namespace__() + other = namespace.DataFrame( + {self.name: old, tmp_name: namespace.Series(new, dtype=dtype)} ) result = self._from_native_series( - self._native_series.to_frame().merge(other, on=self.name, how="left")[ - tmp_name - ] + self.native.to_frame().merge(other, on=self.name, how="left")[tmp_name] ).alias(self.name) if result.is_null().sum() != self.is_null().sum(): msg = ( @@ -674,16 +650,14 @@ def replace_strict( def sort(self: Self, *, descending: bool, nulls_last: bool) -> PandasLikeSeries: na_position = "last" if nulls_last else "first" return self._from_native_series( - self._native_series.sort_values( - ascending=not descending, na_position=na_position - ) + self.native.sort_values(ascending=not descending, na_position=na_position) ).alias(self.name) def alias(self: Self, name: str | Hashable) -> Self: if name != self.name: return self._from_native_series( rename( - self._native_series, + self.native, name, implementation=self._implementation, backend_version=self._backend_version, @@ -703,9 +677,9 @@ def to_numpy(self: Self, dtype: Any = None, *, copy: bool | None = None) -> _1DA copy = copy or self._implementation is Implementation.CUDF dtypes = import_dtypes_module(self._version) if isinstance(self.dtype, dtypes.Datetime) and self.dtype.time_zone is not None: - s = self.dt.convert_time_zone("UTC").dt.replace_time_zone(None)._native_series + s = self.dt.convert_time_zone("UTC").dt.replace_time_zone(None).native else: - s = self._native_series + s = self.native has_missing = s.isna().any() if has_missing and str(s.dtype) in PANDAS_TO_NUMPY_DTYPE_MISSING: @@ -722,51 +696,43 @@ def to_numpy(self: Self, dtype: Any = None, *, copy: bool | None = None) -> _1DA ) if not has_missing and str(s.dtype) in PANDAS_TO_NUMPY_DTYPE_NO_MISSING: return s.to_numpy( - dtype=dtype or PANDAS_TO_NUMPY_DTYPE_NO_MISSING[str(s.dtype)], - copy=copy, + dtype=dtype or PANDAS_TO_NUMPY_DTYPE_NO_MISSING[str(s.dtype)], copy=copy ) return s.to_numpy(dtype=dtype, copy=copy) def to_pandas(self: Self) -> pd.Series[Any]: if self._implementation is Implementation.PANDAS: - return self._native_series + return self.native elif self._implementation is Implementation.CUDF: # pragma: no cover - return self._native_series.to_pandas() + return self.native.to_pandas() elif self._implementation is Implementation.MODIN: - return self._native_series._to_pandas() + return self.native._to_pandas() msg = f"Unknown implementation: {self._implementation}" # pragma: no cover raise AssertionError(msg) def to_polars(self: Self) -> pl.Series: import polars as pl # ignore-banned-import - if self._implementation is Implementation.PANDAS: - return pl.from_pandas(self._native_series) - elif self._implementation is Implementation.CUDF: # pragma: no cover - return pl.from_pandas(self._native_series.to_pandas()) - elif self._implementation is Implementation.MODIN: - return pl.from_pandas(self._native_series._to_pandas()) - msg = f"Unknown implementation: {self._implementation}" # pragma: no cover - raise AssertionError(msg) + return pl.from_pandas(self.to_pandas()) # --- descriptive --- def is_unique(self: Self) -> Self: - return self._from_native_series( - ~self._native_series.duplicated(keep=False) - ).alias(self.name) + return self._from_native_series(~self.native.duplicated(keep=False)).alias( + self.name + ) def null_count(self: Self) -> int: - return self._native_series.isna().sum() + return self.native.isna().sum() def is_first_distinct(self: Self) -> Self: - return self._from_native_series( - ~self._native_series.duplicated(keep="first") - ).alias(self.name) + return self._from_native_series(~self.native.duplicated(keep="first")).alias( + self.name + ) def is_last_distinct(self: Self) -> Self: - return self._from_native_series( - ~self._native_series.duplicated(keep="last") - ).alias(self.name) + return self._from_native_series(~self.native.duplicated(keep="last")).alias( + self.name + ) def is_sorted(self: Self, *, descending: bool) -> bool: if not isinstance(descending, bool): @@ -774,28 +740,20 @@ def is_sorted(self: Self, *, descending: bool) -> bool: raise TypeError(msg) if descending: - return self._native_series.is_monotonic_decreasing + return self.native.is_monotonic_decreasing else: - return self._native_series.is_monotonic_increasing + return self.native.is_monotonic_increasing def value_counts( - self: Self, - *, - sort: bool, - parallel: bool, - name: str | None, - normalize: bool, + self: Self, *, sort: bool, parallel: bool, name: str | None, normalize: bool ) -> PandasLikeDataFrame: """Parallel is unused, exists for compatibility.""" from narwhals._pandas_like.dataframe import PandasLikeDataFrame index_name_ = "index" if self._name is None else self._name value_name_ = name or ("proportion" if normalize else "count") - - val_count = self._native_series.value_counts( - dropna=False, - sort=False, - normalize=normalize, + val_count = self.native.value_counts( + dropna=False, sort=False, normalize=normalize ).reset_index() val_count.columns = [index_name_, value_name_] @@ -816,23 +774,23 @@ def quantile( quantile: float, interpolation: Literal["nearest", "higher", "lower", "midpoint", "linear"], ) -> float: - return self._native_series.quantile(q=quantile, interpolation=interpolation) + return self.native.quantile(q=quantile, interpolation=interpolation) def zip_with(self: Self, mask: Any, other: Any) -> PandasLikeSeries: - ser = self._native_series + ser = self.native _, mask = align_and_extract_native(self, mask) _, other = align_and_extract_native(self, other) res = ser.where(mask, other) return self._from_native_series(res) def head(self: Self, n: int) -> Self: - return self._from_native_series(self._native_series.head(n)) + return self._from_native_series(self.native.head(n)) def tail(self: Self, n: int) -> Self: - return self._from_native_series(self._native_series.tail(n)) + return self._from_native_series(self.native.tail(n)) def round(self: Self, decimals: int) -> Self: - return self._from_native_series(self._native_series.round(decimals=decimals)) + return self._from_native_series(self.native.round(decimals=decimals)) def to_dummies( self: Self, *, separator: str, drop_first: bool @@ -840,7 +798,7 @@ def to_dummies( from narwhals._pandas_like.dataframe import PandasLikeDataFrame plx = self.__native_namespace__() - series = self._native_series + series = self.native name = str(self._name) if self._name else "" null_col_pl = f"{name}{separator}null" @@ -876,7 +834,7 @@ def to_dummies( ) def gather_every(self: Self, n: int, offset: int) -> Self: - return self._from_native_series(self._native_series.iloc[offset::n]) + return self._from_native_series(self.native.iloc[offset::n]) def clip( self: Self, lower_bound: Self | Any | None, upper_bound: Self | Any | None @@ -885,25 +843,24 @@ def clip( _, upper_bound = align_and_extract_native(self, upper_bound) kwargs = {"axis": 0} if self._implementation is Implementation.MODIN else {} return self._from_native_series( - self._native_series.clip(lower_bound, upper_bound, **kwargs) + self.native.clip(lower_bound, upper_bound, **kwargs) ) def to_arrow(self: Self) -> ArrowArray: if self._implementation is Implementation.CUDF: - return self._native_series.to_arrow() + return self.native.to_arrow() import pyarrow as pa # ignore-banned-import() - return pa.Array.from_pandas(self._native_series) + return pa.Array.from_pandas(self.native) def mode(self: Self) -> Self: - native_series = self._native_series - result = native_series.mode() - result.name = native_series.name + result = self.native.mode() + result.name = self.name return self._from_native_series(result) def cum_count(self: Self, *, reverse: bool) -> Self: - not_na_series = ~self._native_series.isna() + not_na_series = ~self.native.isna() result = ( not_na_series.cumsum() if not reverse @@ -912,94 +869,69 @@ def cum_count(self: Self, *, reverse: bool) -> Self: return self._from_native_series(result) def cum_min(self: Self, *, reverse: bool) -> Self: - native_series = self._native_series result = ( - native_series.cummin(skipna=True) + self.native.cummin(skipna=True) if not reverse - else native_series[::-1].cummin(skipna=True)[::-1] + else self.native[::-1].cummin(skipna=True)[::-1] ) return self._from_native_series(result) def cum_max(self: Self, *, reverse: bool) -> Self: - native_series = self._native_series result = ( - native_series.cummax(skipna=True) + self.native.cummax(skipna=True) if not reverse - else native_series[::-1].cummax(skipna=True)[::-1] + else self.native[::-1].cummax(skipna=True)[::-1] ) return self._from_native_series(result) def cum_prod(self: Self, *, reverse: bool) -> Self: - native_series = self._native_series result = ( - native_series.cumprod(skipna=True) + self.native.cumprod(skipna=True) if not reverse - else native_series[::-1].cumprod(skipna=True)[::-1] + else self.native[::-1].cumprod(skipna=True)[::-1] ) return self._from_native_series(result) def rolling_sum( - self: Self, - window_size: int, - *, - min_samples: int, - center: bool, + self: Self, window_size: int, *, min_samples: int, center: bool ) -> Self: - result = self._native_series.rolling( + result = self.native.rolling( window=window_size, min_periods=min_samples, center=center ).sum() return self._from_native_series(result) def rolling_mean( - self: Self, - window_size: int, - *, - min_samples: int, - center: bool, + self: Self, window_size: int, *, min_samples: int, center: bool ) -> Self: - result = self._native_series.rolling( + result = self.native.rolling( window=window_size, min_periods=min_samples, center=center ).mean() return self._from_native_series(result) def rolling_var( - self: Self, - window_size: int, - *, - min_samples: int, - center: bool, - ddof: int, + self: Self, window_size: int, *, min_samples: int, center: bool, ddof: int ) -> Self: - result = self._native_series.rolling( + result = self.native.rolling( window=window_size, min_periods=min_samples, center=center ).var(ddof=ddof) return self._from_native_series(result) def rolling_std( - self: Self, - window_size: int, - *, - min_samples: int, - center: bool, - ddof: int, + self: Self, window_size: int, *, min_samples: int, center: bool, ddof: int ) -> Self: - result = self._native_series.rolling( + result = self.native.rolling( window=window_size, min_periods=min_samples, center=center ).std(ddof=ddof) return self._from_native_series(result) def __iter__(self: Self) -> Iterator[Any]: - yield from self._native_series.__iter__() + yield from self.native.__iter__() def __contains__(self: Self, other: Any) -> bool: - return ( - self._native_series.isna().any() - if other is None - else (self._native_series == other).any() - ) + return self.native.isna().any() if other is None else (self.native == other).any() def is_finite(self: Self) -> Self: - s = self._native_series + s = self.native return self._from_native_series((s > float("-inf")) & (s < float("inf"))) def rank( @@ -1009,47 +941,31 @@ def rank( descending: bool, ) -> Self: pd_method = "first" if method == "ordinal" else method - native_series = self._native_series - dtypes = import_dtypes_module(self._version) + name = self.name if ( self._implementation is Implementation.PANDAS and self._backend_version < (3,) - and self.dtype - in { - dtypes.Int64, - dtypes.Int32, - dtypes.Int16, - dtypes.Int8, - dtypes.UInt64, - dtypes.UInt32, - dtypes.UInt16, - dtypes.UInt8, - } - and (null_mask := native_series.isna()).any() + and self.dtype.is_integer() + and (null_mask := self.native.isna()).any() ): # crazy workaround for the case of `na_option="keep"` and nullable # integer dtypes. This should be supported in pandas > 3.0 # https://github.com/pandas-dev/pandas/issues/56976 ranked_series = ( - native_series.to_frame() - .assign(**{f"{native_series.name}_is_null": null_mask}) - .groupby(f"{native_series.name}_is_null") + self.native.to_frame() + .assign(**{f"{name}_is_null": null_mask}) + .groupby(f"{name}_is_null") .rank( method=pd_method, na_option="keep", ascending=not descending, pct=False, - )[native_series.name] + )[name] ) - else: - ranked_series = native_series.rank( - method=pd_method, - na_option="keep", - ascending=not descending, - pct=False, + ranked_series = self.native.rank( + method=pd_method, na_option="keep", ascending=not descending, pct=False ) - return self._from_native_series(ranked_series) def hist( @@ -1080,7 +996,7 @@ def hist( version=self._version, validate_column_names=True, ) - elif self._native_series.count() < 1: + elif self.native.count() < 1: if bins is not None: data = {"breakpoint": bins[1:], "count": zeros(shape=len(bins) - 1)} else: @@ -1099,7 +1015,7 @@ def hist( ) elif bin_count is not None: # use Polars binning behavior - lower, upper = self._native_series.min(), self._native_series.max() + lower, upper = self.native.min(), self.native.max() pad_lowest_bin = False if lower == upper: lower -= 0.5 @@ -1115,9 +1031,7 @@ def hist( # pandas (2.2.*) .value_counts(bins=int) adjusts the lowest bin twice, result in improper counts. # pandas (2.2.*) .value_counts(bins=[...]) adjusts the lowest bin which should not happen since # the bins were explicitly passed in. - categories = ns.cut( - self._native_series, bins=bins if bin_count is None else bin_count - ) + categories = ns.cut(self.native, bins=bins if bin_count is None else bin_count) # modin (0.32.0) .value_counts(...) silently drops bins with empty observations, .reindex # is necessary to restore these bins. result = categories.value_counts(dropna=True, sort=False).reindex( From 06684271bc3150ffc5f00218ce10165fc052f0ce Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 25 Mar 2025 17:27:26 +0000 Subject: [PATCH 5/7] catch some more strays --- narwhals/_pandas_like/dataframe.py | 2 +- narwhals/_pandas_like/namespace.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/narwhals/_pandas_like/dataframe.py b/narwhals/_pandas_like/dataframe.py index ed99d273cd..ac40e3b256 100644 --- a/narwhals/_pandas_like/dataframe.py +++ b/narwhals/_pandas_like/dataframe.py @@ -424,7 +424,7 @@ def select(self: PandasLikeDataFrame, *exprs: PandasLikeExpr) -> PandasLikeDataF ) new_series = align_series_full_broadcast(*new_series) df = horizontal_concat( - [s._native_series for s in new_series], + [s.native for s in new_series], implementation=self._implementation, backend_version=self._backend_version, ) diff --git a/narwhals/_pandas_like/namespace.py b/narwhals/_pandas_like/namespace.py index 3bdd9519e1..053308b671 100644 --- a/narwhals/_pandas_like/namespace.py +++ b/narwhals/_pandas_like/namespace.py @@ -290,7 +290,7 @@ def func(df: PandasLikeDataFrame) -> list[PandasLikeSeries]: sep_array = init_value.from_iterable( data=[separator] * len(init_value), name="sep", - index=init_value._native_series.index, + index=init_value.native.index, context=self, ) separators = (sep_array.zip_with(~nm, "") for nm in null_mask[:-1]) From 132b39346307fa34b43a8a1acbf5ce32b47a9ca3 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 25 Mar 2025 17:30:01 +0000 Subject: [PATCH 6/7] chore: Update `nw.utils` --- narwhals/utils.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/narwhals/utils.py b/narwhals/utils.py index ce996e92f6..1d1fdd0b15 100644 --- a/narwhals/utils.py +++ b/narwhals/utils.py @@ -760,22 +760,22 @@ def _validate_index(index: Any) -> None: getattr(lhs_any, "_compliant_frame", None), PandasLikeDataFrame ) and isinstance(getattr(rhs_any, "_compliant_series", None), PandasLikeSeries): _validate_index(lhs_any._compliant_frame._native_frame.index) - _validate_index(rhs_any._compliant_series._native_series.index) + _validate_index(rhs_any._compliant_series.native.index) return lhs_any._from_compliant_dataframe( lhs_any._compliant_frame._from_native_frame( lhs_any._compliant_frame._native_frame.loc[ - rhs_any._compliant_series._native_series.index + rhs_any._compliant_series.native.index ] ) ) if isinstance( getattr(lhs_any, "_compliant_series", None), PandasLikeSeries ) and isinstance(getattr(rhs_any, "_compliant_frame", None), PandasLikeDataFrame): - _validate_index(lhs_any._compliant_series._native_series.index) + _validate_index(lhs_any._compliant_series.native.index) _validate_index(rhs_any._compliant_frame._native_frame.index) return lhs_any._from_compliant_series( lhs_any._compliant_series._from_native_series( - lhs_any._compliant_series._native_series.loc[ + lhs_any._compliant_series.native.loc[ rhs_any._compliant_frame._native_frame.index ] ) @@ -783,12 +783,12 @@ def _validate_index(index: Any) -> None: if isinstance( getattr(lhs_any, "_compliant_series", None), PandasLikeSeries ) and isinstance(getattr(rhs_any, "_compliant_series", None), PandasLikeSeries): - _validate_index(lhs_any._compliant_series._native_series.index) - _validate_index(rhs_any._compliant_series._native_series.index) + _validate_index(lhs_any._compliant_series.native.index) + _validate_index(rhs_any._compliant_series.native.index) return lhs_any._from_compliant_series( lhs_any._compliant_series._from_native_series( - lhs_any._compliant_series._native_series.loc[ - rhs_any._compliant_series._native_series.index + lhs_any._compliant_series.native.loc[ + rhs_any._compliant_series.native.index ] ) ) @@ -1124,7 +1124,7 @@ def is_ordered_categorical(series: Series[Any]) -> bool: isinstance(series._compliant_series, InterchangeSeries) and series.dtype == dtypes.Categorical ): - return series._compliant_series._native_series.describe_categorical["is_ordered"] + return series._compliant_series.native.describe_categorical["is_ordered"] if series.dtype == dtypes.Enum: return True if series.dtype != dtypes.Categorical: From a9c552e2701dbf523f6b6f764e8e2e383f51fd0b Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 25 Mar 2025 17:34:09 +0000 Subject: [PATCH 7/7] fix: compat for `InterchangeSeries` --- narwhals/_interchange/series.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/narwhals/_interchange/series.py b/narwhals/_interchange/series.py index baac8a3cf4..4c74a341f5 100644 --- a/narwhals/_interchange/series.py +++ b/narwhals/_interchange/series.py @@ -35,6 +35,10 @@ def dtype(self: Self) -> DType: self._native_series.dtype, version=self._version ) + @property + def native(self) -> Any: + return self._native_series + def __getattr__(self: Self, attr: str) -> NoReturn: msg = ( # pragma: no cover f"Attribute {attr} is not supported for metadata-only dataframes.\n\n"