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

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

642 statements  

1from __future__ import annotations 

2 

3import functools 

4import itertools 

5from typing import ( 

6 TYPE_CHECKING, 

7 Any, 

8 cast, 

9) 

10import warnings 

11 

12import numpy as np 

13 

14from pandas._config import get_option 

15 

16from pandas._libs import ( 

17 NaT, 

18 NaTType, 

19 iNaT, 

20 lib, 

21) 

22from pandas._typing import ( 

23 ArrayLike, 

24 AxisInt, 

25 CorrelationMethod, 

26 Dtype, 

27 DtypeObj, 

28 F, 

29 Scalar, 

30 Shape, 

31 npt, 

32) 

33from pandas.compat._optional import import_optional_dependency 

34 

35from pandas.core.dtypes.common import ( 

36 is_complex, 

37 is_float, 

38 is_float_dtype, 

39 is_integer, 

40 is_numeric_dtype, 

41 is_object_dtype, 

42 needs_i8_conversion, 

43 pandas_dtype, 

44) 

45from pandas.core.dtypes.missing import ( 

46 isna, 

47 na_value_for_dtype, 

48 notna, 

49) 

50 

51if TYPE_CHECKING: 

52 from collections.abc import Callable 

53 

54bn = import_optional_dependency("bottleneck", errors="warn") 

55_BOTTLENECK_INSTALLED = bn is not None 

56_USE_BOTTLENECK = False 

57 

58 

59def set_use_bottleneck(v: bool = True) -> None: 

60 # set/unset to use bottleneck 

61 global _USE_BOTTLENECK 

62 if _BOTTLENECK_INSTALLED: 

63 _USE_BOTTLENECK = v 

64 

65 

66set_use_bottleneck(get_option("compute.use_bottleneck")) 

67 

68 

69class disallow: 

70 def __init__(self, *dtypes: Dtype) -> None: 

71 super().__init__() 

72 self.dtypes = tuple(pandas_dtype(dtype).type for dtype in dtypes) 

73 

74 def check(self, obj) -> bool: 

75 return hasattr(obj, "dtype") and issubclass(obj.dtype.type, self.dtypes) 

76 

77 def __call__(self, f: F) -> F: 

78 @functools.wraps(f) 

79 def _f(*args, **kwargs): 

80 obj_iter = itertools.chain(args, kwargs.values()) 

81 if any(self.check(obj) for obj in obj_iter): 

82 f_name = f.__name__.replace("nan", "") 

83 raise TypeError( 

84 f"reduction operation '{f_name}' not allowed for this dtype" 

85 ) 

86 try: 

87 return f(*args, **kwargs) 

88 except ValueError as e: 

89 # we want to transform an object array 

90 # ValueError message to the more typical TypeError 

91 # e.g. this is normally a disallowed function on 

92 # object arrays that contain strings 

93 if is_object_dtype(args[0]): 

94 raise TypeError(e) from e 

95 raise 

96 

97 return cast(F, _f) 

98 

99 

100class bottleneck_switch: 

101 def __init__(self, name=None, **kwargs) -> None: 

102 self.name = name 

103 self.kwargs = kwargs 

104 

105 def __call__(self, alt: F) -> F: 

106 bn_name = self.name or alt.__name__ 

107 

108 try: 

109 bn_func = getattr(bn, bn_name) 

110 except (AttributeError, NameError): # pragma: no cover 

111 bn_func = None 

112 

113 @functools.wraps(alt) 

114 def f( 

115 values: np.ndarray, 

116 *, 

117 axis: AxisInt | None = None, 

118 skipna: bool = True, 

119 **kwds, 

120 ): 

121 if len(self.kwargs) > 0: 

122 for k, v in self.kwargs.items(): 

123 if k not in kwds: 

124 kwds[k] = v 

125 

126 if values.size == 0 and kwds.get("min_count") is None: 

127 # We are empty, returning NA for our type 

128 # Only applies for the default `min_count` of None 

129 # since that affects how empty arrays are handled. 

130 # TODO(GH-18976) update all the nanops methods to 

131 # correctly handle empty inputs and remove this check. 

132 # It *may* just be `var` 

133 return _na_for_min_count(values, axis) 

134 

135 if _USE_BOTTLENECK and skipna and _bn_ok_dtype(values.dtype, bn_name): 

136 if kwds.get("mask", None) is None: 

137 # `mask` is not recognised by bottleneck, would raise 

138 # TypeError if called 

139 kwds.pop("mask", None) 

140 result = bn_func(values, axis=axis, **kwds) 

141 

142 # prefer to treat inf/-inf as NA, but must compute the func 

143 # twice :( 

144 if _has_infs(result): 

145 result = alt(values, axis=axis, skipna=skipna, **kwds) 

146 else: 

147 result = alt(values, axis=axis, skipna=skipna, **kwds) 

148 else: 

149 result = alt(values, axis=axis, skipna=skipna, **kwds) 

150 

151 return result 

152 

153 return cast(F, f) 

154 

155 

156def _bn_ok_dtype(dtype: DtypeObj, name: str) -> bool: 

157 # Bottleneck chokes on datetime64, PeriodDtype (or and EA) 

158 if dtype != object and not needs_i8_conversion(dtype): 

159 # GH 42878 

160 # Bottleneck uses naive summation leading to O(n) loss of precision 

161 # unlike numpy which implements pairwise summation, which has O(log(n)) loss 

162 # crossref: https://github.com/pydata/bottleneck/issues/379 

163 

164 # GH 15507 

165 # bottleneck does not properly upcast during the sum 

166 # so can overflow 

167 

168 # GH 9422 

169 # further we also want to preserve NaN when all elements 

170 # are NaN, unlike bottleneck/numpy which consider this 

171 # to be 0 

172 return name not in ["nansum", "nanprod", "nanmean"] 

173 return False 

174 

175 

176def _has_infs(result) -> bool: 

177 if isinstance(result, np.ndarray): 

178 if result.dtype in ("f8", "f4"): 

179 # Note: outside of a nanops-specific test, we always have 

180 # result.ndim == 1, so there is no risk of this ravel making a copy. 

181 return lib.has_infs(result.ravel("K")) 

182 try: 

183 return np.isinf(result).any() 

184 except (TypeError, NotImplementedError): 

185 # if it doesn't support infs, then it can't have infs 

186 return False 

187 

188 

189def _get_fill_value( 

190 dtype: DtypeObj, fill_value: Scalar | None = None, fill_value_typ=None 

191): 

192 """return the correct fill value for the dtype of the values""" 

193 if fill_value is not None: 

194 return fill_value 

195 if _na_ok_dtype(dtype): 

196 if fill_value_typ is None: 

197 return np.nan 

198 elif fill_value_typ == "+inf": 

199 return np.inf 

200 else: 

201 return -np.inf 

202 elif fill_value_typ == "+inf": 

203 # need the max int here 

204 # Return as np.int64 so that np.where promotes the dtype 

205 # instead of raising OverflowError (numpy 2.5+) when the 

206 # value doesn't fit in the array's dtype (e.g. int8). 

207 return np.int64(lib.i8max) 

208 else: 

209 return np.int64(iNaT) 

210 

211 

212def _maybe_get_mask( 

213 values: np.ndarray, skipna: bool, mask: npt.NDArray[np.bool_] | None 

214) -> npt.NDArray[np.bool_] | None: 

215 """ 

216 Compute a mask if and only if necessary. 

217 

218 This function will compute a mask iff it is necessary. Otherwise, 

219 return the provided mask (potentially None) when a mask does not need to be 

220 computed. 

221 

222 A mask is never necessary if the values array is of boolean or integer 

223 dtypes, as these are incapable of storing NaNs. If passing a NaN-capable 

224 dtype that is interpretable as either boolean or integer data (eg, 

225 timedelta64), a mask must be provided. 

226 

227 If the skipna parameter is False, a new mask will not be computed. 

228 

229 The mask is computed using isna() by default. Setting invert=True selects 

230 notna() as the masking function. 

231 

232 Parameters 

233 ---------- 

234 values : ndarray 

235 input array to potentially compute mask for 

236 skipna : bool 

237 boolean for whether NaNs should be skipped 

238 mask : Optional[ndarray] 

239 nan-mask if known 

240 

241 Returns 

242 ------- 

243 Optional[np.ndarray[bool]] 

244 """ 

