Repository: modin-project/modin
Pull request: PERF-#6661: Do not convert columns dtypes if the new dtypes are the same
<!--
Thank you for your contribution!
Please review the contributing docs: https://modin.readthedocs.io/en/latest/development/contributing.html
if you have questions about contributing.
-->

## What do these changes do?

<!-- Please give a short brief about these changes. -->

- [x] first commit message and PR title follow format outlined [here](https://modin.readthedocs.io/en/latest/development/contributing.html#commit-message-formatting)
  > **_NOTE:_**  If you edit the PR title to match this format, you need to add another commit (even if it's empty) or amend your last commit for the CI job that checks the PR title to pick up the new PR title.
- [x] passes `flake8 modin/ asv_bench/benchmarks scripts/doc_checker.py`
- [x] passes `black --check modin/ asv_bench/benchmarks scripts/doc_checker.py`
- [x] signed commit with `git commit -s` <!-- you can amend your commit with a signature via `git commit -amend -s` -->
- [x] Resolves #6661 <!-- issue must be created for each patch -->
- [ ] tests added and passing
- [ ] module layout described at `docs/development/architecture.rst` is up-to-date <!-- if you have added, renamed or removed files or directories please update the documentation accordingly -->


Problem statement:
PERF: Do not convert columns dtypes if the new dtypes are the same
The PandasDataframe.astype() method always performs the dtypes conversion, even if the new dtypes are the same. It results in unnecessary computation and remote calls, in case of remote backends.

Hints from issue discussion:

Commit message: PERF-#6661: Do not convert columns dtypes if the new dtypes are the same

Signed-off-by: Andrey Pavlenko <andrey.a.pavlenko@gmail.com>

Patch to review:
diff --git a/modin/core/dataframe/pandas/dataframe/dataframe.py b/modin/core/dataframe/pandas/dataframe/dataframe.py
index 29eaaad3bea..8df572359ad 100644
--- a/modin/core/dataframe/pandas/dataframe/dataframe.py
+++ b/modin/core/dataframe/pandas/dataframe/dataframe.py
@@ -25,7 +25,7 @@
 import pandas
 from pandas._libs.lib import no_default
 from pandas.api.types import is_object_dtype
-from pandas.core.dtypes.common import is_list_like, is_numeric_dtype
+from pandas.core.dtypes.common import is_dtype_equal, is_list_like, is_numeric_dtype
 from pandas.core.indexes.api import Index, RangeIndex
 
 from modin.config import Engine, IsRayCluster, NPartitions
@@ -1486,9 +1486,13 @@ def astype(self, col_dtypes, errors: str = "raise"):
         BaseDataFrame
             Dataframe with updated dtypes.
         """
+        self_dtypes = self.dtypes
+        if all(is_dtype_equal(self_dtypes[k], v) for k, v in col_dtypes.items()):
+            return self
+
         columns = col_dtypes.keys()
         # Create Series for the updated dtypes
-        new_dtypes = self.dtypes.copy()
+        new_dtypes = self_dtypes.copy()
         # When casting to "category" we have to make up the whole axis partition
         # to get the properly encoded table of categories. Every block partition
         # will store the encoded table. That can lead to higher memory footprint.
@@ -1498,8 +1502,8 @@ def astype(self, col_dtypes, errors: str = "raise"):
         for i, column in enumerate(columns):
             dtype = col_dtypes[column]
             if (
-                not isinstance(dtype, type(self.dtypes[column]))
-                or dtype != self.dtypes[column]
+                not isinstance(dtype, type(self_dtypes[column]))
+                or dtype != self_dtypes[column]
             ):
                 # Update the new dtype series to the proper pandas dtype
                 try:


Code review comments:
diff_hunk: @@ -1486,9 +1486,13 @@ def astype(self, col_dtypes, errors: str = "raise"):
         BaseDataFrame
             Dataframe with updated dtypes.
         """
+        self_dtypes = self.dtypes
+        if all(is_dtype_equal(self_dtypes[k], v) for k, v in col_dtypes.items()):
+            return self
path: modin/core/dataframe/pandas/dataframe/dataframe.py
text: @user1:
should probably return a copy here, so in-place modification of the result wouldn't propagate the original frame. All copies are shallow, so it shouldn't be expensive
```suggestion
            return self.copy()
```

@user1:
or maybe just move this `is_dtype_equal()` comparison [to the inside of the for-loop](https://github.com/modin-project/modin/pull/6662/files#diff-718e1f98ecbed40356292a477fc27bafb81e62c796491c81e398d5b397d6d69eR1505), and then after the loop do?:
```python
if not any_dtype_has_changed:
   return self.copy()
```