1from __future__ import annotations
2
3from collections import defaultdict
4from copy import copy
5import csv
6from enum import Enum
7import itertools
8from typing import (
9 TYPE_CHECKING,
10 Any,
11 cast,
12 final,
13 overload,
14)
15import warnings
16
17import numpy as np
18
19from pandas._libs import (
20 lib,
21 parsers,
22)
23import pandas._libs.ops as libops
24from pandas._libs.parsers import STR_NA_VALUES
25from pandas.compat._optional import import_optional_dependency
26from pandas.errors import (
27 ParserError,
28 ParserWarning,
29)
30from pandas.util._exceptions import find_stack_level
31
32from pandas.core.dtypes.common import (
33 is_bool_dtype,
34 is_dict_like,
35 is_float_dtype,
36 is_integer,
37 is_integer_dtype,
38 is_list_like,
39 is_object_dtype,
40 is_string_dtype,
41)
42from pandas.core.dtypes.missing import isna
43
44from pandas import (
45 DataFrame,
46 DatetimeIndex,
47 StringDtype,
48)
49from pandas.core import algorithms
50from pandas.core.arrays import (
51 ArrowExtensionArray,
52 BaseMaskedArray,
53 BooleanArray,
54 FloatingArray,
55 IntegerArray,
56)
57from pandas.core.indexes.api import (
58 Index,
59 MultiIndex,
60 default_index,
61 ensure_index_from_sequences,
62)
63from pandas.core.series import Series
64from pandas.core.tools import datetimes as tools
65
66from pandas.io.common import is_potential_multi_index
67
68if TYPE_CHECKING:
69 from collections.abc import (
70 Callable,
71 Iterable,
72 Mapping,
73 Sequence,
74 )
75
76 from pandas._typing import (
77 ArrayLike,
78 DtypeArg,
79 Hashable,
80 HashableT,
81 Scalar,
82 SequenceT,
83 )
84
85
86class ParserBase:
87 class BadLineHandleMethod(Enum):
88 ERROR = 0
89 WARN = 1
90 SKIP = 2
91
92 _implicit_index: bool
93 _first_chunk: bool
94 keep_default_na: bool
95 dayfirst: bool
96 cache_dates: bool
97 usecols_dtype: str | None
98
99 def __init__(self, kwds) -> None:
100 self._implicit_index = False
101
102 self.names = kwds.get("names")
103 self.orig_names: Sequence[Hashable] | None = None
104
105 self.index_col = kwds.get("index_col", None)
106 self.unnamed_cols: set = set()
107 self.index_names: Sequence[Hashable] | None = None
108 self.col_names: Sequence[Hashable] | None = None
109
110 parse_dates = kwds.pop("parse_dates", False)
111 if parse_dates is None or lib.is_bool(parse_dates):
112 parse_dates = bool(parse_dates)
113 elif not isinstance(parse_dates, list):
114 raise TypeError(
115 "Only booleans and lists are accepted for the 'parse_dates' parameter"
116 )
117 self.parse_dates: bool | list = parse_dates
118 self.date_parser = kwds.pop("date_parser", lib.no_default)
119 self.date_format = kwds.pop("date_format", None)
120 self.dayfirst = kwds.pop("dayfirst", False)
121
122 self.na_values = kwds.get("na_values")
123 self.na_fvalues = kwds.get("na_fvalues")
124 self.na_filter = kwds.get("na_filter", False)
125 self.keep_default_na = kwds.get("keep_default_na", True)
126
127 self.dtype = copy(kwds.get("dtype", None))
128 self.converters = kwds.get("converters")
129 self.dtype_backend = kwds.get("dtype_backend")
130
131 self.true_values = kwds.get("true_values")
132 self.false_values = kwds.get("false_values")
133 self.cache_dates = kwds.pop("cache_dates", True)
134
135 # validate header options for mi
136 self.header = kwds.get("header")
137 if is_list_like(self.header, allow_sets=False):
138 if kwds.get("usecols"):
139 raise ValueError(
140 "cannot specify usecols when specifying a multi-index header"
141 )
142 if kwds.get("names"):
143 raise ValueError(
144 "cannot specify names when specifying a multi-index header"
145 )
146
147 # validate index_col that only contains integers
148 if self.index_col is not None:
149 # In this case we can pin down index_col as list[int]
150 if is_integer(self.index_col):
151 self.index_col = [self.index_col]
152 elif not (
153 is_list_like(self.index_col, allow_sets=False)
154 and all(map(is_integer, self.index_col))
155 ):
156 raise ValueError(
157 "index_col must only contain integers of column positions "
158 "when specifying a multi-index header"
159 )
160 else:
161 self.index_col = list(self.index_col)
162
163 self._first_chunk = True
164
165 self.usecols, self.usecols_dtype = _validate_usecols_arg(kwds["usecols"])
166
167 # Fallback to error to pass a sketchy test(test_override_set_noconvert_columns)
168 # Normally, this arg would get pre-processed earlier on
169 self.on_bad_lines = kwds.get("on_bad_lines", self.BadLineHandleMethod.ERROR)
170
171 def close(self) -> None:
172 pass
173
174 @final
175 def _should_parse_dates(self, i: int) -> bool:
176 if isinstance(self.parse_dates, bool):
177 return self.parse_dates
178 else:
179 if self.index_names is not None:
180 name = self.index_names[i]
181 else:
182 name = None
183 j = i if self.index_col is None else self.index_col[i]
184
185 return (j in self.parse_dates) or (
186 name is not None and name in self.parse_dates
187 )
188
189 @final
190 def _extract_multi_indexer_columns(
191 self,
192 header,
193 index_names: Sequence[Hashable] | None,
194 passed_names: bool = False,
195 ) -> tuple[
196 Sequence[Hashable], Sequence[Hashable] | None, Sequence[Hashable] | None, bool
197 ]:
198 """
199 Extract and return the names, index_names, col_names if the column
200 names are a MultiIndex.
201
202 Parameters
203 ----------
204 header: list of lists
205 The header rows
206 index_names: list, optional
207 The names of the future index
208 passed_names: bool, default False
209 A flag specifying if names where passed
210
211 """
212 if len(header) < 2:
213 return header[0], index_names, None, passed_names
214
215 # the names are the tuples of the header that are not the index cols
216 # 0 is the name of the index, assuming index_col is a list of column
217 # numbers
218 ic = self.index_col
219 if ic is None:
220 ic = []
221
222 if not isinstance(ic, (list, tuple, np.ndarray)):
223 ic = [ic]
224 sic = set(ic)
225
226 # clean the index_names
227 index_names = header.pop(-1)
228 index_names, _, _ = self._clean_index_names(index_names, self.index_col)
229
230 # extract the columns
231 field_count = len(header[0])
232
233 # check if header lengths are equal
234 if not all(len(header_iter) == field_count for header_iter in header[1:]):
235 raise ParserError("Header rows must have an equal number of columns.")
236
237 def extract(r):
238 return tuple(r[i] for i in range(field_count) if i not in sic)
239
240 columns = list(zip(*(extract(r) for r in header), strict=True))
241 names = columns.copy()
242 for single_ic in sorted(ic):
243 names.insert(single_ic, single_ic)
244
245 # Clean the column names (if we have an index_col).
246 if ic:
247 col_names = [
248 r[ic[0]]
249 if ((r[ic[0]] is not None) and r[ic[0]] not in self.unnamed_cols)
250 else None
251 for r in header
252 ]
253 else:
254 col_names = [None] * len(header)
255
256 passed_names = True
257
258 return names, index_names, col_names, passed_names
259
260 @final
261 def _maybe_make_multi_index_columns(
262 self,
263 columns: SequenceT,
264 col_names: Sequence[Hashable] | None = None,
265 ) -> SequenceT | MultiIndex:
266 # possibly create a column mi here
267 if is_potential_multi_index(columns):
268 columns_mi = cast("Sequence[tuple[Hashable, ...]]", columns)
269 return MultiIndex.from_tuples(columns_mi, names=col_names)
270 return columns
271
272 @final
273 def _make_index(
274 self, alldata, columns, indexnamerow: list[Scalar] | None = None
275 ) -> tuple[Index | None, Sequence[Hashable] | MultiIndex]:
276 index: Index | None
277 if isinstance(self.index_col, list) and len(self.index_col):
278 to_remove = []
279 indexes = []
280 for idx in self.index_col:
281 if isinstance(idx, str):
282 raise ValueError(f"Index {idx} invalid")
283 to_remove.append(idx)
284 indexes.append(alldata[idx])
285 # remove index items from content and columns, don't pop in
286 # loop
287 for i in sorted(to_remove, reverse=True):
288 alldata.pop(i)
289 if not self._implicit_index:
290 columns.pop(i)
291 index = self._agg_index(indexes)
292
293 # add names for the index
294 if indexnamerow:
295 coffset = len(indexnamerow) - len(columns)
296 index = index.set_names(indexnamerow[:coffset])
297 else:
298 index = None
299
300 # maybe create a mi on the columns
301 columns = self._maybe_make_multi_index_columns(columns, self.col_names)
302
303 return index, columns
304
305 @final
306 def _clean_mapping(self, mapping):
307 """converts col numbers to names"""
308 if not isinstance(mapping, dict):
309 return mapping
310 clean = {}
311 # for mypy
312 assert self.orig_names is not None
313
314 for col, v in mapping.items():
315 if isinstance(col, int) and col not in self.orig_names:
316 col = self.orig_names[col]
317 clean[col] = v
318 if isinstance(mapping, defaultdict):
319 remaining_cols = set(self.orig_names) - set(clean.keys())
320 clean.update({col: mapping[col] for col in remaining_cols})
321 return clean
322
323 @final
324 def _agg_index(self, index) -> Index:
325 arrays = []
326 converters = self._clean_mapping(self.converters)
327 clean_dtypes = self._clean_mapping(self.dtype)
328
329 if self.index_names is not None:
330 names: Iterable = self.index_names
331 zip_strict = True
332 else:
333 names = itertools.cycle([None])
334 zip_strict = False
335 for i, (arr, name) in enumerate(zip(index, names, strict=zip_strict)):
336 if self._should_parse_dates(i):
337 arr = date_converter(
338 arr,
339 col=self.index_names[i] if self.index_names is not None else None,
340 dayfirst=self.dayfirst,
341 cache_dates=self.cache_dates,
342 date_format=self.date_format,
343 )
344
345 if self.na_filter:
346 col_na_values = self.na_values
347 col_na_fvalues = self.na_fvalues
348 else:
349 col_na_values = set()
350 col_na_fvalues = set()
351
352 if isinstance(self.na_values, dict):
353 assert self.index_names is not None
354 col_name = self.index_names[i]
355 if col_name is not None:
356 col_na_values, col_na_fvalues = get_na_values(
357 col_name, self.na_values, self.na_fvalues, self.keep_default_na
358 )
359 else:
360 col_na_values, col_na_fvalues = set(), set()
361
362 cast_type = None
363 index_converter = False
364 if self.index_names is not None:
365 if isinstance(clean_dtypes, dict):
366 cast_type = clean_dtypes.get(self.index_names[i], None)
367
368 if isinstance(converters, dict):
369 index_converter = converters.get(self.index_names[i]) is not None
370
371 try_num_bool = not (
372 (cast_type and is_string_dtype(cast_type)) or index_converter
373 )
374
375 arr, _ = self._infer_types(
376 arr, col_na_values | col_na_fvalues, cast_type is None, try_num_bool
377 )
378 if cast_type is not None:
379 # Don't perform RangeIndex inference
380 idx = Index(arr, name=name, dtype=cast_type, copy=False)
381 else:
382 idx = ensure_index_from_sequences([arr], [name])
383 arrays.append(idx)
384
385 if len(arrays) == 1:
386 return arrays[0]
387 else:
388 return MultiIndex.from_arrays(arrays)
389
390 @final
391 def _set_noconvert_dtype_columns(
392 self, col_indices: list[int], names: Sequence[Hashable]
393 ) -> set[int]:
394 """
395 Set the columns that should not undergo dtype conversions.
396
397 Currently, any column that is involved with date parsing will not
398 undergo such conversions. If usecols is specified, the positions of the columns
399 not to cast is relative to the usecols not to all columns.
400
401 Parameters
402 ----------
403 col_indices: The indices specifying order and positions of the columns
404 names: The column names which order is corresponding with the order
405 of col_indices
406
407 Returns
408 -------
409 A set of integers containing the positions of the columns not to convert.
410 """
411 usecols: list[int] | list[str] | None
412 noconvert_columns = set()
413 if self.usecols_dtype == "integer":
414 # A set of integers will be converted to a list in
415 # the correct order every single time.
416 usecols = sorted(self.usecols)
417 elif callable(self.usecols) or self.usecols_dtype not in ("empty", None):
418 # The names attribute should have the correct columns
419 # in the proper order for indexing with parse_dates.
420 usecols = col_indices
421 else:
422 # Usecols is empty.
423 usecols = None
424
425 def _set(x) -> int:
426 if usecols is not None and is_integer(x):
427 x = usecols[x]
428
429 if not is_integer(x):
430 x = col_indices[names.index(x)]
431
432 return x
433
434 if isinstance(self.parse_dates, list):
435 validate_parse_dates_presence(self.parse_dates, names)
436 for val in self.parse_dates:
437 noconvert_columns.add(_set(val))
438
439 elif self.parse_dates:
440 if isinstance(self.index_col, list):
441 for k in self.index_col:
442 noconvert_columns.add(_set(k))
443 elif self.index_col is not None:
444 noconvert_columns.add(_set(self.index_col))
445
446 return noconvert_columns
447
448 @final
449 def _infer_types(
450 self, values, na_values, no_dtype_specified, try_num_bool: bool = True
451 ) -> tuple[ArrayLike, int]:
452 """
453 Infer types of values, possibly casting
454
455 Parameters
456 ----------
457 values : ndarray
458 na_values : set
459 no_dtype_specified: Specifies if we want to cast explicitly
460 try_num_bool : bool, default try
461 try to cast values to numeric (first preference) or boolean
462
463 Returns
464 -------
465 converted : ndarray or ExtensionArray
466 na_count : int
467 """
468 na_count = 0
469 if issubclass(values.dtype.type, (np.number, np.bool_)):
470 # If our array has numeric dtype, we don't have to check for strings in isin
471 na_values = np.array([val for val in na_values if not isinstance(val, str)])
472 mask = algorithms.isin(values, na_values)
473 na_count = mask.astype("uint8", copy=False).sum()
474 if na_count > 0:
475 if is_integer_dtype(values):
476 values = values.astype(np.float64)
477 np.putmask(values, mask, np.nan)
478 return values, na_count
479
480 dtype_backend = self.dtype_backend
481 non_default_dtype_backend = (
482 no_dtype_specified and dtype_backend is not lib.no_default
483 )
484 result: ArrayLike
485
486 if try_num_bool and is_object_dtype(values.dtype):
487 # exclude e.g DatetimeIndex here
488 try:
489 result, result_mask = lib.maybe_convert_numeric(
490 values,
491 na_values,
492 False,
493 convert_to_masked_nullable=non_default_dtype_backend, # type: ignore[arg-type]
494 )
495 except (ValueError, TypeError):
496 # e.g. encountering datetime string gets ValueError
497 # TypeError can be raised in floatify
498 na_count = parsers.sanitize_objects(values, na_values)
499 result = values
500 else:
501 if non_default_dtype_backend:
502 if result_mask is None:
503 result_mask = np.zeros(result.shape, dtype=np.bool_)
504
505 if result_mask.all():
506 result = IntegerArray(
507 np.ones(result_mask.shape, dtype=np.int64), result_mask
508 )
509 elif is_integer_dtype(result):
510 result = IntegerArray(result, result_mask)
511 elif is_bool_dtype(result):
512 result = BooleanArray(result, result_mask)
513 elif is_float_dtype(result):
514 result = FloatingArray(result, result_mask)
515
516 na_count = result_mask.sum()
517 else:
518 na_count = isna(result).sum()
519 else:
520 result = values
521 if values.dtype == np.object_:
522 na_count = parsers.sanitize_objects(values, na_values)
523
524 if (
525 result.dtype == np.object_
526 and try_num_bool
527 and (len(result) == 0 or not isinstance(result[0], int))
528 ):
529 result, bool_mask = libops.maybe_convert_bool(
530 np.asarray(values),
531 true_values=self.true_values,
532 false_values=self.false_values,
533 convert_to_masked_nullable=non_default_dtype_backend, # type: ignore[arg-type]
534 )
535 if result.dtype == np.bool_ and non_default_dtype_backend:
536 if bool_mask is None:
537 bool_mask = np.zeros(result.shape, dtype=np.bool_)
538 result = BooleanArray(result, bool_mask)
539 elif result.dtype == np.object_ and non_default_dtype_backend:
540 # read_excel sends array of datetime objects
541 if not lib.is_datetime_array(result, skipna=True):
542 dtype = StringDtype()
543 cls = dtype.construct_array_type()
544 result = cls._from_sequence(values, dtype=dtype)
545
546 if dtype_backend == "pyarrow":
547 pa = import_optional_dependency("pyarrow")
548 if isinstance(result, np.ndarray):
549 result = ArrowExtensionArray(pa.array(result, from_pandas=True))
550 elif isinstance(result, BaseMaskedArray):
551 if result._mask.all():
552 # We want an arrow null array here
553 result = ArrowExtensionArray(pa.array([None] * len(result)))
554 else:
555 result = ArrowExtensionArray(
556 pa.array(result._data, mask=result._mask)
557 )
558 else:
559 result = ArrowExtensionArray(
560 pa.array(result.to_numpy(), from_pandas=True)
561 )
562
563 return result, na_count
564
565 @overload
566 def _do_date_conversions(
567 self,
568 names: Index,
569 data: DataFrame,
570 ) -> DataFrame: ...
571
572 @overload
573 def _do_date_conversions(
574 self,
575 names: Sequence[Hashable],
576 data: Mapping[Hashable, ArrayLike],
577 ) -> Mapping[Hashable, ArrayLike]: ...
578
579 @final
580 def _do_date_conversions(
581 self,
582 names: Sequence[Hashable] | Index,
583 data: Mapping[Hashable, ArrayLike] | DataFrame,
584 ) -> Mapping[Hashable, ArrayLike] | DataFrame:
585 if not isinstance(self.parse_dates, list):
586 return data
587 for colspec in self.parse_dates:
588 if isinstance(colspec, int) and colspec not in data:
589 colspec = names[colspec]
590 if (isinstance(self.index_col, list) and colspec in self.index_col) or (
591 isinstance(self.index_names, list) and colspec in self.index_names
592 ):
593 continue
594 result = date_converter(
595 data[colspec],
596 col=colspec,
597 dayfirst=self.dayfirst,
598 cache_dates=self.cache_dates,
599 date_format=self.date_format,
600 )
601 # error: Unsupported target for indexed assignment
602 # ("Mapping[Hashable, ExtensionArray | ndarray[Any, Any]] | DataFrame")
603 data[colspec] = result # type: ignore[index]
604
605 return data
606
607 @final
608 def _check_data_length(
609 self,
610 columns: Sequence[Hashable],
611 data: Sequence[ArrayLike],
612 ) -> None:
613 """Checks if length of data is equal to length of column names.
614
615 One set of trailing commas is allowed. self.index_col not False
616 results in a ParserError previously when lengths do not match.
617
618 Parameters
619 ----------
620 columns: list of column names
621 data: list of array-likes containing the data column-wise.
622 """
623 if not self.index_col and len(columns) != len(data) and columns:
624 empty_str = is_object_dtype(data[-1]) and data[-1] == ""
625 # error: No overload variant of "__ror__" of "ndarray" matches
626 # argument type "ExtensionArray"
627 empty_str_or_na = empty_str | isna(data[-1]) # type: ignore[operator]
628 if len(columns) == len(data) - 1 and np.all(empty_str_or_na):
629 return
630 warnings.warn(
631 "Length of header or names does not match length of data. This leads "
632 "to a loss of data with index_col=False.",
633 ParserWarning,
634 stacklevel=find_stack_level(),
635 )
636
637 @final
638 def _validate_usecols_names(self, usecols: SequenceT, names: Sequence) -> SequenceT:
639 """
640 Validates that all usecols are present in a given
641 list of names. If not, raise a ValueError that
642 shows what usecols are missing.
643
644 Parameters
645 ----------
646 usecols : iterable of usecols
647 The columns to validate are present in names.
648 names : iterable of names
649 The column names to check against.
650
651 Returns
652 -------
653 usecols : iterable of usecols
654 The `usecols` parameter if the validation succeeds.
655
656 Raises
657 ------
658 ValueError : Columns were missing. Error message will list them.
659 """
660 missing = [c for c in usecols if c not in names]
661 if len(missing) > 0:
662 raise ValueError(
663 f"Usecols do not match columns, columns expected but not found: "
664 f"{missing}"
665 )
666
667 return usecols
668
669 @final
670 def _clean_index_names(self, columns, index_col) -> tuple[list | None, list, list]:
671 if not is_index_col(index_col):
672 return None, columns, index_col
673
674 columns = list(columns)
675
676 # In case of no rows and multiindex columns we have to set index_names to
677 # list of Nones GH#38292
678 if not columns:
679 return [None] * len(index_col), columns, index_col
680
681 cp_cols = list(columns)
682 index_names: list[str | int | None] = []
683
684 # don't mutate
685 index_col = list(index_col)
686
687 for i, c in enumerate(index_col):
688 if isinstance(c, str):
689 index_names.append(c)
690 for j, name in enumerate(cp_cols):
691 if name == c:
692 index_col[i] = j
693 columns.remove(name)
694 break
695 else:
696 name = cp_cols[c]
697 columns.remove(name)
698 index_names.append(name)
699
700 # Only clean index names that were placeholders.
701 for i, name in enumerate(index_names):
702 if isinstance(name, str) and name in self.unnamed_cols:
703 index_names[i] = None
704
705 return index_names, columns, index_col
706
707 @final
708 def _get_empty_meta(
709 self, columns: Sequence[HashableT], dtype: DtypeArg | None = None
710 ) -> tuple[Index, list[HashableT], dict[HashableT, Series]]:
711 columns = list(columns)
712
713 index_col = self.index_col
714 index_names = self.index_names
715
716 # Convert `dtype` to a defaultdict of some kind.
717 # This will enable us to write `dtype[col_name]`
718 # without worrying about KeyError issues later on.
719 dtype_dict: defaultdict[Hashable, Any]
720 if not is_dict_like(dtype):
721 # if dtype == None, default will be object.
722 dtype_dict = defaultdict(lambda: dtype)
723 else:
724 dtype = cast(dict, dtype)
725 dtype_dict = defaultdict(
726 lambda: None,
727 {columns[k] if is_integer(k) else k: v for k, v in dtype.items()},
728 )
729
730 # Even though we have no data, the "index" of the empty DataFrame
731 # could for example still be an empty MultiIndex. Thus, we need to
732 # check whether we have any index columns specified, via either:
733 #
734 # 1) index_col (column indices)
735 # 2) index_names (column names)
736 #
737 # Both must be non-null to ensure a successful construction. Otherwise,
738 # we have to create a generic empty Index.
739 index: Index
740 if (index_col is None or index_col is False) or index_names is None:
741 index = default_index(0)
742 else:
743 # TODO: We could return default_index(0) if dtype_dict[name] is None
744 data = [
745 Index([], name=name, dtype=dtype_dict[name]) for name in index_names
746 ]
747 if len(data) == 1:
748 index = data[0]
749 else:
750 index = MultiIndex.from_arrays(data)
751 index_col.sort()
752
753 for i, n in enumerate(index_col):
754 columns.pop(n - i)
755
756 col_dict = {
757 col_name: Series([], dtype=dtype_dict[col_name]) for col_name in columns
758 }
759
760 return index, columns, col_dict
761
762
763def date_converter(
764 date_col,
765 col: Hashable,
766 dayfirst: bool = False,
767 cache_dates: bool = True,
768 date_format: dict[Hashable, str] | str | None = None,
769):
770 if date_col.dtype.kind in "Mm":
771 return date_col
772
773 date_fmt = date_format.get(col) if isinstance(date_format, dict) else date_format
774
775 str_objs = lib.ensure_string_array(np.asarray(date_col))
776 try:
777 result = tools.to_datetime(
778 str_objs,
779 format=date_fmt,
780 utc=False,
781 dayfirst=dayfirst,
782 cache=cache_dates,
783 )
784 except (ValueError, TypeError):
785 # test_usecols_with_parse_dates4
786 # test_multi_index_parse_dates
787 return str_objs
788
789 if isinstance(result, DatetimeIndex):
790 arr = result.to_numpy()
791 arr.flags.writeable = True
792 return arr
793 return result._values
794
795
796parser_defaults = {
797 "delimiter": None,
798 "escapechar": None,
799 "quotechar": '"',
800 "quoting": csv.QUOTE_MINIMAL,
801 "doublequote": True,
802 "skipinitialspace": False,
803 "lineterminator": None,
804 "header": "infer",
805 "index_col": None,
806 "names": None,
807 "skiprows": None,
808 "skipfooter": 0,
809 "nrows": None,
810 "na_values": None,
811 "keep_default_na": True,
812 "true_values": None,
813 "false_values": None,
814 "converters": None,
815 "dtype": None,
816 "cache_dates": True,
817 "thousands": None,
818 "comment": None,
819 "decimal": ".",
820 # 'engine': 'c',
821 "parse_dates": False,
822 "dayfirst": False,
823 "date_format": None,
824 "usecols": None,
825 # 'iterator': False,
826 "chunksize": None,
827 "encoding": None,
828 "compression": None,
829 "skip_blank_lines": True,
830 "encoding_errors": "strict",
831 "on_bad_lines": ParserBase.BadLineHandleMethod.ERROR,
832 "dtype_backend": lib.no_default,
833}
834
835
836def get_na_values(col, na_values, na_fvalues, keep_default_na: bool):
837 """
838 Get the NaN values for a given column.
839
840 Parameters
841 ----------
842 col : str
843 The name of the column.
844 na_values : array-like, dict
845 The object listing the NaN values as strings.
846 na_fvalues : array-like, dict
847 The object listing the NaN values as floats.
848 keep_default_na : bool
849 If `na_values` is a dict, and the column is not mapped in the
850 dictionary, whether to return the default NaN values or the empty set.
851
852 Returns
853 -------
854 nan_tuple : A length-two tuple composed of
855
856 1) na_values : the string NaN values for that column.
857 2) na_fvalues : the float NaN values for that column.
858 """
859 if isinstance(na_values, dict):
860 if col in na_values:
861 return na_values[col], na_fvalues[col]
862 else:
863 if keep_default_na:
864 return STR_NA_VALUES, set()
865
866 return set(), set()
867 else:
868 return na_values, na_fvalues
869
870
871def is_index_col(col) -> bool:
872 return col is not None and col is not False
873
874
875def validate_parse_dates_presence(
876 parse_dates: bool | list, columns: Sequence[Hashable]
877) -> set:
878 """
879 Check if parse_dates are in columns.
880
881 If user has provided names for parse_dates, check if those columns
882 are available.
883
884 Parameters
885 ----------
886 columns : list
887 List of names of the dataframe.
888
889 Returns
890 -------
891 The names of the columns which will get parsed later if a list
892 is given as specification.
893
894 Raises
895 ------
896 ValueError
897 If column to parse_date is not in dataframe.
898
899 """
900 if not isinstance(parse_dates, list):
901 return set()
902
903 missing = set()
904 unique_cols = set()
905 for col in parse_dates:
906 if isinstance(col, str):
907 if col not in columns:
908 missing.add(col)
909 else:
910 unique_cols.add(col)
911 elif col in columns:
912 unique_cols.add(col)
913 else:
914 unique_cols.add(columns[col])
915 if missing:
916 missing_cols = ", ".join(sorted(missing))
917 raise ValueError(f"Missing column provided to 'parse_dates': '{missing_cols}'")
918 return unique_cols
919
920
921def _validate_usecols_arg(usecols):
922 """
923 Validate the 'usecols' parameter.
924
925 Checks whether or not the 'usecols' parameter contains all integers
926 (column selection by index), strings (column by name) or is a callable.
927 Raises a ValueError if that is not the case.
928
929 Parameters
930 ----------
931 usecols : list-like, callable, or None
932 List of columns to use when parsing or a callable that can be used
933 to filter a list of table columns.
934
935 Returns
936 -------
937 usecols_tuple : tuple
938 A tuple of (verified_usecols, usecols_dtype).
939
940 'verified_usecols' is either a set if an array-like is passed in or
941 'usecols' if a callable or None is passed in.
942
943 'usecols_dtype` is the inferred dtype of 'usecols' if an array-like
944 is passed in or None if a callable or None is passed in.
945 """
946 msg = (
947 "'usecols' must either be list-like of all strings, all unicode, "
948 "all integers or a callable."
949 )
950 if usecols is not None:
951 if callable(usecols):
952 return usecols, None
953
954 if not is_list_like(usecols):
955 # see gh-20529
956 #
957 # Ensure it is iterable container but not string.
958 raise ValueError(msg)
959
960 usecols_dtype = lib.infer_dtype(usecols, skipna=False)
961
962 if usecols_dtype not in ("empty", "integer", "string"):
963 raise ValueError(msg)
964
965 usecols = set(usecols)
966
967 return usecols, usecols_dtype
968 return usecols, None
969
970
971@overload
972def evaluate_callable_usecols(
973 usecols: Callable[[Hashable], object],
974 names: Iterable[Hashable],
975) -> set[int]: ...
976
977
978@overload
979def evaluate_callable_usecols(
980 usecols: SequenceT, names: Iterable[Hashable]
981) -> SequenceT: ...
982
983
984def evaluate_callable_usecols(
985 usecols: Callable[[Hashable], object] | SequenceT,
986 names: Iterable[Hashable],
987) -> SequenceT | set[int]:
988 """
989 Check whether or not the 'usecols' parameter
990 is a callable. If so, enumerates the 'names'
991 parameter and returns a set of indices for
992 each entry in 'names' that evaluates to True.
993 If not a callable, returns 'usecols'.
994 """
995 if callable(usecols):
996 return {i for i, name in enumerate(names) if usecols(name)}
997 return usecols