Repository: narwhals-dev/narwhals

Issue:
enh: implement `DataFrame.to_native`
Currently, we can call `nw.to_native`

It would be more user-friendly to have `DataFrame.to_native` though, I keep finding myself reaching for this

Contributing guide: https://github.com/narwhals-dev/narwhals/blob/main/CONTRIBUTING.md

Discussion:

Patch:
diff --git a/docs/api-reference/dataframe.md b/docs/api-reference/dataframe.md
index fe0c4025..f593293f 100644
--- a/docs/api-reference/dataframe.md
+++ b/docs/api-reference/dataframe.md
@@ -37,6 +37,7 @@
         - tail
         - to_arrow
         - to_dict
+        - to_native
         - to_numpy
         - to_pandas
         - unique
diff --git a/narwhals/dataframe.py b/narwhals/dataframe.py
index d100e8ef..2a7a94cf 100644
--- a/narwhals/dataframe.py
+++ b/narwhals/dataframe.py
@@ -30,10 +30,10 @@ if TYPE_CHECKING:
     from narwhals.group_by import GroupBy
     from narwhals.group_by import LazyGroupBy
     from narwhals.series import Series
-    from narwhals.typing import IntoDataFrame
     from narwhals.typing import IntoExpr
+    from narwhals.typing import IntoFrame
 
-FrameT = TypeVar("FrameT", bound="IntoDataFrame")
+FrameT = TypeVar("FrameT", bound="IntoFrame")
 
 
 class BaseFrame(Generic[FrameT]):
@@ -390,6 +390,54 @@ class DataFrame(BaseFrame[FrameT]):
         """
         return super().lazy()
 
+    def to_native(self) -> FrameT:
+        """
+        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 to_pandas(self) -> pd.DataFrame:
         """
         Convert this DataFrame to a pandas DataFrame.


Test patch:
diff --git a/tests/frame/to_native_test.py b/tests/frame/to_native_test.py
new file mode 100644
index 00000000..236403e2
--- /dev/null
+++ b/tests/frame/to_native_test.py
@@ -0,0 +1,11 @@
+from typing import Any
+
+import narwhals.stable.v1 as nw
+
+
+def test_to_native(constructor_eager: Any) -> None:
+    data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.1, 8, 9]}
+    df_raw = constructor_eager(data)
+    df = nw.from_native(df_raw, eager_only=True)
+
+    assert isinstance(df.to_native(), df_raw.__class__)