Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/core/common.py: 30%

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

218 statements  

1""" 

2Misc tools for implementing data structures 

3 

4Note: pandas.core.common is *not* part of the public API. 

5""" 

6 

7from __future__ import annotations 

8 

9import builtins 

10from collections import ( 

11 abc, 

12 defaultdict, 

13) 

14import contextlib 

15import dis 

16from functools import partial 

17import inspect 

18import sys 

19from typing import ( 

20 TYPE_CHECKING, 

21 Any, 

22 Concatenate, 

23 TypeVar, 

24 cast, 

25 overload, 

26) 

27 

28import numpy as np 

29 

30from pandas._libs import lib 

31 

32from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike 

33from pandas.core.dtypes.common import ( 

34 is_bool_dtype, 

35 is_integer, 

36) 

37from pandas.core.dtypes.generic import ( 

38 ABCExtensionArray, 

39 ABCIndex, 

40 ABCMultiIndex, 

41 ABCNumpyExtensionArray, 

42 ABCSeries, 

43) 

44from pandas.core.dtypes.inference import iterable_not_string 

45 

46from pandas.core.col import Expression 

47 

48if TYPE_CHECKING: 

49 from collections.abc import ( 

50 Callable, 

51 Collection, 

52 Generator, 

53 Hashable, 

54 Iterable, 

55 Sequence, 

56 ) 

57 

58 from pandas._typing import ( 

59 AnyArrayLike, 

60 ArrayLike, 

61 NpDtype, 

62 P, 

63 RandomState, 

64 T, 

65 ) 

66 

67 from pandas import ( 

68 DataFrame, 

69 Index, 

70 Series, 

71 ) 

72 from pandas.core.generic import NDFrame 

73 

74 

75def flatten(line: Iterable) -> Generator[Any]: 

76 """ 

77 Flatten an arbitrarily nested sequence. 

78 

79 Parameters 

80 ---------- 

81 line : sequence 

82 The non string sequence to flatten 

83 

84 Notes 

85 ----- 

86 This doesn't consider strings sequences. 

87 

88 Returns 

89 ------- 

90 flattened : generator 

91 """ 

92 for element in line: 

93 if iterable_not_string(element): 

94 yield from flatten(element) 

95 else: 

96 yield element 

97 

98 

99def consensus_name_attr(objs: list[Series | DataFrame]) -> Hashable | None: 

100 name = objs[0].name 

101 for obj in objs[1:]: 

102 try: 

103 if obj.name != name: 

104 name = None 

105 break 

106 except ValueError: 

107 name = None 

108 break 

109 return name 

110 

111 

112def is_bool_indexer(key: Any) -> bool: 

113 """ 

114 Check whether `key` is a valid boolean indexer. 

115 

116 Parameters 

117 ---------- 

118 key : Any 

119 Only list-likes may be considered boolean indexers. 

120 All other types are not considered a boolean indexer. 

121 For array-like input, boolean ndarrays or ExtensionArrays 

122 with ``_is_boolean`` set are considered boolean indexers. 

123 

124 Returns 

125 ------- 

126 bool 

127 Whether `key` is a valid boolean indexer. 

128 

129 Raises 

130 ------ 

131 ValueError 

132 When the array is an object-dtype ndarray or ExtensionArray 

133 and contains missing values. 

134 

135 See Also 

136 -------- 

137 check_array_indexer : Check that `key` is a valid array to index, 

138 and convert to an ndarray. 

139 """ 

140 if isinstance( 

141 key, 

142 (ABCSeries, np.ndarray, ABCIndex, ABCExtensionArray, ABCNumpyExtensionArray), 

143 ) and not isinstance(key, ABCMultiIndex): 

144 if key.dtype == np.object_: 

145 key_array = np.asarray(key) 

146 

147 if not lib.is_bool_array(key_array): 

148 na_msg = "Cannot mask with non-boolean array containing NA / NaN values" 

149 if lib.is_bool_array(key_array, skipna=True): 

150 # Don't raise on e.g. ["A", "B", np.nan], see 

151 # test_loc_getitem_list_of_labels_categoricalindex_with_na 

152 raise ValueError(na_msg) 

153 return False 

154 return True 

155 elif is_bool_dtype(key.dtype): 

156 return True 

157 elif isinstance(key, list): 

158 # check if np.array(key).dtype would be bool 

159 if len(key) > 0: 

160 if type(key) is not list: 

161 # GH#42461 cython will raise TypeError if we pass a subclass 

162 key = list(key) 

163 return lib.is_bool_list(key) 

164 

165 return False 

166 

167 