245 if mask is None: 

246 if values.dtype.kind in "biu": 

247 # Boolean data cannot contain nulls, so signal via mask being None 

248 return None 

249 

250 if skipna or values.dtype.kind in "mM": 

251 mask = isna(values) 

252 

253 return mask 

254 

255 

256def _get_values( 

257 values: np.ndarray, 

258 skipna: bool, 

259 fill_value: Any = None, 

260 fill_value_typ: str | None = None, 

261 mask: npt.NDArray[np.bool_] | None = None, 

262) -> tuple[np.ndarray, npt.NDArray[np.bool_] | None]: 

263 """ 

264 Utility to get the values view, mask, dtype, dtype_max, and fill_value. 

265 

266 If both mask and fill_value/fill_value_typ are not None and skipna is True, 

267 the values array will be copied. 

268 

269 For input arrays of boolean or integer dtypes, copies will only occur if a 

270 precomputed mask, a fill_value/fill_value_typ, and skipna=True are 

271 provided. 

272 

273 Parameters 

274 ---------- 

275 values : ndarray 

276 input array to potentially compute mask for 

277 skipna : bool 

278 boolean for whether NaNs should be skipped 

279 fill_value : Any 

280 value to fill NaNs with 

281 fill_value_typ : str 

282 Set to '+inf' or '-inf' to handle dtype-specific infinities 

283 mask : Optional[np.ndarray[bool]] 

284 nan-mask if known 

285 

286 Returns 

287 ------- 

288 values : ndarray 

289 Potential copy of input value array 

290 mask : Optional[ndarray[bool]] 

291 Mask for values, if deemed necessary to compute 

292 """ 

293 # In _get_values is only called from within nanops, and in all cases 

294 # with scalar fill_value. This guarantee is important for the 

295 # np.where call below 

296 

297 mask = _maybe_get_mask(values, skipna, mask) 

298 

299 dtype = values.dtype 

300 

301 datetimelike = False 

302 if values.dtype.kind in "mM": 

303 # changing timedelta64/datetime64 to int64 needs to happen after 

304 # finding `mask` above 

305 values = np.asarray(values.view("i8")) 

306 datetimelike = True 

307 

308 if skipna and (mask is not None): 

309 # get our fill value (in case we need to provide an alternative 

310 # dtype for it) 

311 fill_value = _get_fill_value( 

312 dtype, fill_value=fill_value, fill_value_typ=fill_value_typ 

313 ) 

314 

315 if fill_value is not None: 

316 if mask.any(): 

317 if datetimelike or _na_ok_dtype(dtype): 

318 values = values.copy() 

319 np.putmask(values, mask, fill_value) 

320 else: 

321 # np.where will promote if needed 

322 values = np.where(~mask, values, fill_value) 

323 

324 return values, mask 

325 

326 

327def _get_dtype_max(dtype: np.dtype) -> np.dtype: 

328 # return a platform independent precision dtype 

329 dtype_max = dtype 

330 if dtype.kind in "bi": 

331 dtype_max = np.dtype(np.int64) 

332 elif dtype.kind == "u": 

333 dtype_max = np.dtype(np.uint64) 

334 elif dtype.kind == "f": 

335 dtype_max = np.dtype(np.float64) 

336 return dtype_max 

337 

338 

339def _na_ok_dtype(dtype: DtypeObj) -> bool: 

340 if needs_i8_conversion(dtype): 

341 return False 

342 return not issubclass(dtype.type, np.integer) 

343 

344 

345def _wrap_results(result, dtype: np.dtype, fill_value=None): 

346 """wrap our results if needed""" 

347 if result is NaT: 

348 pass 

349 

350 elif dtype.kind == "M": 

351 if fill_value is None: 

352 # GH#24293 

353 fill_value = iNaT 

354 if not isinstance(result, np.ndarray): 

355 assert not isna(fill_value), "Expected non-null fill_value" 

356 if result == fill_value: 

357 result = np.nan 

358 

359 if isna(result): 

360 result = np.datetime64("NaT", "ns").astype(dtype) 

361 else: 

362 result = np.int64(result).view(dtype) 

363 # retain original unit 

364 result = result.astype(dtype, copy=False) 

365 else: 

366 # If we have float dtype, taking a view will give the wrong result 

367 result = result.astype(dtype) 

368 elif dtype.kind == "m": 

369 if not isinstance(result, np.ndarray): 

370 if result == fill_value or np.isnan(result): 

371 unit = np.datetime_data(dtype)[0] 

372 result = np.timedelta64("NaT", unit) # type: ignore[call-overload] 

373 

374 elif np.fabs(result) > lib.i8max: 

375 # raise if we have a timedelta64[ns] which is too large 

376 raise ValueError("overflow in timedelta operation") 

377 else: 

378 # return a timedelta64 with the original unit 

379 result = np.int64(result).astype(dtype, copy=False) 

380 

381 else: 

382 result = result.astype("m8[ns]").view(dtype) 

383 

384 return result 

385 

386 

387def _datetimelike_compat(func: F) -> F: 

388 """ 

389 If we have datetime64 or timedelta64 values, ensure we have a correct 

390 mask before calling the wrapped function, then cast back afterwards. 

391 """ 

392 

393 @functools.wraps(func) 

394 def new_func( 

395 values: np.ndarray, 

396 *, 

397 axis: AxisInt | None = None, 

398 skipna: bool = True, 

399 mask: npt.NDArray[np.bool_] | None = None, 

400 **kwargs, 

401 ): 

402 orig_values = values 

403 

404 datetimelike = values.dtype.kind in "mM" 

405 if datetimelike and mask is None: 

406 mask = isna(values) 

407 

408 result = func(values, axis=axis, skipna=skipna, mask=mask, **kwargs) 

409 

410 if datetimelike: 

411 result = _wrap_results(result, orig_values.dtype, fill_value=iNaT) 

412 if not skipna: 

413 assert mask is not None # checked above 

414 result = _mask_datetimelike_result(result, axis, mask, orig_values) 

415 

416 return result 

417 

418 return cast(F, new_func) 

419 

420 

421def _na_for_min_count(values: np.ndarray, axis: AxisInt | None) -> Scalar | np.ndarray: 

422 """ 

423 Return the missing value for `values`. 

424 

425 Parameters 

426 ---------- 

427 values : ndarray 

428 axis : int or None 

429 axis for the reduction, required if values.ndim > 1. 

430 

431 Returns 

432 ------- 

433 result : scalar or ndarray 

434 For 1-D values, returns a scalar of the correct missing type. 

435 For 2-D values, returns a 1-D array where each element is missing. 

436 """ 

437 # we either return np.nan or pd.NaT 

438 if values.dtype.kind in "iufcb": 

439 values = values.astype("float64") 

440 fill_value = na_value_for_dtype(values.dtype) 

441 

442 if values.ndim == 1: 

443 return fill_value 

444 elif axis is None: 

445 return fill_value 

446 else: 

447 result_shape = values.shape[:axis] + values.shape[axis + 1 :] 

448 

449 return np.full(result_shape, fill_value, dtype=values.dtype) 

450 

451 

452def maybe_operate_rowwise(func: F) -> F: 

453 """ 

454 NumPy operations on C-contiguous ndarrays with axis=1 can be 

455 very slow if axis 1 >> axis 0. 

456 Operate row-by-row and concatenate the results. 

457 """ 

458 

459 @functools.wraps(func) 

460 def newfunc(values: np.ndarray, *, axis: AxisInt | None = None, **kwargs): 

461 if ( 

462 axis == 1 

463 and values.ndim == 2 

464 and values.flags["C_CONTIGUOUS"] 

465 # only takes this path for wide arrays (long dataframes), for threshold see 

466 # https://github.com/pandas-dev/pandas/pull/43311#issuecomment-974891737 

467 and (values.shape[1] / 1000) > values.shape[0] 

468 and values.dtype not in (object, bool) 

469 ): 

