|
55 | 55 | from .indexes import Indexes, default_indexes, propagate_indexes
|
56 | 56 | from .indexing import is_fancy_indexer
|
57 | 57 | from .merge import PANDAS_TYPES, _extract_indexes_from_coords
|
| 58 | +from .nanops import dask_array |
58 | 59 | from .options import OPTIONS
|
| 60 | +from .pycompat import dask_array_type |
59 | 61 | from .utils import Default, ReprObject, _check_inplace, _default, either_dict_or_kwargs
|
60 | 62 | from .variable import (
|
61 | 63 | IndexVariable,
|
@@ -3430,6 +3432,225 @@ def pad(
|
3430 | 3432 | )
|
3431 | 3433 | return self._from_temp_dataset(ds)
|
3432 | 3434 |
|
| 3435 | + def _calc_idxminmax( |
| 3436 | + self, |
| 3437 | + *, |
| 3438 | + func: str, |
| 3439 | + dim: Optional[Hashable], |
| 3440 | + axis: Optional[int], |
| 3441 | + skipna: Optional[bool], |
| 3442 | + promote: Optional[bool], |
| 3443 | + keep_attrs: Optional[bool], |
| 3444 | + **kwargs: Any, |
| 3445 | + ) -> "DataArray": |
| 3446 | + """Apply common operations for idxmin and idxmax.""" |
| 3447 | + # This function doesn't make sense for scalars so don't try |
| 3448 | + if not self.ndim: |
| 3449 | + ValueError("This function does not apply for scalars") |
| 3450 | + |
| 3451 | + if dim is not None: |
| 3452 | + pass # Use the dim if available |
| 3453 | + elif axis is not None: |
| 3454 | + dim = self.dims[axis] |
| 3455 | + elif self.ndim == 1: |
| 3456 | + # it is okay to guess the dim if there is only 1 |
| 3457 | + dim = self.dims[0] |
| 3458 | + else: |
| 3459 | + # The dim is not specified and ambiguous. Don't guess. |
| 3460 | + raise ValueError( |
| 3461 | + "Must supply either 'dim' or 'axis' argument " |
| 3462 | + "for multidimensional arrays" |
| 3463 | + ) |
| 3464 | + |
| 3465 | + if dim in self.coords: |
| 3466 | + pass # This is okay |
| 3467 | + elif axis is not None: |
| 3468 | + raise IndexError(f'Axis "{axis}" does not have coordinates') |
| 3469 | + else: |
| 3470 | + raise KeyError(f'Dimension "{dim}" does not have coordinates') |
| 3471 | + |
| 3472 | + # These are dtypes with NaN values argmin and argmax can handle |
| 3473 | + na_dtypes = "cf0" |
| 3474 | + |
| 3475 | + if skipna or (skipna is None and self.dtype.kind in na_dtypes): |
| 3476 | + # Need to skip NaN values since argmin and argmax can't handle them |
| 3477 | + allna = self.isnull().all(dim) |
| 3478 | + array = self.where(~allna, 0) |
| 3479 | + hasna = allna.any() |
| 3480 | + else: |
| 3481 | + array = self |
| 3482 | + allna = None |
| 3483 | + hasna = False |
| 3484 | + |
| 3485 | + # If promote is None we only promote if there are NaN values. |
| 3486 | + if promote is None: |
| 3487 | + promote = hasna |
| 3488 | + |
| 3489 | + if not promote and hasna and array.coords[dim].dtype.kind not in na_dtypes: |
| 3490 | + raise TypeError( |
| 3491 | + "NaN values present for NaN-incompatible dtype and Promote=False" |
| 3492 | + ) |
| 3493 | + |
| 3494 | + # This will run argmin or argmax. |
| 3495 | + indx = getattr(array, func)( |
| 3496 | + dim=dim, axis=None, keep_attrs=False, skipna=skipna, **kwargs |
| 3497 | + ) |
| 3498 | + |
| 3499 | + # Get the coordinate we want. |
| 3500 | + coordarray = array[dim] |
| 3501 | + |
| 3502 | + # Handle dask arrays. |
| 3503 | + if isinstance(array, dask_array_type): |
| 3504 | + res = dask_array.map_blocks(coordarray, indx, dtype=indx.dtype) |
| 3505 | + else: |
| 3506 | + res = coordarray[ |
| 3507 | + indx, |
| 3508 | + ] |
| 3509 | + |
| 3510 | + # Promote to a dtype that can handle NaN values if needed. |
| 3511 | + newdtype, fill_value = dtypes.maybe_promote(res.dtype) |
| 3512 | + if promote and newdtype != res.dtype: |
| 3513 | + res = res.astype(newdtype) |
| 3514 | + |
| 3515 | + # Put the NaN values back in after removing them, if necessary. |
| 3516 | + if hasna and allna is not None: |
| 3517 | + res = res.where(~allna, fill_value) |
| 3518 | + |
| 3519 | + # The dim is gone but we need to remove the corresponding coordinate. |
| 3520 | + del res.coords[dim] |
| 3521 | + |
| 3522 | + # Put the attrs back in if needed |
| 3523 | + if keep_attrs: |
| 3524 | + res.attrs = self.attrs |
| 3525 | + |
| 3526 | + return res |
| 3527 | + |
| 3528 | + def idxmin( |
| 3529 | + self, |
| 3530 | + dim: Optional[Hashable] = None, |
| 3531 | + axis: Optional[int] = None, |
| 3532 | + skipna: Optional[bool] = None, |
| 3533 | + promote: Optional[bool] = None, |
| 3534 | + keep_attrs: Optional[bool] = False, |
| 3535 | + **kwargs: Any, |
| 3536 | + ) -> "DataArray": |
| 3537 | + """Return the coordinate of the minimum value along a dimension. |
| 3538 | +
|
| 3539 | + Returns a new DataArray named after the dimension with the values of |
| 3540 | + the coordinate along that dimension corresponding to minimum value |
| 3541 | + along that dimension. |
| 3542 | +
|
| 3543 | + In comparison to `argmin`, this returns the coordinate while `argmin` |
| 3544 | + returns the index. |
| 3545 | +
|
| 3546 | + Parameters |
| 3547 | + ---------- |
| 3548 | + dim : str (optional) |
| 3549 | + Dimension over which to apply `idxmin`. |
| 3550 | + axis : int (optional) |
| 3551 | + Axis(es) over which to repeatedly apply `idxmin`. Exactly one of |
| 3552 | + the 'dim' and 'axis' arguments must be supplied. |
| 3553 | + skipna : bool, optional |
| 3554 | + If True, skip missing values (as marked by NaN). By default, only |
| 3555 | + skips missing values for float dtypes; other dtypes either do not |
| 3556 | + have a sentinel missing value (int) or skipna=True has not been |
| 3557 | + implemented (object, datetime64 or timedelta64).{min_count_docs} |
| 3558 | + promote : bool (optional) |
| 3559 | + If True (default) dtypes that do not support NaN values will be |
| 3560 | + automatically promoted to those that do. If False a NaN in the |
| 3561 | + results will raise a TypeError. If None the result will only be |
| 3562 | + promoted if a NaN is actually present. |
| 3563 | + keep_attrs : bool, optional |
| 3564 | + If True, the attributes (`attrs`) will be copied from the original |
| 3565 | + object to the new one. If False (default), the new object will be |
| 3566 | + returned without attributes. |
| 3567 | + **kwargs : dict |
| 3568 | + Additional keyword arguments passed on to the appropriate array |
| 3569 | + function for calculating `{name}` on this object's data. |
| 3570 | +
|
| 3571 | + Returns |
| 3572 | + ------- |
| 3573 | + reduced : DataArray |
| 3574 | + New DataArray object with `idxmin` applied to its data and the |
| 3575 | + indicated dimension removed. |
| 3576 | +
|
| 3577 | + See also |
| 3578 | + -------- |
| 3579 | + Dataset.idxmin, DataArray.idxmax, DataArray.min, DataArray.argmin |
| 3580 | + """ |
| 3581 | + return self._calc_idxminmax( |
| 3582 | + func="argmin", |
| 3583 | + dim=dim, |
| 3584 | + axis=axis, |
| 3585 | + skipna=skipna, |
| 3586 | + promote=promote, |
| 3587 | + keep_attrs=keep_attrs, |
| 3588 | + **kwargs, |
| 3589 | + ) |
| 3590 | + |
| 3591 | + def idxmax( |
| 3592 | + self, |
| 3593 | + dim: Optional[Hashable] = None, |
| 3594 | + axis: Optional[int] = None, |
| 3595 | + skipna: Optional[bool] = None, |
| 3596 | + promote: Optional[bool] = None, |
| 3597 | + keep_attrs: Optional[bool] = False, |
| 3598 | + **kwargs: Any, |
| 3599 | + ) -> "DataArray": |
| 3600 | + """Return the coordinate of the maximum value along a dimension. |
| 3601 | +
|
| 3602 | + Returns a new DataArray named after the dimension with the values of |
| 3603 | + the coordinate along that dimension corresponding to maximum value |
| 3604 | + along that dimension. |
| 3605 | +
|
| 3606 | + In comparison to `argmax`, this returns the coordinate while `argmax` |
| 3607 | + returns the index. |
| 3608 | +
|
| 3609 | + Parameters |
| 3610 | + ---------- |
| 3611 | + dim : str (optional) |
| 3612 | + Dimension over which to apply `idxmax`. |
| 3613 | + axis : int (optional) |
| 3614 | + Axis(es) over which to repeatedly apply `idxmax`. Exactly one of |
| 3615 | + the 'dim' and 'axis' arguments must be supplied. |
| 3616 | + skipna : bool (optional) |
| 3617 | + If True, skip missing values (as marked by NaN). By default, only |
| 3618 | + skips missing values for float dtypes; other dtypes either do not |
| 3619 | + have a sentinel missing value (int) or skipna=True has not been |
| 3620 | + implemented (object, datetime64 or timedelta64).{min_count_docs} |
| 3621 | + promote : bool (optional) |
| 3622 | + If True (default) dtypes that do not support NaN values will be |
| 3623 | + automatically promoted to those that do. If False a NaN in the |
| 3624 | + results will raise a TypeError. If None the result will only be |
| 3625 | + promoted if a NaN is actually present. |
| 3626 | + keep_attrs : bool (optional) |
| 3627 | + If True, the attributes (`attrs`) will be copied from the original |
| 3628 | + object to the new one. If False (default), the new object will be |
| 3629 | + returned without attributes. |
| 3630 | + **kwargs : dict |
| 3631 | + Additional keyword arguments passed on to the appropriate array |
| 3632 | + function for calculating `{name}` on this object's data. |
| 3633 | +
|
| 3634 | + Returns |
| 3635 | + ------- |
| 3636 | + reduced : DataArray |
| 3637 | + New DataArray object with `idxmax` applied to its data and the |
| 3638 | + indicated dimension removed. |
| 3639 | +
|
| 3640 | + See also |
| 3641 | + -------- |
| 3642 | + Dataset.idxmax, DataArray.idxmin, DataArray.max, DataArray.argmax |
| 3643 | + """ |
| 3644 | + return self._calc_idxminmax( |
| 3645 | + func="argmax", |
| 3646 | + dim=dim, |
| 3647 | + axis=axis, |
| 3648 | + skipna=skipna, |
| 3649 | + promote=promote, |
| 3650 | + keep_attrs=keep_attrs, |
| 3651 | + **kwargs, |
| 3652 | + ) |
| 3653 | + |
3433 | 3654 | # this needs to be at the end, or mypy will confuse with `str`
|
3434 | 3655 | # https://mypy.readthedocs.io/en/latest/common_issues.html#dealing-with-conflicting-names
|
3435 | 3656 | str = property(StringAccessor)
|
|
0 commit comments