168def cast_scalar_indexer(val: Any) -> Any: 

169 """ 

170 Disallow indexing with a float key, even if that key is a round number. 

171 

172 Parameters 

173 ---------- 

174 val : scalar 

175 

176 Returns 

177 ------- 

178 outval : scalar 

179 """ 

180 # assumes lib.is_scalar(val) 

181 if lib.is_float(val) and val.is_integer(): 

182 raise IndexError( 

183 # GH#34193 

184 "Indexing with a float is no longer supported. Manually convert " 

185 "to an integer key instead." 

186 ) 

187 return val 

188 

189 

190def not_none(*args: object) -> Generator[object]: 

191 """ 

192 Returns a generator consisting of the arguments that are not None. 

193 """ 

194 return (arg for arg in args if arg is not None) 

195 

196 

197def any_none(*args: object) -> bool: 

198 """ 

199 Returns a boolean indicating if any argument is None. 

200 """ 

201 return any(arg is None for arg in args) 

202 

203 

204def all_none(*args: object) -> bool: 

205 """ 

206 Returns a boolean indicating if all arguments are None. 

207 """ 

208 return all(arg is None for arg in args) 

209 

210 

211def any_not_none(*args: object) -> bool: 

212 """ 

213 Returns a boolean indicating if any argument is not None. 

214 """ 

215 return any(arg is not None for arg in args) 

216 

217 

218def all_not_none(*args: object) -> bool: 

219 """ 

220 Returns a boolean indicating if all arguments are not None. 

221 """ 

222 return all(arg is not None for arg in args) 

223 

224 

225def count_not_none(*args: object) -> int: 

226 """ 

227 Returns the count of arguments that are not None. 

228 """ 

229 return sum(x is not None for x in args) 

230 

231 

232@overload 

233def asarray_tuplesafe( 

234 values: ArrayLike | list | tuple | zip, dtype: NpDtype | None = ... 

235) -> np.ndarray: 

236 # ExtensionArray can only be returned when values is an Index, all other iterables 

237 # will return np.ndarray. Unfortunately "all other" cannot be encoded in a type 

238 # signature, so instead we special-case some common types. 

239 ... 

240 

241 

242@overload 

243def asarray_tuplesafe(values: Iterable, dtype: NpDtype | None = ...) -> ArrayLike: ... 

244 

245 

246def asarray_tuplesafe(values: Iterable, dtype: NpDtype | None = None) -> ArrayLike: 

247 if not (isinstance(values, (list, tuple)) or hasattr(values, "__array__")): 

248 values = list(values) 

249 elif isinstance(values, ABCIndex): 

250 return values._values 

251 elif isinstance(values, ABCSeries): 

252 return values._values 

253 

254 if isinstance(values, list) and dtype in [np.object_, object]: 

255 return construct_1d_object_array_from_listlike(values) 

256 

257 try: 

258 result = np.asarray(values, dtype=dtype) 

259 except ValueError: 

260 # Using try/except since it's more performant than checking is_list_like 

261 # over each element 

262 # error: Argument 1 to "construct_1d_object_array_from_listlike" 

263 # has incompatible type "Iterable[Any]"; expected "Sized" 

264 return construct_1d_object_array_from_listlike(values) # type: ignore[arg-type] 

265 

266 if issubclass(result.dtype.type, str): 

267 result = np.asarray(values, dtype=object) 

268 

269 if result.ndim == 2: 

270 # Avoid building an array of arrays: 

271 values = [tuple(x) for x in values] 

272 result = construct_1d_object_array_from_listlike(values) 

273 

274 return result 

275 

276 

277def index_labels_to_array( 

278 labels: np.ndarray | Iterable, dtype: NpDtype | None = None 

279) -> np.ndarray: 

280 """ 

281 Transform label or iterable of labels to array, for use in Index. 

282 

283 Parameters 

284 ---------- 

285 dtype : dtype 

286 If specified, use as dtype of the resulting array, otherwise infer. 

287 

288 Returns 

289 ------- 

290 array 

291 """ 

292 if isinstance(labels, (str, tuple)): 

293 labels = [labels] 

294 

295 if not isinstance(labels, (list, np.ndarray)): 

296 try: 

297 labels = list(labels) 

298 except TypeError: # non-iterable 

299 labels = [labels] 

300 

301 rlabels = asarray_tuplesafe(labels, dtype=dtype) 

302 

303 return rlabels 

304 

305 

306def maybe_make_list(obj: Any) -> list | tuple | None: 

307 if obj is not None and not isinstance(obj, (tuple, list)): 

308 return [obj] 

309 return obj 

310 

311 

