Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/io/parsers/base_parser.py: 19%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

418 statements  

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