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

424 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 BLHM_ERROR = 0 

89 BLHM_WARN = 1 

90 BLHM_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( 

170 "on_bad_lines", self.BadLineHandleMethod.BLHM_ERROR 

171 ) 

172 

173 # See TextReader.warning_sink: when not None, ParserWarnings are 

174 # collected here instead of being raised, so the parallel reader can 

175 # raise one copy of a warning its several reads each hit (GH#66259). 

176 self._warning_sink: list[tuple[str, type[Warning]]] | None = None 

177 

178 def close(self) -> None: 

179 pass 

180 

181 @final 

182 def _warn_parser(self, msg: str) -> None: 

183 if self._warning_sink is not None: 

184 self._warning_sink.append((msg, ParserWarning)) 

185 else: 

186 warnings.warn(msg, ParserWarning, stacklevel=find_stack_level()) 

187 

188 @final 

189 def _should_parse_dates(self, i: int) -> bool: 

190 if isinstance(self.parse_dates, bool): 

191 return self.parse_dates 

192 else: 

193 if self.index_names is not None: 

194 name = self.index_names[i] 

195 else: 

196 name = None 

197 j = i if self.index_col is None else self.index_col[i] 

198 

199 return (j in self.parse_dates) or ( 

200 name is not None and name in self.parse_dates 

201 ) 

202 

203 @final 

204 def _extract_multi_indexer_columns( 

205 self, 

206 header, 

207 index_names: Sequence[Hashable] | None, 

208 passed_names: bool = False, 

209 ) -> tuple[ 

210 Sequence[Hashable], Sequence[Hashable] | None, Sequence[Hashable] | None, bool 

211 ]: 

212 """ 

213 Extract and return the names, index_names, col_names if the column 

214 names are a MultiIndex. 

215 

216 Parameters 

217 ---------- 

218 header: list of lists 

219 The header rows 

220 index_names: list, optional 

221 The names of the future index 

222 passed_names: bool, default False 

223 A flag specifying if names where passed 

224 

225 """ 

226 if len(header) < 2: 

227 return header[0], index_names, None, passed_names 

228 

229 # the names are the tuples of the header that are not the index cols 

230 # 0 is the name of the index, assuming index_col is a list of column 

231 # numbers 

232 ic = self.index_col 

233 if ic is None: 

234 ic = [] 

235 

236 if not isinstance(ic, (list, tuple, np.ndarray)): 

237 ic = [ic] 

238 sic = set(ic) 

239 

240 # clean the index_names 

241 index_names = header.pop(-1) 

242 index_names, _, _ = self._clean_index_names(index_names, self.index_col) 

243 

244 # extract the columns 

245 field_count = len(header[0]) 

246 

247 # check if header lengths are equal 

248 if not all(len(header_iter) == field_count for header_iter in header[1:]): 

249 raise ParserError("Header rows must have an equal number of columns.") 

250 

251 def extract(r): 

252 return tuple(r[i] for i in range(field_count) if i not in sic) 

253 

254 columns = list(zip(*(extract(r) for r in header), strict=True)) 

255 names = columns.copy() 

256 for single_ic in sorted(ic): 

257 names.insert(single_ic, single_ic) 

258 

259 # Clean the column names (if we have an index_col). 

260 if ic: 

261 col_names = [ 

262 r[ic[0]] 

263 if ((r[ic[0]] is not None) and r[ic[0]] not in self.unnamed_cols) 

264 else None 

265 for r in header 

266 ] 

267 else: 

268 col_names = [None] * len(header) 

269 

270 passed_names = True 

271 

272 return names, index_names, col_names, passed_names 

273 

274 @final 

275 def _maybe_make_multi_index_columns( 

276 self, 

277 columns: SequenceT, 

278 col_names: Sequence[Hashable] | None = None, 

279 ) -> SequenceT | MultiIndex: 

280 # possibly create a column mi here 

281 if is_potential_multi_index(columns): 

282 columns_mi = cast("Sequence[tuple[Hashable, ...]]", columns) 

283 return MultiIndex.from_tuples(columns_mi, names=col_names) 

284 return columns 

285 

286 @final 

287 def _make_index( 

288 self, alldata, columns, indexnamerow: list[Scalar] | None = None 

289 ) -> tuple[Index | None, Sequence[Hashable] | MultiIndex]: 

290 index: Index | None 

291 if isinstance(self.index_col, list) and len(self.index_col): 

292 to_remove = [] 

293 indexes = [] 

294 for idx in self.index_col: 

295 if isinstance(idx, str): 

296 raise ValueError(f"Index {idx} invalid") 

297 to_remove.append(idx) 

298 indexes.append(alldata[idx]) 

299 # remove index items from content and columns, don't pop in 

300 # loop 

301 for i in sorted(to_remove, reverse=True): 

302 alldata.pop(i) 

303 if not self._implicit_index: 

304 columns.pop(i) 

305 index = self._agg_index(indexes) 

306 

307 # add names for the index 

308 if indexnamerow: 

309 coffset = len(indexnamerow) - len(columns) 

310 index = index.set_names(indexnamerow[:coffset]) 

311 else: 

312 index = None 

313 

314 # maybe create a mi on the columns 

315 columns = self._maybe_make_multi_index_columns(columns, self.col_names) 

316 

317 return index, columns 

318 

319 @final 

320 def _clean_mapping(self, mapping): 

321 """converts col numbers to names""" 

322 if not isinstance(mapping, dict): 

323 return mapping 

324 clean = {} 

325 # for mypy 

326 assert self.orig_names is not None 

327 

328 for col, v in mapping.items(): 

329 if isinstance(col, int) and col not in self.orig_names: 

330 col = self.orig_names[col] 

331 clean[col] = v 

332 if isinstance(mapping, defaultdict): 

333 remaining_cols = set(self.orig_names) - set(clean.keys()) 

334 clean.update({col: mapping[col] for col in remaining_cols}) 

335 return clean 

336 

337 @final 

338 def _agg_index(self, index) -> Index: 

339 arrays = [] 

340 converters = self._clean_mapping(self.converters) 

341 clean_dtypes = self._clean_mapping(self.dtype) 

342 

343 if self.index_names is not None: 

344 names: Iterable = self.index_names 

345 zip_strict = True 

346 else: 

347 names = itertools.cycle([None]) 

348 zip_strict = False 

349 for i, (arr, name) in enumerate(zip(index, names, strict=zip_strict)): 

350 if self._should_parse_dates(i): 

351 arr = date_converter( 

352 arr, 

353 col=self.index_names[i] if self.index_names is not None else None, 

354 dayfirst=self.dayfirst, 

355 cache_dates=self.cache_dates, 

356 date_format=self.date_format, 

357 ) 

358 

359 if self.na_filter: 

360 col_na_values = self.na_values 

361 col_na_fvalues = self.na_fvalues 

362 else: 

363 col_na_values = set() 

364 col_na_fvalues = set() 

365 

366 if isinstance(self.na_values, dict): 

367 assert self.index_names is not None 

368 col_name = self.index_names[i] 

369 if col_name is not None: 

370 col_na_values, col_na_fvalues = get_na_values( 

371 col_name, self.na_values, self.na_fvalues, self.keep_default_na 

372 ) 

373 else: 

374 col_na_values, col_na_fvalues = set(), set() 

375 

376 cast_type = None 

377 index_converter = False 

378 if self.index_names is not None: 

379 if isinstance(clean_dtypes, dict): 

380 cast_type = clean_dtypes.get(self.index_names[i], None) 

381 

382 if isinstance(converters, dict): 

383 index_converter = converters.get(self.index_names[i]) is not None 

384 

385 try_num_bool = not ( 

386 (cast_type and is_string_dtype(cast_type)) or index_converter 

387 ) 

388 

389 arr, _ = self._infer_types( 

390 arr, col_na_values | col_na_fvalues, cast_type is None, try_num_bool 

391 ) 

392 if cast_type is not None: 

393 # Don't perform RangeIndex inference 

394 idx = Index(arr, name=name, dtype=cast_type, copy=False) 

395 else: 

396 idx = ensure_index_from_sequences([arr], [name]) 

397 arrays.append(idx) 

398 

399 if len(arrays) == 1: 

400 return arrays[0] 

401 else: 

