Repository: modin-project/modin

Issue:
read_csv with Ray engine fails with parse_dates parameter passed as dict
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Linux Ubuntu 18.04
- **Modin version** (`modin.__version__`): 0.7.3+350.g2ca3f34
- **Python version**: 3.8.5
- **Code we can use to reproduce**:
```
import os
os.environ["MODIN_ENGINE"] = "ray"

import pandas
import modin.pandas as pd

test_filename = "test.csv"
csv_single_col = """col1,col2
2000-01-01,2000-01-02
2000-01-03,2000-01-04
"""

try :
    with open(test_filename, "w") as f:
        f.write(csv_single_col)

    df_pandas = pandas.read_csv(test_filename, parse_dates={"foo": ["col1", "col2"]})
    print(df_pandas)
    df_pd = pd.read_csv(test_filename, parse_dates={"foo": ["col1", "col2"]})
    repr(df_pd)
    print(df_pd)
finally:
    os.remove(test_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. -->
```
                         foo
0  2000-01-01 20:00:00-02:00
1  2000-01-03 20:00:00-04:00
Traceback (most recent call last):
  File "test_read_csv.py", line 20, in <module>
    repr(df_pd)
  File "/localdisk/amyskov/modin2/modin/pandas/dataframe.py", line 169, in __repr__
    result = repr(self._build_repr_df(num_rows, num_cols))
  File "/localdisk/amyskov/modin2/modin/pandas/base.py", line 108, in _build_repr_df
    return self.iloc[indexer]._query_compiler.to_pandas()
  File "/localdisk/amyskov/modin2/modin/pandas/indexing.py", line 377, in __getitem__
    result = super(_iLocIndexer, self).__getitem__(row_lookup, col_lookup, ndim)
  File "/localdisk/amyskov/modin2/modin/pandas/indexing.py", line 129, in __getitem__
    qc_view = self.qc.view(row_lookup, col_lookup)
  File "/localdisk/amyskov/modin2/modin/backends/pandas/query_compiler.py", line 2470, in view
    self._modin_frame.mask(row_numeric_idx=index, col_numeric_idx=columns)
  File "/localdisk/amyskov/modin2/modin/engines/base/frame/data.py", line 472, in mask
    return self.copy()
  File "/localdisk/amyskov/modin2/modin/engines/base/frame/data.py", line 643, in copy
    return self.__constructor__(
  File "/localdisk/amyskov/modin2/modin/engines/base/frame/data.py", line 73, in __init__
    ErrorMessage.catch_bugs_and_request_email(
  File "/localdisk/amyskov/modin2/modin/error_message.py", line 51, in catch_bugs_and_request_email
    raise Exception(
Exception: Internal Error. Please email bug_reports@modin.org with the traceback and command that caused this error.
Column widths: 2 != 1
```


Discussion:

Resolving patch:
diff --git a/modin/engines/base/io/text/csv_dispatcher.py b/modin/engines/base/io/text/csv_dispatcher.py
--- a/modin/engines/base/io/text/csv_dispatcher.py
+++ b/modin/engines/base/io/text/csv_dispatcher.py
@@ -72,13 +72,13 @@ def _read(cls, filepath_or_buffer, **kwargs):
         # Now we need to define parameters, which are common for all partitions. These
         # parameters can be `sniffed` from empty dataframes created further
         if names is None:
-            # For the sake of the empty df, we assume no `index_col` to get the correct
+            # When reading the one row df, we assume no `index_col` to get the correct
             # column names before we build the index. Because we pass `names` in, this
             # step has to happen without removing the `index_col` otherwise it will not
             # be assigned correctly
             names = pandas.read_csv(
                 filepath_or_buffer,
-                **dict(kwargs, usecols=None, nrows=0, skipfooter=0, index_col=None),
+                **dict(kwargs, usecols=None, nrows=1, skipfooter=0, index_col=None),
             ).columns
         elif index_col is None and not kwargs.get("usecols", None):
             # When names is set to some list that is smaller than the number of columns
@@ -91,11 +91,11 @@ def _read(cls, filepath_or_buffer, **kwargs):
                 index_col = list(range(num_cols - len(names)))
                 if len(index_col) == 1:
                     index_col = index_col[0]
-        empty_pd_df = pandas.read_csv(
+        pd_df_metadata = pandas.read_csv(
             filepath_or_buffer,
-            **dict(kwargs, nrows=0, skipfooter=0, index_col=index_col),
+            **dict(kwargs, nrows=1, skipfooter=0, index_col=index_col),
         )
-        column_names = empty_pd_df.columns
+        column_names = pd_df_metadata.columns
 
         # Max number of partitions available
         num_partitions = NPartitions.get()
@@ -103,7 +103,7 @@ def _read(cls, filepath_or_buffer, **kwargs):
         num_splits = min(len(column_names), num_partitions)
         # Metadata definition
         column_widths, num_splits = cls._define_metadata(
-            empty_pd_df, num_splits, column_names
+            pd_df_metadata, num_splits, column_names
         )
 
         # kwargs that will be passed to the workers
@@ -139,7 +139,7 @@ def _read(cls, filepath_or_buffer, **kwargs):
             index_ids=index_ids,
             dtypes_ids=dtypes_ids,
             index_col_md=index_col,
-            index_name=empty_pd_df.index.name,
+            index_name=pd_df_metadata.index.name,
             column_widths=column_widths,
             column_names=column_names,
             squeeze=kwargs.get("squeeze", False),
@@ -234,51 +234,6 @@ def _define_index(
 
         return new_index, row_lengths
 
-    @classmethod
-    def _define_column_names(
-        cls,
-        column_names: ColumnNamesTypes,
-        parse_dates: Union[bool, dict, Sequence],
-        column_widths: list,
-    ) -> Tuple[ColumnNamesTypes, list]:
-        """
-        Redefine columns names in accordance to the parse_dates parameter.
-        ----------
-        column_names: ColumnNamesTypes
-                Array with columns names.
-        parse_dates: array, bool or dict
-                `parse_dates` parameter of read_csv function.
-        column_widths: list
-                Number of columns in each partition.
-
-        Returns
-        -------
-        column_names: ColumnNamesTypes
-                Array with redefined columns names.
-        column_widths: list
-                Updated `column_widths` parameter.
-        """
-        # If parse_dates is present, the column names that we have might not be
-        # the same length as the returned column names. If we do need to modify
-        # the column names, we remove the old names from the column names and
-        # insert the new one at the front of the Index.
-        if parse_dates is not None:
-            # We have to recompute the column widths if `parse_dates` is set because
-            # we are not guaranteed to have the correct information regarding how many
-            # columns are on each partition.
-            column_widths = None
-            # Check if is list of lists
-            if isinstance(parse_dates, list) and isinstance(parse_dates[0], list):
-                for group in parse_dates:
-                    new_col_name = "_".join(group)
-                    column_names = column_names.drop(group).insert(0, new_col_name)
-            # Check if it is a dictionary
-            elif isinstance(parse_dates, dict):
-                for new_col_name, group in parse_dates.items():
-                    column_names = column_names.drop(group).insert(0, new_col_name)
-
-        return column_names, column_widths
-
     @classmethod
     def _get_new_qc(
         cls,
@@ -323,9 +278,6 @@ def _get_new_qc(
         dtypes = cls.get_dtypes(dtypes_ids) if len(dtypes_ids) > 0 else None
         # Compose modin partitions from `partition_ids`
         partition_ids = cls.build_partition(partition_ids, row_lengths, column_widths)
-        column_names, column_widths = cls._define_column_names(
-            column_names, kwargs.get("parse_dates", False), column_widths
-        )
 
         # Set the index for the dtypes to the column names
         if isinstance(dtypes, pandas.Series):


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
@@ -459,21 +459,7 @@ def test_read_csv_nans_handling(
 
     # Datetime Handling tests
     @pytest.mark.parametrize(
-        "parse_dates",
-        [
-            True,
-            False,
-            ["col2"],
-            ["col2", "col4"],
-            [1, 3],
-            pytest.param(
-                {"foo": ["col2", "col4"]},
-                marks=pytest.mark.xfail(
-                    Engine.get() != "Python",
-                    reason="Exception: Internal Error - issue #2073",
-                ),
-            ),
-        ],
+        "parse_dates", [True, False, ["col2"], ["col2", "col4"], [1, 3]]
     )
     @pytest.mark.parametrize("infer_datetime_format", [True, False])
     @pytest.mark.parametrize("keep_date_col", [True, False])