470 arrs = list(values) 

471 if kwargs.get("mask") is not None: 

472 mask = kwargs.pop("mask") 

473 results = [ 

474 func(arrs[i], mask=mask[i], **kwargs) for i in range(len(arrs)) 

475 ] 

476 else: 

477 results = [func(x, **kwargs) for x in arrs] 

478 return np.array(results) 

479 

480 return func(values, axis=axis, **kwargs) 

481 

482 return cast(F, newfunc) 

483 

484 

485def nanany( 

486 values: np.ndarray, 

487 *, 

488 axis: AxisInt | None = None, 

489 skipna: bool = True, 

490 mask: npt.NDArray[np.bool_] | None = None, 

491) -> bool: 

492 """ 

493 Check if any elements along an axis evaluate to True. 

494 

495 Parameters 

496 ---------- 

497 values : ndarray 

498 axis : int, optional 

499 skipna : bool, default True 

500 mask : ndarray[bool], optional 

501 nan-mask if known 

502 

503 Returns 

504 ------- 

505 result : bool 

506 

507 Examples 

508 -------- 

509 >>> from pandas.core import nanops 

510 >>> s = pd.Series([1, 2]) 

511 >>> nanops.nanany(s.values) 

512 np.True_ 

513 

514 >>> from pandas.core import nanops 

515 >>> s = pd.Series([np.nan]) 

516 >>> nanops.nanany(s.values) 

517 np.False_ 

518 """ 

519 if values.dtype.kind in "iub" and mask is None: 

520 # GH#26032 fastpath 

521 # error: Incompatible return value type (got "Union[bool_, ndarray]", 

522 # expected "bool") 

523 return values.any(axis) # type: ignore[return-value] 

524 

525 if values.dtype.kind == "M": 

526 # GH#34479 

527 raise TypeError("datetime64 type does not support operation 'any'") 

528 

529 values, _ = _get_values(values, skipna, fill_value=False, mask=mask) 

530 

531 # For object type, any won't necessarily return 

532 # boolean values (numpy/numpy#4352) 

533 if values.dtype == object: 

534 values = values.astype(bool) 

535 

536 # error: Incompatible return value type (got "Union[bool_, ndarray]", expected 

537 # "bool") 

538 return values.any(axis) # type: ignore[return-value] 

539 

540 

541def nanall( 

542 values: np.ndarray, 

543 *, 

544 axis: AxisInt | None = None, 

545 skipna: bool = True, 

546 mask: npt.NDArray[np.bool_] | None = None, 

547) -> bool: 

548 """ 

549 Check if all elements along an axis evaluate to True. 

550 

551 Parameters 

552 ---------- 

553 values : ndarray 

554 axis : int, optional 

555 skipna : bool, default True 

556 mask : ndarray[bool], optional 

557 nan-mask if known 

558 

559 Returns 

560 ------- 

561 result : bool 

562 

563 Examples 

564 -------- 

565 >>> from pandas.core import nanops 

566 >>> s = pd.Series([1, 2, np.nan]) 

567 >>> nanops.nanall(s.values) 

568 np.True_ 

569 

570 >>> from pandas.core import nanops 

571 >>> s = pd.Series([1, 0]) 

572 >>> nanops.nanall(s.values) 

573 np.False_ 

574 """ 

575 if values.dtype.kind in "iub" and mask is None: 

576 # GH#26032 fastpath 

577 # error: Incompatible return value type (got "Union[bool_, ndarray]", 

578 # expected "bool") 

579 return values.all(axis) # type: ignore[return-value] 

580 

581 if values.dtype.kind == "M": 

582 # GH#34479 

583 raise TypeError("datetime64 type does not support operation 'all'") 

584 

585 values, _ = _get_values(values, skipna, fill_value=True, mask=mask) 

586 

587 # For object type, all won't necessarily return 

588 # boolean values (numpy/numpy#4352) 

589 if values.dtype == object: 

590 values = values.astype(bool) 

591 

592 # error: Incompatible return value type (got "Union[bool_, ndarray]", expected 

593 # "bool") 

594 return values.all(axis) # type: ignore[return-value] 

595 

596 

597@disallow("M8") 

598@_datetimelike_compat 

599@maybe_operate_rowwise 

600def nansum( 

601 values: np.ndarray, 

602 *, 

603 axis: AxisInt | None = None, 

604 skipna: bool = True, 

605 min_count: int = 0, 

606 mask: npt.NDArray[np.bool_] | None = None, 

607) -> npt.NDArray[np.floating] | float | NaTType: 

608 """ 

609 Sum the elements along an axis ignoring NaNs 

610 

611 Parameters 

612 ---------- 

613 values : ndarray[dtype] 

614 axis : int, optional 

615 skipna : bool, default True 

616 min_count: int, default 0 

617 mask : ndarray[bool], optional 

618 nan-mask if known 

619 

620 Returns 

621 ------- 

622 result : dtype 

623 

624 Examples 

625 -------- 

626 >>> from pandas.core import nanops 

627 >>> s = pd.Series([1, 2, np.nan]) 

628 >>> nanops.nansum(s.values) 

629 np.float64(3.0) 

630 """ 

631 dtype = values.dtype 

632 values, mask = _get_values(values, skipna, fill_value=0, mask=mask) 

633 dtype_sum = _get_dtype_max(dtype) 

634 if dtype.kind == "f": 

635 dtype_sum = dtype 

636 elif dtype.kind == "m": 

637 dtype_sum = np.dtype(np.float64) 

638 

639 the_sum = values.sum(axis, dtype=dtype_sum) 

640 the_sum = _maybe_null_out(the_sum, axis, mask, values.shape, min_count=min_count) 

641 

642 return the_sum 

643 

644 

645def _mask_datetimelike_result( 

646 result: np.ndarray | np.datetime64 | np.timedelta64, 

647 axis: AxisInt | None, 

648 mask: npt.NDArray[np.bool_], 

649 orig_values: np.ndarray, 

650) -> np.ndarray | np.datetime64 | np.timedelta64 | NaTType: 

651 if isinstance(result, np.ndarray): 

652 # we need to apply the mask 

653 result = result.astype("i8").view(orig_values.dtype) 

654 axis_mask = mask.any(axis=axis) 

655 result[axis_mask] = iNaT 

656 elif mask.any(): 

657 return np.int64(iNaT).view(orig_values.dtype) 

658 return result 

659 

660 

661@bottleneck_switch() 

662@_datetimelike_compat 

663def nanmean( 

664 values: np.ndarray, 

665 *, 

666 axis: AxisInt | None = None, 

667 skipna: bool = True, 

668 mask: npt.NDArray[np.bool_] | None = None, 

669) -> float: 

670 """ 

671 Compute the mean of the element along an axis ignoring NaNs 

672 

673 Parameters 

674 ---------- 

675 values : ndarray 

676 axis : int, optional 

677 skipna : bool, default True 

678 mask : ndarray[bool], optional 

679 nan-mask if known 

680 

681 Returns 

682 ------- 

683 float 

684 Unless input is a float array, in which case use the same 

685 precision as the input array. 

686 

687 Examples 

688 -------- 

689 >>> from pandas.core import nanops 

690 >>> s = pd.Series([1, 2, np.nan]) 

691 >>> nanops.nanmean(s.values) 

692 np.float64(1.5) 

693 """ 

694 if values.dtype == object and len(values) > 1_000 and mask is None: 

695 # GH#54754 if we are going to fail, try to fail-fast 

696 nanmean(values[:1000], axis=axis, skipna=skipna) 

697 

698 dtype = values.dtype 

699 values, mask = _get_values(values, skipna, fill_value=0, mask=mask) 

700 dtype_sum = _get_dtype_max(dtype) 

701 dtype_count = np.dtype(np.float64) 

702 

703 # not using needs_i8_conversion because that includes period 

704 if dtype.kind in "mM": 

705 dtype_sum = np.dtype(np.float64) 

706 elif dtype.kind in "iu": 