312def maybe_iterable_to_list(obj: Iterable[T] | T) -> Collection[T] | T: 

313 """ 

314 If obj is Iterable but not list-like, consume into list. 

315 """ 

316 if isinstance(obj, abc.Iterable) and not isinstance(obj, abc.Sized): 

317 return list(obj) 

318 obj = cast("Collection", obj) 

319 return obj 

320 

321 

322def is_null_slice(obj: object) -> bool: 

323 """ 

324 We have a null slice. 

325 """ 

326 return ( 

327 isinstance(obj, slice) 

328 and obj.start is None 

329 and obj.stop is None 

330 and obj.step is None 

331 ) 

332 

333 

334def is_empty_slice(obj: object) -> bool: 

335 """ 

336 We have an empty slice, e.g. no values are selected. 

337 """ 

338 return ( 

339 isinstance(obj, slice) 

340 and obj.start is not None 

341 and obj.stop is not None 

342 and obj.start == obj.stop 

343 ) 

344 

345 

346def is_true_slices(line: abc.Iterable) -> abc.Generator[bool, None, None]: 

347 """ 

348 Find non-trivial slices in "line": yields a bool. 

349 """ 

350 for k in line: 

351 yield isinstance(k, slice) and not is_null_slice(k) 

352 

353 

354# TODO: used only once in indexing; belongs elsewhere? 

355def is_full_slice(obj: object, line: int) -> bool: 

356 """ 

357 We have a full length slice. 

358 """ 

359 return ( 

360 isinstance(obj, slice) 

361 and obj.start == 0 

362 and obj.stop == line 

363 and obj.step is None 

364 ) 

365 

366 

367def get_callable_name(obj: object) -> str | None: 

368 # typical case has name 

369 if hasattr(obj, "__name__"): 

370 return obj.__name__ # pyright: ignore[reportAttributeAccessIssue] 

371 # some objects don't; could recurse 

372 if isinstance(obj, partial): 

373 return get_callable_name(obj.func) 

374 # fall back to class name 

375 if callable(obj): 

376 return type(obj).__name__ 

377 # everything failed (probably because the argument 

378 # wasn't actually callable); we return None 

379 # instead of the empty string in this case to allow 

380 # distinguishing between no name and a name of '' 

381 return None 

382 

383 

384def apply_if_callable(maybe_callable: Any, obj: Any, **kwargs: Any) -> Any: 

385 """ 

386 Evaluate possibly callable input using obj and kwargs if it is callable, 

387 otherwise return as it is. 

388 

389 Parameters 

390 ---------- 

391 maybe_callable : possibly a callable 

392 obj : NDFrame 

393 **kwargs 

394 """ 

395 if isinstance(maybe_callable, Expression): 

396 return maybe_callable._eval_expression(obj, **kwargs) 

397 elif callable(maybe_callable): 

398 return maybe_callable(obj, **kwargs) 

399 

400 return maybe_callable 

401 

402 

403def standardize_mapping(into: type | abc.Mapping) -> type | partial: 

404 """ 

405 Helper function to standardize a supplied mapping. 

406 

407 Parameters 

408 ---------- 

409 into : instance or subclass of collections.abc.Mapping 

410 Must be a class, an initialized collections.defaultdict, 

411 or an instance of a collections.abc.Mapping subclass. 

412 

413 Returns 

414 ------- 

415 mapping : a collections.abc.Mapping subclass or other constructor 

416 a callable object that can accept an iterator to create 

417 the desired Mapping. 

418 

419 See Also 

420 -------- 

421 DataFrame.to_dict 

422 Series.to_dict 

423 """ 

424 if not inspect.isclass(into): 

425 if isinstance(into, defaultdict): 

426 return partial(defaultdict, into.default_factory) 

427 into = type(into) 

428 if not issubclass(into, abc.Mapping): 

429 raise TypeError(f"unsupported type: {into}") 

430 if into == defaultdict: 

431 raise TypeError("to_dict() only accepts initialized defaultdicts") 

432 return into 

433 

434 

435@overload 

436def random_state(state: np.random.Generator) -> np.random.Generator: ... 

437 

438 

439@overload 

440def random_state( 

441 state: int | np.ndarray | np.random.BitGenerator | np.random.RandomState | None, 

442) -> np.random.RandomState: ... 

443 

444 

445def random_state( 

446 state: RandomState | None = None, 

447) -> np.random.RandomState | np.random.Generator: 

