Repository: narwhals-dev/narwhals

Issue:
perf: make `maybe_reset_index` a no-op if object has default index
In pandas < 3.0 without copy-on-write enabled, `reset_index` always creates a copy. This makes sense in pandas, as the pandas api has quite a few in-place methods

In the Narwhals API, we have zero in-place methods, so we can optimise `maybe_reset_index` by doing an early return of the object if:
- the input object `obj` is a pandas-like dataframe or pandas_like series
- the index `idx` is an instance of `native_namespace.RangeIndex`
- `idx.start == 0`
- `idx.stop == len(obj) - 1`
- `idx.step == 1`

This should help with performance in the plotly PR

Discussion:
MarcoGorelli: 😄 thanks, and great gif!

Patch:
diff --git a/narwhals/utils.py b/narwhals/utils.py
index 37cce17d..34b45447 100644
--- a/narwhals/utils.py
+++ b/narwhals/utils.py
@@ -31,6 +31,7 @@ from narwhals.translate import to_native
 if TYPE_CHECKING:
     from types import ModuleType
 
+    import pandas as pd
     from typing_extensions import Self
     from typing_extensions import TypeGuard
 
@@ -335,10 +336,16 @@ def maybe_reset_index(obj: T) -> T:
     obj_any = cast(Any, obj)
     native_obj = to_native(obj_any)
     if is_pandas_like_dataframe(native_obj):
+        native_namespace = obj_any.__native_namespace__()
+        if _has_default_index(native_obj, native_namespace):
+            return obj_any  # type: ignore[no-any-return]
         return obj_any._from_compliant_dataframe(  # type: ignore[no-any-return]
             obj_any._compliant_frame._from_native_frame(native_obj.reset_index(drop=True))
         )
     if is_pandas_like_series(native_obj):
+        native_namespace = obj_any.__native_namespace__()
+        if _has_default_index(native_obj, native_namespace):
+            return obj_any  # type: ignore[no-any-return]
         return obj_any._from_compliant_series(  # type: ignore[no-any-return]
             obj_any._compliant_series._from_native_series(
                 native_obj.reset_index(drop=True)
@@ -347,6 +354,18 @@ def maybe_reset_index(obj: T) -> T:
     return obj_any  # type: ignore[no-any-return]
 
 
+def _has_default_index(
+    native_frame_or_series: pd.Series | pd.DataFrame, native_namespace: Any
+) -> bool:
+    index = native_frame_or_series.index
+    return (
+        isinstance(index, native_namespace.RangeIndex)
+        and index.start == 0
+        and index.stop == len(index)
+        and index.step == 1
+    )
+
+
 def maybe_convert_dtypes(obj: T, *args: bool, **kwargs: bool | str) -> T:
     """
     Convert columns or series to the best possible dtypes using dtypes supporting ``pd.NA``, if df is pandas-like.


Test patch:
diff --git a/tests/utils_test.py b/tests/utils_test.py
index aede36ce..e06cc9ca 100644
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -94,12 +94,22 @@ def test_maybe_reset_index_pandas() -> None:
     result = nw.maybe_reset_index(pandas_df)
     expected = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}, index=[0, 1, 2])
     assert_frame_equal(nw.to_native(result), expected)
+    pandas_df = nw.from_native(pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}))
+    result = nw.maybe_reset_index(pandas_df)
+    expected = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+    assert_frame_equal(nw.to_native(result), expected)
+    assert result.to_native() is pandas_df.to_native()
     pandas_series = nw.from_native(
         pd.Series([1, 2, 3], index=[7, 8, 9]), series_only=True
     )
     result_s = nw.maybe_reset_index(pandas_series)
     expected_s = pd.Series([1, 2, 3], index=[0, 1, 2])
     assert_series_equal(nw.to_native(result_s), expected_s)
+    pandas_series = nw.from_native(pd.Series([1, 2, 3]), series_only=True)
+    result_s = nw.maybe_reset_index(pandas_series)
+    expected_s = pd.Series([1, 2, 3])
+    assert_series_equal(nw.to_native(result_s), expected_s)
+    assert result_s.to_native() is pandas_series.to_native()
 
 
 def test_maybe_reset_index_polars() -> None: