Repository: modin-project/modin

Issue:
BUG: `.dtypes` are not copied on `df.copy(deep=True)`
### Modin version checks

- [X] I have checked that this issue has not already been reported.

- [X] I have confirmed this bug exists on the latest released version of Modin.

- [X] I have confirmed this bug exists on the main branch of Modin. (In order to do this you can follow [this guide](https://modin.readthedocs.io/en/stable/getting_started/installation.html#installing-from-the-github-master-branch).)


### Reproducible Example

```python
import modin.pandas as pd

df = pd.DataFrame({"a": [1, 2]})
df_renamed = df.rename(columns={"a": "new_a"}, copy=True, inplace=False)

# both `.dtypes` has changed
print(df_renamed.dtypes) # new_a: int64
print(df.dtypes) # new_a: int64
```


### Issue Description

The `PandasDataframe` logic doesn't copy `.dtypes` as it should:
https://github.com/modin-project/modin/blob/91d6825c93ea58d790352229d798ff7cbecf50dd/modin/core/dataframe/pandas/dataframe/dataframe.py#L1167-L1174


### Expected Behavior

`df.dtypes is df.copy().dtypes == False` 

### Error Logs

<details>

```python-traceback

Replace this line with the error backtrace (if applicable).

```

</details>


### Installed Versions

<details>

Replace this line with the output of pd.show_versions()

</details>



Discussion:

Resolving patch:
diff --git a/modin/core/dataframe/pandas/dataframe/dataframe.py b/modin/core/dataframe/pandas/dataframe/dataframe.py
--- a/modin/core/dataframe/pandas/dataframe/dataframe.py
+++ b/modin/core/dataframe/pandas/dataframe/dataframe.py
@@ -1170,7 +1170,7 @@ def copy(self):
             self._columns_cache.copy() if self._columns_cache is not None else None,
             self._row_lengths_cache,
             self._column_widths_cache,
-            self._dtypes,
+            self._dtypes.copy() if self._dtypes is not None else None,
         )
 
     @lazy_metadata_decorator(apply_axis="both")


Test patch:
diff --git a/modin/pandas/test/dataframe/test_indexing.py b/modin/pandas/test/dataframe/test_indexing.py
--- a/modin/pandas/test/dataframe/test_indexing.py
+++ b/modin/pandas/test/dataframe/test_indexing.py
@@ -1183,6 +1183,20 @@ def test_rename_axis_inplace():
     df_equals(modin_result, result)
 
 
+def test_rename_issue5600():
+    # Check the issue for more details
+    # https://github.com/modin-project/modin/issues/5600
+    df = pd.DataFrame({"a": [1, 2]})
+    df_renamed = df.rename(columns={"a": "new_a"}, copy=True, inplace=False)
+
+    # Check that the source frame was untouched
+    assert df.dtypes.keys().tolist() == ["a"]
+    assert df.columns.tolist() == ["a"]
+
+    assert df_renamed.dtypes.keys().tolist() == ["new_a"]
+    assert df_renamed.columns.tolist() == ["new_a"]
+
+
 def test_reorder_levels():
     data = np.random.randint(1, 100, 12)
     modin_df = pd.DataFrame(
diff --git a/modin/pandas/test/dataframe/test_map_metadata.py b/modin/pandas/test/dataframe/test_map_metadata.py
--- a/modin/pandas/test/dataframe/test_map_metadata.py
+++ b/modin/pandas/test/dataframe/test_map_metadata.py
@@ -287,20 +287,30 @@ def test_copy(data):
 
     # pandas_df is unused but there so there won't be confusing list comprehension
     # stuff in the pytest.mark.parametrize
-    new_modin_df = modin_df.copy()
+    new_modin_df = modin_df.copy(deep=True)
 
     assert new_modin_df is not modin_df
+    assert new_modin_df.index is not modin_df.index
+    assert new_modin_df.columns is not modin_df.columns
+    assert new_modin_df.dtypes is not modin_df.dtypes
+
     if get_current_execution() != "BaseOnPython":
         assert np.array_equal(
             new_modin_df._query_compiler._modin_frame._partitions,
             modin_df._query_compiler._modin_frame._partitions,
         )
-    assert new_modin_df is not modin_df
     df_equals(new_modin_df, modin_df)
 
     # Shallow copy tests
     modin_df = pd.DataFrame(data)
-    modin_df_cp = modin_df.copy(False)
+    modin_df_cp = modin_df.copy(deep=False)
+
+    assert modin_df_cp is not modin_df
+    assert modin_df_cp.index is modin_df.index
+    assert modin_df_cp.columns is modin_df.columns
+    # FIXME: we're different from pandas here as modin doesn't copy dtypes for a shallow copy
+    # https://github.com/modin-project/modin/issues/5602
+    # assert modin_df_cp.dtypes is not modin_df.dtypes
 
     modin_df[modin_df.columns[0]] = 0
     df_equals(modin_df, modin_df_cp)