Skip to content

TYP: disallow decorator preserves function signature #33521

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 9 commits into from
May 2, 2020
16 changes: 14 additions & 2 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -8305,7 +8305,7 @@ def nunique(self, axis=0, dropna=True) -> Series:
"""
return self.apply(Series.nunique, axis=axis, dropna=dropna)

def idxmin(self, axis=0, skipna=True) -> Series:
def idxmin(self, axis: Axis = 0, skipna: bool = True) -> Series:
"""
Return index of first occurrence of minimum over requested axis.

Expand Down Expand Up @@ -8368,11 +8368,17 @@ def idxmin(self, axis=0, skipna=True) -> Series:
"""
axis = self._get_axis_number(axis)
indices = nanops.nanargmin(self.values, axis=axis, skipna=skipna)

# indices will always be np.ndarray since axis is not None and
# values is a 2d array for DataFrame
# error: Item "int" of "Union[int, Any]" has no attribute "__iter__"
indices = cast(np.ndarray, indices)
Copy link
Member

Choose a reason for hiding this comment

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

What’s the reasoning here for cast instead of assert? The latter is generally easier to refactor

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

@WillAyd is this concern a blocker?


index = self._get_axis(axis)
result = [index[i] if i >= 0 else np.nan for i in indices]
return Series(result, index=self._get_agg_axis(axis))

def idxmax(self, axis=0, skipna=True) -> Series:
def idxmax(self, axis: Axis = 0, skipna: bool = True) -> Series:
"""
Return index of first occurrence of maximum over requested axis.

Expand Down Expand Up @@ -8435,6 +8441,12 @@ def idxmax(self, axis=0, skipna=True) -> Series:
"""
axis = self._get_axis_number(axis)
indices = nanops.nanargmax(self.values, axis=axis, skipna=skipna)

# indices will always be np.ndarray since axis is not None and
# values is a 2d array for DataFrame
# error: Item "int" of "Union[int, Any]" has no attribute "__iter__"
indices = cast(np.ndarray, indices)

index = self._get_axis(axis)
result = [index[i] if i >= 0 else np.nan for i in indices]
return Series(result, index=self._get_agg_axis(axis))
Expand Down
2 changes: 1 addition & 1 deletion pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ def _construct_axes_from_arguments(
return axes, kwargs

@classmethod
def _get_axis_number(cls, axis):
def _get_axis_number(cls, axis) -> int:
axis = cls._AXIS_ALIASES.get(axis, axis)
if is_integer(axis):
if axis in cls._AXIS_NAMES:
Expand Down
51 changes: 37 additions & 14 deletions pandas/core/nanops.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import functools
import itertools
import operator
from typing import Any, Optional, Tuple, Union
from typing import TYPE_CHECKING, Any, Optional, Tuple, Union, cast

import numpy as np

from pandas._config import get_option

from pandas._libs import NaT, Period, Timedelta, Timestamp, iNaT, lib
from pandas._typing import ArrayLike, Dtype, Scalar
from pandas._typing import ArrayLike, Dtype, F, Scalar
from pandas.compat._optional import import_optional_dependency

from pandas.core.dtypes.cast import _int64_max, maybe_upcast_putmask
Expand All @@ -34,6 +34,9 @@

from pandas.core.construction import extract_array

if TYPE_CHECKING:
from pandas import Series # noqa: F401

bn = import_optional_dependency("bottleneck", raise_on_missing=False, on_version="warn")
_BOTTLENECK_INSTALLED = bn is not None
_USE_BOTTLENECK = False
Expand All @@ -57,7 +60,7 @@ def __init__(self, *dtypes):
def check(self, obj) -> bool:
return hasattr(obj, "dtype") and issubclass(obj.dtype.type, self.dtypes)

def __call__(self, f):
def __call__(self, f: F) -> F:
@functools.wraps(f)
def _f(*args, **kwargs):
obj_iter = itertools.chain(args, kwargs.values())
Expand All @@ -78,7 +81,7 @@ def _f(*args, **kwargs):
raise TypeError(e) from e
raise

return _f
return cast(F, _f)


class bottleneck_switch:
Expand Down Expand Up @@ -879,31 +882,41 @@ def reduction(

@disallow("O")
def nanargmax(
values: np.ndarray,
values: Union[np.ndarray, "Series"],
axis: Optional[int] = None,
skipna: bool = True,
mask: Optional[np.ndarray] = None,
) -> int:
) -> Union[int, np.ndarray]:
"""
Parameters
----------
values : ndarray
values : ndarray or Series
axis: int, optional
skipna : bool, default True
mask : ndarray[bool], optional
nan-mask if known

Returns
-------
result : int
The index of max value in specified axis or -1 in the NA case
result : int or ndarray[int]
The index/indices of max value in specified axis or -1 in the NA case

Examples
--------
>>> import pandas.core.nanops as nanops
>>> s = pd.Series([1, 2, 3, np.nan, 4])
>>> nanops.nanargmax(s)
4

>>> arr = np.array(range(12), dtype=np.float64).reshape(4, 3)
>>> arr[2:, 2] = np.nan
>>> arr
array([[ 0., 1., 2.],
[ 3., 4., 5.],
[ 6., 7., nan],
[ 9., 10., nan]])
>>> nanops.nanargmax(arr, axis=1)
array([2, 2, 1, 1], dtype=int64)
"""
values, mask, dtype, _, _ = _get_values(
values, True, fill_value_typ="-inf", mask=mask
Expand All @@ -915,31 +928,41 @@ def nanargmax(

@disallow("O")
def nanargmin(
values: np.ndarray,
values: Union[np.ndarray, "Series"],
axis: Optional[int] = None,
skipna: bool = True,
mask: Optional[np.ndarray] = None,
) -> int:
) -> Union[int, np.ndarray]:
"""
Parameters
----------
values : ndarray
values : ndarray or Series
axis: int, optional
skipna : bool, default True
mask : ndarray[bool], optional
nan-mask if known

Returns
-------
result : int
The index of min value in specified axis or -1 in the NA case
result : int or ndarray[int]
The index/indices of min value in specified axis or -1 in the NA case

Examples
--------
>>> import pandas.core.nanops as nanops
>>> s = pd.Series([1, 2, 3, np.nan, 4])
>>> nanops.nanargmin(s)
0

>>> arr = np.array(range(12), dtype=np.float64).reshape(4, 3)
>>> arr[2:, 0] = np.nan
>>> arr
array([[ 0., 1., 2.],
[ 3., 4., 5.],
[nan, 7., 8.],
[nan, 10., 11.]])
>>> nanops.nanargmin(arr, axis=1)
array([0, 0, 1, 1], dtype=int64)
"""
values, mask, dtype, _, _ = _get_values(
values, True, fill_value_typ="+inf", mask=mask
Expand Down