From 6cbfd75577e0128352753fc57eafe011efb4cbf8 Mon Sep 17 00:00:00 2001 From: Brock Date: Sun, 20 Jun 2021 16:01:21 -0700 Subject: [PATCH 1/5] REF: share Datetimelike argmin/min etc with Index --- pandas/core/indexes/base.py | 79 +++++++++++++++++++ pandas/core/indexes/datetimelike.py | 115 ---------------------------- 2 files changed, 79 insertions(+), 115 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 88fded29810ff..22e27a12500b3 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -6052,6 +6052,9 @@ def __inv__(self): # TODO: __inv__ vs __invert__? return self._unary_method(lambda x: -x) + # -------------------------------------------------------------------- + # Reductions + def any(self, *args, **kwargs): """ Return whether any element is Truthy. @@ -6172,6 +6175,82 @@ def _maybe_disable_logical_methods(self, opname: str_t): # This call will raise make_invalid_op(opname)(self) + @Appender(IndexOpsMixin.argmin.__doc__) + def argmin(self, axis=None, skipna=True, *args, **kwargs): + nv.validate_argmin(args, kwargs) + nv.validate_minmax_axis(axis) + + if self.hasnans: + # Take advantage of cache + mask = self._isnan + if not skipna or mask.all(): + return -1 + return super().argmin(skipna=skipna) + + @Appender(IndexOpsMixin.argmax.__doc__) + def argmax(self, axis=None, skipna=True, *args, **kwargs): + nv.validate_argmax(args, kwargs) + nv.validate_minmax_axis(axis) + + if self.hasnans: + # Take advantage of cache + mask = self._isnan + if not skipna or mask.all(): + return -1 + return super().argmax(skipna=skipna) + + @doc(IndexOpsMixin.min) + def min(self, axis=None, skipna=True, *args, **kwargs): + nv.validate_min(args, kwargs) + nv.validate_minmax_axis(axis) + + if not len(self): + return self._na_value + + data = self._data + + if len(self) and self.is_monotonic_increasing: + # quick check + first = self[0] + if not isna(first): + return first + + if self.hasnans: + # Take advantage of cache + mask = self._isnan + if not skipna or mask.all(): + return self._na_value + data = data[~mask] + + return data.min() # alt: super().min(skipna=skipna) + + @doc(IndexOpsMixin.max) + def max(self, axis=None, skipna=True, *args, **kwargs): + nv.validate_max(args, kwargs) + nv.validate_minmax_axis(axis) + + if not len(self): + return self._na_value + + data = self._data + + if len(self) and self.is_monotonic_increasing: + # quick check + last = self[-1] + if not isna(last): + return last + + if self.hasnans: + # Take advantage of cache + mask = self._isnan + if not skipna or mask.all(): + return self._na_value + data = data[~mask] + + return data.max() # alt: super().max(skipna=skipna) + + # -------------------------------------------------------------------- + @final @property def shape(self) -> Shape: diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 53ab2e45edede..b52c30c1caa2d 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -17,7 +17,6 @@ from pandas._libs import ( NaT, Timedelta, - iNaT, lib, ) from pandas._libs.tslibs import ( @@ -198,120 +197,6 @@ def tolist(self) -> list: """ return list(self.astype(object)) - def min(self, axis=None, skipna=True, *args, **kwargs): - """ - Return the minimum value of the Index or minimum along - an axis. - - See Also - -------- - numpy.ndarray.min - Series.min : Return the minimum value in a Series. - """ - nv.validate_min(args, kwargs) - nv.validate_minmax_axis(axis) - - if not len(self): - return self._na_value - - i8 = self.asi8 - - if len(i8) and self.is_monotonic_increasing: - # quick check - if i8[0] != iNaT: - return self._data._box_func(i8[0]) - - if self.hasnans: - if not skipna: - return self._na_value - i8 = i8[~self._isnan] - - if not len(i8): - return self._na_value - - min_stamp = i8.min() - return self._data._box_func(min_stamp) - - def argmin(self, axis=None, skipna=True, *args, **kwargs): - """ - Returns the indices of the minimum values along an axis. - - See `numpy.ndarray.argmin` for more information on the - `axis` parameter. - - See Also - -------- - numpy.ndarray.argmin - """ - nv.validate_argmin(args, kwargs) - nv.validate_minmax_axis(axis) - - i8 = self.asi8 - if self.hasnans: - mask = self._isnan - if mask.all() or not skipna: - return -1 - i8 = i8.copy() - i8[mask] = np.iinfo("int64").max - return i8.argmin() - - def max(self, axis=None, skipna=True, *args, **kwargs): - """ - Return the maximum value of the Index or maximum along - an axis. - - See Also - -------- - numpy.ndarray.max - Series.max : Return the maximum value in a Series. - """ - nv.validate_max(args, kwargs) - nv.validate_minmax_axis(axis) - - if not len(self): - return self._na_value - - i8 = self.asi8 - - if len(i8) and self.is_monotonic: - # quick check - if i8[-1] != iNaT: - return self._data._box_func(i8[-1]) - - if self.hasnans: - if not skipna: - return self._na_value - i8 = i8[~self._isnan] - - if not len(i8): - return self._na_value - - max_stamp = i8.max() - return self._data._box_func(max_stamp) - - def argmax(self, axis=None, skipna=True, *args, **kwargs): - """ - Returns the indices of the maximum values along an axis. - - See `numpy.ndarray.argmax` for more information on the - `axis` parameter. - - See Also - -------- - numpy.ndarray.argmax - """ - nv.validate_argmax(args, kwargs) - nv.validate_minmax_axis(axis) - - i8 = self.asi8 - if self.hasnans: - mask = self._isnan - if mask.all() or not skipna: - return -1 - i8 = i8.copy() - i8[mask] = 0 - return i8.argmax() - # -------------------------------------------------------------------- # Rendering Methods From bcc871ba537e2e7552502822a79d95e8b0ee6b56 Mon Sep 17 00:00:00 2001 From: Brock Date: Sun, 20 Jun 2021 18:48:30 -0700 Subject: [PATCH 2/5] mypy fixup --- pandas/core/indexes/base.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 22e27a12500b3..14ae650086379 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -6207,8 +6207,6 @@ def min(self, axis=None, skipna=True, *args, **kwargs): if not len(self): return self._na_value - data = self._data - if len(self) and self.is_monotonic_increasing: # quick check first = self[0] @@ -6220,9 +6218,8 @@ def min(self, axis=None, skipna=True, *args, **kwargs): mask = self._isnan if not skipna or mask.all(): return self._na_value - data = data[~mask] - return data.min() # alt: super().min(skipna=skipna) + return super().min(skipna=skipna) @doc(IndexOpsMixin.max) def max(self, axis=None, skipna=True, *args, **kwargs): @@ -6232,8 +6229,6 @@ def max(self, axis=None, skipna=True, *args, **kwargs): if not len(self): return self._na_value - data = self._data - if len(self) and self.is_monotonic_increasing: # quick check last = self[-1] @@ -6245,9 +6240,8 @@ def max(self, axis=None, skipna=True, *args, **kwargs): mask = self._isnan if not skipna or mask.all(): return self._na_value - data = data[~mask] - return data.max() # alt: super().max(skipna=skipna) + return super().max(skipna=skipna) # -------------------------------------------------------------------- From c6c8a494ae798da29241beab0fb79bd6b54de510 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 21 Jun 2021 10:23:21 -0700 Subject: [PATCH 3/5] MultiIndex compat --- pandas/core/indexes/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 3f59de6426a22..e7e8454a3a397 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -6186,7 +6186,7 @@ def argmin(self, axis=None, skipna=True, *args, **kwargs): nv.validate_argmin(args, kwargs) nv.validate_minmax_axis(axis) - if self.hasnans: + if not self._is_multi and self.hasnans: # Take advantage of cache mask = self._isnan if not skipna or mask.all(): @@ -6198,7 +6198,7 @@ def argmax(self, axis=None, skipna=True, *args, **kwargs): nv.validate_argmax(args, kwargs) nv.validate_minmax_axis(axis) - if self.hasnans: + if not self._is_multi and self.hasnans: # Take advantage of cache mask = self._isnan if not skipna or mask.all(): @@ -6219,7 +6219,7 @@ def min(self, axis=None, skipna=True, *args, **kwargs): if not isna(first): return first - if self.hasnans: + if not self._is_multi and self.hasnans: # Take advantage of cache mask = self._isnan if not skipna or mask.all(): @@ -6241,7 +6241,7 @@ def max(self, axis=None, skipna=True, *args, **kwargs): if not isna(last): return last - if self.hasnans: + if not self._is_multi and self.hasnans: # Take advantage of cache mask = self._isnan if not skipna or mask.all(): From 4ee9f6ba7f9fc6d5bbef44d4ef3f290b5be4e31c Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 21 Jun 2021 16:55:31 -0700 Subject: [PATCH 4/5] DTA/TDA fixup --- pandas/core/indexes/base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index e7e8454a3a397..26bb01a1040e8 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -6225,6 +6225,9 @@ def min(self, axis=None, skipna=True, *args, **kwargs): if not skipna or mask.all(): return self._na_value + if not self._is_multi and not isinstance(self._values, np.ndarray): + return self._values.min(skipna=skipna) + return super().min(skipna=skipna) @doc(IndexOpsMixin.max) @@ -6247,6 +6250,9 @@ def max(self, axis=None, skipna=True, *args, **kwargs): if not skipna or mask.all(): return self._na_value + if not self._is_multi and not isinstance(self._values, np.ndarray): + return self._values.max(skipna=skipna) + return super().max(skipna=skipna) # -------------------------------------------------------------------- From b44fcc82d72bc4ce1f393f00395c20a76d154c44 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 21 Jun 2021 18:13:51 -0700 Subject: [PATCH 5/5] mypy fixup --- pandas/core/indexes/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 26bb01a1040e8..da58144fdaa2d 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -6226,7 +6226,8 @@ def min(self, axis=None, skipna=True, *args, **kwargs): return self._na_value if not self._is_multi and not isinstance(self._values, np.ndarray): - return self._values.min(skipna=skipna) + # "ExtensionArray" has no attribute "min" + return self._values.min(skipna=skipna) # type: ignore[attr-defined] return super().min(skipna=skipna) @@ -6251,7 +6252,8 @@ def max(self, axis=None, skipna=True, *args, **kwargs): return self._na_value if not self._is_multi and not isinstance(self._values, np.ndarray): - return self._values.max(skipna=skipna) + # "ExtensionArray" has no attribute "max" + return self._values.max(skipna=skipna) # type: ignore[attr-defined] return super().max(skipna=skipna)