402 return MultiIndex.from_arrays(arrays) 

403 

404 @final 

405 def _set_noconvert_dtype_columns( 

406 self, col_indices: list[int], names: Sequence[Hashable] 

407 ) -> set[int]: 

408 """ 

409 Set the columns that should not undergo dtype conversions. 

410 

411 Currently, any column that is involved with date parsing will not 

412 undergo such conversions. If usecols is specified, the positions of the columns 

413 not to cast is relative to the usecols not to all columns. 

414 

415 Parameters 

416 ---------- 

417 col_indices: The indices specifying order and positions of the columns 

418 names: The column names which order is corresponding with the order 

419 of col_indices 

420 

421 Returns 

422 ------- 

423 A set of integers containing the positions of the columns not to convert. 

424 """ 

425 usecols: list[int] | list[str] | None 

426 noconvert_columns = set() 

427 if self.usecols_dtype == "integer": 

428 # A set of integers will be converted to a list in 

429 # the correct order every single time. 

430 usecols = sorted(self.usecols) 

431 elif callable(self.usecols) or self.usecols_dtype not in ("empty", None): 

432 # The names attribute should have the correct columns 

433 # in the proper order for indexing with parse_dates. 

434 usecols = col_indices 

435 else: 

436 # Usecols is empty. 

437 usecols = None 

438 

439 def _set(x) -> int: 

440 if usecols is not None and is_integer(x): 

441 x = usecols[x] 

442 

443 if not is_integer(x): 

444 x = col_indices[names.index(x)] 

445 

446 return x 

447 

448 if isinstance(self.parse_dates, list): 

449 validate_parse_dates_presence(self.parse_dates, names) 

450 for val in self.parse_dates: 

451 noconvert_columns.add(_set(val)) 

452 

453 elif self.parse_dates: 

454 if isinstance(self.index_col, list): 

455 for k in self.index_col: 

456 noconvert_columns.add(_set(k)) 

457 elif self.index_col is not None: 

458 noconvert_columns.add(_set(self.index_col)) 

459 

460 return noconvert_columns 

461 

462 @final 

463 def _infer_types( 

464 self, values, na_values, no_dtype_specified, try_num_bool: bool = True 

465 ) -> tuple[ArrayLike, int]: 

466 """ 

467 Infer types of values, possibly casting 

468 

469 Parameters 

470 ---------- 

471 values : ndarray 

472 na_values : set 

473 no_dtype_specified: Specifies if we want to cast explicitly 

474 try_num_bool : bool, default try 

475 try to cast values to numeric (first preference) or boolean 

476 

477 Returns 

478 ------- 

479 converted : ndarray or ExtensionArray 

480 na_count : int 

481 """ 

482 na_count = 0 

483 if issubclass(values.dtype.type, (np.number, np.bool_)): 

484 # If our array has numeric dtype, we don't have to check for strings in isin 

485 na_values = np.array([val for val in na_values if not isinstance(val, str)]) 

486 mask = algorithms.isin(values, na_values) 

487 na_count = mask.astype("uint8", copy=False).sum() 

488 if na_count > 0: 

489 if is_integer_dtype(values): 

490 values = values.astype(np.float64) 

491 np.putmask(values, mask, np.nan) 

492 return values, na_count 

493 

494 dtype_backend = self.dtype_backend 

495 non_default_dtype_backend = ( 

496 no_dtype_specified and dtype_backend is not lib.no_default 

497 ) 

498 result: ArrayLike 

499 

500 if try_num_bool and is_object_dtype(values.dtype): 

501 # exclude e.g DatetimeIndex here 

502 try: 

503 result, result_mask = lib.maybe_convert_numeric( 

504 values, 

505 na_values, 

506 False, 

507 convert_to_masked_nullable=non_default_dtype_backend, # type: ignore[arg-type] 

508 ) 

509 except (ValueError, TypeError): 

510 # e.g. encountering datetime string gets ValueError 

511 # TypeError can be raised in floatify 

512 na_count = parsers.sanitize_objects(values, na_values) 

513 result = values 

514 else: 

515 if non_default_dtype_backend: 

516 if result_mask is None: 

517 result_mask = np.zeros(result.shape, dtype=np.bool_) 

518 

519 if result_mask.all(): 

520 result = IntegerArray( 

521 np.ones(result_mask.shape, dtype=np.int64), result_mask 

522 ) 

523 elif is_integer_dtype(result): 

524 result = IntegerArray(result, result_mask) 

525 elif is_bool_dtype(result): 

526 result = BooleanArray(result, result_mask) 

527 elif is_float_dtype(result): 

528 result = FloatingArray(result, result_mask) 

529 

530 na_count = result_mask.sum() # type: ignore[assignment] 

531 else: 

532 na_count = isna(result).sum() # type: ignore[assignment] 

533 else: 

534 result = values 

535 if values.dtype == np.object_: 

536 na_count = parsers.sanitize_objects(values, na_values) 

537 

538 if ( 

539 result.dtype == np.object_ 

540 and try_num_bool 

541 and (len(result) == 0 or not isinstance(result[0], int)) 

542 ): 

543 result, bool_mask = libops.maybe_convert_bool( 

544 np.asarray(values), 

545 true_values=self.true_values, 

546 false_values=self.false_values, 

547 convert_to_masked_nullable=non_default_dtype_backend, # type: ignore[arg-type] 

548 ) 

549 if result.dtype == np.bool_ and non_default_dtype_backend: 

550 if bool_mask is None: 

551 bool_mask = np.zeros(result.shape, dtype=np.bool_) 

552 result = BooleanArray(result, bool_mask) 

553 elif result.dtype == np.object_ and non_default_dtype_backend: 

554 # read_excel sends array of datetime objects 

555 if not lib.is_datetime_array(result, skipna=True): 

556 dtype = StringDtype() 

557 cls = dtype.construct_array_type() 

558 result = cls._from_sequence(values, dtype=dtype) 

559 

560 if dtype_backend == "pyarrow": 

561 pa = import_optional_dependency("pyarrow") 

562 if isinstance(result, np.ndarray): 

563 result = ArrowExtensionArray(pa.array(result, from_pandas=True)) 

564 elif isinstance(result, BaseMaskedArray): 

565 if result._mask.all(): 

566 # We want an arrow null array here 

567 result = ArrowExtensionArray(pa.array([None] * len(result))) 

568 else: 

569 result = ArrowExtensionArray( 

570 pa.array(result._data, mask=result._mask) 

571 ) 

572 else: 

573 result = ArrowExtensionArray( 

574 pa.array(result.to_numpy(), from_pandas=True) 

575 ) 

576 

577 return result, na_count 

578 

579 @overload 

580 def _do_date_conversions( 

581 self, 

582 names: Index, 

583 data: DataFrame, 

584 ) -> DataFrame: ... 

585 

586 @overload 

587 def _do_date_conversions( 

588 self, 

589 names: Sequence[Hashable], 

590 data: Mapping[Hashable, ArrayLike], 

591 ) -> Mapping[Hashable, ArrayLike]: ... 

592 

593 @final 

594 def _do_date_conversions( 

595 self, 

596 names: Sequence[Hashable] | Index, 

597 data: Mapping[Hashable, ArrayLike] | DataFrame, 

598 ) -> Mapping[Hashable, ArrayLike] | DataFrame: 

599 if not isinstance(self.parse_dates, list): 

600 return data 

601 for colspec in self.parse_dates: 

602 if isinstance(colspec, int) and colspec not in data: 

603 colspec = names[colspec] 

604 if (isinstance(self.index_col, list) and colspec in self.index_col) or ( 

605 isinstance(self.index_names, list) and colspec in self.index_names 

606 ): 

607 continue 

608 result = date_converter( 

609 data[colspec], 

610 col=colspec, 

611 dayfirst=self.dayfirst, 

612 cache_dates=self.cache_dates, 

613 date_format=self.date_format, 

614 ) 

615 # error: Unsupported target for indexed assignment 

616 # ("Mapping[Hashable, ExtensionArray | ndarray[Any, Any]] | DataFrame") 

617 data[colspec] = result # type: ignore[index] 

618 

619 return data 

620 

621 @final 

622 def _check_data_length( 

623 self, 

624 columns: Sequence[Hashable], 

625 data: Sequence[ArrayLike], 

626 ) -> None: 

627 """Checks if length of data is equal to length of column names. 

628 

629 One set of trailing commas is allowed. self.index_col not False 

630 results in a ParserError previously when lengths do not match. 

631 

632 Parameters 

633 ---------- 

634 columns: list of column names 

635 data: list of array-likes containing the data column-wise. 

636 """ 

637 if not self.index_col and len(columns) != len(data) and columns: 

638 empty_str = is_object_dtype(data[-1]) and data[-1] == "" 

639 # error: No overload variant of "__ror__" of "ndarray" matches 

640 # argument type "ExtensionArray" 

641 empty_str_or_na = empty_str | isna(data[-1]) # type: ignore[operator] 

642 if len(columns) == len(data) - 1 and np.all(empty_str_or_na): 

643 return 

644 self._warn_parser( 

645 "Length of header or names does not match length of data. This leads " 

646 "to a loss of data with index_col=False." 

647 ) 

648 

649 @final 

650 def _validate_usecols_names(self, usecols: SequenceT, names: Sequence) -> SequenceT: 

651 """ 

652 Validates that all usecols are present in a given 

653 list of names. If not, raise a ValueError that 

654 shows what usecols are missing. 

655 

656 Parameters 

657 ---------- 

658 usecols : iterable of usecols 

659 The columns to validate are present in names. 

660 names : iterable of names 

661 The column names to check against. 

662 

663 Returns 

664 ------- 

665 usecols : iterable of usecols 

666 The `usecols` parameter if the validation succeeds. 

667 

668 Raises 

669 ------ 

670 ValueError : Columns were missing. Error message will list them. 

671 """ 

672 missing = [c for c in usecols if c not in names] 

673 if len(missing) > 0: 

674 raise ValueError( 

675 f"Usecols do not match columns, columns expected but not found: " 

676 f"{missing}" 

677 ) 

678 

679 return usecols 

680 

681 @final 

682 def _clean_index_names(self, columns, index_col) -> tuple[list | None, list, list]: 

683 if not is_index_col(index_col): 

684 return None, columns, index_col 

685 

686 columns = list(columns) 

687 

688 # In case of no rows and multiindex columns we have to set index_names to 

689 # list of Nones GH#38292 

690 if not columns: 

691 return [None] * len(index_col), columns, index_col 

692 

693 cp_cols = list(columns) 

694 index_names: list[str | int | None] = [] 

695 

696 # don't mutate 

697 index_col = list(index_col) 

698 

699 for i, c in enumerate(index_col): 

700 if isinstance(c, str): 

701 index_names.append(c) 

702 for j, name in enumerate(cp_cols): 

703 if name == c: 

704 index_col[i] = j 

705 columns.remove(name) 

706 break 

707 else: 

708 name = cp_cols[c] 

709 columns.remove(name) 

710 index_names.append(name) 

711 

712 # Only clean index names that were placeholders. 

713 for i, name in enumerate(index_names): 

714 if isinstance(name, str) and name in self.unnamed_cols: 

715 index_names[i] = None 

716 

717 return index_names, columns, index_col 

718 

719 @final 

720 def _get_empty_meta( 

721 self, columns: Sequence[HashableT], dtype: DtypeArg | None = None 

722 ) -> tuple[Index, list[HashableT], dict[HashableT, Series]]: 

723 columns = list(columns) 

724 

725 index_col = self.index_col 

726 index_names = self.index_names 

727 

728 # Convert `dtype` to a defaultdict of some kind. 

729 # This will enable us to write `dtype[col_name]` 

730 # without worrying about KeyError issues later on. 

731 dtype_dict: defaultdict[Hashable, Any] 

732 if not is_dict_like(dtype): 

733 # if dtype == None, default will be object. 

734 dtype_dict = defaultdict(lambda: dtype) 

735 else: 

736 dtype = cast("dict", dtype) 

737 dtype_dict = defaultdict( 

738 lambda: None, 

739 {columns[k] if is_integer(k) else k: v for k, v in dtype.items()}, 

740 ) 

741 

742 # Even though we have no data, the "index" of the empty DataFrame 

743 # could for example still be an empty MultiIndex. Thus, we need to 

744 # check whether we have any index columns specified, via either: 

745 # 

746 # 1) index_col (column indices) 

747 # 2) index_names (column names) 

748 # 

749 # Both must be non-null to ensure a successful construction. Otherwise, 

750 # we have to create a generic empty Index. 

751 index: Index 

752 if (index_col is None or index_col is False) or index_names is None: 

753 index = default_index(0) 

754 else: 

755 # TODO: We could return default_index(0) if dtype_dict[name] is None 

756 data = [ 

757 Index([], name=name, dtype=dtype_dict[name]) for name in index_names 

758 ] 

759 if len(data) == 1: 

760 index = data[0] 

761 else: 

762 index = MultiIndex.from_arrays(data) 

763 index_col.sort() 

764 

765 for i, n in enumerate(index_col): 

766 columns.pop(n - i) 

767 

768 col_dict = { 

769 col_name: Series([], dtype=dtype_dict[col_name]) for col_name in columns 

770 } 

771 

772 return index, columns, col_dict 

773 

774 

775def date_converter( 

776 date_col, 

777 col: Hashable, 

778 dayfirst: bool = False, 

779 cache_dates: bool = True, 

780 date_format: dict[Hashable, str] | str | None = None, 

781): 

782 if date_col.dtype.kind in "Mm": 

783 return date_col 

784 

785 date_fmt = date_format.get(col) if isinstance(date_format, dict) else date_format 

786 

787 str_objs = lib.ensure_string_array(np.asarray(date_col)) 

788 try: 

789 result = tools.to_datetime( 

790 str_objs, 

791 format=date_fmt, 

792 utc=False, 

793 dayfirst=dayfirst, 

794 cache=cache_dates, 

795 ) 

796 except (ValueError, TypeError): 

797 # test_usecols_with_parse_dates4 

798 # test_multi_index_parse_dates 

799 return str_objs 

800 

801 if isinstance(result, DatetimeIndex): 

802 arr = result.to_numpy() 

803 arr.flags.writeable = True 

804 return arr 

805 return result._values 

806 

807 

808parser_defaults = { 

809 "delimiter": None, 

810 "escapechar": None, 

811 "quotechar": '"', 

812 "quoting": csv.QUOTE_MINIMAL, 

813 "doublequote": True, 

814 "skipinitialspace": False, 

815 "lineterminator": None, 

816 "header": "infer", 

817 "index_col": None, 

818 "names": None, 

819 "skiprows": None, 

820 "skipfooter": 0, 

821 "nrows": None, 

822 "na_values": None, 

823 "keep_default_na": True, 

824 "true_values": None, 

825 "false_values": None, 

826 "converters": None, 

827 "dtype": None, 

828 "cache_dates": True, 

829 "thousands": None, 

830 "comment": None, 

831 "decimal": ".", 

832 # 'engine': 'c', 

833 "parse_dates": False, 

834 "dayfirst": False, 

835 "date_format": None, 

836 "usecols": None, 

837 # 'iterator': False, 

838 "chunksize": None, 

839 "encoding": None, 

840 "compression": None, 

841 "skip_blank_lines": True, 

842 "encoding_errors": "strict", 

843 "on_bad_lines": ParserBase.BadLineHandleMethod.BLHM_ERROR, 

844 "dtype_backend": lib.no_default, 

845} 

846 

847 

848def get_na_values(col, na_values, na_fvalues, keep_default_na: bool): 

849 """ 

850 Get the NaN values for a given column. 

851 

852 Parameters 

853 ---------- 

854 col : str 

855 The name of the column. 

856 na_values : array-like, dict 

857 The object listing the NaN values as strings. 

858 na_fvalues : array-like, dict 

859 The object listing the NaN values as floats. 

860 keep_default_na : bool 

861 If `na_values` is a dict, and the column is not mapped in the 

862 dictionary, whether to return the default NaN values or the empty set. 

863 

864 Returns 

865 ------- 

866 nan_tuple : A length-two tuple composed of 

867 

868 1) na_values : the string NaN values for that column. 

869 2) na_fvalues : the float NaN values for that column. 

870 """ 

871 if isinstance(na_values, dict): 

872 if col in na_values: 