448 """ 

449 Helper function for processing random_state arguments. 

450 

451 Parameters 

452 ---------- 

453 state : int, array-like, BitGenerator, Generator, np.random.RandomState, None. 

454 If receives an int, array-like, or BitGenerator, passes to 

455 np.random.RandomState() as seed. 

456 If receives an np.random RandomState or Generator, just returns that unchanged. 

457 If receives `None`, returns np.random. 

458 If receives anything else, raises an informative ValueError. 

459 

460 Default None. 

461 

462 Returns 

463 ------- 

464 np.random.RandomState or np.random.Generator. If state is None, returns np.random 

465 

466 """ 

467 if is_integer(state) or isinstance(state, (np.ndarray, np.random.BitGenerator)): 

468 return np.random.RandomState(state) 

469 elif isinstance(state, np.random.RandomState): 

470 return state 

471 elif isinstance(state, np.random.Generator): 

472 return state 

473 elif state is None: 

474 return np.random # type: ignore[return-value] 

475 else: 

476 raise ValueError( 

477 "random_state must be an integer, array-like, a BitGenerator, Generator, " 

478 "a numpy RandomState, or None" 

479 ) 

480 

481 

482_T = TypeVar("_T") # Secondary TypeVar for use in pipe's type hints 

483 

484 

485@overload 

486def pipe( 

487 obj: _T, 

488 func: Callable[Concatenate[_T, P], T], 

489 *args: P.args, 

490 **kwargs: P.kwargs, 

491) -> T: ... 

492 

493 

494@overload 

495def pipe( 

496 obj: Any, 

497 func: tuple[Callable[..., T], str], 

498 *args: Any, 

499 **kwargs: Any, 

500) -> T: ... 

501 

502 

503def pipe( 

504 obj: _T, 

505 func: Callable[Concatenate[_T, P], T] | tuple[Callable[..., T], str], 

506 *args: Any, 

507 **kwargs: Any, 

508) -> T: 

509 """ 

510 Apply a function ``func`` to object ``obj`` either by passing obj as the 

511 first argument to the function or, in the case that the func is a tuple, 

512 interpret the first element of the tuple as a function and pass the obj to 

513 that function as a keyword argument whose key is the value of the second 

514 element of the tuple. 

515 

516 Parameters 

517 ---------- 

518 func : callable or tuple of (callable, str) 

519 Function to apply to this object or, alternatively, a 

520 ``(callable, data_keyword)`` tuple where ``data_keyword`` is a 

521 string indicating the keyword of ``callable`` that expects the 

522 object. 

523 *args : iterable, optional 

524 Positional arguments passed into ``func``. 

525 **kwargs : dict, optional 

526 A dictionary of keyword arguments passed into ``func``. 

527 

528 Returns 

529 ------- 

530 object : the return type of ``func``. 

531 """ 

532 if isinstance(func, tuple): 

533 # Assigning to func_ so pyright understands that it's a callable 

534 func_, target = func 

535 if target in kwargs: 

536 msg = f"{target} is both the pipe target and a keyword argument" 

537 raise ValueError(msg) 

538 kwargs[target] = obj 

539 return func_(*args, **kwargs) 

540 else: 

541 return func(obj, *args, **kwargs) 

542 

543 

544def get_rename_function(mapper: Any) -> Callable: 

545 """ 

546 Returns a function that will map names/labels, dependent if mapper 

547 is a dict, Series or just a function. 

548 """ 

549 

550 def f(x: Hashable) -> Any: 

551 if x in mapper: 

552 return mapper[x] 

553 else: 

554 return x 

555 

556 return f if isinstance(mapper, (abc.Mapping, ABCSeries)) else mapper 

557 

558 

559def convert_to_list_like( 

560 values: Hashable | Iterable | AnyArrayLike, 

561) -> list | AnyArrayLike: 

562 """ 

563 Convert list-like or scalar input to list-like. List, numpy and pandas array-like 

564 inputs are returned unmodified whereas others are converted to list. 

565 """ 

566 if isinstance(values, (list, np.ndarray, ABCIndex, ABCSeries, ABCExtensionArray)): 

567 return values 

568 elif isinstance(values, abc.Iterable) and not isinstance(values, str): 

569 return list(values) 

570 

571 return [values] 

572 

573 

574@contextlib.contextmanager 

575def temp_setattr( 

576 obj: Any, attr: str, value: Any, condition: bool = True 

577) -> Generator[Any]: 

578 """ 

579 Temporarily set attribute on an object. 

580 

581 Parameters 

582 ---------- 

583 obj : object 

584 Object whose attribute will be modified. 

585 attr : str 

586 Attribute to modify. 

587 value : Any 

588 Value to temporarily set attribute to. 

589 condition : bool, default True 

590 Whether to set the attribute. Provided in order to not have to 

591 conditionally use this context manager. 

592 

593 Yields 

594 ------ 

595 object : obj with modified attribute. 

596 """ 

