Repository: modin-project/modin (python)

Issue:
BUG: columns mismatch after df.update
### 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-main-branch).)


### Reproducible Example

```python
import modin.pandas as pd

df = pd.DataFrame({'C': [1, 2, 3], 'B': [400, 500, 600]})
new_df = pd.DataFrame({'B': [4, 5, 6], 'A': [7, 8, 9]})
df.update(new_df)
print(df)
   B  C
0  4  1
1  5  2
2  6  3
```


### Issue Description

Columns get ordered while in pandas not.

### Expected Behavior

Should match pandas.

### 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>


Pull request description:
FIX-#7329: Do not sort columns on df.update
<!--
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 #7329  <!-- issue must be created for each patch -->
- [x] tests added and passing
- [x] 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 -->


Patch:
diff --git a/NOTICE b/NOTICE
index 47193941..77a9b840 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,3 +1,3 @@
 Modin
 
-Copyright (c) 2018-2023 Modin Developers.
+Copyright (c) 2018-2024 Modin Developers.
diff --git a/docs/conf.py b/docs/conf.py
index cde50b8c..7022f054 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -61,7 +61,7 @@ configs_file_path = os.path.abspath(
 export_config_help(configs_file_path)
 
 project = "Modin"
-copyright = "2018-2023, Modin Developers."
+copyright = "2018-2024, Modin Developers."
 author = "Modin contributors"
 
 # The short X.Y version
diff --git a/modin/core/dataframe/algebra/binary.py b/modin/core/dataframe/algebra/binary.py
index b5e701d2..b107089e 100644
--- a/modin/core/dataframe/algebra/binary.py
+++ b/modin/core/dataframe/algebra/binary.py
@@ -298,6 +298,7 @@ class Binary(Operator):
         cls,
         func: Callable[..., pandas.DataFrame],
         join_type: str = "outer",
+        sort: bool = None,
         labels: str = "replace",
         infer_dtypes: Optional[str] = None,
     ) -> Callable[..., PandasQueryCompiler]:
@@ -310,6 +311,8 @@ class Binary(Operator):
             Binary function to execute. Have to be able to accept at least two arguments.
         join_type : {'left', 'right', 'outer', 'inner', None}, default: 'outer'
             Type of join that will be used if indices of operands are not aligned.
+        sort : bool, default: None
+            Whether to sort index and columns or not.
         labels : {"keep", "replace", "drop"}, default: "replace"
             Whether keep labels from left Modin DataFrame, replace them with labels
             from joined DataFrame or drop altogether to make them be computed lazily later.
@@ -419,6 +422,7 @@ class Binary(Operator):
                             lambda x, y: func(x, y, *args, **kwargs),
                             [other._modin_frame],
                             join_type=join_type,
+                            sort=sort,
                             labels=labels,
                             dtypes=dtypes,
                         ),
diff --git a/modin/core/dataframe/pandas/dataframe/dataframe.py b/modin/core/dataframe/pandas/dataframe/dataframe.py
index b8467ebe..5456f28f 100644
--- a/modin/core/dataframe/pandas/dataframe/dataframe.py
+++ b/modin/core/dataframe/pandas/dataframe/dataframe.py
@@ -3255,7 +3255,6 @@ class PandasDataframe(
                 axis,
                 other,
                 join_type,
-                sort=not self.get_axis(axis).equals(other.get_axis(axis)),
             )
             # unwrap list returned by `copartition`.
             right_parts = right_parts[0]