707 dtype_sum = np.dtype(np.float64) 

708 elif dtype.kind == "f": 

709 dtype_sum = dtype 

710 dtype_count = dtype 

711 

712 count = _get_counts(values.shape, mask, axis, dtype=dtype_count) 

713 the_sum = values.sum(axis, dtype=dtype_sum) 

714 the_sum = _ensure_numeric(the_sum) 

715 

716 if axis is not None and getattr(the_sum, "ndim", False): 

717 count = cast(np.ndarray, count) 

718 with np.errstate(all="ignore"): 

719 # suppress division by zero warnings 

720 the_mean = the_sum / count 

721 ct_mask = count == 0 

722 if ct_mask.any(): 

723 the_mean[ct_mask] = np.nan 

724 else: 

725 the_mean = the_sum / count if count > 0 else np.nan 

726 

727 return the_mean 

728 

729 

730@bottleneck_switch() 

731def nanmedian( 

732 values: np.ndarray, *, axis: AxisInt | None = None, skipna: bool = True, mask=None 

733) -> float | np.ndarray: 

734 """ 

735 Parameters 

736 ---------- 

737 values : ndarray 

738 axis : int, optional 

739 skipna : bool, default True 

740 mask : ndarray[bool], optional 

741 nan-mask if known 

742 

743 Returns 

744 ------- 

745 result : float | ndarray 

746 Unless input is a float array, in which case use the same 

747 precision as the input array. 

748 

749 Examples 

750 -------- 

751 >>> from pandas.core import nanops 

752 >>> s = pd.Series([1, np.nan, 2, 2]) 

753 >>> nanops.nanmedian(s.values) 

754 2.0 

755 

756 >>> s = pd.Series([np.nan, np.nan, np.nan]) 

757 >>> nanops.nanmedian(s.values) 

758 nan 

759 """ 

760 # for floats without mask, the data already uses NaN as missing value 

761 # indicator, and `mask` will be calculated from that below -> in those 

762 # cases we never need to set NaN to the masked values 

763 using_nan_sentinel = values.dtype.kind == "f" and mask is None 

764 

765 def get_median(x: np.ndarray, _mask=None): 

766 if _mask is None: 

767 _mask = notna(x) 

768 else: 

769 _mask = ~_mask 

770 if not skipna and not _mask.all(): 

771 return np.nan 

772 with warnings.catch_warnings(): 

773 # Suppress RuntimeWarning about All-NaN slice 

774 warnings.filterwarnings( 

775 "ignore", "All-NaN slice encountered", RuntimeWarning 

776 ) 

777 warnings.filterwarnings("ignore", "Mean of empty slice", RuntimeWarning) 

778 res = np.nanmedian(x[_mask]) 

779 return res 

780 

781 dtype = values.dtype 

782 values, mask = _get_values(values, skipna, mask=mask, fill_value=None) 

783 if values.dtype.kind != "f": 

784 if values.dtype == object: 

785 # GH#34671 avoid casting strings to numeric 

786 inferred = lib.infer_dtype(values) 

787 if inferred in ["string", "mixed"]: 

788 raise TypeError(f"Cannot convert {values} to numeric") 

789 try: 

790 values = values.astype("f8") 

791 except ValueError as err: 

792 # e.g. "could not convert string to float: 'a'" 

793 raise TypeError(str(err)) from err 

794 if not using_nan_sentinel and mask is not None: 

795 if not values.flags.writeable: 

796 values = values.copy() 

797 values[mask] = np.nan 

798 

799 notempty = values.size 

800 

801 res: float | np.ndarray 

802 

803 # an array from a frame 

804 if values.ndim > 1 and axis is not None: 

805 # there's a non-empty array to apply over otherwise numpy raises 

806 if notempty: 

807 if not skipna: 

808 res = np.apply_along_axis(get_median, axis, values) 

809 

810 else: 

811 # fastpath for the skipna case 

812 with warnings.catch_warnings(): 

813 # Suppress RuntimeWarning about All-NaN slice 

814 warnings.filterwarnings( 

815 "ignore", "All-NaN slice encountered", RuntimeWarning 

816 ) 

817 if (values.shape[1] == 1 and axis == 0) or ( 

818 values.shape[0] == 1 and axis == 1 

819 ): 

820 # GH52788: fastpath when squeezable, nanmedian for 2D array slow 

821 res = np.nanmedian(np.squeeze(values), keepdims=True) 

822 else: 

823 res = np.nanmedian(values, axis=axis) 

824 

825 else: 

826 # must return the correct shape, but median is not defined for the 

827 # empty set so return nans of shape "everything but the passed axis" 

828 # since "axis" is where the reduction would occur if we had a nonempty 

829 # array 

830 res = _get_empty_reduction_result(values.shape, axis) 

831 

832 else: 

833 # otherwise return a scalar value 

834 res = get_median(values, mask) if notempty else np.nan 

835 return _wrap_results(res, dtype) 

836 

837 

838def _get_empty_reduction_result( 

839 shape: Shape, 

840 axis: AxisInt, 

841) -> np.ndarray: 

842 """ 

843 The result from a reduction on an empty ndarray. 

844 

845 Parameters 

846 ---------- 

847 shape : Tuple[int, ...] 

848 axis : int 

849 

850 Returns 

851 ------- 

852 np.ndarray 

853 """ 

854 shp = np.array(shape) 

855 dims = np.arange(len(shape)) 

856 ret = np.empty(shp[dims != axis], dtype=np.float64) 

857 ret.fill(np.nan) 

858 return ret 

859 

860 

861def _get_counts_nanvar( 

862 values_shape: Shape, 

863 mask: npt.NDArray[np.bool_] | None, 

864 axis: AxisInt | None, 

865 ddof: int, 

866 dtype: np.dtype = np.dtype(np.float64), 

867) -> tuple[float | np.ndarray, float | np.ndarray]: 

868 """ 

869 Get the count of non-null values along an axis, accounting 

870 for degrees of freedom. 

871 

872 Parameters 

873 ---------- 

874 values_shape : Tuple[int, ...] 

875 shape tuple from values ndarray, used if mask is None 

876 mask : Optional[ndarray[bool]] 

877 locations in values that should be considered missing 

878 axis : Optional[int] 

879 axis to count along 

880 ddof : int 

881 degrees of freedom 

882 dtype : type, optional 

883 type to use for count 

884 

885 Returns 

886 ------- 

887 count : int, np.nan or np.ndarray 

888 d : int, np.nan or np.ndarray 

889 """ 

890 count = _get_counts(values_shape, mask, axis, dtype=dtype) 

891 d = count - dtype.type(ddof) 

892 

893 # always return NaN, never inf 

894 if is_float(count): 

895 if count <= ddof: 

896 # error: Incompatible types in assignment (expression has type 

897 # "float", variable has type "Union[floating[Any], ndarray[Any, 

898 # dtype[floating[Any]]]]") 

899 count = np.nan # type: ignore[assignment] 

900 d = np.nan 

901 else: 

902 # count is not narrowed by is_float check 

903 count = cast(np.ndarray, count) 

904 mask = count <= ddof 

905 if mask.any(): 

906 np.putmask(d, mask, np.nan) 

907 np.putmask(count, mask, np.nan) 

908 return count, d 

909 

910 

911@bottleneck_switch(ddof=1) 

912def nanstd( 

913 values, 

914 *, 

915 axis: AxisInt | None = None, 

916 skipna: bool = True, 

917 ddof: int = 1, 

918 mask=None, 

919): 

920 """ 

921 Compute the standard deviation along given axis while ignoring NaNs 

922 

923 Parameters 

924 ---------- 

925 values : ndarray 

926 axis : int, optional 

927 skipna : bool, default True 

928 ddof : int, default 1 

929 Delta Degrees of Freedom. The divisor used in calculations is N - ddof, 

930 where N represents the number of elements. 

931 mask : ndarray[bool], optional 

932 nan-mask if known 

933 

934 Returns 

935 ------- 

936 result : float 

937 Unless input is a float array, in which case use the same 

938 precision as the input array. 

939 

940 Examples 

941 -------- 

942 >>> from pandas.core import nanops 

943 >>> s = pd.Series([1, np.nan, 2, 3]) 

944 >>> nanops.nanstd(s.values) 

945 1.0 

946 """ 

