Skip to content

Commit f9d88cd

Browse files
Lucas KushnerTomAugspurger
Lucas Kushner
authored andcommitted
Deprecating Series.argmin and Series.argmax (#16830) (#16955)
* Deprecating Series.argmin and Series.argmax (#16830) Added statements about correcting behavior in future commit Add reference to github ticket Fixing placement of github comment Made test code more explicit Fixing unrelated tests that are also throwing warnings Updating whatsnew to give more detail about deprecation Fixing whatsnew and breaking out tests to catch warnings Additional comments and more concise whatsnew Updating deprecate decorator to support custom message DOC: Update docstrings, depr message, and whatsnew * Added debug prints * Try splitting the filters * Reword whatsnew * Change sparse series test * Skip on py2 * Change to idxmin * Remove py2 skips * Catch more warnings * Final switch to idxmax * Consistent tests, refactor to_string * Fixed tests
1 parent 44747c8 commit f9d88cd

File tree

8 files changed

+124
-45
lines changed

8 files changed

+124
-45
lines changed

doc/source/whatsnew/v0.21.0.txt

+22
Original file line numberDiff line numberDiff line change
@@ -487,11 +487,33 @@ Other API Changes
487487

488488
Deprecations
489489
~~~~~~~~~~~~
490+
490491
- :func:`read_excel()` has deprecated ``sheetname`` in favor of ``sheet_name`` for consistency with ``.to_excel()`` (:issue:`10559`).
491492
- ``pd.options.html.border`` has been deprecated in favor of ``pd.options.display.html.border`` (:issue:`15793`).
492493
- :func:`SeriesGroupBy.nth` has deprecated ``True`` in favor of ``'all'`` for its kwarg ``dropna`` (:issue:`11038`).
493494
- :func:`DataFrame.as_blocks` is deprecated, as this is exposing the internal implementation (:issue:`17302`)
494495

496+
.. _whatsnew_0210.deprecations.argmin_min
497+
498+
Series.argmax and Series.argmin
499+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
500+
501+
- The behavior of :func:`Series.argmax` has been deprecated in favor of :func:`Series.idxmax` (:issue:`16830`)
502+
- The behavior of :func:`Series.argmin` has been deprecated in favor of :func:`Series.idxmin` (:issue:`16830`)
503+
504+
For compatibility with NumPy arrays, ``pd.Series`` implements ``argmax`` and
505+
``argmin``. Since pandas 0.13.0, ``argmax`` has been an alias for
506+
:meth:`pandas.Series.idxmax`, and ``argmin`` has been an alias for
507+
:meth:`pandas.Series.idxmin`. They return the *label* of the maximum or minimum,
508+
rather than the *position*.
509+
510+
We've deprecated the current behavior of ``Series.argmax`` and
511+
``Series.argmin``. Using either of these will emit a ``FutureWarning``. Use
512+
:meth:`Series.idxmax` if you want the label of the maximum. Use
513+
``Series.values.argmax()`` if you want the position of the maximum. Likewise for
514+
the minimum. In a future release ``Series.argmax`` and ``Series.argmin`` will
515+
return the position of the maximum or minimum.
516+
495517
.. _whatsnew_0210.prior_deprecations:
496518

497519
Removal of prior version deprecations/changes

pandas/core/series.py

+22-7
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@
6969
import pandas.core.common as com
7070
import pandas.core.nanops as nanops
7171
import pandas.io.formats.format as fmt
72-
from pandas.util._decorators import Appender, deprecate_kwarg, Substitution
72+
from pandas.util._decorators import (
73+
Appender, deprecate, deprecate_kwarg, Substitution)
7374
from pandas.util._validators import validate_bool_kwarg
7475

7576
from pandas._libs import index as libindex, tslib as libts, lib, iNaT
@@ -1274,7 +1275,7 @@ def duplicated(self, keep='first'):
12741275

12751276
def idxmin(self, axis=None, skipna=True, *args, **kwargs):
12761277
"""
1277-
Index of first occurrence of minimum of values.
1278+
Index *label* of the first occurrence of minimum of values.
12781279
12791280
Parameters
12801281
----------
@@ -1287,7 +1288,9 @@ def idxmin(self, axis=None, skipna=True, *args, **kwargs):
12871288
12881289
Notes
12891290
-----
1290-
This method is the Series version of ``ndarray.argmin``.
1291+
This method is the Series version of ``ndarray.argmin``. This method
1292+
returns the label of the minimum, while ``ndarray.argmin`` returns
1293+
the position. To get the position, use ``series.values.argmin()``.
12911294
12921295
See Also
12931296
--------
@@ -1302,7 +1305,7 @@ def idxmin(self, axis=None, skipna=True, *args, **kwargs):
13021305

13031306
def idxmax(self, axis=None, skipna=True, *args, **kwargs):
13041307
"""
1305-
Index of first occurrence of maximum of values.
1308+
Index *label* of the first occurrence of maximum of values.
13061309
13071310
Parameters
13081311
----------
@@ -1315,7 +1318,9 @@ def idxmax(self, axis=None, skipna=True, *args, **kwargs):
13151318
13161319
Notes
13171320
-----
1318-
This method is the Series version of ``ndarray.argmax``.
1321+
This method is the Series version of ``ndarray.argmax``. This method
1322+
returns the label of the maximum, while ``ndarray.argmax`` returns
1323+
the position. To get the position, use ``series.values.argmax()``.
13191324
13201325
See Also
13211326
--------
@@ -1329,8 +1334,18 @@ def idxmax(self, axis=None, skipna=True, *args, **kwargs):
13291334
return self.index[i]
13301335

13311336
# ndarray compat
1332-
argmin = idxmin
1333-
argmax = idxmax
1337+
argmin = deprecate('argmin', idxmin,
1338+
msg="'argmin' is deprecated. Use 'idxmin' instead. "
1339+
"The behavior of 'argmin' will be corrected to "
1340+
"return the positional minimum in the future. "
1341+
"Use 'series.values.argmin' to get the position of "
1342+
"the minimum now.")
1343+
argmax = deprecate('argmax', idxmax,
1344+
msg="'argmax' is deprecated. Use 'idxmax' instead. "
1345+
"The behavior of 'argmax' will be corrected to "
1346+
"return the positional maximum in the future. "
1347+
"Use 'series.values.argmax' to get the position of "
1348+
"the maximum now.")
13341349

13351350
def round(self, decimals=0, *args, **kwargs):
13361351
"""

pandas/io/formats/format.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -598,9 +598,7 @@ def to_string(self):
598598
text = self._join_multiline(*strcols)
599599
else: # max_cols == 0. Try to fit frame to terminal
600600
text = self.adj.adjoin(1, *strcols).split('\n')
601-
row_lens = Series(text).apply(len)
602-
max_len_col_ix = np.argmax(row_lens)
603-
max_len = row_lens[max_len_col_ix]
601+
max_len = Series(text).str.len().max()
604602
headers = [ele[0] for ele in strcols]
605603
# Size of last col determines dot col size. See
606604
# `self._to_str_columns

pandas/tests/series/test_analytics.py

+44-16
Original file line numberDiff line numberDiff line change
@@ -1242,16 +1242,31 @@ def test_idxmin(self):
12421242
result = s.idxmin()
12431243
assert result == 1
12441244

1245-
def test_numpy_argmin(self):
1246-
# argmin is aliased to idxmin
1247-
data = np.random.randint(0, 11, size=10)
1248-
result = np.argmin(Series(data))
1249-
assert result == np.argmin(data)
1245+
def test_numpy_argmin_deprecated(self):
1246+
# See gh-16830
1247+
data = np.arange(1, 11)
1248+
1249+
s = Series(data, index=data)
1250+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
1251+
# The deprecation of Series.argmin also causes a deprecation
1252+
# warning when calling np.argmin. This behavior is temporary
1253+
# until the implemention of Series.argmin is corrected.
1254+
result = np.argmin(s)
1255+
1256+
assert result == 1
1257+
1258+
with tm.assert_produces_warning(FutureWarning):
1259+
# argmin is aliased to idxmin
1260+
result = s.argmin()
1261+
1262+
assert result == 1
12501263

12511264
if not _np_version_under1p10:
1252-
msg = "the 'out' parameter is not supported"
1253-
tm.assert_raises_regex(ValueError, msg, np.argmin,
1254-
Series(data), out=data)
1265+
with tm.assert_produces_warning(FutureWarning,
1266+
check_stacklevel=False):
1267+
msg = "the 'out' parameter is not supported"
1268+
tm.assert_raises_regex(ValueError, msg, np.argmin,
1269+
s, out=data)
12551270

12561271
def test_idxmax(self):
12571272
# test idxmax
@@ -1297,17 +1312,30 @@ def test_idxmax(self):
12971312
result = s.idxmin()
12981313
assert result == 1.1
12991314

1300-
def test_numpy_argmax(self):
1315+
def test_numpy_argmax_deprecated(self):
1316+
# See gh-16830
1317+
data = np.arange(1, 11)
1318+
1319+
s = Series(data, index=data)
1320+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
1321+
# The deprecation of Series.argmax also causes a deprecation
1322+
# warning when calling np.argmax. This behavior is temporary
1323+
# until the implemention of Series.argmax is corrected.
1324+
result = np.argmax(s)
1325+
assert result == 10
1326+
1327+
with tm.assert_produces_warning(FutureWarning):
1328+
# argmax is aliased to idxmax
1329+
result = s.argmax()
13011330

1302-
# argmax is aliased to idxmax
1303-
data = np.random.randint(0, 11, size=10)
1304-
result = np.argmax(Series(data))
1305-
assert result == np.argmax(data)
1331+
assert result == 10
13061332

13071333
if not _np_version_under1p10:
1308-
msg = "the 'out' parameter is not supported"
1309-
tm.assert_raises_regex(ValueError, msg, np.argmax,
1310-
Series(data), out=data)
1334+
with tm.assert_produces_warning(FutureWarning,
1335+
check_stacklevel=False):
1336+
msg = "the 'out' parameter is not supported"
1337+
tm.assert_raises_regex(ValueError, msg, np.argmax,
1338+
s, out=data)
13111339

13121340
def test_ptp(self):
13131341
N = 1000

pandas/tests/series/test_api.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ def test_ndarray_compat(self):
345345
index=date_range('1/1/2000', periods=1000))
346346

347347
def f(x):
348-
return x[x.argmax()]
348+
return x[x.idxmax()]
349349

350350
result = tsdf.apply(f)
351351
expected = tsdf.max()

pandas/tests/series/test_operators.py

+14-14
Original file line numberDiff line numberDiff line change
@@ -1872,33 +1872,33 @@ def test_op_duplicate_index(self):
18721872
),
18731873
]
18741874
)
1875-
def test_assert_argminmax_raises(self, test_input, error_type):
1875+
def test_assert_idxminmax_raises(self, test_input, error_type):
18761876
"""
18771877
Cases where ``Series.argmax`` and related should raise an exception
18781878
"""
18791879
with pytest.raises(error_type):
1880-
test_input.argmin()
1880+
test_input.idxmin()
18811881
with pytest.raises(error_type):
1882-
test_input.argmin(skipna=False)
1882+
test_input.idxmin(skipna=False)
18831883
with pytest.raises(error_type):
1884-
test_input.argmax()
1884+
test_input.idxmax()
18851885
with pytest.raises(error_type):
1886-
test_input.argmax(skipna=False)
1886+
test_input.idxmax(skipna=False)
18871887

1888-
def test_argminmax_with_inf(self):
1888+
def test_idxminmax_with_inf(self):
18891889
# For numeric data with NA and Inf (GH #13595)
18901890
s = pd.Series([0, -np.inf, np.inf, np.nan])
18911891

1892-
assert s.argmin() == 1
1893-
assert np.isnan(s.argmin(skipna=False))
1892+
assert s.idxmin() == 1
1893+
assert np.isnan(s.idxmin(skipna=False))
18941894

1895-
assert s.argmax() == 2
1896-
assert np.isnan(s.argmax(skipna=False))
1895+
assert s.idxmax() == 2
1896+
assert np.isnan(s.idxmax(skipna=False))
18971897

18981898
# Using old-style behavior that treats floating point nan, -inf, and
18991899
# +inf as missing
19001900
with pd.option_context('mode.use_inf_as_na', True):
1901-
assert s.argmin() == 0
1902-
assert np.isnan(s.argmin(skipna=False))
1903-
assert s.argmax() == 0
1904-
np.isnan(s.argmax(skipna=False))
1901+
assert s.idxmin() == 0
1902+
assert np.isnan(s.idxmin(skipna=False))
1903+
assert s.idxmax() == 0
1904+
np.isnan(s.idxmax(skipna=False))

pandas/tests/sparse/test_series.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -1379,11 +1379,25 @@ def test_numpy_func_call(self):
13791379
# numpy passes in 'axis=None' or `axis=-1'
13801380
funcs = ['sum', 'cumsum', 'var', 'mean',
13811381
'prod', 'cumprod', 'std', 'argsort',
1382-
'argmin', 'argmax', 'min', 'max']
1382+
'min', 'max']
13831383
for func in funcs:
13841384
for series in ('bseries', 'zbseries'):
13851385
getattr(np, func)(getattr(self, series))
13861386

1387+
def test_deprecated_numpy_func_call(self):
1388+
# NOTE: These should be add to the 'test_numpy_func_call' test above
1389+
# once the behavior of argmin/argmax is corrected.
1390+
funcs = ['argmin', 'argmax']
1391+
for func in funcs:
1392+
for series in ('bseries', 'zbseries'):
1393+
with tm.assert_produces_warning(FutureWarning,
1394+
check_stacklevel=False):
1395+
getattr(np, func)(getattr(self, series))
1396+
1397+
with tm.assert_produces_warning(FutureWarning,
1398+
check_stacklevel=False):
1399+
getattr(getattr(self, series), func)()
1400+
13871401

13881402
@pytest.mark.parametrize(
13891403
'datetime_type', (np.datetime64,

pandas/util/_decorators.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
def deprecate(name, alternative, alt_name=None, klass=None,
10-
stacklevel=2):
10+
stacklevel=2, msg=None):
1111
"""
1212
Return a new function that emits a deprecation warning on use.
1313
@@ -21,14 +21,16 @@ def deprecate(name, alternative, alt_name=None, klass=None,
2121
Name to use in preference of alternative.__name__
2222
klass : Warning, default FutureWarning
2323
stacklevel : int, default 2
24+
msg : str
25+
The message to display in the warning.
26+
Default is '{name} is deprecated. Use {alt_name} instead.'
2427
"""
2528

2629
alt_name = alt_name or alternative.__name__
2730
klass = klass or FutureWarning
31+
msg = msg or "{} is deprecated. Use {} instead".format(name, alt_name)
2832

2933
def wrapper(*args, **kwargs):
30-
msg = "{name} is deprecated. Use {alt_name} instead".format(
31-
name=name, alt_name=alt_name)
3234
warnings.warn(msg, klass, stacklevel=stacklevel)
3335
return alternative(*args, **kwargs)
3436
return wrapper

0 commit comments

Comments
 (0)