597 if condition: 

598 old_value = getattr(obj, attr) 

599 setattr(obj, attr, value) 

600 try: 

601 yield obj 

602 finally: 

603 if condition: 

604 setattr(obj, attr, old_value) 

605 

606 

607def require_length_match(data: Any, index: Index) -> None: 

608 """ 

609 Check the length of data matches the length of the index. 

610 """ 

611 if len(data) != len(index): 

612 raise ValueError( 

613 "Length of values " 

614 f"({len(data)}) " 

615 "does not match length of index " 

616 f"({len(index)})" 

617 ) 

618 

619 

620_cython_table = { 

621 builtins.sum: "sum", 

622 builtins.max: "max", 

623 builtins.min: "min", 

624 np.all: "all", 

625 np.any: "any", 

626 np.sum: "sum", 

627 np.nansum: "sum", 

628 np.mean: "mean", 

629 np.nanmean: "mean", 

630 np.prod: "prod", 

631 np.nanprod: "prod", 

632 np.std: "std", 

633 np.nanstd: "std", 

634 np.var: "var", 

635 np.nanvar: "var", 

636 np.median: "median", 

637 np.nanmedian: "median", 

638 np.max: "max", 

639 np.nanmax: "max", 

640 np.min: "min", 

641 np.nanmin: "min", 

642 np.cumprod: "cumprod", 

643 np.nancumprod: "cumprod", 

644 np.cumsum: "cumsum", 

645 np.nancumsum: "cumsum", 

646} 

647 

648 

649def get_cython_func(arg: Callable) -> str | None: 

650 """ 

651 if we define an internal function for this argument, return it 

652 """ 

653 return _cython_table.get(arg) 

654 

655 

656def fill_missing_names(names: Sequence[Hashable | None]) -> list[Hashable]: 

657 """ 

658 If a name is missing then replace it by level_n, where n is the count 

659 

660 Parameters 

661 ---------- 

662 names : list-like 

663 list of column names or None values. 

664 

665 Returns 

666 ------- 

667 list 

668 list of column names with the None values replaced. 

669 """ 

670 return [f"level_{i}" if name is None else name for i, name in enumerate(names)] 

671 

672 

673# opcodes through which the interpreter calls ``__setitem__`` 

674# (STORE_SLICE only exists on Python 3.12+, before that slices went through 

675# BUILD_SLICE + STORE_SUBSCR) 

676_SETITEM_OPCODES = frozenset( 

677 dis.opmap[name] for name in ("STORE_SUBSCR", "STORE_SLICE") if name in dis.opmap 

678) 

679 

680 

681def is_setitem_syntax_in_caller_frame() -> bool: 

682 """ 

683 Helper function used in detecting chained assignment. 

684 

685 Whether the frame calling ``__setitem__`` is executing a subscript 

686 assignment (``obj[key] = value``) right now. 

687 

688 The refcount check can only tell a temporary apart from a named object for 

689 calls coming from the interpreter, because it relies on the extra 

690 reference the interpreter holds on its value stack. Code compiled with 

691 Cython calls ``PyObject_SetItem`` directly, so it has one reference less 

692 and a perfectly valid ``df[col] = value`` there looks exactly like chained 

693 assignment (GH#51315). Such a call leaves no Python frame of its own, and 

694 the nearest Python frame is executing a call rather than a subscript 

695 assignment, so checking the opcode filters those out. 

696 """ 

697 try: 

698 frame = sys._getframe(2) 

699 except ValueError: 

700 return False 

701 return frame.f_code.co_code[frame.f_lasti] in _SETITEM_OPCODES 

702 

703 

704def is_local_in_caller_frame(obj: NDFrame) -> bool: 

705 """ 

706 Helper function used in detecting chained assignment. 

707 

708 If the pandas object (DataFrame/Series) is a local variable 

709 in the caller's frame, it should not be a case of chained 

710 assignment or method call. 

711 

712 For example: 

713 

714 def test(): 

715 df = pd.DataFrame(...) 

716 df["a"] = 1 # not chained assignment 

717 

718 Inside ``df.__setitem__``, we call this function to check whether `df` 

719 (`self`) is a local variable in `test` frame (the frame calling setitem). If 

720 so, we know it is not a case of chained assignment (even when the refcount 

721 of `df` is below the threshold due to optimization of local variables). 

722 """ 

723 frame = sys._getframe(2) 

724 for v in frame.f_locals.values(): 

725 if v is obj: 

726 return True 

727 return False