947 if values.dtype.kind == "M": 

948 unit = np.datetime_data(values.dtype)[0] 

949 values = values.view(f"m8[{unit}]") 

950 

951 orig_dtype = values.dtype 

952 values, mask = _get_values(values, skipna, mask=mask) 

953 

954 result = np.sqrt(nanvar(values, axis=axis, skipna=skipna, ddof=ddof, mask=mask)) 

955 return _wrap_results(result, orig_dtype) 

956 

957 

958@disallow("M8", "m8") 

959@bottleneck_switch(ddof=1) 

960def nanvar( 

961 values: np.ndarray, 

962 *, 

963 axis: AxisInt | None = None, 

964 skipna: bool = True, 

965 ddof: int = 1, 

966 mask=None, 

967): 

968 """ 

969 Compute the variance along given axis while ignoring NaNs 

970 

971 Parameters 

972 ---------- 

973 values : ndarray 

974 axis : int, optional 

975 skipna : bool, default True 

976 ddof : int, default 1 

977 Delta Degrees of Freedom. The divisor used in calculations is N - ddof, 

978 where N represents the number of elements. 

979 mask : ndarray[bool], optional 

980 nan-mask if known 

981 

982 Returns 

983 ------- 

984 result : float 

985 Unless input is a float array, in which case use the same 

986 precision as the input array. 

987 

988 Examples 

989 -------- 

990 >>> from pandas.core import nanops 

991 >>> s = pd.Series([1, np.nan, 2, 3]) 

992 >>> nanops.nanvar(s.values) 

993 1.0 

994 """ 

995 dtype = values.dtype 

996 mask = _maybe_get_mask(values, skipna, mask) 

997 if dtype.kind in "iu": 

998 values = values.astype("f8") 

999 if mask is not None: 

1000 values[mask] = np.nan 

1001 elif dtype.kind == "c": 

1002 # https://en.wikipedia.org/wiki/Complex_random_variable#Variance_and_pseudo-variance 

1003 # The variance is equal to the sum of 

1004 # the variances of the real and imaginary part of the complex random variable. 

1005 return nanvar( 

1006 values.real, axis=axis, skipna=skipna, ddof=ddof, mask=mask 

1007 ) + nanvar(values.imag, axis=axis, skipna=skipna, ddof=ddof, mask=mask) 

1008 

1009 if values.dtype.kind == "f": 

1010 count, d = _get_counts_nanvar(values.shape, mask, axis, ddof, values.dtype) 

1011 else: 

1012 count, d = _get_counts_nanvar(values.shape, mask, axis, ddof) 

1013 

1014 if skipna and mask is not None: 

1015 values = values.copy() 

1016 np.putmask(values, mask, 0) 

1017 

1018 # xref GH10242 

1019 # Compute variance via two-pass algorithm, which is stable against 

1020 # cancellation errors and relatively accurate for small numbers of 

1021 # observations. 

1022 # 

1023 # See https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance 

1024 avg = _ensure_numeric(values.sum(axis=axis, dtype=np.float64)) / count 

1025 if axis is not None: 

1026 avg = np.expand_dims(avg, axis) 

1027 

1028 sqr = _ensure_numeric((avg - values) ** 2) 

1029 if mask is not None: 

1030 np.putmask(sqr, mask, 0) 

1031 result = sqr.sum(axis=axis, dtype=np.float64) / d 

1032 

1033 # Return variance as np.float64 (the datatype used in the accumulator), 

1034 # unless we were dealing with a float array, in which case use the same 

1035 # precision as the original values array. 

1036 if dtype.kind == "f": 

1037 result = result.astype(dtype, copy=False) 

1038 return result 

1039 

1040 

1041@disallow("M8", "m8") 

1042def nansem( 

1043 values: np.ndarray, 

1044 *, 

1045 axis: AxisInt | None = None, 

1046 skipna: bool = True, 

1047 ddof: int = 1, 

1048 mask: npt.NDArray[np.bool_] | None = None, 

1049) -> float: 

1050 """ 

1051 Compute the standard error in the mean along given axis while ignoring NaNs 

1052 

1053 Parameters 

1054 ---------- 

1055 values : ndarray 

1056 axis : int, optional 

1057 skipna : bool, default True 

1058 ddof : int, default 1 

1059 Delta Degrees of Freedom. The divisor used in calculations is N - ddof, 

1060 where N represents the number of elements. 

1061 mask : ndarray[bool], optional 

1062 nan-mask if known 

1063 

1064 Returns 

1065 ------- 

1066 result : float64 

1067 Unless input is a float array, in which case use the same 

1068 precision as the input array. 

1069 

1070 Examples 

1071 -------- 

1072 >>> from pandas.core import nanops 

1073 >>> s = pd.Series([1, np.nan, 2, 3]) 

1074 >>> nanops.nansem(s.values) 

1075 np.float64(0.5773502691896258) 

1076 """ 

1077 # This checks if non-numeric-like data is passed with numeric_only=False 

1078 # and raises a TypeError otherwise 

1079 nanvar(values, axis=axis, skipna=skipna, ddof=ddof, mask=mask) 

1080 

1081 mask = _maybe_get_mask(values, skipna, mask) 

1082 # Convert to bottleneck return a float 

1083 if values.dtype.kind not in "fc": 

1084 values = values.astype("f8") 

1085 

1086 if not skipna and mask is not None and mask.any(): 

1087 return np.nan 

1088 

1089 dtype_count = np.dtype(np.float64) 

1090 if values.dtype.kind == "f": 

1091 dtype_count = values.dtype 

1092 count, _ = _get_counts_nanvar(values.shape, mask, axis, ddof, dtype_count) 

1093 var = nanvar(values, axis=axis, skipna=skipna, ddof=ddof, mask=mask) 

1094 

1095 return np.sqrt(var) / np.sqrt(count) 

1096 

1097 

1098def _nanminmax(meth, fill_value_typ): 

1099 @bottleneck_switch(name=f"nan{meth}") 

1100 @_datetimelike_compat 

1101 def reduction( 

1102 values: np.ndarray, 

1103 *, 

1104 axis: AxisInt | None = None, 

1105 skipna: bool = True, 

1106 mask: npt.NDArray[np.bool_] | None = None, 

1107 ): 

1108 if values.size == 0: 

1109 return _na_for_min_count(values, axis) 

1110 

1111 dtype = values.dtype 

1112 values, mask = _get_values( 

1113 values, skipna, fill_value_typ=fill_value_typ, mask=mask 

1114 ) 

1115 result = getattr(values, meth)(axis) 

1116 result = _maybe_null_out( 

1117 result, axis, mask, values.shape, datetimelike=dtype.kind in "mM" 

1118 ) 

1119 return result 

1120 

1121 return reduction 

1122 

1123 

1124nanmin = _nanminmax("min", fill_value_typ="+inf") 

1125nanmax = _nanminmax("max", fill_value_typ="-inf") 

1126 

1127 

1128def nanargmax( 

1129 values: np.ndarray, 

1130 *, 

1131 axis: AxisInt | None = None, 

1132 skipna: bool = True, 

1133 mask: npt.NDArray[np.bool_] | None = None, 

1134) -> int | np.ndarray: 

1135 """ 

1136 Parameters 

1137 ---------- 

1138 values : ndarray 

1139 axis : int, optional 

1140 skipna : bool, default True 

1141 mask : ndarray[bool], optional 

1142 nan-mask if known 

1143 

1144 Returns 

1145 ------- 

1146 result : int or ndarray[int] 

1147 The index/indices of max value in specified axis or -1 in the NA case 

1148 

1149 Examples 

1150 -------- 

1151 >>> from pandas.core import nanops 

1152 >>> arr = np.array([1, 2, 3, np.nan, 4]) 

1153 >>> nanops.nanargmax(arr) 

1154 np.int64(4) 

1155 

1156 >>> arr = np.array(range(12), dtype=np.float64).reshape(4, 3) 

1157 >>> arr[2:, 2] = np.nan 

1158 >>> arr 

1159 array([[ 0., 1., 2.], 

1160 [ 3., 4., 5.], 

1161 [ 6., 7., nan], 

1162 [ 9., 10., nan]]) 

1163 >>> nanops.nanargmax(arr, axis=1) 

1164 array([2, 2, 1, 1]) 

1165 """ 

