Repository: modin-project/modin

Issue:
`read_excel` fails if `usecols` and `index_cols` parameters are provided
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Ubuntu 20.04
- **Modin version**: 54898ef78e83209fba3233606841ca988e531aee
- **Python version**: 3.8.10
- **Code we can use to reproduce**:
```
import pandas
import numpy as np
import os

import modin.pandas as pd
from modin.pandas.test.utils import df_equals
from modin.config import NPartitions
NPartitions.put(4)

filename = "test_excel.xlsx"
row_size = 256
kwargs = {
    "usecols": [0],
    "index_col": 0,
}


try:
    df = pandas.DataFrame(
        {"col1": np.arange(row_size), "col2": np.arange(row_size)}
    )
    df.to_excel(filename)

    df_pd = pd.read_excel(filename, **kwargs)
    df_pandas = pandas.read_excel(filename, **kwargs)

    df_equals(df_pandas, df_pd)

finally:
    os.remove(filename)
```
<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
<!-- Describe the problem clearly here. -->

### Source code / logs
<!-- Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. Try to provide a reproducible test case that is the bare minimum necessary to generate the problem. -->
```
Traceback (most recent call last):
  File "test.py", line 824, in <module>
    df_pd = pd.read_excel(filename, **kwargs)
  File "/modin/modin/pandas/io.py", line 347, in read_excel
    intermediate = FactoryDispatcher.read_excel(**kwargs)
  File "/modin/modin/data_management/factories/dispatcher.py", line 202, in read_excel
    return cls.__factory._read_excel(**kwargs)
  File "/modin/modin/data_management/factories/factories.py", line 256, in _read_excel
    return cls.io_cls.read_excel(**kwargs)
  File "/modin/modin/engines/base/io/file_dispatcher.py", line 67, in read
    query_compiler = cls._read(*args, **kwargs)
  File "/modin/modin/engines/base/io/text/excel_dispatcher.py", line 246, in _read
    dtypes = pandas.Series(dtypes, index=column_names)
  File "/miniconda3/envs/modin/lib/python3.8/site-packages/pandas/core/series.py", line 355, in __init__
    if is_empty_data(data) and dtype is None:
  File "/miniconda3/envs/modin/lib/python3.8/site-packages/pandas/core/construction.py", line 794, in is_empty_data
    is_simple_empty = is_list_like_without_dtype and not data
  File "/miniconda3/envs/modin/lib/python3.8/site-packages/pandas/core/generic.py", line 1534, in __nonzero__
    raise ValueError(
ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
```



Discussion:
Confirmed that this bug is reproducible on master, although the error is different:
```python
AssertionError                            Traceback (most recent call last)
Input In [1], in <cell line: 18>()
     24     df_pd = pd.read_excel(filename, **kwargs)
     25     df_pandas = pandas.read_excel(filename, **kwargs)
---> 27     df_equals(df_pandas, df_pd)
     29 finally:
     30     os.remove(filename)

File ~/Documents/modin/modin/pandas/test/utils.py:583, in df_equals(df1, df2)
    580     df2 = to_pandas(df2)
    582 if isinstance(df1, pandas.DataFrame) and isinstance(df2, pandas.DataFrame):
--> 583     assert_empty_frame_equal(df1, df2)
    585 if isinstance(df1, pandas.DataFrame) and isinstance(df2, pandas.DataFrame):
    586     assert_frame_equal(
    587         df1,
    588         df2,
   (...)
    593         check_categorical=False,
    594     )

File ~/Documents/modin/modin/pandas/test/utils.py:528, in assert_empty_frame_equal(df1, df2)
    513 """
    514 Test if df1 and df2 are empty.
    515
   (...)
    524     If check fails.
    525 """
    527 if (df1.empty and not df2.empty) or (df2.empty and not df1.empty):
--> 528     assert False, "One of the passed frames is empty, when other isn't"
    529 elif df1.empty and df2.empty and type(df1) != type(df2):
    530     assert False, f"Empty frames have different types: {type(df1)} != {type(df2)}"

AssertionError: One of the passed frames is empty, when other isn't
```

Resolving patch:
diff --git a/modin/core/io/text/excel_dispatcher.py b/modin/core/io/text/excel_dispatcher.py
--- a/modin/core/io/text/excel_dispatcher.py
+++ b/modin/core/io/text/excel_dispatcher.py
@@ -153,7 +153,7 @@ def _read(cls, io, **kwargs):
             if index_col is not None:
                 column_names = column_names.drop(column_names[index_col])
 
-            if not all(column_names):
+            if not all(column_names) or kwargs.get("usecols"):
                 # some column names are empty, use pandas reader to take the names from it
                 pandas_kw["nrows"] = 1
                 df = pandas.read_excel(io, **pandas_kw)
diff --git a/modin/core/storage_formats/pandas/parsers.py b/modin/core/storage_formats/pandas/parsers.py
--- a/modin/core/storage_formats/pandas/parsers.py
+++ b/modin/core/storage_formats/pandas/parsers.py
@@ -585,7 +585,11 @@ def update_row_nums(match):
             **kwargs,
         )
         pandas_df = parser.read()
-        if len(pandas_df) > 1 and pandas_df.isnull().all().all():
+        if (
+            len(pandas_df) > 1
+            and len(pandas_df.columns) != 0
+            and pandas_df.isnull().all().all()
+        ):
             # Drop NaN rows at the end of the DataFrame
             pandas_df = pandas.DataFrame(columns=pandas_df.columns)
 


Test patch:
diff --git a/modin/pandas/test/test_io.py b/modin/pandas/test/test_io.py
--- a/modin/pandas/test/test_io.py
+++ b/modin/pandas/test/test_io.py
@@ -1928,10 +1928,6 @@ def test_to_excel(self):
 
             assert assert_files_eq(unique_filename_modin, unique_filename_pandas)
 
-    @pytest.mark.xfail(
-        Engine.get() != "Python",
-        reason="Test fails because of issue 3305",
-    )
     @check_file_leaks
     @pytest.mark.xfail(
         condition="config.getoption('--simulate-cloud').lower() != 'off'",