Skip to content

Commit

Permalink
DEPR: mad (#46707)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhshadrach committed Apr 9, 2022
1 parent 860797b commit 6864608
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 55 deletions.
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v1.5.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ Other Deprecations
- Deprecated :attr:`Timedelta.freq` and :attr:`Timedelta.is_populated` (:issue:`46430`)
- Deprecated :attr:`Timedelta.delta` (:issue:`46476`)
- Deprecated the ``closed`` argument in :meth:`interval_range` in favor of ``inclusive`` argument; In a future version passing ``closed`` will raise (:issue:`40245`)
-
- Deprecated the methods :meth:`DataFrame.mad`, :meth:`Series.mad`, and the corresponding groupby methods (:issue:`11787`)

.. ---------------------------------------------------------------------------
.. _whatsnew_150.performance:
Expand Down
9 changes: 9 additions & 0 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -10881,6 +10881,9 @@ def mad(
"""
{desc}
.. deprecated:: 1.5.0
mad is deprecated.
Parameters
----------
axis : {axis_descr}
Expand All @@ -10897,6 +10900,12 @@ def mad(
{see_also}\
{examples}
"""
msg = (
"The 'mad' method is deprecated and will be removed in a future version. "
"To compute the same result, you may do `(df - df.mean()).abs().mean()`."
)
warnings.warn(msg, FutureWarning, stacklevel=find_stack_level())

if not is_bool(skipna):
warnings.warn(
"Passing None for skipna is deprecated and will raise in a future"
Expand Down
78 changes: 53 additions & 25 deletions pandas/tests/frame/test_reductions.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def assert_stat_op_calc(
skipna_alternative : function, default None
NaN-safe version of alternative
"""
warn = FutureWarning if opname == "mad" else None
f = getattr(frame, opname)

if check_dates:
Expand All @@ -88,8 +89,9 @@ def wrapper(x):
return alternative(x.values)

skipna_wrapper = tm._make_skipna_wrapper(alternative, skipna_alternative)
result0 = f(axis=0, skipna=False)
result1 = f(axis=1, skipna=False)
with tm.assert_produces_warning(warn, match="The 'mad' method is deprecated"):
result0 = f(axis=0, skipna=False)
result1 = f(axis=1, skipna=False)
tm.assert_series_equal(
result0, frame.apply(wrapper), check_dtype=check_dtype, rtol=rtol, atol=atol
)
Expand All @@ -102,8 +104,9 @@ def wrapper(x):
else:
skipna_wrapper = alternative

result0 = f(axis=0)
result1 = f(axis=1)
with tm.assert_produces_warning(warn, match="The 'mad' method is deprecated"):
result0 = f(axis=0)
result1 = f(axis=1)
tm.assert_series_equal(
result0,
frame.apply(skipna_wrapper),
Expand All @@ -125,14 +128,18 @@ def wrapper(x):
assert lcd_dtype == result1.dtype

# bad axis
with pytest.raises(ValueError, match="No axis named 2"):
f(axis=2)
with tm.assert_produces_warning(warn, match="The 'mad' method is deprecated"):
with pytest.raises(ValueError, match="No axis named 2"):
f(axis=2)

# all NA case
if has_skipna:
all_na = frame * np.NaN
r0 = getattr(all_na, opname)(axis=0)
r1 = getattr(all_na, opname)(axis=1)
with tm.assert_produces_warning(
warn, match="The 'mad' method is deprecated", raise_on_extra_warnings=False
):
r0 = getattr(all_na, opname)(axis=0)
r1 = getattr(all_na, opname)(axis=1)
if opname in ["sum", "prod"]:
unit = 1 if opname == "prod" else 0 # result for empty sum/prod
expected = Series(unit, index=r0.index, dtype=r0.dtype)
Expand Down Expand Up @@ -167,9 +174,13 @@ class TestDataFrameAnalytics:
],
)
def test_stat_op_api_float_string_frame(self, float_string_frame, axis, opname):
getattr(float_string_frame, opname)(axis=axis)
if opname not in ("nunique", "mad"):
getattr(float_string_frame, opname)(axis=axis, numeric_only=True)
warn = FutureWarning if opname == "mad" else None
with tm.assert_produces_warning(
warn, match="The 'mad' method is deprecated", raise_on_extra_warnings=False
):
getattr(float_string_frame, opname)(axis=axis)
if opname not in ("nunique", "mad"):
getattr(float_string_frame, opname)(axis=axis, numeric_only=True)

@pytest.mark.filterwarnings("ignore:Dropping of nuisance:FutureWarning")
@pytest.mark.parametrize("axis", [0, 1])
Expand Down Expand Up @@ -1424,7 +1435,9 @@ def test_frame_any_with_timedelta(self):
def test_reductions_deprecation_skipna_none(self, frame_or_series):
# GH#44580
obj = frame_or_series([1, 2, 3])
with tm.assert_produces_warning(FutureWarning, match="skipna"):
with tm.assert_produces_warning(
FutureWarning, match="skipna", raise_on_extra_warnings=False
):
obj.mad(skipna=None)

def test_reductions_deprecation_level_argument(
Expand All @@ -1445,7 +1458,7 @@ def test_reductions_skipna_none_raises(
pytest.mark.xfail(reason="Count does not accept skipna")
)
elif reduction_functions == "mad":
pytest.skip("Mad needs a deprecation cycle: GH 11787")
pytest.skip("Mad is deprecated: GH#11787")
obj = frame_or_series([1, 2, 3])
msg = 'For argument "skipna" expected type bool, received type NoneType.'
with pytest.raises(ValueError, match=msg):
Expand Down Expand Up @@ -1644,25 +1657,37 @@ def test_mad_nullable_integer(any_signed_int_ea_dtype):
df = DataFrame(np.random.randn(100, 4).astype(np.int64))
df2 = df.astype(any_signed_int_ea_dtype)

result = df2.mad()
expected = df.mad()
with tm.assert_produces_warning(
FutureWarning, match="The 'mad' method is deprecated"
):
result = df2.mad()
expected = df.mad()
tm.assert_series_equal(result, expected)

result = df2.mad(axis=1)
expected = df.mad(axis=1)
with tm.assert_produces_warning(
FutureWarning, match="The 'mad' method is deprecated"
):
result = df2.mad(axis=1)
expected = df.mad(axis=1)
tm.assert_series_equal(result, expected)

# case with NAs present
df2.iloc[::2, 1] = pd.NA

result = df2.mad()
expected = df.mad()
expected[1] = df.iloc[1::2, 1].mad()
with tm.assert_produces_warning(
FutureWarning, match="The 'mad' method is deprecated"
):
result = df2.mad()
expected = df.mad()
expected[1] = df.iloc[1::2, 1].mad()
tm.assert_series_equal(result, expected)

result = df2.mad(axis=1)
expected = df.mad(axis=1)
expected[::2] = df.T.loc[[0, 2, 3], ::2].mad()
with tm.assert_produces_warning(
FutureWarning, match="The 'mad' method is deprecated"
):
result = df2.mad(axis=1)
expected = df.mad(axis=1)
expected[::2] = df.T.loc[[0, 2, 3], ::2].mad()
tm.assert_series_equal(result, expected)


Expand All @@ -1675,8 +1700,11 @@ def test_mad_nullable_integer_all_na(any_signed_int_ea_dtype):
# case with all-NA row/column
df2.iloc[:, 1] = pd.NA # FIXME(GH#44199): this doesn't operate in-place
df2.iloc[:, 1] = pd.array([pd.NA] * len(df2), dtype=any_signed_int_ea_dtype)
result = df2.mad()
expected = df.mad()
with tm.assert_produces_warning(
FutureWarning, match="The 'mad' method is deprecated"
):
result = df2.mad()
expected = df.mad()
expected[1] = pd.NA
expected = expected.astype("Float64")
tm.assert_series_equal(result, expected)
Expand Down
6 changes: 4 additions & 2 deletions pandas/tests/groupby/aggregate/test_aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -1325,11 +1325,13 @@ def test_groupby_aggregate_directory(reduction_func):
# GH#32793
if reduction_func in ["corrwith", "nth"]:
return None
warn = FutureWarning if reduction_func == "mad" else None

obj = DataFrame([[0, 1], [0, np.nan]])

result_reduced_series = obj.groupby(0).agg(reduction_func)
result_reduced_frame = obj.groupby(0).agg({1: reduction_func})
with tm.assert_produces_warning(warn, match="The 'mad' method is deprecated"):
result_reduced_series = obj.groupby(0).agg(reduction_func)
result_reduced_frame = obj.groupby(0).agg({1: reduction_func})

if reduction_func in ["size", "ngroup"]:
# names are different: None / 1
Expand Down
4 changes: 3 additions & 1 deletion pandas/tests/groupby/test_allowlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ def test_regression_allowlist_methods(raw_frame, op, level, axis, skipna, sort):
# GH6944
# GH 17537
# explicitly test the allowlist methods
warn = FutureWarning if op == "mad" else None

if axis == 0:
frame = raw_frame
Expand All @@ -186,7 +187,8 @@ def test_regression_allowlist_methods(raw_frame, op, level, axis, skipna, sort):

if op in AGG_FUNCTIONS_WITH_SKIPNA:
grouped = frame.groupby(level=level, axis=axis, sort=sort)
result = getattr(grouped, op)(skipna=skipna)
with tm.assert_produces_warning(warn, match="The 'mad' method is deprecated"):
result = getattr(grouped, op)(skipna=skipna)
with tm.assert_produces_warning(FutureWarning):
expected = getattr(frame, op)(level=level, axis=axis, skipna=skipna)
if sort:
Expand Down
5 changes: 4 additions & 1 deletion pandas/tests/groupby/test_apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,8 @@ def test_apply_with_timezones_aware():
def test_apply_is_unchanged_when_other_methods_are_called_first(reduction_func):
# GH #34656
# GH #34271
warn = FutureWarning if reduction_func == "mad" else None

df = DataFrame(
{
"a": [99, 99, 99, 88, 88, 88],
Expand All @@ -1068,7 +1070,8 @@ def test_apply_is_unchanged_when_other_methods_are_called_first(reduction_func):
# Check output when another method is called before .apply()
grp = df.groupby(by="a")
args = {"nth": [0], "corrwith": [df]}.get(reduction_func, [])
_ = getattr(grp, reduction_func)(*args)
with tm.assert_produces_warning(warn, match="The 'mad' method is deprecated"):
_ = getattr(grp, reduction_func)(*args)
result = grp.apply(sum)
tm.assert_frame_equal(result, expected)

Expand Down
16 changes: 12 additions & 4 deletions pandas/tests/groupby/test_categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -1357,6 +1357,7 @@ def test_series_groupby_on_2_categoricals_unobserved(reduction_func, observed, r
reason="TODO: implemented SeriesGroupBy.corrwith. See GH 32293"
)
request.node.add_marker(mark)
warn = FutureWarning if reduction_func == "mad" else None

df = DataFrame(
{
Expand All @@ -1371,7 +1372,8 @@ def test_series_groupby_on_2_categoricals_unobserved(reduction_func, observed, r

series_groupby = df.groupby(["cat_1", "cat_2"], observed=observed)["value"]
agg = getattr(series_groupby, reduction_func)
result = agg(*args)
with tm.assert_produces_warning(warn, match="The 'mad' method is deprecated"):
result = agg(*args)

assert len(result) == expected_length

Expand All @@ -1390,6 +1392,7 @@ def test_series_groupby_on_2_categoricals_unobserved_zeroes_or_nans(
reason="TODO: implemented SeriesGroupBy.corrwith. See GH 32293"
)
request.node.add_marker(mark)
warn = FutureWarning if reduction_func == "mad" else None

df = DataFrame(
{
Expand All @@ -1403,7 +1406,8 @@ def test_series_groupby_on_2_categoricals_unobserved_zeroes_or_nans(

series_groupby = df.groupby(["cat_1", "cat_2"], observed=False)["value"]
agg = getattr(series_groupby, reduction_func)
result = agg(*args)
with tm.assert_produces_warning(warn, match="The 'mad' method is deprecated"):
result = agg(*args)

zero_or_nan = _results_for_groupbys_with_missing_categories[reduction_func]

Expand All @@ -1426,6 +1430,7 @@ def test_dataframe_groupby_on_2_categoricals_when_observed_is_true(reduction_fun
# does not return the categories that are not in df when observed=True
if reduction_func == "ngroup":
pytest.skip("ngroup does not return the Categories on the index")
warn = FutureWarning if reduction_func == "mad" else None

df = DataFrame(
{
Expand All @@ -1439,7 +1444,8 @@ def test_dataframe_groupby_on_2_categoricals_when_observed_is_true(reduction_fun
df_grp = df.groupby(["cat_1", "cat_2"], observed=True)

args = {"nth": [0], "corrwith": [df]}.get(reduction_func, [])
res = getattr(df_grp, reduction_func)(*args)
with tm.assert_produces_warning(warn, match="The 'mad' method is deprecated"):
res = getattr(df_grp, reduction_func)(*args)

for cat in unobserved_cats:
assert cat not in res.index
Expand All @@ -1456,6 +1462,7 @@ def test_dataframe_groupby_on_2_categoricals_when_observed_is_false(

if reduction_func == "ngroup":
pytest.skip("ngroup does not return the Categories on the index")
warn = FutureWarning if reduction_func == "mad" else None

df = DataFrame(
{
Expand All @@ -1469,7 +1476,8 @@ def test_dataframe_groupby_on_2_categoricals_when_observed_is_false(
df_grp = df.groupby(["cat_1", "cat_2"], observed=observed)

args = {"nth": [0], "corrwith": [df]}.get(reduction_func, [])
res = getattr(df_grp, reduction_func)(*args)
with tm.assert_produces_warning(warn, match="The 'mad' method is deprecated"):
res = getattr(df_grp, reduction_func)(*args)

expected = _results_for_groupbys_with_missing_categories[reduction_func]

Expand Down
10 changes: 8 additions & 2 deletions pandas/tests/groupby/test_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,11 +321,17 @@ def test_mad(self, gb, gni):
# mad
expected = DataFrame([[0], [np.nan]], columns=["B"], index=[1, 3])
expected.index.name = "A"
result = gb.mad()
with tm.assert_produces_warning(
FutureWarning, match="The 'mad' method is deprecated"
):
result = gb.mad()
tm.assert_frame_equal(result, expected)

expected = DataFrame([[1, 0.0], [3, np.nan]], columns=["A", "B"], index=[0, 1])
result = gni.mad()
with tm.assert_produces_warning(
FutureWarning, match="The 'mad' method is deprecated"
):
result = gni.mad()
tm.assert_frame_equal(result, expected)

def test_describe(self, df, gb, gni):
Expand Down

0 comments on commit 6864608

Please sign in to comment.