1166 values, mask = _get_values(values, True, fill_value_typ="-inf", mask=mask) 

1167 result = values.argmax(axis) 

1168 # error: Argument 1 to "_maybe_arg_null_out" has incompatible type "Any | 

1169 # signedinteger[Any]"; expected "ndarray[Any, Any]" 

1170 result = _maybe_arg_null_out(result, axis, mask, skipna) # type: ignore[arg-type] 

1171 return result 

1172 

1173 

1174def nanargmin( 

1175 values: np.ndarray, 

1176 *, 

1177 axis: AxisInt | None = None, 

1178 skipna: bool = True, 

1179 mask: npt.NDArray[np.bool_] | None = None, 

1180) -> int | np.ndarray: 

1181 """ 

1182 Parameters 

1183 ---------- 

1184 values : ndarray 

1185 axis : int, optional 

1186 skipna : bool, default True 

1187 mask : ndarray[bool], optional 

1188 nan-mask if known 

1189 

1190 Returns 

1191 ------- 

1192 result : int or ndarray[int] 

1193 The index/indices of min value in specified axis or -1 in the NA case 

1194 

1195 Examples 

1196 -------- 

1197 >>> from pandas.core import nanops 

1198 >>> arr = np.array([1, 2, 3, np.nan, 4]) 

1199 >>> nanops.nanargmin(arr) 

1200 np.int64(0) 

1201 

1202 >>> arr = np.array(range(12), dtype=np.float64).reshape(4, 3) 

1203 >>> arr[2:, 0] = np.nan 

1204 >>> arr 

1205 array([[ 0., 1., 2.], 

1206 [ 3., 4., 5.], 

1207 [nan, 7., 8.], 

1208 [nan, 10., 11.]]) 

1209 >>> nanops.nanargmin(arr, axis=1) 

1210 array([0, 0, 1, 1]) 

1211 """ 

1212 values, mask = _get_values(values, True, fill_value_typ="+inf", mask=mask) 

1213 result = values.argmin(axis) 

1214 # error: Argument 1 to "_maybe_arg_null_out" has incompatible type "Any | 

1215 # signedinteger[Any]"; expected "ndarray[Any, Any]" 

1216 result = _maybe_arg_null_out(result, axis, mask, skipna) # type: ignore[arg-type] 

1217 return result 

1218 

1219 

1220@disallow("M8", "m8") 

1221@maybe_operate_rowwise 

1222def nanskew( 

1223 values: np.ndarray, 

1224 *, 

1225 axis: AxisInt | None = None, 

1226 skipna: bool = True, 

1227 mask: npt.NDArray[np.bool_] | None = None, 

1228) -> float: 

1229 """ 

1230 Compute the sample skewness. 

1231 

1232 The statistic computed here is the adjusted Fisher-Pearson standardized 

1233 moment coefficient G1. The algorithm computes this coefficient directly 

1234 from the second and third central moment. 

1235 

1236 Parameters 

1237 ---------- 

1238 values : ndarray 

1239 axis : int, optional 

1240 skipna : bool, default True 

1241 mask : ndarray[bool], optional 

1242 nan-mask if known 

1243 

1244 Returns 

1245 ------- 

1246 result : float64 

1247 Unless input is a float array, in which case use the same 

1248 precision as the input array. 

1249 

1250 Examples 

1251 -------- 

1252 >>> from pandas.core import nanops 

1253 >>> s = pd.Series([1, np.nan, 1, 2]) 

1254 >>> nanops.nanskew(s.values) 

1255 np.float64(1.7320508075688787) 

1256 """ 

1257 mask = _maybe_get_mask(values, skipna, mask) 

1258 if values.dtype.kind != "f": 

1259 values = values.astype("f8") 

1260 count = _get_counts(values.shape, mask, axis) 

1261 else: 

1262 count = _get_counts(values.shape, mask, axis, dtype=values.dtype) 

1263 

1264 if skipna and mask is not None: 

1265 values = values.copy() 

1266 np.putmask(values, mask, 0) 

1267 elif not skipna and mask is not None and mask.any(): 

1268 return np.nan 

1269 

1270 with np.errstate(invalid="ignore", divide="ignore"): 

1271 mean = values.sum(axis, dtype=np.float64) / count 

1272 if axis is not None: 

1273 mean = np.expand_dims(mean, axis) 

1274 

1275 adjusted = values - mean 

1276 if skipna and mask is not None: 

1277 np.putmask(adjusted, mask, 0) 

1278 adjusted2 = adjusted**2 

1279 adjusted3 = adjusted2 * adjusted 

1280 m2 = adjusted2.sum(axis, dtype=np.float64) 

1281 m3 = adjusted3.sum(axis, dtype=np.float64) 

1282 

1283 # floating point error. See comment in [nankurt] 

1284 max_abs = np.abs(values).max(axis, initial=0.0) 

1285 eps = np.finfo(m2.dtype).eps 

1286 constant_tolerance2 = ((eps * max_abs) ** 2) * count 

1287 constant_tolerance3 = ((eps * max_abs) ** 3) * count 

1288 m2 = _zero_out_fperr(m2, constant_tolerance2) 

1289 m3 = _zero_out_fperr(m3, constant_tolerance3) 

1290 

1291 with np.errstate(invalid="ignore", divide="ignore"): 

1292 result = (count * (count - 1) ** 0.5 / (count - 2)) * (m3 / m2**1.5) 

1293 

1294 dtype = values.dtype 

1295 if dtype.kind == "f": 

1296 result = result.astype(dtype, copy=False) 

1297 

1298 if isinstance(result, np.ndarray): 

1299 result = np.where(m2 == 0, 0, result) 

1300 result[count < 3] = np.nan 

1301 else: 

1302 result = dtype.type(0) if m2 == 0 else result 

1303 if count < 3: 

1304 return np.nan 

1305 

1306 return result 

1307 

1308 

1309@disallow("M8", "m8") 

1310@maybe_operate_rowwise 

1311def nankurt( 

1312 values: np.ndarray, 

1313 *, 

1314 axis: AxisInt | None = None, 

1315 skipna: bool = True, 

1316 mask: npt.NDArray[np.bool_] | None = None, 

1317) -> float: 

1318 """ 

1319 Compute the sample excess kurtosis 

1320 

1321 The statistic computed here is the adjusted Fisher-Pearson standardized 

1322 moment coefficient G2, computed directly from the second and fourth 

1323 central moment. 

1324 

1325 Parameters 

1326 ---------- 

1327 values : ndarray 

1328 axis : int, optional 

1329 skipna : bool, default True 

1330 mask : ndarray[bool], optional 

1331 nan-mask if known 

1332 

1333 Returns 

1334 ------- 

1335 result : float64 

1336 Unless input is a float array, in which case use the same 

1337 precision as the input array. 

1338 

1339 Examples 

1340 -------- 

1341 >>> from pandas.core import nanops 

1342 >>> s = pd.Series([1, np.nan, 1, 3, 2]) 

1343 >>> nanops.nankurt(s.values) 

1344 np.float64(-1.2892561983471076) 

1345 """ 

1346 mask = _maybe_get_mask(values, skipna, mask) 

1347 if values.dtype.kind != "f": 

1348 values = values.astype("f8") 

1349 count = _get_counts(values.shape, mask, axis) 

1350 else: 

1351 count = _get_counts(values.shape, mask, axis, dtype=values.dtype) 

1352 

1353 if skipna and mask is not None: 

1354 values = values.copy() 

1355 np.putmask(values, mask, 0) 

1356 elif not skipna and mask is not None and mask.any(): 

1357 return np.nan 

1358 

1359 with np.errstate(invalid="ignore", divide="ignore"): 

