From 783865a23c531645b2d80ca692a4081ac91f9b7c Mon Sep 17 00:00:00 2001 From: FBruzzesi Date: Fri, 1 Nov 2024 20:29:42 +0100 Subject: [PATCH] docstring, deprecation --- docs/basics/dataframe_conversion.md | 2 +- narwhals/dataframe.py | 105 +++++++++++++++++++++-- narwhals/series.py | 48 ++++++++++- narwhals/stable/v1/__init__.py | 124 ++++++++++++++++++++++++++++ tests/frame/to_native_test.py | 12 +++ tests/series_only/to_native_test.py | 13 +++ 6 files changed, 291 insertions(+), 13 deletions(-) diff --git a/docs/basics/dataframe_conversion.md b/docs/basics/dataframe_conversion.md index 690f5d093..d07d37615 100644 --- a/docs/basics/dataframe_conversion.md +++ b/docs/basics/dataframe_conversion.md @@ -49,7 +49,7 @@ Similarly, if your library uses Polars internally, you can convert any user-supp ```python exec="1" source="above" session="conversion" result="python" def df_to_polars(df: IntoDataFrame) -> pl.DataFrame: - return nw.from_arrow(nw.from_native(df), native_namespace=pl).to_native() + return nw.from_arrow(nw.from_native(df), native_namespace=pl).native print(df_to_polars(df_duckdb)) # You can only execute this line of code once. diff --git a/narwhals/dataframe.py b/narwhals/dataframe.py index 3fd2fdd28..e1a797ef1 100644 --- a/narwhals/dataframe.py +++ b/narwhals/dataframe.py @@ -11,11 +11,11 @@ from typing import Sequence from typing import TypeVar from typing import overload +from warnings import warn from narwhals.dependencies import get_polars from narwhals.dependencies import is_numpy_array from narwhals.schema import Schema -from narwhals.translate import to_native from narwhals.utils import flatten from narwhals.utils import is_sequence_but_not_str from narwhals.utils import parse_version @@ -330,7 +330,50 @@ def _lazyframe(self) -> type[LazyFrame[Any]]: @property def native(self: Self) -> DataFrameT: - """Returns native frame underlying Narwhals DataFrame.""" + """ + Convert Narwhals DataFrame to native one. + + Returns: + Object of class that user started with. + + Examples: + >>> import pandas as pd + >>> import polars as pl + >>> import pyarrow as pa + >>> import narwhals as nw + >>> data = {"foo": [1, 2, 3], "bar": [6.0, 7.0, 8.0], "ham": ["a", "b", "c"]} + >>> df_pd = pd.DataFrame(data) + >>> df_pl = pl.DataFrame(data) + >>> df_pa = pa.table(data) + + Calling `to_native` on a Narwhals DataFrame returns the native object: + + >>> nw.from_native(df_pd).to_native() + foo bar ham + 0 1 6.0 a + 1 2 7.0 b + 2 3 8.0 c + >>> nw.from_native(df_pl).to_native() + shape: (3, 3) + ┌─────┬─────┬─────┐ + │ foo ┆ bar ┆ ham │ + │ --- ┆ --- ┆ --- │ + │ i64 ┆ f64 ┆ str │ + ╞═════╪═════╪═════╡ + │ 1 ┆ 6.0 ┆ a │ + │ 2 ┆ 7.0 ┆ b │ + │ 3 ┆ 8.0 ┆ c │ + └─────┴─────┴─────┘ + >>> nw.from_native(df_pa).to_native() + pyarrow.Table + foo: int64 + bar: double + ham: string + ---- + foo: [[1,2,3]] + bar: [[6,7,8]] + ham: [["a","b","c"]] + """ return self._compliant_frame._native_frame # type: ignore[no-any-return] def __init__( @@ -435,7 +478,7 @@ def lazy(self) -> LazyFrame[Any]: """ return self._lazyframe(self._compliant_frame.lazy(), level=self._level) - def to_native(self) -> DataFrameT: + def to_native(self: Self) -> DataFrameT: """ Convert Narwhals DataFrame to native one. @@ -480,8 +523,13 @@ def to_native(self) -> DataFrameT: bar: [[6,7,8]] ham: [["a","b","c"]] """ - - return self._compliant_frame._native_frame # type: ignore[no-any-return] + warn( + "Use `.native` property instead. `.to_native()` is " + "deprecated and it will be removed in future versions", + DeprecationWarning, + stacklevel=2, + ) + return self.native def to_pandas(self) -> pd.DataFrame: """ @@ -2772,7 +2820,41 @@ def _dataframe(self) -> type[DataFrame[Any]]: @property def native(self: Self) -> FrameT: - """Returns native frame underlying Narwhals LazyFrame.""" + """ + Convert Narwhals LazyFrame to native one. + + Returns: + Object of class that user started with. + + Examples: + >>> import pandas as pd + >>> import polars as pl + >>> import pyarrow as pa + >>> import narwhals as nw + >>> data = {"foo": [1, 2, 3], "bar": [6.0, 7.0, 8.0], "ham": ["a", "b", "c"]} + >>> df_pd = pd.DataFrame(data) + >>> df_pl = pl.LazyFrame(data) + >>> df_pa = pa.table(data) + + Calling `to_native` on a Narwhals DataFrame returns the native object: + + >>> nw.from_native(df_pd).lazy().to_native() + foo bar ham + 0 1 6.0 a + 1 2 7.0 b + 2 3 8.0 c + >>> nw.from_native(df_pl).to_native().collect() + shape: (3, 3) + ┌─────┬─────┬─────┐ + │ foo ┆ bar ┆ ham │ + │ --- ┆ --- ┆ --- │ + │ i64 ┆ f64 ┆ str │ + ╞═════╪═════╪═════╡ + │ 1 ┆ 6.0 ┆ a │ + │ 2 ┆ 7.0 ┆ b │ + │ 3 ┆ 8.0 ┆ c │ + └─────┴─────┴─────┘ + """ return self._compliant_frame._native_frame # type: ignore[no-any-return] def __init__( @@ -2847,7 +2929,7 @@ def collect(self) -> DataFrame[Any]: level=self._level, ) - def to_native(self) -> FrameT: + def to_native(self: Self) -> FrameT: """ Convert Narwhals LazyFrame to native one. @@ -2883,8 +2965,13 @@ def to_native(self) -> FrameT: │ 3 ┆ 8.0 ┆ c │ └─────┴─────┴─────┘ """ - - return to_native(narwhals_object=self, strict=True) + warn( + "Use `.native` property instead. `.to_native()` is " + "deprecated and it will be removed in future versions", + DeprecationWarning, + stacklevel=2, + ) + return self.native # inherited def pipe(self, function: Callable[[Any], Self], *args: Any, **kwargs: Any) -> Self: diff --git a/narwhals/series.py b/narwhals/series.py index 3a0c013b0..86b2f6b3f 100644 --- a/narwhals/series.py +++ b/narwhals/series.py @@ -9,6 +9,7 @@ from typing import Sequence from typing import TypeVar from typing import overload +from warnings import warn from narwhals.utils import parse_version @@ -43,7 +44,42 @@ def _dataframe(self) -> type[DataFrame[Any]]: @property def native(self: Self) -> Any: - """Returns native series underlying Narwhals Series.""" + """ + Convert Narwhals series to native series. + + Returns: + Series of class that user started with. + + Examples: + >>> import pandas as pd + >>> import polars as pl + >>> import narwhals as nw + >>> s = [1, 2, 3] + >>> s_pd = pd.Series(s) + >>> s_pl = pl.Series(s) + + We define a library agnostic function: + + >>> @nw.narwhalify + ... def func(s): + ... return s.to_native() + + We can then pass either pandas or Polars to `func`: + + >>> func(s_pd) + 0 1 + 1 2 + 2 3 + dtype: int64 + >>> func(s_pl) # doctest: +NORMALIZE_WHITESPACE + shape: (3,) + Series: '' [i64] + [ + 1 + 2 + 3 + ] + """ return self._compliant_series._native_series def __init__( @@ -102,7 +138,7 @@ def __arrow_c_stream__(self, requested_schema: object | None = None) -> object: ca = pa.chunked_array([self.to_arrow()]) return ca.__arrow_c_stream__(requested_schema=requested_schema) - def to_native(self) -> Any: + def to_native(self: Self) -> Any: """ Convert Narwhals series to native series. @@ -139,7 +175,13 @@ def to_native(self) -> Any: 3 ] """ - return self._compliant_series._native_series + warn( + "Use `.native` property instead. `.to_native()` is " + "deprecated and it will be removed in future versions", + DeprecationWarning, + stacklevel=2, + ) + return self.native def scatter(self, indices: int | Sequence[int], values: Any) -> Self: """ diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py index 68aca7706..a0789a245 100644 --- a/narwhals/stable/v1/__init__.py +++ b/narwhals/stable/v1/__init__.py @@ -326,6 +326,53 @@ def is_unique(self: Self) -> Series: """ return super().is_unique() # type: ignore[return-value] + def to_native(self: Self) -> IntoDataFrameT: + """ + Convert Narwhals DataFrame to native one. + + Returns: + Object of class that user started with. + + Examples: + >>> import pandas as pd + >>> import polars as pl + >>> import pyarrow as pa + >>> import narwhals as nw + >>> data = {"foo": [1, 2, 3], "bar": [6.0, 7.0, 8.0], "ham": ["a", "b", "c"]} + >>> df_pd = pd.DataFrame(data) + >>> df_pl = pl.DataFrame(data) + >>> df_pa = pa.table(data) + + Calling `to_native` on a Narwhals DataFrame returns the native object: + + >>> nw.from_native(df_pd).to_native() + foo bar ham + 0 1 6.0 a + 1 2 7.0 b + 2 3 8.0 c + >>> nw.from_native(df_pl).to_native() + shape: (3, 3) + ┌─────┬─────┬─────┐ + │ foo ┆ bar ┆ ham │ + │ --- ┆ --- ┆ --- │ + │ i64 ┆ f64 ┆ str │ + ╞═════╪═════╪═════╡ + │ 1 ┆ 6.0 ┆ a │ + │ 2 ┆ 7.0 ┆ b │ + │ 3 ┆ 8.0 ┆ c │ + └─────┴─────┴─────┘ + >>> nw.from_native(df_pa).to_native() + pyarrow.Table + foo: int64 + bar: double + ham: string + ---- + foo: [[1,2,3]] + bar: [[6,7,8]] + ham: [["a","b","c"]] + """ + return self.native + def _l1_norm(self: Self) -> Self: """Private, just used to test the stable API.""" return self.select(all()._l1_norm()) @@ -383,6 +430,44 @@ def collect(self) -> DataFrame[Any]: """ return super().collect() # type: ignore[return-value] + def to_native(self: Self) -> IntoFrameT: + """ + Convert Narwhals LazyFrame to native one. + + Returns: + Object of class that user started with. + + Examples: + >>> import pandas as pd + >>> import polars as pl + >>> import pyarrow as pa + >>> import narwhals.stable.v1 as nw + >>> data = {"foo": [1, 2, 3], "bar": [6.0, 7.0, 8.0], "ham": ["a", "b", "c"]} + >>> df_pd = pd.DataFrame(data) + >>> df_pl = pl.LazyFrame(data) + >>> df_pa = pa.table(data) + + Calling `to_native` on a Narwhals DataFrame returns the native object: + + >>> nw.from_native(df_pd).lazy().to_native() + foo bar ham + 0 1 6.0 a + 1 2 7.0 b + 2 3 8.0 c + >>> nw.from_native(df_pl).to_native().collect() + shape: (3, 3) + ┌─────┬─────┬─────┐ + │ foo ┆ bar ┆ ham │ + │ --- ┆ --- ┆ --- │ + │ i64 ┆ f64 ┆ str │ + ╞═════╪═════╪═════╡ + │ 1 ┆ 6.0 ┆ a │ + │ 2 ┆ 7.0 ┆ b │ + │ 3 ┆ 8.0 ┆ c │ + └─────┴─────┴─────┘ + """ + return self.native + def _l1_norm(self: Self) -> Self: """Private, just used to test the stable API.""" return self.select(all()._l1_norm()) @@ -501,6 +586,45 @@ def value_counts( sort=sort, parallel=parallel, name=name, normalize=normalize ) + def to_native(self: Self) -> Any: + """ + Convert Narwhals series to native series. + + Returns: + Series of class that user started with. + + Examples: + >>> import pandas as pd + >>> import polars as pl + >>> import narwhals as nw + >>> s = [1, 2, 3] + >>> s_pd = pd.Series(s) + >>> s_pl = pl.Series(s) + + We define a library agnostic function: + + >>> @nw.narwhalify + ... def func(s): + ... return s.to_native() + + We can then pass either pandas or Polars to `func`: + + >>> func(s_pd) + 0 1 + 1 2 + 2 3 + dtype: int64 + >>> func(s_pl) # doctest: +NORMALIZE_WHITESPACE + shape: (3,) + Series: '' [i64] + [ + 1 + 2 + 3 + ] + """ + return self.native + class Expr(NwExpr): def _l1_norm(self) -> Self: diff --git a/tests/frame/to_native_test.py b/tests/frame/to_native_test.py index 464850196..4a7a2bae9 100644 --- a/tests/frame/to_native_test.py +++ b/tests/frame/to_native_test.py @@ -2,6 +2,9 @@ from typing import TYPE_CHECKING +import pytest + +import narwhals as nw_unstable import narwhals.stable.v1 as nw if TYPE_CHECKING: @@ -15,3 +18,12 @@ def test_to_native(constructor: Constructor) -> None: assert isinstance(df.to_native(), df_raw.__class__) assert isinstance(df.native, df_raw.__class__) + + +def test_raise_warning(constructor: Constructor) -> None: + data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.1, 8, 9]} + df_raw = constructor(data) + df = nw_unstable.from_native(df_raw) + + with pytest.deprecated_call(): + assert isinstance(df.to_native(), df_raw.__class__) diff --git a/tests/series_only/to_native_test.py b/tests/series_only/to_native_test.py index 06fcd4563..9203d341f 100644 --- a/tests/series_only/to_native_test.py +++ b/tests/series_only/to_native_test.py @@ -2,6 +2,9 @@ from typing import TYPE_CHECKING +import pytest + +import narwhals as nw_unstable import narwhals.stable.v1 as nw if TYPE_CHECKING: @@ -15,3 +18,13 @@ def test_to_native(constructor_eager: ConstructorEager) -> None: nw_series = nw.from_native(constructor_eager({"a": data}), eager_only=True)["a"] assert isinstance(nw_series.to_native(), orig_series.__class__) assert isinstance(nw_series.native, orig_series.__class__) + + +def test_raise_warning(constructor_eager: ConstructorEager) -> None: + orig_series = constructor_eager({"a": data})["a"] # type: ignore[index] + nw_series = nw_unstable.from_native(constructor_eager({"a": data}), eager_only=True)[ + "a" + ] + + with pytest.deprecated_call(): + assert isinstance(nw_series.to_native(), orig_series.__class__)