@@ -3681,7 +3680,7 @@ class PandasDataframe(
         ) and self._get_axis_lengths(axis) == other._get_axis_lengths(axis)
 
     def _copartition(
-        self, axis, other, how, sort, force_repartition=False, fill_value=None
+        self, axis, other, how, sort=None, force_repartition=False, fill_value=None
     ):
         """
         Copartition two Modin DataFrames.
@@ -3696,8 +3695,9 @@ class PandasDataframe(
             Other Modin DataFrame(s) to copartition against.
         how : str
             How to manage joining the index object ("left", "right", etc.).
-        sort : bool
+        sort : bool, default: None
             Whether sort the joined index or not.
+            If ``None``, sort is defined in depend on labels equality along the axis.
         force_repartition : bool, default: False
             Whether force the repartitioning or not. By default,
             this method will skip repartitioning if it is possible. This is because
@@ -3730,6 +3730,9 @@ class PandasDataframe(
                 self._get_axis_lengths_cache(axis),
             )
 
+        if sort is None:
+            sort = not all(self.get_axis(axis).equals(o.get_axis(axis)) for o in other)
+
         self_index = self.get_axis(axis)
         others_index = [o.get_axis(axis) for o in other]
         joined_index, make_reindexer = self._join_index_objects(
@@ -3823,6 +3826,7 @@ class PandasDataframe(
         op,
         right_frames: list[PandasDataframe],
         join_type="outer",
+        sort=None,
         copartition_along_columns=True,
         labels="replace",
         dtypes: Optional[pandas.Series] = None,
@@ -3838,6 +3842,8 @@ class PandasDataframe(
             Modin DataFrames to join with.
         join_type : str, default: "outer"
             Type of join to apply.
+        sort : bool, default: None
+            Whether to sort index and columns or not.
         copartition_along_columns : bool, default: True
             Whether to perform copartitioning along columns or not.
             For some ops this isn't needed (e.g., `fillna`).
@@ -3854,7 +3860,10 @@ class PandasDataframe(
             New Modin DataFrame.
         """
         left_parts, list_of_right_parts, joined_index, row_lengths = self._copartition(
-            0, right_frames, join_type, sort=True
+            0,
+            right_frames,
+            join_type,
+            sort=sort,
         )
         if copartition_along_columns:
             new_left_frame = self.__constructor__(
@@ -3886,7 +3895,7 @@ class PandasDataframe(
                 1,
                 new_right_frames,
                 join_type,
-                sort=True,
+                sort=sort,
             )
         else:
             joined_columns = self.copy_columns_cache(copy_lengths=True)
@@ -3978,7 +3987,7 @@ class PandasDataframe(
                 joined_index,
                 partition_sizes_along_axis,
             ) = self._copartition(
-                axis.value ^ 1, others, how, sort, force_repartition=False
+                axis.value ^ 1, others, how, sort=sort, force_repartition=False
             )
             if axis == Axis.COL_WISE:
                 new_lengths = partition_sizes_along_axis
diff --git a/modin/core/storage_formats/pandas/query_compiler.py b/modin/core/storage_formats/pandas/query_compiler.py
index 7c4f7e79..9d4467c2 100644
--- a/modin/core/storage_formats/pandas/query_compiler.py
+++ b/modin/core/storage_formats/pandas/query_compiler.py
@@ -460,6 +460,7 @@ class PandasQueryCompiler(BaseQueryCompiler):
     df_update = Binary.register(
         copy_df_for_func(pandas.DataFrame.update, display_name="update"),
         join_type="left",
+        sort=False,
     )
     series_update = Binary.register(
         copy_df_for_func(
@@ -467,6 +468,7 @@ class PandasQueryCompiler(BaseQueryCompiler):
             display_name="update",
         ),
         join_type="left",
+        sort=False,
     )
 
     # Needed for numpy API


Test patch:
diff --git a/modin/tests/pandas/dataframe/test_binary.py b/modin/tests/pandas/dataframe/test_binary.py
index e153f9f8..108e2620 100644
--- a/modin/tests/pandas/dataframe/test_binary.py
+++ b/modin/tests/pandas/dataframe/test_binary.py
@@ -527,3 +527,30 @@ def test_arithmetic_with_tricky_dtypes(val1, val2, op, request):
         lambda dfs: getattr(dfs[0], op)(dfs[1]),
         expected_exception=expected_exception,
     )
+
+
+@pytest.mark.parametrize(
+    "data, other_data",
+    [
+        ({"A": [1, 2, 3], "B": [400, 500, 600]}, {"B": [4, 5, 6], "C": [7, 8, 9]}),
+        ({"C": [1, 2, 3], "B": [400, 500, 600]}, {"B": [4, 5, 6], "A": [7, 8, 9]}),
+    ],
+)
+@pytest.mark.parametrize("axis", [0, 1])
+@pytest.mark.parametrize("match_index", [True, False])
+def test_bin_op_mismatched_columns(data, other_data, axis, match_index):
+    modin_df, pandas_df = create_test_dfs(data)
+    other_modin_df, other_pandas_df = create_test_dfs(other_data)
+    if axis == 0:
+        if not match_index:
+            modin_df.index = pandas_df.index = ["1", "2", "3"]
+            other_modin_df.index = other_pandas_df.index = ["2", "1", "3"]
+    eval_general(
+        modin_df,
+        pandas_df,
+        lambda df: (
+            df.add(other_modin_df, axis=axis)
+            if isinstance(df, pd.DataFrame)
+            else df.add(other_pandas_df, axis=axis)
+        ),
+    )
diff --git a/modin/tests/pandas/dataframe/test_map_metadata.py b/modin/tests/pandas/dataframe/test_map_metadata.py
index 4b19d5fb..d6980cd6 100644
--- a/modin/tests/pandas/dataframe/test_map_metadata.py
+++ b/modin/tests/pandas/dataframe/test_map_metadata.py
@@ -1592,6 +1592,7 @@ def test_transpose(data):
     "data, other_data",
     [
         ({"A": [1, 2, 3], "B": [400, 500, 600]}, {"B": [4, 5, 6], "C": [7, 8, 9]}),
+        ({"C": [1, 2, 3], "B": [400, 500, 600]}, {"B": [4, 5, 6], "A": [7, 8, 9]}),
         (
             {"A": ["a", "b", "c"], "B": ["x", "y", "z"]},
             {"B": ["d", "e", "f", "g", "h", "i"]},