1360 mean = values.sum(axis, dtype=np.float64) / count 

1361 if axis is not None: 

1362 mean = np.expand_dims(mean, axis) 

1363 

1364 adjusted = values - mean 

1365 if skipna and mask is not None: 

1366 np.putmask(adjusted, mask, 0) 

1367 adjusted2 = adjusted**2 

1368 adjusted4 = adjusted2**2 

1369 m2 = adjusted2.sum(axis, dtype=np.float64) 

1370 m4 = adjusted4.sum(axis, dtype=np.float64) 

1371 

1372 # Several floating point errors may occur during the summation due to rounding. 

1373 # This computation is similar to the one in Scipy 

1374 # https://github.com/scipy/scipy/blob/04d6d9c460b1fed83f2919ecec3d743cfa2e8317/scipy/stats/_stats_py.py#L1429 

1375 # With a few modifications, like using the maximum value instead of the averages 

1376 # and some adaptations because they use the average and we use the sum for `m2`. 

1377 # We need to estimate an upper bound to the error to consider the data constant. 

1378 # Let's call: 

1379 # x: true value in data 

1380 # y: floating point representation 

1381 # e: relative approximation error 

1382 # n: number of observations in array 

1383 # 

1384 # We have that: 

1385 # |x - y|/|x| <= e (See https://en.wikipedia.org/wiki/Machine_epsilon) 

1386 # (|x - y|/|x|)² <= e² 

1387 # Σ (|x - y|/|x|)² <= ne² 

1388 # 

1389 # Let's say that the fperr upper bound for m2 is constrained by the summation. 

1390 # |m2 - y|/|m2| <= ne² 

1391 # |m2 - y| <= n|m2|e² 

1392 # 

1393 # We will use max (x²) to estimate |m2| 

1394 max_abs = np.abs(values).max(axis, initial=0.0) 

1395 eps = np.finfo(m2.dtype).eps 

1396 constant_tolerance2 = ((eps * max_abs) ** 2) * count 

1397 constant_tolerance4 = ((eps * max_abs) ** 4) * count 

1398 m2 = _zero_out_fperr(m2, constant_tolerance2) 

1399 m4 = _zero_out_fperr(m4, constant_tolerance4) 

1400 

1401 with np.errstate(invalid="ignore", divide="ignore"): 

1402 adj = 3 * (count - 1) ** 2 / ((count - 2) * (count - 3)) 

1403 numerator = count * (count + 1) * (count - 1) * m4 

1404 denominator = (count - 2) * (count - 3) * m2**2 

1405 

1406 if not isinstance(denominator, np.ndarray): 

1407 # if ``denom`` is a scalar, check these corner cases first before 

1408 # doing division 

1409 if count < 4: 

1410 return np.nan 

1411 if denominator == 0: 

1412 return values.dtype.type(0) 

1413 

1414 with np.errstate(invalid="ignore", divide="ignore"): 

1415 result = numerator / denominator - adj 

1416 

1417 dtype = values.dtype 

1418 if dtype.kind == "f": 

1419 result = result.astype(dtype, copy=False) 

1420 

1421 if isinstance(result, np.ndarray): 

1422 result = np.where(denominator == 0, 0, result) 

1423 result[count < 4] = np.nan 

1424 

1425 return result 

1426 

1427 

1428@disallow("M8", "m8") 

1429@maybe_operate_rowwise 

1430def nanprod( 

1431 values: np.ndarray, 

1432 *, 

1433 axis: AxisInt | None = None, 

1434 skipna: bool = True, 

1435 min_count: int = 0, 

1436 mask: npt.NDArray[np.bool_] | None = None, 

1437) -> float: 

1438 """ 

1439 Parameters 

1440 ---------- 

1441 values : ndarray[dtype] 

1442 axis : int, optional 

1443 skipna : bool, default True 

1444 min_count: int, default 0 

1445 mask : ndarray[bool], optional 

1446 nan-mask if known 

1447 

1448 Returns 

1449 ------- 

1450 Dtype 

1451 The product of all elements on a given axis. ( NaNs are treated as 1) 

1452 

1453 Examples 

1454 -------- 

1455 >>> from pandas.core import nanops 

1456 >>> s = pd.Series([1, 2, 3, np.nan]) 

1457 >>> nanops.nanprod(s.values) 

1458 np.float64(6.0) 

1459 """ 

1460 mask = _maybe_get_mask(values, skipna, mask) 

1461 

1462 if skipna and mask is not None: 

1463 values = values.copy() 

1464 values[mask] = 1 

1465 result = values.prod(axis) 

1466 # error: Incompatible return value type (got "Union[ndarray, float]", expected 

1467 # "float") 

1468 return _maybe_null_out( # type: ignore[return-value] 

1469 result, axis, mask, values.shape, min_count=min_count 

1470 ) 

1471 

1472 

1473def _maybe_arg_null_out( 

1474 result: np.ndarray, 

1475 axis: AxisInt | None, 

1476 mask: npt.NDArray[np.bool_] | None, 

1477 skipna: bool, 

1478) -> np.ndarray | int: 

1479 # helper function for nanargmin/nanargmax 

1480 if mask is None: 

1481 return result 

1482 

1483 if axis is None or not getattr(result, "ndim", False): 

1484 if skipna and mask.all(): 

1485 raise ValueError("Encountered all NA values") 

1486 elif not skipna and mask.any(): 

1487 raise ValueError("Encountered an NA value with skipna=False") 

1488 elif skipna and mask.all(axis).any(): 

1489 raise ValueError("Encountered all NA values") 

1490 elif not skipna and mask.any(axis).any(): 

1491 raise ValueError("Encountered an NA value with skipna=False") 

1492 return result 

1493 

1494 

1495def _get_counts( 

1496 values_shape: Shape, 

1497 mask: npt.NDArray[np.bool_] | None, 

1498 axis: AxisInt | None, 

1499 dtype: np.dtype[np.floating] = np.dtype(np.float64), 

1500) -> np.floating | npt.NDArray[np.floating]: 

1501 """ 

1502 Get the count of non-null values along an axis 

1503 

1504 Parameters 

1505 ---------- 

1506 values_shape : tuple of int 

1507 shape tuple from values ndarray, used if mask is None 

1508 mask : Optional[ndarray[bool]] 

1509 locations in values that should be considered missing 

1510 axis : Optional[int] 

1511 axis to count along 

1512 dtype : type, optional 

1513 type to use for count 

1514 

1515 Returns 

1516 ------- 

1517 count : scalar or array 

1518 """ 

1519 if axis is None: 

1520 if mask is not None: 

1521 n = mask.size - mask.sum() 

1522 else: 

1523 n = np.prod(values_shape) 

1524 return dtype.type(n) 

1525 

1526 if mask is not None: 

1527 count = mask.shape[axis] - mask.sum(axis) 

1528 else: 

1529 count = values_shape[axis] 

1530 

1531 if is_integer(count): 

1532 return dtype.type(count) 

1533 return count.astype(dtype, copy=False) 

1534 

1535 

1536def _maybe_null_out( 

1537 result: np.ndarray | float | NaTType, 

1538 axis: AxisInt | None, 

1539 mask: npt.NDArray[np.bool_] | None, 

1540 shape: tuple[int, ...], 

1541 min_count: int = 1, 

1542 datetimelike: bool = False, 

1543) -> np.ndarray | float | NaTType: 

1544 """ 

1545 Returns 

1546 ------- 

1547 Dtype 

1548 The product of all elements on a given axis. ( NaNs are treated as 1) 

1549 """ 

1550 if mask is None and min_count == 0: 

1551 # nothing to check; short-circuit 

1552 return result 

1553 

1554 if axis is not None and isinstance(result, np.ndarray): 

1555 if mask is not None: 

1556 null_mask = (mask.shape[axis] - mask.sum(axis) - min_count) < 0 

1557 else: 

1558 # we have no nulls, kept mask=None in _maybe_get_mask 

1559 below_count = shape[axis] - min_count < 0 

1560 new_shape = shape[:axis] + shape[axis + 1 :] 

