Repository: modin-project/modin

Issue:
BUG: read_csv_glob raises KeyError for non-empty parse_dates dict
### 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 pandas
from modin.experimental.pandas.io import read_csv_glob

path = '/tmp/glob.csv'
pandas.DataFrame([1]).to_csv(path)
print(pandas.read_csv(path, parse_dates={'date': [0]}))
print(read_csv_glob(path, parse_dates={'date': [0]}))
```


### Issue Description

works in pandas; read_csv_glob raises KeyError

### Expected Behavior

should read the csv and use the parse_dates.

### Error Logs

<details>

```python-traceback


---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Input In [1], in <cell line: 7>()
      5 pandas.DataFrame([1]).to_csv(path)
      6 print(pandas.read_csv(path, parse_dates={'date': [0]}))
----> 7 print(read_csv_glob(path, parse_dates={'date': [0]}))

File ~/software_sources/modin/modin/experimental/pandas/io.py:231, in _make_parser_func.<locals>.parser_func(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, squeeze, prefix, mangle_dupe_cols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, escapechar, comment, encoding, encoding_errors, dialect, error_bad_lines, warn_bad_lines, on_bad_lines, skipfooter, doublequote, delim_whitespace, low_memory, memory_map, float_precision, storage_options)
    228     f_locals["sep"] = "\t"
    230 kwargs = {k: v for k, v in f_locals.items() if k in _pd_read_csv_signature}
--> 231 return _read(**kwargs)

File ~/software_sources/modin/modin/experimental/pandas/io.py:278, in _read(**kwargs)
    275 from modin.core.execution.dispatching.factories.dispatcher import FactoryDispatcher
    277 try:
--> 278     pd_obj = FactoryDispatcher.read_csv_glob(**kwargs)
    279 except AttributeError:
    280     raise AttributeError("read_csv_glob() is only implemented for pandas on Ray.")

File ~/software_sources/modin/modin/core/execution/dispatching/factories/dispatcher.py:188, in FactoryDispatcher.read_csv_glob(cls, **kwargs)
    185 @classmethod
    186 @_inherit_docstrings(factories.ExperimentalPandasOnRayFactory._read_csv_glob)
    187 def read_csv_glob(cls, **kwargs):
--> 188     return cls.__factory._read_csv_glob(**kwargs)

File ~/software_sources/modin/modin/core/execution/dispatching/factories/factories.py:524, in ExperimentalPandasOnRayFactory._read_csv_glob(cls, **kwargs)
    517 @classmethod
    518 @doc(
    519     _doc_io_method_raw_template,
   (...)
    522 )
    523 def _read_csv_glob(cls, **kwargs):
--> 524     return cls.io_cls.read_csv_glob(**kwargs)

File ~/software_sources/modin/modin/logging/logger_decorator.py:128, in enable_logging.<locals>.decorator.<locals>.run_and_log(*args, **kwargs)
    113 """
    114 Compute function with logging if Modin logging is enabled.
    115
   (...)
    125 Any
    126 """
    127 if LogMode.get() == "disable":
--> 128     return obj(*args, **kwargs)
    130 logger = get_logger()
    131 logger_level = getattr(logger, log_level)

File ~/software_sources/modin/modin/core/io/text/csv_glob_dispatcher.py:291, in CSVGlobDispatcher._read(cls, filepath_or_buffer, **kwargs)
    289     elif isinstance(parse_dates, dict):
    290         for new_col_name, group in parse_dates.items():
--> 291             column_names = column_names.drop(group).insert(0, new_col_name)
    292 # Set the index for the dtypes to the column names
    293 if isinstance(dtypes, pandas.Series):

File ~/opt/anaconda3/envs/modin-dev-py38/lib/python3.8/site-packages/pandas/core/indexes/base.py:6977, in Index.drop(self, labels, errors)
   6975 if mask.any():
   6976     if errors != "ignore":
-> 6977         raise KeyError(f"{list(labels[mask])} not found in axis")
   6978     indexer = indexer[~mask]
   6979 return self.delete(indexer)

KeyError: '[0] not found in axis'

```

</details>


### Installed Versions

<details>
<summary>Details</summary>

```python-traceback
UserWarning: Setuptools is replacing distutils.

INSTALLED VERSIONS
------------------
commit           : 5e420b9c433d3d6c068794130210e6914e40d5ca
python           : 3.8.13.final.0
python-bits      : 64
OS               : Darwin
OS-release       : 21.5.0
Version          : Darwin Kernel Version 21.5.0: Tue Apr 26 21:08:22 PDT 2022; root:xnu-8020.121.3~4/RELEASE_X86_64
machine          : x86_64
processor        : i386
byteorder        : little
LC_ALL           : None
LANG             : en_US.UTF-8
LOCALE           : en_US.UTF-8

Modin dependencies
------------------
modin            : 0.17.0+38.g5e420b9c
ray              : 2.1.0
dask             : 2022.11.1
distributed      : None
hdk              : None

pandas dependencies
-------------------
pandas           : 1.5.2
numpy            : 1.23.1
pytz             : 2022.1
dateutil         : 2.8.2
setuptools       : 61.2.0
pip              : 22.1.2
Cython           : None
pytest           : 7.1.2
hypothesis       : None
sphinx           : 5.1.1
blosc            : None
feather          : 0.4.1
xlsxwriter       : None
lxml.etree       : 4.9.1
html5lib         : None
pymysql          : None
psycopg2         : None
jinja2           : 3.1.2
IPython          : 8.4.0
pandas_datareader: None
bs4              : 4.11.1
bottleneck       : None
brotli           : None
fastparquet      : 2022.11.0
fsspec           : 2022.7.1
gcsfs            : None
matplotlib       : 3.5.2
numba            : None
numexpr          : 2.8.3
odfpy            : None
openpyxl         : 3.0.10
pandas_gbq       : 0.17.7
pyarrow          : 8.0.0
pyreadstat       : None
pyxlsb           : None
s3fs             : 2022.7.1
scipy            : 1.9.0
snappy           : None
sqlalchemy       : 1.4.39
tables           : 3.7.0
tabulate         : 0.9.0
xarray           : 2022.6.0
xlrd             : 2.0.1
xlwt             : None
zstandard        : None
tzdata           : None
```


</details>


Discussion:

Resolving patch:
diff --git a/modin/core/io/text/csv_glob_dispatcher.py b/modin/core/io/text/csv_glob_dispatcher.py
--- a/modin/core/io/text/csv_glob_dispatcher.py
+++ b/modin/core/io/text/csv_glob_dispatcher.py
@@ -151,10 +151,10 @@ def _read(cls, filepath_or_buffer, **kwargs):
                 if len(index_col) == 1:
                     index_col = index_col[0]
                 kwargs["index_col"] = index_col
-        empty_pd_df = pandas.read_csv(
-            filepath_or_buffer, **dict(kwargs, nrows=0, skipfooter=0)
+        pd_df_metadata = pandas.read_csv(
+            filepath_or_buffer, **dict(kwargs, nrows=1, skipfooter=0)
         )
-        column_names = empty_pd_df.columns
+        column_names = pd_df_metadata.columns
         skipfooter = kwargs.get("skipfooter", None)
         skiprows = kwargs.pop("skiprows", None)
         usecols_md = cls._validate_usecols_arg(usecols)
@@ -214,7 +214,9 @@ def _read(cls, filepath_or_buffer, **kwargs):
             if kwargs.get("encoding", None) is not None:
                 partition_kwargs["skiprows"] = 1
             # Launch tasks to read partitions
-            column_widths, num_splits = cls._define_metadata(empty_pd_df, column_names)
+            column_widths, num_splits = cls._define_metadata(
+                pd_df_metadata, column_names
+            )
 
             args = {
                 "num_splits": num_splits,
@@ -251,7 +253,7 @@ def _read(cls, filepath_or_buffer, **kwargs):
             index_objs = cls.materialize(index_ids)
             row_lengths = [len(o) for o in index_objs]
             new_index = index_objs[0].append(index_objs[1:])
-            new_index.name = empty_pd_df.index.name
+            new_index.name = pd_df_metadata.index.name
 
         # Compute dtypes by getting collecting and combining all of the partitions. The
         # reported dtypes from differing rows can be different based on the inference in
@@ -260,35 +262,6 @@ def _read(cls, filepath_or_buffer, **kwargs):
         dtypes = cls.get_dtypes(dtypes_ids) if len(dtypes_ids) > 0 else None
 
         partition_ids = cls.build_partition(partition_ids, row_lengths, column_widths)
-        # 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
-            if isinstance(parse_dates, list):
-                for date in parse_dates:
-                    # Lists within the parse_dates list are sequences of
-                    # CSV columns that are parsed together as a single date
-                    # column. They can be a list of either string column names
-                    # or integer column indices. e.g. if parse_dates is
-                    # [[1, 2]] and columns at indices 1 and 2 are "b" and "c",
-                    # the output dataframe has the single date column "b_c". If
-                    # parse_dates is [["a", 1]] and the column at index 1 is
-                    # named "b", the output dataframe has the single date
-                    # column "a_b".
-                    if isinstance(date, list):
-                        for i, part in enumerate(date):
-                            if isinstance(part, int):
-                                date[i] = column_names[part]
-                        new_col_name = "_".join(date)
-                        column_names = column_names.drop(date).insert(0, new_col_name)
-            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)
         # Set the index for the dtypes to the column names
         if isinstance(dtypes, pandas.Series):
             dtypes.index = column_names


Test patch:
diff --git a/modin/experimental/pandas/test/test_io_exp.py b/modin/experimental/pandas/test/test_io_exp.py
--- a/modin/experimental/pandas/test/test_io_exp.py
+++ b/modin/experimental/pandas/test/test_io_exp.py
@@ -148,8 +148,6 @@ def test_read_csv_glob_4373(self):
         [pytest.param(value, id=id) for id, value in parse_dates_values_by_id.items()],
     )
     def test_read_single_csv_with_parse_dates(self, parse_dates):
-        if isinstance(parse_dates, dict):
-            pytest.xfail("raises KeyError when dict is not empty. See GH#5325")
         try:
             pandas_df = pandas.read_csv(time_parsing_csv_path, parse_dates=parse_dates)
         except Exception as pandas_exception: