Repository: modin-project/modin

Issue:
BUG: Constructing a DataFrame from a Modin Series with tuple name should produce MultiIndex columns
### 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, pandas
pd.DataFrame(pandas.Series(name=("a", 1))).columns  # MultiIndex([('a', 1)])
pd.DataFrame(pd.Series(name=("a", 1))).columns  # Index([('a', 1)], dtype='object')
```


### Issue Description

Creating a DataFrame from a Series object where `name` is a tuple should result in MultiIndex columns, as is the case in vanilla pandas. Modin behaves correctly when the DataFrame constructor receives a vanilla pandas Series, but fails when the passed-in object is Modin Series.

### Expected Behavior
```python
import pandas
pandas.DataFrame(pandas.Series(name=("a", 1))).columns  # MultiIndex([('a', 1)])
```

### Error Logs

<details>

```python-traceback

Replace this line with the error backtrace (if applicable).

```

</details>


### Installed Versions

<details>

INSTALLED VERSIONS
------------------
commit                : 9afc04933a32c0a4d70f57488f61ed7a78e017f0
python                : 3.10.13.final.0
python-bits           : 64
OS                    : Darwin
OS-release            : 23.4.0
Version               : Darwin Kernel Version 23.4.0: Fri Mar 15 00:12:49 PDT 2024; root:xnu-10063.101.17~1/RELEASE_ARM64_T6020
machine               : arm64
processor             : arm
byteorder             : little
LC_ALL                : None
LANG                  : en_US.UTF-8
LOCALE                : en_US.UTF-8

Modin dependencies
------------------
modin                 : 0.28.0+41.g9afc0493.dirty
ray                   : 2.7.1
dask                  : 2023.10.1
distributed           : 2023.10.1
hdk                   : None

pandas dependencies
-------------------
pandas                : 2.2.0
numpy                 : 1.26.1
pytz                  : 2023.3.post1
dateutil              : 2.8.2
setuptools            : 68.0.0
pip                   : 23.3
Cython                : None
pytest                : 7.4.3
hypothesis            : None
sphinx                : 5.3.0
blosc                 : None
feather               : None
xlsxwriter            : None
lxml.etree            : 4.9.3
html5lib              : None
pymysql               : None
psycopg2              : 2.9.9
jinja2                : 3.1.2
IPython               : 8.17.2
pandas_datareader     : None
adbc-driver-postgresql: None
adbc-driver-sqlite    : None
bs4                   : 4.12.2
bottleneck            : None
dataframe-api-compat  : None
fastparquet           : 2023.10.1
fsspec                : 2023.10.0
gcsfs                 : None
matplotlib            : 3.8.1
numba                 : None
numexpr               : 2.8.4
odfpy                 : None
openpyxl              : 3.1.2
pandas_gbq            : 0.19.2
pyarrow               : 14.0.0
pyreadstat            : None
python-calamine       : None
pyxlsb                : None
s3fs                  : 2023.10.0
scipy                 : 1.11.3
sqlalchemy            : None
tables                : 3.9.1
tabulate              : None
xarray                : 2023.10.1
xlrd                  : 2.0.1
zstandard             : None
tzdata                : 2023.3
qtpy                  : None
pyqt5                 : None
</details>



Discussion:
Should be a fairly simple fix (just adding a few lines in the DataFrame constructor): https://github.com/modin-project/modin/blob/9afc04933a32c0a4d70f57488f61ed7a78e017f0/modin/pandas/dataframe.py#L140-L157

Resolving patch:
diff --git a/modin/pandas/series.py b/modin/pandas/series.py
--- a/modin/pandas/series.py
+++ b/modin/pandas/series.py
@@ -181,7 +181,11 @@ def _set_name(self, name: Hashable) -> None:
         """
         if name is None:
             name = MODIN_UNNAMED_SERIES_LABEL
-        self._query_compiler.columns = [name]
+        if isinstance(name, tuple):
+            columns = pandas.MultiIndex.from_tuples(tuples=[name])
+        else:
+            columns = [name]
+        self._query_compiler.columns = columns
 
     name: Hashable = property(_get_name, _set_name)
     _parent = None


Test patch:
diff --git a/modin/tests/pandas/dataframe/test_default.py b/modin/tests/pandas/dataframe/test_default.py
--- a/modin/tests/pandas/dataframe/test_default.py
+++ b/modin/tests/pandas/dataframe/test_default.py
@@ -1485,3 +1485,13 @@ def test_setattr_axes():
 def test_attrs(data):
     modin_df, pandas_df = create_test_dfs(data)
     eval_general(modin_df, pandas_df, lambda df: df.attrs)
+
+
+def test_df_from_series_with_tuple_name():
+    # Tests that creating a DataFrame from a series with a tuple name results in
+    # a DataFrame with MultiIndex columns.
+    pandas_result = pandas.DataFrame(pandas.Series(name=("a", 1)))
+    # 1. Creating a Modin DF from native pandas Series
+    df_equals(pd.DataFrame(pandas.Series(name=("a", 1))), pandas_result)
+    # 2. Creating a Modin DF from Modin Series
+    df_equals(pd.DataFrame(pd.Series(name=("a", 1))), pandas_result)
diff --git a/modin/tests/pandas/test_series.py b/modin/tests/pandas/test_series.py
--- a/modin/tests/pandas/test_series.py
+++ b/modin/tests/pandas/test_series.py
@@ -2642,6 +2642,23 @@ def test_name(data):
     assert modin_series._query_compiler.columns == ["New_name"]
 
 
+def test_tuple_name():
+    names = [("a", 1), ("a", "b", "c"), "flat"]
+    s = pd.Series(name=names[0])
+    # The internal representation of the Series stores the name as a column label.
+    # When it is a tuple, this label is a MultiIndex object, and this test ensures that
+    # the Series's name property remains a tuple.
+    assert s.name == names[0]
+    assert isinstance(s.name, tuple)
+    # Setting the name to a tuple of a different level or a non-tuple should not error.
+    s.name = names[1]
+    assert s.name == names[1]
+    assert isinstance(s.name, tuple)
+    s.name = names[2]
+    assert s.name == names[2]
+    assert isinstance(s.name, str)
+
+
 @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)
 def test_nbytes(data):
     modin_series, pandas_series = create_test_series(data)