1561 null_mask = np.broadcast_to(below_count, new_shape) 

1562 

1563 if np.any(null_mask): 

1564 if datetimelike: 

1565 # GH#60646 For datetimelike, no need to cast to float 

1566 result[null_mask] = iNaT 

1567 elif is_numeric_dtype(result): 

1568 if np.iscomplexobj(result): 

1569 result = result.astype("c16") 

1570 elif not is_float_dtype(result): 

1571 result = result.astype("f8", copy=False) 

1572 result[null_mask] = np.nan 

1573 else: 

1574 # GH12941, use None to auto cast null 

1575 result[null_mask] = None 

1576 elif result is not NaT: 

1577 if check_below_min_count(shape, mask, min_count): 

1578 result_dtype = getattr(result, "dtype", None) 

1579 if is_float_dtype(result_dtype): 

1580 # error: Item "None" of "Optional[Any]" has no attribute "type" 

1581 result = result_dtype.type("nan") # type: ignore[union-attr] 

1582 else: 

1583 result = np.nan 

1584 

1585 return result 

1586 

1587 

1588def check_below_min_count( 

1589 shape: tuple[int, ...], mask: npt.NDArray[np.bool_] | None, min_count: int 

1590) -> bool: 

1591 """ 

1592 Check for the `min_count` keyword. Returns True if below `min_count` (when 

1593 missing value should be returned from the reduction). 

1594 

1595 Parameters 

1596 ---------- 

1597 shape : tuple 

1598 The shape of the values (`values.shape`). 

1599 mask : ndarray[bool] or None 

1600 Boolean numpy array (typically of same shape as `shape`) or None. 

1601 min_count : int 

1602 Keyword passed through from sum/prod call. 

1603 

1604 Returns 

1605 ------- 

1606 bool 

1607 """ 

1608 if min_count > 0: 

1609 if mask is None: 

1610 # no missing values, only check size 

1611 non_nulls = np.prod(shape) 

1612 else: 

1613 non_nulls = mask.size - mask.sum() 

1614 if non_nulls < min_count: 

1615 return True 

1616 return False 

1617 

1618 

1619def _zero_out_fperr(arg, tol: float | np.ndarray): 

1620 # #18044 reference this behavior to fix rolling skew/kurt issue 

1621 if isinstance(arg, np.ndarray): 

1622 return np.where(np.abs(arg) < tol, 0, arg) 

1623 else: 

1624 return arg.dtype.type(0) if np.abs(arg) < tol else arg 

1625 

1626 

1627@disallow("M8", "m8") 

1628def nancorr( 

1629 a: np.ndarray, 

1630 b: np.ndarray, 

1631 *, 

1632 method: CorrelationMethod = "pearson", 

1633 min_periods: int | None = None, 

1634) -> float: 

1635 """ 

1636 a, b: ndarrays 

1637 """ 

1638 if len(a) != len(b): 

1639 raise AssertionError("Operands to nancorr must have same size") 

1640 

1641 if min_periods is None: 

1642 min_periods = 1 

1643 

1644 valid = notna(a) & notna(b) 

1645 if not valid.all(): 

1646 a = a[valid] 

1647 b = b[valid] 

1648 

1649 if len(a) < min_periods: 

1650 return np.nan 

1651 

1652 a = _ensure_numeric(a) 

1653 b = _ensure_numeric(b) 

1654 

1655 f = get_corr_func(method) 

1656 return f(a, b) 

1657 

1658 

1659def get_corr_func( 

1660 method: CorrelationMethod, 

1661) -> Callable[[np.ndarray, np.ndarray], float]: 

1662 if method == "kendall": 

1663 from scipy.stats import kendalltau 

1664 

1665 def func(a, b): 

1666 return kendalltau(a, b)[0] 

1667 

1668 return func 

1669 elif method == "spearman": 

1670 from scipy.stats import spearmanr 

1671 

1672 def func(a, b): 

1673 return spearmanr(a, b)[0] 

1674 

1675 return func 

1676 elif method == "pearson": 

1677 

1678 def func(a, b): 

1679 return np.corrcoef(a, b)[0, 1] 

1680 

1681 return func 

1682 elif callable(method): 

1683 return method 

1684 

1685 raise ValueError( 

1686 f"Unknown method '{method}', expected one of " 

1687 "'kendall', 'spearman', 'pearson', or callable" 

1688 ) 

1689 

1690 

1691@disallow("M8", "m8") 

1692def nancov( 

1693 a: np.ndarray, 

1694 b: np.ndarray, 

1695 *, 

1696 min_periods: int | None = None, 

1697 ddof: int | None = 1, 

1698) -> float: 

1699 if len(a) != len(b): 

1700 raise AssertionError("Operands to nancov must have same size") 

1701 

1702 if min_periods is None: 

1703 min_periods = 1 

1704 

1705 valid = notna(a) & notna(b) 

1706 if not valid.all(): 

1707 a = a[valid] 

1708 b = b[valid] 

1709 

1710 if len(a) < min_periods: 

1711 return np.nan 

1712 

1713 a = _ensure_numeric(a) 

1714 b = _ensure_numeric(b) 

1715 

1716 return np.cov(a, b, ddof=ddof)[0, 1] 

1717 

1718 

1719def _ensure_numeric(x): 

1720 if isinstance(x, np.ndarray): 

1721 if x.dtype.kind in "biu": 

1722 x = x.astype(np.float64) 

1723 elif x.dtype == object: 

1724 inferred = lib.infer_dtype(x) 

1725 if inferred in ["string", "mixed"]: 

1726 # GH#44008, GH#36703 avoid casting e.g. strings to numeric 

1727 raise TypeError(f"Could not convert {x} to numeric") 

1728 try: 

1729 x = x.astype(np.complex128) 

1730 except (TypeError, ValueError): 

1731 try: 

1732 x = x.astype(np.float64) 

1733 except ValueError as err: 

1734 # GH#29941 we get here with object arrays containing strs 

1735 raise TypeError(f"Could not convert {x} to numeric") from err 

1736 else: 

1737 if not np.any(np.imag(x)): 

1738 x = x.real 

1739 elif not (is_float(x) or is_integer(x) or is_complex(x)): 

1740 if isinstance(x, str): 

1741 # GH#44008, GH#36703 avoid casting e.g. strings to numeric 

1742 raise TypeError(f"Could not convert string '{x}' to numeric") 

1743 try: 

1744 x = float(x) 

1745 except (TypeError, ValueError): 

1746 # e.g. "1+1j" or "foo" 

1747 try: 

1748 x = complex(x) 

1749 except ValueError as err: 

1750 # e.g. "foo" 

1751 raise TypeError(f"Could not convert {x} to numeric") from err 

1752 return x 

1753 

1754 

1755def na_accum_func(values: ArrayLike, accum_func, *, skipna: bool) -> ArrayLike: 

1756 """ 

1757 Cumulative function with skipna support. 

1758 

1759 Parameters 

1760 ---------- 

1761 values : np.ndarray or ExtensionArray 

1762 accum_func : {np.cumprod, np.maximum.accumulate, np.cumsum, np.minimum.accumulate} 

1763 skipna : bool 

1764 

1765 Returns 

1766 ------- 

1767 np.ndarray or ExtensionArray 

1768 """ 

1769 mask_a, mask_b = { 

1770 np.cumprod: (1.0, np.nan), 

1771 np.maximum.accumulate: (-np.inf, np.nan), 

1772 np.cumsum: (0.0, np.nan), 

1773 np.minimum.accumulate: (np.inf, np.nan), 

1774 }[accum_func] 

1775 

1776 # This should go through ea interface 

1777 assert values.dtype.kind not in "mM" 

1778 

1779 # We will be applying this function to block values 

1780 if skipna and not issubclass(values.dtype.type, (np.integer, np.bool_)): 

1781 vals = values.copy() 

1782 mask = isna(vals) 

1783 vals[mask] = mask_a 

1784 result = accum_func(vals, axis=0) 

1785 result[mask] = mask_b 

1786 else: 

1787 result = accum_func(values, axis=0) 

1788 

1789 return result