Skip to content

Commit

Permalink
pandas nightly compat
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcoGorelli committed Mar 30, 2024
1 parent 8ec067d commit 74c5281
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 35 deletions.
9 changes: 3 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@
[![PyPI version](https://badge.fury.io/py/narwhals.svg)](https://badge.fury.io/py/narwhals)
[![Documentation](https://img.shields.io/badge/Documentation-coolgreen?style=flat&link=https://marcogorelli.github.io/narwhals/)](https://marcogorelli.github.io/narwhals/)

Extremely lightweight compatibility layer between Polars, pandas, modin, and cuDF (and possibly more?).
Extremely lightweight and extensible compatibility layer between Polars, pandas, modin, and cuDF (and more!).

Seamlessly support all, without depending on any!

-**Just use** a subset of **the Polars API**, no need to learn anything new
-**No dependencies** (not even Polars), keep your library lightweight
- ✅ Separate **lazy** and eager APIs
- ✅ Use Polars **Expressions**

**Note: this is work-in-progress, and a bit of an experiment, don't take it too seriously**.
- ✅ Tested against pandas and Polars nightly builds!

## Installation

Expand Down Expand Up @@ -133,15 +132,13 @@ Magic! 🪄
## Scope

- Do you maintain a dataframe-consuming library?
- Is there a Polars function which you'd like Narwhals to have, which would make your job easier?
- Is there a Polars function which you'd like Narwhals to have, which would make your work easier?

If, I'd love to hear from you!

**Note**: You might suspect that this is a secret ploy to infiltrate the Polars API everywhere.
Indeed, you may suspect that.

Also, please note that this is not intended as a "dataframe standard" project.

## Why "Narwhals"?

Because they are so awesome.
Expand Down
66 changes: 39 additions & 27 deletions narwhals/_pandas_like/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from narwhals._pandas_like.utils import reverse_translate_dtype
from narwhals._pandas_like.utils import translate_dtype
from narwhals._pandas_like.utils import validate_column_comparand
from narwhals.utils import parse_version

if TYPE_CHECKING:
from typing_extensions import Self
Expand All @@ -33,13 +34,24 @@ def __init__(
self._name = str(series.name) if series.name is not None else ""
self._series = series
self._implementation = implementation
self._use_copy_false = False
if self._implementation == "pandas":
import pandas as pd

if parse_version(pd.__version__) < parse_version("3.0.0"):
self._use_copy_false = True

def __narwhals_series__(self) -> Self:
return self

def _rename(self, series: Any, name: str) -> Any:
if self._use_copy_false:
return series.rename(name, copy=False)
return series.rename(name)

def _from_series(self, series: Any) -> Self:
return self.__class__(
series.rename(series.name, copy=False),
series,
implementation=self._implementation,
)

Expand All @@ -56,7 +68,7 @@ def shape(self) -> tuple[int]:

def rename(self, name: str) -> PandasSeries:
ser = self._series
return self._from_series(ser.rename(name, copy=False))
return self._from_series(self._rename(ser, name))

@property
def dtype(self) -> DType:
Expand Down Expand Up @@ -98,122 +110,122 @@ def is_in(self, other: Any) -> PandasSeries:
def __eq__(self, other: object) -> PandasSeries: # type: ignore[override]
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__eq__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__eq__(other), ser.name))

def __ne__(self, other: object) -> PandasSeries: # type: ignore[override]
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__ne__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__ne__(other), ser.name))

def __ge__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__ge__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__ge__(other), ser.name))

def __gt__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__gt__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__gt__(other), ser.name))

def __le__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__le__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__le__(other), ser.name))

def __lt__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__lt__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__lt__(other), ser.name))

def __and__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__and__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__and__(other), ser.name))

def __rand__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__rand__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__rand__(other), ser.name))

def __or__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__or__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__or__(other), ser.name))

def __ror__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__ror__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__ror__(other), ser.name))

def __add__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__add__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__add__(other), ser.name))

def __radd__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__radd__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__radd__(other), ser.name))

def __sub__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__sub__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__sub__(other), ser.name))

def __rsub__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__rsub__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__rsub__(other), ser.name))

def __mul__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__mul__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__mul__(other), ser.name))

def __rmul__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__rmul__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__rmul__(other), ser.name))

def __truediv__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__truediv__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__truediv__(other), ser.name))

def __rtruediv__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__rtruediv__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__rtruediv__(other), ser.name))

def __floordiv__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__floordiv__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__floordiv__(other), ser.name))

def __rfloordiv__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__rfloordiv__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__rfloordiv__(other), ser.name))

def __pow__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__pow__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__pow__(other), ser.name))

def __rpow__(self, other: Any) -> PandasSeries: # pragma: no cover
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__rpow__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__rpow__(other), ser.name))

def __mod__(self, other: Any) -> PandasSeries:
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__mod__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__mod__(other), ser.name))

def __rmod__(self, other: Any) -> PandasSeries: # pragma: no cover
ser = self._series
other = validate_column_comparand(self._series.index, other)
return self._from_series((ser.__rmod__(other)).rename(ser.name, copy=False))
return self._from_series(self._rename(ser.__rmod__(other), ser.name))

# Unary

Expand Down Expand Up @@ -333,7 +345,7 @@ def sort(

def alias(self, name: str) -> Self:
ser = self._series
return self._from_series(ser.rename(name, copy=False))
return self._from_series(self._rename(ser, name))

def to_numpy(self) -> Any:
return self._series.to_numpy()
Expand Down
9 changes: 7 additions & 2 deletions narwhals/_pandas_like/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from narwhals.utils import flatten
from narwhals.utils import isinstance_or_issubclass
from narwhals.utils import parse_version
from narwhals.utils import remove_prefix

T = TypeVar("T")
Expand Down Expand Up @@ -263,7 +264,9 @@ def horizontal_concat(dfs: list[Any], implementation: str) -> Any:
if implementation == "pandas":
import pandas as pd

return pd.concat(dfs, axis=1, copy=False)
if parse_version(pd.__version__) < parse_version("3.0.0"):
return pd.concat(dfs, axis=1, copy=False)
return pd.concat(dfs, axis=1)
if implementation == "cudf":
import cudf

Expand Down Expand Up @@ -294,7 +297,9 @@ def vertical_concat(dfs: list[Any], implementation: str) -> Any:
if implementation == "pandas":
import pandas as pd

return pd.concat(dfs, axis=0, copy=False)
if parse_version(pd.__version__) < parse_version("3.0.0"):
return pd.concat(dfs, axis=0, copy=False)
return pd.concat(dfs, axis=0)
if implementation == "cudf":
import cudf

Expand Down

0 comments on commit 74c5281

Please sign in to comment.