873 return na_values[col], na_fvalues[col] 

874 else: 

875 if keep_default_na: 

876 return STR_NA_VALUES, set() 

877 

878 return set(), set() 

879 else: 

880 return na_values, na_fvalues 

881 

882 

883def is_index_col(col) -> bool: 

884 return col is not None and col is not False 

885 

886 

887def validate_parse_dates_presence( 

888 parse_dates: bool | list, columns: Sequence[Hashable] 

889) -> set: 

890 """ 

891 Check if parse_dates are in columns. 

892 

893 If user has provided names for parse_dates, check if those columns 

894 are available. 

895 

896 Parameters 

897 ---------- 

898 columns : list 

899 List of names of the dataframe. 

900 

901 Returns 

902 ------- 

903 The names of the columns which will get parsed later if a list 

904 is given as specification. 

905 

906 Raises 

907 ------ 

908 ValueError 

909 If column to parse_date is not in dataframe. 

910 

911 """ 

912 if not isinstance(parse_dates, list): 

913 return set() 

914 

915 missing = set() 

916 unique_cols = set() 

917 for col in parse_dates: 

918 if isinstance(col, str): 

919 if col not in columns: 

920 missing.add(col) 

921 else: 

922 unique_cols.add(col) 

923 elif col in columns: 

924 unique_cols.add(col) 

925 else: 

926 unique_cols.add(columns[col]) 

927 if missing: 

928 missing_cols = ", ".join(sorted(missing)) 

929 raise ValueError(f"Missing column provided to 'parse_dates': '{missing_cols}'") 

930 return unique_cols 

931 

932 

933def _validate_usecols_arg(usecols): 

934 """ 

935 Validate the 'usecols' parameter. 

936 

937 Checks whether or not the 'usecols' parameter contains all integers 

938 (column selection by index), strings (column by name) or is a callable. 

939 Raises a ValueError if that is not the case. 

940 

941 Parameters 

942 ---------- 

943 usecols : list-like, callable, or None 

944 List of columns to use when parsing or a callable that can be used 

945 to filter a list of table columns. 

946 

947 Returns 

948 ------- 

949 usecols_tuple : tuple 

950 A tuple of (verified_usecols, usecols_dtype). 

951 

952 'verified_usecols' is either a set if an array-like is passed in or 

953 'usecols' if a callable or None is passed in. 

954 

955 'usecols_dtype` is the inferred dtype of 'usecols' if an array-like 

956 is passed in or None if a callable or None is passed in. 

957 """ 

958 msg = ( 

959 "'usecols' must either be list-like of all strings, all unicode, " 

960 "all integers or a callable." 

961 ) 

962 if usecols is not None: 

963 if callable(usecols): 

964 return usecols, None 

965 

966 if not is_list_like(usecols): 

967 # see gh-20529 

968 # 

969 # Ensure it is iterable container but not string. 

970 raise ValueError(msg) 

971 

972 usecols_dtype = lib.infer_dtype(usecols, skipna=False) 

973 

974 if usecols_dtype not in ("empty", "integer", "string"): 

975 raise ValueError(msg) 

976 

977 usecols = set(usecols) 

978 

979 return usecols, usecols_dtype 

980 return usecols, None 

981 

982 

983@overload 

984def evaluate_callable_usecols( 

985 usecols: Callable[[Hashable], object], 

986 names: Iterable[Hashable], 

987) -> set[int]: ... 

988 

989 

990@overload 

991def evaluate_callable_usecols( 

992 usecols: SequenceT, names: Iterable[Hashable] 

993) -> SequenceT: ... 

994 

995 

996def evaluate_callable_usecols( 

997 usecols: Callable[[Hashable], object] | SequenceT, 

998 names: Iterable[Hashable], 

999) -> SequenceT | set[int]: 

1000 """ 

1001 Check whether or not the 'usecols' parameter 

1002 is a callable. If so, enumerates the 'names' 

1003 parameter and returns a set of indices for 

1004 each entry in 'names' that evaluates to True. 

1005 If not a callable, returns 'usecols'. 

1006 """ 

1007 if callable(usecols): 

1008 return {i for i, name in enumerate(names) if usecols(name)} 

1009 return usecols