Skip to content

CLN: Enforce deprecation of argmin/max and idxmin/max with NA values #57971

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 25, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
@@ -259,6 +259,7 @@ Removal of prior version deprecations/changes
- Removed the :class:`Grouper` attributes ``ax``, ``groups``, ``indexer``, and ``obj`` (:issue:`51206`, :issue:`51182`)
- Removed deprecated keyword ``verbose`` on :func:`read_csv` and :func:`read_table` (:issue:`56556`)
- Removed the attribute ``dtypes`` from :class:`.DataFrameGroupBy` (:issue:`51997`)
- Enforced deprecation of ``argmin``, ``argmax``, ``idxmin``, and ``idxmax`` returning a result when ``skipna=False`` and an NA value is encountered or all values are NA values; these operations will now raise in such cases (:issue:`33941`, :issue:`51276`)

.. ---------------------------------------------------------------------------
.. _whatsnew_300.performance:
55 changes: 14 additions & 41 deletions pandas/core/base.py
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@
final,
overload,
)
import warnings

import numpy as np

@@ -35,7 +34,6 @@
cache_readonly,
doc,
)
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.cast import can_hold_element
from pandas.core.dtypes.common import (
@@ -686,7 +684,8 @@ def argmax(
axis : {{None}}
Unused. Parameter needed for compatibility with DataFrame.
skipna : bool, default True
Exclude NA/null values when showing the result.
Exclude NA/null values. If the entire Series is NA, or if ``skipna=False``
and there is an NA value, this method will raise a ``ValueError``.
*args, **kwargs
Additional arguments and keywords for compatibility with NumPy.
@@ -736,28 +735,15 @@ def argmax(
nv.validate_minmax_axis(axis)
skipna = nv.validate_argmax_with_skipna(skipna, args, kwargs)

if skipna and len(delegate) > 0 and isna(delegate).all():
raise ValueError("Encountered all NA values")
elif not skipna and isna(delegate).any():
raise ValueError("Encountered an NA value with skipna=False")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh IIRC the idea with the deprecation was that eventually we'd be able to make this method just return self.array.argmax(skipna=skipna)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks - makes sense. Right now the various arrays raise NotImplementedError. I think changing this to a ValueError will make this doable. Will followup.

One other mistake I've realized here (thanks to #58013) - I thought it would be more helpful to raise "all NA values" even when skipna=False if all values are NA, but that requires doing an extra O(n) check. I plan to change the message to "Encountered an NA value with skipna=False" even when all values are NA for perf.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i might be confusing this with argsort; there is a TODO(3.0) comment in Series.argsort about using self.array.argsort directly

if isinstance(delegate, ExtensionArray):
if not skipna and delegate.isna().any():
warnings.warn(
f"The behavior of {type(self).__name__}.argmax/argmin "
"with skipna=False and NAs, or with all-NAs is deprecated. "
"In a future version this will raise ValueError.",
FutureWarning,
stacklevel=find_stack_level(),
)
return -1
else:
return delegate.argmax()
return delegate.argmax()
else:
result = nanops.nanargmax(delegate, skipna=skipna)
if result == -1:
warnings.warn(
f"The behavior of {type(self).__name__}.argmax/argmin "
"with skipna=False and NAs, or with all-NAs is deprecated. "
"In a future version this will raise ValueError.",
FutureWarning,
stacklevel=find_stack_level(),
)
# error: Incompatible return value type (got "Union[int, ndarray]", expected
# "int")
return result # type: ignore[return-value]
@@ -770,28 +756,15 @@ def argmin(
nv.validate_minmax_axis(axis)
skipna = nv.validate_argmin_with_skipna(skipna, args, kwargs)

if skipna and len(delegate) > 0 and isna(delegate).all():
raise ValueError("Encountered all NA values")
elif not skipna and isna(delegate).any():
raise ValueError("Encountered an NA value with skipna=False")

if isinstance(delegate, ExtensionArray):
if not skipna and delegate.isna().any():
warnings.warn(
f"The behavior of {type(self).__name__}.argmax/argmin "
"with skipna=False and NAs, or with all-NAs is deprecated. "
"In a future version this will raise ValueError.",
FutureWarning,
stacklevel=find_stack_level(),
)
return -1
else:
return delegate.argmin()
return delegate.argmin()
else:
result = nanops.nanargmin(delegate, skipna=skipna)
if result == -1:
warnings.warn(
f"The behavior of {type(self).__name__}.argmax/argmin "
"with skipna=False and NAs, or with all-NAs is deprecated. "
"In a future version this will raise ValueError.",
FutureWarning,
stacklevel=find_stack_level(),
)
# error: Incompatible return value type (got "Union[int, ndarray]", expected
# "int")
return result # type: ignore[return-value]
28 changes: 8 additions & 20 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
@@ -6976,16 +6976,10 @@ def argmin(self, axis=None, skipna: bool = True, *args, **kwargs) -> int:

if not self._is_multi and self.hasnans:
# Take advantage of cache
mask = self._isnan
if not skipna or mask.all():
warnings.warn(
f"The behavior of {type(self).__name__}.argmax/argmin "
"with skipna=False and NAs, or with all-NAs is deprecated. "
"In a future version this will raise ValueError.",
FutureWarning,
stacklevel=find_stack_level(),
)
return -1
if self._isnan.all():
raise ValueError("Encountered all NA values")
elif not skipna:
raise ValueError("Encountered an NA value with skipna=False")
return super().argmin(skipna=skipna)

@Appender(IndexOpsMixin.argmax.__doc__)
@@ -6995,16 +6989,10 @@ def argmax(self, axis=None, skipna: bool = True, *args, **kwargs) -> int:

if not self._is_multi and self.hasnans:
# Take advantage of cache
mask = self._isnan
if not skipna or mask.all():
warnings.warn(
f"The behavior of {type(self).__name__}.argmax/argmin "
"with skipna=False and NAs, or with all-NAs is deprecated. "
"In a future version this will raise ValueError.",
FutureWarning,
stacklevel=find_stack_level(),
)
return -1
if self._isnan.all():
raise ValueError("Encountered all NA values")
elif not skipna:
raise ValueError("Encountered an NA value with skipna=False")
return super().argmax(skipna=skipna)

def min(self, axis=None, skipna: bool = True, *args, **kwargs):
15 changes: 8 additions & 7 deletions pandas/core/nanops.py
Original file line number Diff line number Diff line change
@@ -1441,17 +1441,18 @@ def _maybe_arg_null_out(
if axis is None or not getattr(result, "ndim", False):
if skipna:
if mask.all():
return -1
raise ValueError("Encountered all NA values")
else:
if mask.any():
return -1
raise ValueError("Encountered an NA value with skipna=False")
else:
if skipna:
na_mask = mask.all(axis)
else:
na_mask = mask.any(axis)
na_mask = mask.all(axis)
if na_mask.any():
result[na_mask] = -1
raise ValueError("Encountered all NA values")
elif not skipna:
na_mask = mask.any(axis)
if na_mask.any():
raise ValueError("Encountered an NA value with skipna=False")
return result


60 changes: 8 additions & 52 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
@@ -2333,8 +2333,8 @@ def idxmin(self, axis: Axis = 0, skipna: bool = True, *args, **kwargs) -> Hashab
axis : {0 or 'index'}
Unused. Parameter needed for compatibility with DataFrame.
skipna : bool, default True
Exclude NA/null values. If the entire Series is NA, the result
will be NA.
Exclude NA/null values. If the entire Series is NA, or if ``skipna=False``
and there is an NA value, this method will raise a ``ValueError``.
*args, **kwargs
Additional arguments and keywords have no effect but might be
accepted for compatibility with NumPy.
@@ -2376,32 +2376,10 @@ def idxmin(self, axis: Axis = 0, skipna: bool = True, *args, **kwargs) -> Hashab
>>> s.idxmin()
'A'
If `skipna` is False and there is an NA value in the data,
the function returns ``nan``.
>>> s.idxmin(skipna=False)
nan
"""
axis = self._get_axis_number(axis)
with warnings.catch_warnings():
# TODO(3.0): this catching/filtering can be removed
# ignore warning produced by argmin since we will issue a different
# warning for idxmin
warnings.simplefilter("ignore")
i = self.argmin(axis, skipna, *args, **kwargs)

if i == -1:
# GH#43587 give correct NA value for Index.
warnings.warn(
f"The behavior of {type(self).__name__}.idxmin with all-NA "
"values, or any-NA and skipna=False, is deprecated. In a future "
"version this will raise ValueError",
FutureWarning,
stacklevel=find_stack_level(),
)
return self.index._na_value
return self.index[i]
iloc = self.argmin(axis, skipna, *args, **kwargs)
return self.index[iloc]

def idxmax(self, axis: Axis = 0, skipna: bool = True, *args, **kwargs) -> Hashable:
"""
@@ -2415,8 +2393,8 @@ def idxmax(self, axis: Axis = 0, skipna: bool = True, *args, **kwargs) -> Hashab
axis : {0 or 'index'}
Unused. Parameter needed for compatibility with DataFrame.
skipna : bool, default True
Exclude NA/null values. If the entire Series is NA, the result
will be NA.
Exclude NA/null values. If the entire Series is NA, or if ``skipna=False``
and there is an NA value, this method will raise a ``ValueError``.
*args, **kwargs
Additional arguments and keywords have no effect but might be
accepted for compatibility with NumPy.
@@ -2459,32 +2437,10 @@ def idxmax(self, axis: Axis = 0, skipna: bool = True, *args, **kwargs) -> Hashab
>>> s.idxmax()
'C'
If `skipna` is False and there is an NA value in the data,
the function returns ``nan``.
>>> s.idxmax(skipna=False)
nan
"""
axis = self._get_axis_number(axis)
with warnings.catch_warnings():
# TODO(3.0): this catching/filtering can be removed
# ignore warning produced by argmax since we will issue a different
# warning for argmax
warnings.simplefilter("ignore")
i = self.argmax(axis, skipna, *args, **kwargs)

if i == -1:
# GH#43587 give correct NA value for Index.
warnings.warn(
f"The behavior of {type(self).__name__}.idxmax with all-NA "
"values, or any-NA and skipna=False, is deprecated. In a future "
"version this will raise ValueError",
FutureWarning,
stacklevel=find_stack_level(),
)
return self.index._na_value
return self.index[i]
iloc = self.argmax(axis, skipna, *args, **kwargs)
return self.index[iloc]

def round(self, decimals: int = 0, *args, **kwargs) -> Series:
"""
8 changes: 4 additions & 4 deletions pandas/core/shared_docs.py
Original file line number Diff line number Diff line change
@@ -692,8 +692,8 @@
axis : {{0 or 'index', 1 or 'columns'}}, default 0
The axis to use. 0 or 'index' for row-wise, 1 or 'columns' for column-wise.
skipna : bool, default True
Exclude NA/null values. If an entire row/column is NA, the result
will be NA.
Exclude NA/null values. If the entire Series is NA, or if ``skipna=False``
and there is an NA value, this method will raise a ``ValueError``.
numeric_only : bool, default {numeric_only_default}
Include only `float`, `int` or `boolean` data.
@@ -757,8 +757,8 @@
axis : {{0 or 'index', 1 or 'columns'}}, default 0
The axis to use. 0 or 'index' for row-wise, 1 or 'columns' for column-wise.
skipna : bool, default True
Exclude NA/null values. If an entire row/column is NA, the result
will be NA.
Exclude NA/null values. If the entire Series is NA, or if ``skipna=False``
and there is an NA value, this method will raise a ``ValueError``.
numeric_only : bool, default {numeric_only_default}
Include only `float`, `int` or `boolean` data.
18 changes: 7 additions & 11 deletions pandas/tests/extension/base/methods.py
Original file line number Diff line number Diff line change
@@ -169,8 +169,8 @@ def test_argmin_argmax_all_na(self, method, data, na_value):
("idxmin", True, 2),
("argmax", True, 0),
("argmin", True, 2),
("idxmax", False, np.nan),
("idxmin", False, np.nan),
("idxmax", False, -1),
("idxmin", False, -1),
("argmax", False, -1),
("argmin", False, -1),
],
@@ -179,17 +179,13 @@ def test_argreduce_series(
self, data_missing_for_sorting, op_name, skipna, expected
):
# data_missing_for_sorting -> [B, NA, A] with A < B and NA missing.
warn = None
msg = "The behavior of Series.argmax/argmin"
if op_name.startswith("arg") and expected == -1:
warn = FutureWarning
if op_name.startswith("idx") and np.isnan(expected):
warn = FutureWarning
msg = f"The behavior of Series.{op_name}"
ser = pd.Series(data_missing_for_sorting)
with tm.assert_produces_warning(warn, match=msg):
if expected == -1:
with pytest.raises(ValueError, match="Encountered an NA value"):
getattr(ser, op_name)(skipna=skipna)
else:
result = getattr(ser, op_name)(skipna=skipna)
tm.assert_almost_equal(result, expected)
tm.assert_almost_equal(result, expected)

def test_argmax_argmin_no_skipna_notimplemented(self, data_missing_for_sorting):
# GH#38733
56 changes: 30 additions & 26 deletions pandas/tests/frame/test_reductions.py
Original file line number Diff line number Diff line change
@@ -1065,18 +1065,20 @@ def test_idxmin(self, float_frame, int_frame, skipna, axis):
frame.iloc[5:10] = np.nan
frame.iloc[15:20, -2:] = np.nan
for df in [frame, int_frame]:
warn = None
if skipna is False or axis == 1:
warn = None if df is int_frame else FutureWarning
msg = "The behavior of DataFrame.idxmin with all-NA values"
with tm.assert_produces_warning(warn, match=msg):
if (not skipna or axis == 1) and df is not int_frame:
if axis == 1:
msg = "Encountered all NA values"
else:
msg = "Encountered an NA value"
with pytest.raises(ValueError, match=msg):
df.idxmin(axis=axis, skipna=skipna)
with pytest.raises(ValueError, match=msg):
df.idxmin(axis=axis, skipna=skipna)
else:
result = df.idxmin(axis=axis, skipna=skipna)

msg2 = "The behavior of Series.idxmin"
with tm.assert_produces_warning(warn, match=msg2):
expected = df.apply(Series.idxmin, axis=axis, skipna=skipna)
expected = expected.astype(df.index.dtype)
tm.assert_series_equal(result, expected)
expected = expected.astype(df.index.dtype)
tm.assert_series_equal(result, expected)

@pytest.mark.parametrize("axis", [0, 1])
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
@@ -1113,16 +1115,17 @@ def test_idxmax(self, float_frame, int_frame, skipna, axis):
frame.iloc[5:10] = np.nan
frame.iloc[15:20, -2:] = np.nan
for df in [frame, int_frame]:
warn = None
if skipna is False or axis == 1:
warn = None if df is int_frame else FutureWarning
msg = "The behavior of DataFrame.idxmax with all-NA values"
with tm.assert_produces_warning(warn, match=msg):
result = df.idxmax(axis=axis, skipna=skipna)
if (skipna is False or axis == 1) and df is frame:
if axis == 1:
msg = "Encountered all NA values"
else:
msg = "Encountered an NA value"
with pytest.raises(ValueError, match=msg):
df.idxmax(axis=axis, skipna=skipna)
return

msg2 = "The behavior of Series.idxmax"
with tm.assert_produces_warning(warn, match=msg2):
expected = df.apply(Series.idxmax, axis=axis, skipna=skipna)
result = df.idxmax(axis=axis, skipna=skipna)
expected = df.apply(Series.idxmax, axis=axis, skipna=skipna)
expected = expected.astype(df.index.dtype)
tm.assert_series_equal(result, expected)

@@ -2118,15 +2121,16 @@ def test_numeric_ea_axis_1(method, skipna, min_count, any_numeric_ea_dtype):
if method in ("prod", "product", "sum"):
kwargs["min_count"] = min_count

warn = None
msg = None
if not skipna and method in ("idxmax", "idxmin"):
warn = FutureWarning
# GH#57745 - EAs use groupby for axis=1 which still needs a proper deprecation.
msg = f"The behavior of DataFrame.{method} with all-NA values"
with tm.assert_produces_warning(warn, match=msg):
result = getattr(df, method)(axis=1, **kwargs)
with tm.assert_produces_warning(warn, match=msg):
expected = getattr(expected_df, method)(axis=1, **kwargs)
with tm.assert_produces_warning(FutureWarning, match=msg):
getattr(df, method)(axis=1, **kwargs)
with pytest.raises(ValueError, match="Encountered an NA value"):
getattr(expected_df, method)(axis=1, **kwargs)
return
result = getattr(df, method)(axis=1, **kwargs)
expected = getattr(expected_df, method)(axis=1, **kwargs)
if method not in ("idxmax", "idxmin"):
expected = expected.astype(expected_dtype)
tm.assert_series_equal(result, expected)
129 changes: 49 additions & 80 deletions pandas/tests/reductions/test_reductions.py
Original file line number Diff line number Diff line change
@@ -128,28 +128,14 @@ def test_nanargminmax(self, opname, index_or_series):
obj = klass([NaT, datetime(2011, 11, 1)])
assert getattr(obj, arg_op)() == 1

msg = (
"The behavior of (DatetimeIndex|Series).argmax/argmin with "
"skipna=False and NAs"
)
if klass is Series:
msg = "The behavior of Series.(idxmax|idxmin) with all-NA"
with tm.assert_produces_warning(FutureWarning, match=msg):
result = getattr(obj, arg_op)(skipna=False)
if klass is Series:
assert np.isnan(result)
else:
assert result == -1
with pytest.raises(ValueError, match="Encountered an NA value"):
getattr(obj, arg_op)(skipna=False)

obj = klass([NaT, datetime(2011, 11, 1), NaT])
# check DatetimeIndex non-monotonic path
assert getattr(obj, arg_op)() == 1
with tm.assert_produces_warning(FutureWarning, match=msg):
result = getattr(obj, arg_op)(skipna=False)
if klass is Series:
assert np.isnan(result)
else:
assert result == -1
with pytest.raises(ValueError, match="Encountered an NA value"):
getattr(obj, arg_op)(skipna=False)

@pytest.mark.parametrize("opname", ["max", "min"])
@pytest.mark.parametrize("dtype", ["M8[ns]", "datetime64[ns, UTC]"])
@@ -175,40 +161,38 @@ def test_argminmax(self):
obj = Index([np.nan, 1, np.nan, 2])
assert obj.argmin() == 1
assert obj.argmax() == 3
msg = "The behavior of Index.argmax/argmin with skipna=False and NAs"
with tm.assert_produces_warning(FutureWarning, match=msg):
assert obj.argmin(skipna=False) == -1
with tm.assert_produces_warning(FutureWarning, match=msg):
assert obj.argmax(skipna=False) == -1
with pytest.raises(ValueError, match="Encountered an NA value"):
obj.argmin(skipna=False)
with pytest.raises(ValueError, match="Encountered an NA value"):
obj.argmax(skipna=False)

obj = Index([np.nan])
with tm.assert_produces_warning(FutureWarning, match=msg):
assert obj.argmin() == -1
with tm.assert_produces_warning(FutureWarning, match=msg):
assert obj.argmax() == -1
with tm.assert_produces_warning(FutureWarning, match=msg):
assert obj.argmin(skipna=False) == -1
with tm.assert_produces_warning(FutureWarning, match=msg):
assert obj.argmax(skipna=False) == -1
with pytest.raises(ValueError, match="Encountered all NA values"):
obj.argmin()
with pytest.raises(ValueError, match="Encountered all NA values"):
obj.argmax()
with pytest.raises(ValueError, match="Encountered all NA values"):
obj.argmin(skipna=False)
with pytest.raises(ValueError, match="Encountered all NA values"):
obj.argmax(skipna=False)

msg = "The behavior of DatetimeIndex.argmax/argmin with skipna=False and NAs"
obj = Index([NaT, datetime(2011, 11, 1), datetime(2011, 11, 2), NaT])
assert obj.argmin() == 1
assert obj.argmax() == 2
with tm.assert_produces_warning(FutureWarning, match=msg):
assert obj.argmin(skipna=False) == -1
with tm.assert_produces_warning(FutureWarning, match=msg):
assert obj.argmax(skipna=False) == -1
with pytest.raises(ValueError, match="Encountered an NA value"):
obj.argmin(skipna=False)
with pytest.raises(ValueError, match="Encountered an NA value"):
obj.argmax(skipna=False)

obj = Index([NaT])
with tm.assert_produces_warning(FutureWarning, match=msg):
assert obj.argmin() == -1
with tm.assert_produces_warning(FutureWarning, match=msg):
assert obj.argmax() == -1
with tm.assert_produces_warning(FutureWarning, match=msg):
assert obj.argmin(skipna=False) == -1
with tm.assert_produces_warning(FutureWarning, match=msg):
assert obj.argmax(skipna=False) == -1
with pytest.raises(ValueError, match="Encountered all NA values"):
obj.argmin()
with pytest.raises(ValueError, match="Encountered all NA values"):
obj.argmax()
with pytest.raises(ValueError, match="Encountered all NA values"):
obj.argmin(skipna=False)
with pytest.raises(ValueError, match="Encountered all NA values"):
obj.argmax(skipna=False)

@pytest.mark.parametrize("op, expected_col", [["max", "a"], ["min", "b"]])
def test_same_tz_min_max_axis_1(self, op, expected_col):
@@ -841,26 +825,16 @@ def test_idxmin_dt64index(self, unit):
# GH#43587 should have NaT instead of NaN
dti = DatetimeIndex(["NaT", "2015-02-08", "NaT"]).as_unit(unit)
ser = Series([1.0, 2.0, np.nan], index=dti)
msg = "The behavior of Series.idxmin with all-NA values"
with tm.assert_produces_warning(FutureWarning, match=msg):
res = ser.idxmin(skipna=False)
assert res is NaT
msg = "The behavior of Series.idxmax with all-NA values"
with tm.assert_produces_warning(FutureWarning, match=msg):
res = ser.idxmax(skipna=False)
assert res is NaT
with pytest.raises(ValueError, match="Encountered an NA value"):
ser.idxmin(skipna=False)
with pytest.raises(ValueError, match="Encountered an NA value"):
ser.idxmax(skipna=False)

df = ser.to_frame()
msg = "The behavior of DataFrame.idxmin with all-NA values"
with tm.assert_produces_warning(FutureWarning, match=msg):
res = df.idxmin(skipna=False)
assert res.dtype == f"M8[{unit}]"
assert res.isna().all()
msg = "The behavior of DataFrame.idxmax with all-NA values"
with tm.assert_produces_warning(FutureWarning, match=msg):
res = df.idxmax(skipna=False)
assert res.dtype == f"M8[{unit}]"
assert res.isna().all()
with pytest.raises(ValueError, match="Encountered an NA value"):
df.idxmin(skipna=False)
with pytest.raises(ValueError, match="Encountered an NA value"):
df.idxmax(skipna=False)

def test_idxmin(self):
# test idxmin
@@ -872,9 +846,8 @@ def test_idxmin(self):

# skipna or no
assert string_series[string_series.idxmin()] == string_series.min()
msg = "The behavior of Series.idxmin"
with tm.assert_produces_warning(FutureWarning, match=msg):
assert isna(string_series.idxmin(skipna=False))
with pytest.raises(ValueError, match="Encountered an NA value"):
string_series.idxmin(skipna=False)

# no NaNs
nona = string_series.dropna()
@@ -883,8 +856,8 @@ def test_idxmin(self):

# all NaNs
allna = string_series * np.nan
with tm.assert_produces_warning(FutureWarning, match=msg):
assert isna(allna.idxmin())
with pytest.raises(ValueError, match="Encountered all NA values"):
allna.idxmin()

# datetime64[ns]
s = Series(date_range("20130102", periods=6))
@@ -905,8 +878,7 @@ def test_idxmax(self):

# skipna or no
assert string_series[string_series.idxmax()] == string_series.max()
msg = "The behavior of Series.idxmax with all-NA values"
with tm.assert_produces_warning(FutureWarning, match=msg):
with pytest.raises(ValueError, match="Encountered an NA value"):
assert isna(string_series.idxmax(skipna=False))

# no NaNs
@@ -916,9 +888,8 @@ def test_idxmax(self):

# all NaNs
allna = string_series * np.nan
msg = "The behavior of Series.idxmax with all-NA values"
with tm.assert_produces_warning(FutureWarning, match=msg):
assert isna(allna.idxmax())
with pytest.raises(ValueError, match="Encountered all NA values"):
allna.idxmax()

s = Series(date_range("20130102", periods=6))
result = s.idxmax()
@@ -1175,12 +1146,12 @@ def test_idxminmax_object_dtype(self, using_infer_string):
msg = "'>' not supported between instances of 'float' and 'str'"
with pytest.raises(TypeError, match=msg):
ser3.idxmax()
with pytest.raises(TypeError, match=msg):
with pytest.raises(ValueError, match="Encountered an NA value"):
ser3.idxmax(skipna=False)
msg = "'<' not supported between instances of 'float' and 'str'"
with pytest.raises(TypeError, match=msg):
ser3.idxmin()
with pytest.raises(TypeError, match=msg):
with pytest.raises(ValueError, match="Encountered an NA value"):
ser3.idxmin(skipna=False)

def test_idxminmax_object_frame(self):
@@ -1228,14 +1199,12 @@ def test_idxminmax_with_inf(self):
s = Series([0, -np.inf, np.inf, np.nan])

assert s.idxmin() == 1
msg = "The behavior of Series.idxmin with all-NA values"
with tm.assert_produces_warning(FutureWarning, match=msg):
assert np.isnan(s.idxmin(skipna=False))
with pytest.raises(ValueError, match="Encountered an NA value"):
s.idxmin(skipna=False)

assert s.idxmax() == 2
msg = "The behavior of Series.idxmax with all-NA values"
with tm.assert_produces_warning(FutureWarning, match=msg):
assert np.isnan(s.idxmax(skipna=False))
with pytest.raises(ValueError, match="Encountered an NA value"):
s.idxmax(skipna=False)

def test_sum_uint64(self):
# GH 53401
10 changes: 10 additions & 0 deletions pandas/tests/test_nanops.py
Original file line number Diff line number Diff line change
@@ -296,6 +296,7 @@ def check_fun_data(
self,
testfunc,
targfunc,
testar,
testarval,
targarval,
skipna,
@@ -319,6 +320,13 @@ def check_fun_data(
else:
targ = bool(targ)

if testfunc.__name__ in ["nanargmax", "nanargmin"] and (
testar.startswith("arr_nan")
or (testar.endswith("nan") and (not skipna or axis == 1))
):
with pytest.raises(ValueError, match="Encountered .* NA value"):
testfunc(testarval, axis=axis, skipna=skipna, **kwargs)
return
res = testfunc(testarval, axis=axis, skipna=skipna, **kwargs)

if (
@@ -350,6 +358,7 @@ def check_fun_data(
self.check_fun_data(
testfunc,
targfunc,
testar,
testarval2,
targarval2,
skipna=skipna,
@@ -370,6 +379,7 @@ def check_fun(
self.check_fun_data(
testfunc,
targfunc,
testar,
testarval,
targarval,
skipna=skipna,