From e07d18664311e6e780d67c83032fe8759922ee14 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Mon, 1 Sep 2025 13:28:55 +0200 Subject: [PATCH 1/4] feat(index): arithmetic subtraction --- pandas-stubs/core/arraylike.pyi | 2 - pandas-stubs/core/frame.pyi | 37 ++--- pandas-stubs/core/indexes/base.pyi | 164 +++++++++++++++++++ pandas-stubs/core/indexes/datetimelike.pyi | 2 +- pandas-stubs/core/indexes/datetimes.pyi | 4 +- pandas-stubs/core/indexes/period.pyi | 6 +- pandas-stubs/core/indexes/timedeltas.pyi | 4 +- pandas-stubs/core/series.pyi | 160 ++++++++++++++---- tests/indexes/arithmetic/bool/test_sub.py | 95 +++++++++++ tests/indexes/arithmetic/complex/test_sub.py | 86 ++++++++++ tests/indexes/arithmetic/float/test_sub.py | 84 ++++++++++ tests/indexes/arithmetic/int/test_sub.py | 84 ++++++++++ tests/indexes/arithmetic/test_sub.py | 103 ++++++++++++ tests/series/arithmetic/bool/test_sub.py | 35 +++- 14 files changed, 807 insertions(+), 59 deletions(-) create mode 100644 tests/indexes/arithmetic/bool/test_sub.py create mode 100644 tests/indexes/arithmetic/complex/test_sub.py create mode 100644 tests/indexes/arithmetic/float/test_sub.py create mode 100644 tests/indexes/arithmetic/int/test_sub.py create mode 100644 tests/indexes/arithmetic/test_sub.py diff --git a/pandas-stubs/core/arraylike.pyi b/pandas-stubs/core/arraylike.pyi index ac981e291..bdceaf21d 100644 --- a/pandas-stubs/core/arraylike.pyi +++ b/pandas-stubs/core/arraylike.pyi @@ -19,8 +19,6 @@ class OpsMixin: def __rxor__(self, other: Any) -> Self: ... # ------------------------------------------------------------- # Arithmetic Methods - def __sub__(self, other: Any) -> Self: ... - def __rsub__(self, other: Any) -> Self: ... def __mul__(self, other: Any) -> Self: ... def __rmul__(self, other: Any) -> Self: ... # Handled by subclasses that specify only the valid values diff --git a/pandas-stubs/core/frame.pyi b/pandas-stubs/core/frame.pyi index ae4c6a830..95e1501da 100644 --- a/pandas-stubs/core/frame.pyi +++ b/pandas-stubs/core/frame.pyi @@ -1788,6 +1788,22 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack): level: Level | None = None, fill_value: float | None = None, ) -> Self: ... + def __sub__(self, other: Any) -> Self: ... + def sub( + self, + other: num | ListLike | DataFrame, + axis: Axis | None = ..., + level: Level | None = ..., + fill_value: float | None = None, + ) -> Self: ... + def __rsub__(self, other: Any) -> Self: ... + def rsub( + self, + other, + axis: Axis = ..., + level: Level | None = ..., + fill_value: float | None = None, + ) -> Self: ... @final def add_prefix(self, prefix: _str, axis: Axis | None = None) -> Self: ... @final @@ -2353,13 +2369,6 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack): level: Level | None = ..., fill_value: float | None = None, ) -> Self: ... - def rsub( - self, - other, - axis: Axis = ..., - level: Level | None = ..., - fill_value: float | None = None, - ) -> Self: ... def rtruediv( self, other, @@ -2405,20 +2414,6 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack): numeric_only: _bool = False, **kwargs: Any, ) -> Series: ... - def sub( - self, - other: num | ListLike | DataFrame, - axis: Axis | None = ..., - level: Level | None = ..., - fill_value: float | None = None, - ) -> Self: ... - def subtract( - self, - other: num | ListLike | DataFrame, - axis: Axis | None = ..., - level: Level | None = ..., - fill_value: float | None = None, - ) -> Self: ... def sum( self, axis: Axis = 0, diff --git a/pandas-stubs/core/indexes/base.pyi b/pandas-stubs/core/indexes/base.pyi index 14a02923c..03a78e67d 100644 --- a/pandas-stubs/core/indexes/base.pyi +++ b/pandas-stubs/core/indexes/base.pyi @@ -31,7 +31,9 @@ from pandas import ( PeriodDtype, PeriodIndex, Series, + Timedelta, TimedeltaIndex, + Timestamp, ) from pandas.core.arrays import ExtensionArray from pandas.core.base import IndexOpsMixin @@ -60,6 +62,7 @@ from pandas._typing import ( GenericT_co, HashableT, IgnoreRaise, + Just, Label, Level, MaskType, @@ -77,12 +80,23 @@ from pandas._typing import ( np_ndarray_complex, np_ndarray_float, np_ndarray_str, + np_ndarray_td, type_t, ) class InvalidIndexError(Exception): ... _ListLike: TypeAlias = ArrayLike | dict[_str, np.ndarray] | SequenceNotStr[S1] +_NumListLike: TypeAlias = ( + ExtensionArray + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | np_ndarray_complex + | dict[_str, np.ndarray] + | Sequence[complex] + | IndexOpsMixin[complex] +) class Index(IndexOpsMixin[S1]): __hash__: ClassVar[None] # type: ignore[assignment] @@ -626,6 +640,156 @@ class Index(IndexOpsMixin[S1]): self: Index[_str], other: _str | Sequence[_str] | np_ndarray_str | Index[_str] ) -> Index[_str]: ... @overload + def __sub__(self: Index[Never], other: DatetimeIndex) -> Never: ... + @overload + def __sub__(self: Index[Never], other: complex | _NumListLike | Index) -> Index: ... + @overload + def __sub__(self, other: Index[Never]) -> Index: ... # type: ignore[overload-overlap] + @overload + def __sub__( + self: Index[bool], + other: Just[int] | Sequence[Just[int]] | np_ndarray_anyint | Index[int], + ) -> Index[int]: ... + @overload + def __sub__( + self: Index[bool], + other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Index[float], + ) -> Index[float]: ... + @overload + def __sub__( + self: Index[int], + other: ( + int + | Sequence[int] + | np_ndarray_bool + | np_ndarray_anyint + | Index[bool] + | Index[int] + ), + ) -> Index[int]: ... + @overload + def __sub__( + self: Index[int], + other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Index[float], + ) -> Index[float]: ... + @overload + def __sub__( + self: Index[float], + other: ( + float + | Sequence[float] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Index[bool] + | Index[int] + | Index[float] + ), + ) -> Index[float]: ... + @overload + def __sub__( + self: Index[complex], + other: ( + T_COMPLEX + | Sequence[T_COMPLEX] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Index[T_COMPLEX] + ), + ) -> Index[complex]: ... + @overload + def __sub__( + self: Index[T_COMPLEX], + other: ( + Just[complex] + | Sequence[Just[complex]] + | np_ndarray_complex + | Index[complex] + ), + ) -> Index[complex]: ... + @overload + def __sub__( + self: Index[Timestamp], + other: timedelta | np.timedelta64 | np_ndarray_td | TimedeltaIndex, + ) -> DatetimeIndex: ... + @overload + def __sub__( + self: Index[Timedelta], + other: timedelta | np.timedelta64 | np_ndarray_td | TimedeltaIndex, + ) -> TimedeltaIndex: ... + @overload + def __rsub__(self: Index[Never], other: DatetimeIndex) -> Never: ... # type: ignore[misc] + @overload + def __rsub__( + self: Index[Never], other: complex | _NumListLike | Index + ) -> Index: ... + @overload + def __rsub__(self, other: Index[Never]) -> Index: ... + @overload + def __rsub__( + self: Index[bool], + other: Just[int] | Sequence[Just[int]] | np_ndarray_anyint | Index[int], + ) -> Index[int]: ... + @overload + def __rsub__( + self: Index[bool], + other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Index[float], + ) -> Index[float]: ... + @overload + def __rsub__( + self: Index[int], + other: ( + int + | Sequence[int] + | np_ndarray_bool + | np_ndarray_anyint + | Index[bool] + | Index[int] + ), + ) -> Index[int]: ... + @overload + def __rsub__( + self: Index[int], + other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Index[float], + ) -> Index[float]: ... + @overload + def __rsub__( + self: Index[float], + other: ( + float + | Sequence[float] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Index[bool] + | Index[int] + | Index[float] + ), + ) -> Index[float]: ... + @overload + def __rsub__( + self: Index[complex], + other: ( + T_COMPLEX + | Sequence[T_COMPLEX] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Index[T_COMPLEX] + ), + ) -> Index[complex]: ... + @overload + def __rsub__( + self: Index[T_COMPLEX], + other: ( + Just[complex] + | Sequence[Just[complex]] + | np_ndarray_complex + | Index[complex] + ), + ) -> Index[complex]: ... + @overload def __mul__( self: Index[int] | Index[float], other: timedelta ) -> TimedeltaIndex: ... diff --git a/pandas-stubs/core/indexes/datetimelike.pyi b/pandas-stubs/core/indexes/datetimelike.pyi index f3f86dedd..9ed25ad18 100644 --- a/pandas-stubs/core/indexes/datetimelike.pyi +++ b/pandas-stubs/core/indexes/datetimelike.pyi @@ -30,7 +30,7 @@ class DatetimeIndexOpsMixin(ExtensionIndex[S1, GenericT_co]): def argmax( self, axis: AxisIndex | None = None, skipna: bool = True, *args, **kwargs ) -> np.int64: ... - def __rsub__( # type: ignore[override] + def __rsub__( # type: ignore[misc,override] # pyright: ignore[reportIncompatibleMethodOverride] self, other: DatetimeIndexOpsMixin ) -> TimedeltaIndex: ... diff --git a/pandas-stubs/core/indexes/datetimes.pyi b/pandas-stubs/core/indexes/datetimes.pyi index 3e4e14f61..31a64514b 100644 --- a/pandas-stubs/core/indexes/datetimes.pyi +++ b/pandas-stubs/core/indexes/datetimes.pyi @@ -67,14 +67,14 @@ class DatetimeIndex( def __add__( # pyright: ignore[reportIncompatibleMethodOverride] self, other: timedelta | Timedelta | TimedeltaIndex | BaseOffset ) -> DatetimeIndex: ... - @overload + @overload # type: ignore[override] def __sub__(self, other: TimedeltaSeries) -> TimestampSeries: ... @overload def __sub__( self, other: timedelta | Timedelta | TimedeltaIndex | BaseOffset ) -> DatetimeIndex: ... @overload - def __sub__( + def __sub__( # pyright: ignore[reportIncompatibleMethodOverride] self, other: datetime | Timestamp | DatetimeIndex ) -> TimedeltaIndex: ... @final diff --git a/pandas-stubs/core/indexes/period.pyi b/pandas-stubs/core/indexes/period.pyi index 87ee0af11..98c4efad4 100644 --- a/pandas-stubs/core/indexes/period.pyi +++ b/pandas-stubs/core/indexes/period.pyi @@ -36,7 +36,7 @@ class PeriodIndex(DatetimeIndexOpsMixin[pd.Period, np.object_], PeriodIndexField ) -> Self: ... @property def values(self) -> np_1darray[np.object_]: ... - @overload + @overload # type: ignore[override] def __sub__(self, other: Period) -> Index: ... @overload def __sub__(self, other: Self) -> Index: ... @@ -45,7 +45,9 @@ class PeriodIndex(DatetimeIndexOpsMixin[pd.Period, np.object_], PeriodIndexField @overload def __sub__(self, other: NaTType) -> NaTType: ... @overload - def __sub__(self, other: TimedeltaIndex | pd.Timedelta) -> Self: ... + def __sub__( # pyright: ignore[reportIncompatibleMethodOverride] + self, other: TimedeltaIndex | pd.Timedelta + ) -> Self: ... @overload # type: ignore[override] def __rsub__(self, other: Period) -> Index: ... @overload diff --git a/pandas-stubs/core/indexes/timedeltas.pyi b/pandas-stubs/core/indexes/timedeltas.pyi index 309770dba..aa27d4e4c 100644 --- a/pandas-stubs/core/indexes/timedeltas.pyi +++ b/pandas-stubs/core/indexes/timedeltas.pyi @@ -58,7 +58,9 @@ class TimedeltaIndex( self, other: dt.timedelta | Timedelta | Self ) -> Self: ... def __radd__(self, other: dt.datetime | Timestamp | DatetimeIndex) -> DatetimeIndex: ... # type: ignore[override] - def __sub__(self, other: dt.timedelta | Timedelta | Self) -> Self: ... + def __sub__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] + self, other: dt.timedelta | Timedelta | Self + ) -> Self: ... def __mul__(self, other: num) -> Self: ... @overload # type: ignore[override] def __truediv__(self, other: num | Sequence[float]) -> Self: ... diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 364da6029..67dc176fc 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -2550,22 +2550,36 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __rxor__(self, other: int | np_ndarray_anyint | Series[int]) -> Series[int]: ... @overload - def __sub__(self: Series[Never], other: TimestampSeries) -> Never: ... + def __sub__( + self: Series[Never], other: DatetimeIndex | TimestampSeries + ) -> Never: ... @overload def __sub__( - self: Series[Never], other: complex | _NumListLike | Series + self: Series[Never], other: complex | _NumListLike | Index | Series ) -> Series: ... @overload - def __sub__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] + def __sub__(self, other: Index[Never] | Series[Never]) -> Series: ... # type: ignore[overload-overlap] @overload def __sub__( self: Series[bool], - other: Just[int] | Sequence[Just[int]] | np_ndarray_anyint | Series[int], + other: ( + Just[int] + | Sequence[Just[int]] + | np_ndarray_anyint + | Index[int] + | Series[int] + ), ) -> Series[int]: ... @overload def __sub__( self: Series[bool], - other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], + other: ( + Just[float] + | Sequence[Just[float]] + | np_ndarray_float + | Index[float] + | Series[float] + ), ) -> Series[float]: ... @overload def __sub__( @@ -2575,14 +2589,22 @@ class Series(IndexOpsMixin[S1], NDFrame): | Sequence[int] | np_ndarray_bool | np_ndarray_anyint + | Index[bool] | Series[bool] + | Index[int] | Series[int] ), ) -> Series[int]: ... @overload def __sub__( self: Series[int], - other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], + other: ( + Just[float] + | Sequence[Just[float]] + | np_ndarray_float + | Index[float] + | Series[float] + ), ) -> Series[float]: ... @overload def __sub__( @@ -2593,8 +2615,11 @@ class Series(IndexOpsMixin[S1], NDFrame): | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float + | Index[bool] | Series[bool] + | Index[int] | Series[int] + | Index[float] | Series[float] ), ) -> Series[float]: ... @@ -2607,6 +2632,7 @@ class Series(IndexOpsMixin[S1], NDFrame): | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float + | Index[T_COMPLEX] | Series[T_COMPLEX] ), ) -> Series[complex]: ... @@ -2617,6 +2643,7 @@ class Series(IndexOpsMixin[S1], NDFrame): Just[complex] | Sequence[Just[complex]] | np_ndarray_complex + | Index[complex] | Series[complex] ), ) -> Series[complex]: ... @@ -2627,8 +2654,8 @@ class Series(IndexOpsMixin[S1], NDFrame): timedelta | np.timedelta64 | np_ndarray_td - | TimedeltaSeries | TimedeltaIndex + | TimedeltaSeries ), ) -> TimestampSeries: ... @overload @@ -2638,14 +2665,14 @@ class Series(IndexOpsMixin[S1], NDFrame): timedelta | np.timedelta64 | np_ndarray_td - | TimedeltaSeries | TimedeltaIndex + | TimedeltaSeries ), ) -> TimedeltaSeries: ... @overload def sub( self: Series[Never], - other: TimestampSeries, + other: DatetimeIndex | TimestampSeries, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2653,7 +2680,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[Never], - other: complex | _NumListLike | Series, + other: complex | _NumListLike | Index | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2661,7 +2688,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( # type: ignore[overload-overlap] self, - other: Series[Never], + other: Index[Never] | Series[Never], level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2669,7 +2696,13 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[bool], - other: Just[int] | Sequence[Just[int]] | np_ndarray_anyint | Series[int], + other: ( + Just[int] + | Sequence[Just[int]] + | np_ndarray_anyint + | Index[int] + | Series[int] + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2677,7 +2710,13 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[bool], - other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], + other: ( + Just[float] + | Sequence[Just[float]] + | np_ndarray_float + | Index[float] + | Series[float] + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2690,7 +2729,9 @@ class Series(IndexOpsMixin[S1], NDFrame): | Sequence[int] | np_ndarray_bool | np_ndarray_anyint + | Index[bool] | Series[bool] + | Index[int] | Series[int] ), level: Level | None = None, @@ -2700,7 +2741,13 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[int], - other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], + other: ( + Just[float] + | Sequence[Just[float]] + | np_ndarray_float + | Index[float] + | Series[float] + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2714,8 +2761,11 @@ class Series(IndexOpsMixin[S1], NDFrame): | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float + | Index[bool] | Series[bool] + | Index[int] | Series[int] + | Index[float] | Series[float] ), level: Level | None = None, @@ -2731,6 +2781,7 @@ class Series(IndexOpsMixin[S1], NDFrame): | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float + | Index[T_COMPLEX] | Series[T_COMPLEX] ), level: Level | None = None, @@ -2744,6 +2795,7 @@ class Series(IndexOpsMixin[S1], NDFrame): Just[complex] | Sequence[Just[complex]] | np_ndarray_complex + | Index[complex] | Series[complex] ), level: Level | None = None, @@ -2757,8 +2809,8 @@ class Series(IndexOpsMixin[S1], NDFrame): timedelta | np.timedelta64 | np_ndarray_td - | TimedeltaSeries | TimedeltaIndex + | TimedeltaSeries ), level: Level | None = None, fill_value: float | None = None, @@ -2771,30 +2823,42 @@ class Series(IndexOpsMixin[S1], NDFrame): timedelta | np.timedelta64 | np_ndarray_td - | TimedeltaSeries | TimedeltaIndex + | TimedeltaSeries ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, ) -> TimedeltaSeries: ... @overload - def __rsub__(self: Series[Never], other: TimestampSeries) -> Never: ... # type: ignore[misc] + def __rsub__(self: Series[Never], other: DatetimeIndex | TimestampSeries) -> Never: ... # type: ignore[misc] @overload def __rsub__( - self: Series[Never], other: complex | _NumListLike | Series + self: Series[Never], other: complex | _NumListLike | Index | Series ) -> Series: ... @overload - def __rsub__(self, other: Series[Never]) -> Series: ... + def __rsub__(self, other: Index[Never] | Series[Never]) -> Series: ... @overload def __rsub__( self: Series[bool], - other: Just[int] | Sequence[Just[int]] | np_ndarray_anyint | Series[int], + other: ( + Just[int] + | Sequence[Just[int]] + | np_ndarray_anyint + | Index[int] + | Series[int] + ), ) -> Series[int]: ... @overload def __rsub__( self: Series[bool], - other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], + other: ( + Just[float] + | Sequence[Just[float]] + | np_ndarray_float + | Index[float] + | Series[float] + ), ) -> Series[float]: ... @overload def __rsub__( @@ -2804,14 +2868,22 @@ class Series(IndexOpsMixin[S1], NDFrame): | Sequence[int] | np_ndarray_bool | np_ndarray_anyint + | Index[bool] | Series[bool] + | Index[int] | Series[int] ), ) -> Series[int]: ... @overload def __rsub__( self: Series[int], - other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], + other: ( + Just[float] + | Sequence[Just[float]] + | np_ndarray_float + | Index[float] + | Series[float] + ), ) -> Series[float]: ... @overload def __rsub__( @@ -2822,8 +2894,11 @@ class Series(IndexOpsMixin[S1], NDFrame): | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float + | Index[bool] | Series[bool] + | Index[int] | Series[int] + | Index[float] | Series[float] ), ) -> Series[float]: ... @@ -2836,6 +2911,7 @@ class Series(IndexOpsMixin[S1], NDFrame): | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float + | Index[T_COMPLEX] | Series[T_COMPLEX] ), ) -> Series[complex]: ... @@ -2846,13 +2922,14 @@ class Series(IndexOpsMixin[S1], NDFrame): Just[complex] | Sequence[Just[complex]] | np_ndarray_complex + | Index[complex] | Series[complex] ), ) -> Series[complex]: ... @overload def rsub( self: Series[Never], - other: TimestampSeries, + other: DatetimeIndex | TimestampSeries, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2860,7 +2937,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[Never], - other: complex | _NumListLike | Series, + other: complex | _NumListLike | Index | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2868,7 +2945,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self, - other: Series[Never], + other: Index[Never] | Series[Never], level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2876,7 +2953,13 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[bool], - other: Just[int] | Sequence[Just[int]] | np_ndarray_anyint | Series[int], + other: ( + Just[int] + | Sequence[Just[int]] + | np_ndarray_anyint + | Index[int] + | Series[int] + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2884,7 +2967,13 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[bool], - other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], + other: ( + Just[float] + | Sequence[Just[float]] + | np_ndarray_float + | Index[float] + | Series[float] + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2897,7 +2986,9 @@ class Series(IndexOpsMixin[S1], NDFrame): | Sequence[int] | np_ndarray_bool | np_ndarray_anyint + | Index[bool] | Series[bool] + | Index[int] | Series[int] ), level: Level | None = None, @@ -2907,7 +2998,13 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[int], - other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], + other: ( + Just[float] + | Sequence[Just[float]] + | np_ndarray_float + | Index[float] + | Series[float] + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2921,8 +3018,11 @@ class Series(IndexOpsMixin[S1], NDFrame): | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float + | Index[bool] | Series[bool] + | Index[int] | Series[int] + | Index[float] | Series[float] ), level: Level | None = None, @@ -2938,6 +3038,7 @@ class Series(IndexOpsMixin[S1], NDFrame): | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float + | Index[T_COMPLEX] | Series[T_COMPLEX] ), level: Level | None = None, @@ -2951,6 +3052,7 @@ class Series(IndexOpsMixin[S1], NDFrame): Just[complex] | Sequence[Just[complex]] | np_ndarray_complex + | Index[complex] | Series[complex] ), level: Level | None = None, @@ -3830,7 +3932,7 @@ class TimedeltaSeries(_SeriesSubclassBase[Timedelta, np.timedelta64]): def __add__(self, other: Period) -> PeriodSeries: ... @overload def __add__( - self, other: datetime | Timestamp | TimestampSeries | DatetimeIndex + self, other: datetime | Timestamp | DatetimeIndex | TimestampSeries ) -> TimestampSeries: ... @overload def __add__( # pyright: ignore[reportIncompatibleMethodOverride] diff --git a/tests/indexes/arithmetic/bool/test_sub.py b/tests/indexes/arithmetic/bool/test_sub.py new file mode 100644 index 000000000..d86847e4a --- /dev/null +++ b/tests/indexes/arithmetic/bool/test_sub.py @@ -0,0 +1,95 @@ +import numpy as np +from numpy import typing as npt # noqa: F401 +import pandas as pd +from typing_extensions import ( + Never, + assert_type, +) + +from tests import ( + TYPE_CHECKING_INVALID_USAGE, + check, +) + +left = pd.Index([True, True, False]) # left operand + + +def test_sub_py_scalar() -> None: + """Test pd.Index[bool] - Python native scalars""" + b, i, f, c = True, 1, 1.0, 1j + + if TYPE_CHECKING_INVALID_USAGE: + _0 = left - b # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(left - i, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(left - f, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + if TYPE_CHECKING_INVALID_USAGE: + _1 = b - left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(i - left, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(f - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(c - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + + +def test_sub_py_sequence() -> None: + """Test pd.Index[bool] - Python native sequence""" + b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + + if TYPE_CHECKING_INVALID_USAGE: + _0 = left - b # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(left - i, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(left - f, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + if TYPE_CHECKING_INVALID_USAGE: + _1 = b - left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(i - left, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(f - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(c - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + + +def test_sub_numpy_array() -> None: + """Test pd.Index[bool] - numpy array""" + b = np.array([True, False, True], np.bool_) + i = np.array([2, 3, 5], np.int64) + f = np.array([1.0, 2.0, 3.0], np.float64) + c = np.array([1.1j, 2.2j, 4.1j], np.complex128) + + if TYPE_CHECKING_INVALID_USAGE: + assert_type(left - b, Never) + check(assert_type(left - i, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(left - f, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rsub__` cannot override. At runtime, they return + # `Index`s with the correct element type. + if TYPE_CHECKING_INVALID_USAGE: + assert_type(b - left, Never) + check(assert_type(i - left, "npt.NDArray[np.int64]"), pd.Index, np.integer) + check(assert_type(f - left, "npt.NDArray[np.float64]"), pd.Index, np.floating) + check( + assert_type(c - left, "npt.NDArray[np.complex128]"), + pd.Index, + np.complexfloating, + ) + + +def test_sub_pd_index() -> None: + """Test pd.Index[bool] - pandas index""" + b = pd.Index([True, False, True]) + i = pd.Index([2, 3, 5]) + f = pd.Index([1.0, 2.0, 3.0]) + c = pd.Index([1.1j, 2.2j, 4.1j]) + + if TYPE_CHECKING_INVALID_USAGE: + _0 = left - b # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(left - i, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(left - f, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + if TYPE_CHECKING_INVALID_USAGE: + _1 = b - left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(i - left, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(f - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(c - left, "pd.Index[complex]"), pd.Index, np.complexfloating) diff --git a/tests/indexes/arithmetic/complex/test_sub.py b/tests/indexes/arithmetic/complex/test_sub.py new file mode 100644 index 000000000..167f1abee --- /dev/null +++ b/tests/indexes/arithmetic/complex/test_sub.py @@ -0,0 +1,86 @@ +from typing import NoReturn + +import numpy as np +from numpy import typing as npt # noqa: F401 +import pandas as pd +from typing_extensions import assert_type + +from tests import check + +# left operand +left = pd.Index([1j, 2j, 3j]) + + +def test_sub_py_scalar() -> None: + """Test pd.Index[complex] - Python native scalars""" + b, i, f, c = True, 1, 1.0, 1j + + check(assert_type(left - b, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(left - i, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(left - f, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + check(assert_type(b - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(i - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(f - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(c - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + + +def test_sub_py_sequence() -> None: + """Test pd.Index[complex] - Python native sequence""" + b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + + check(assert_type(left - b, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(left - i, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(left - f, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + check(assert_type(b - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(i - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(f - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(c - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + + +def test_sub_numpy_array() -> None: + """Test pd.Index[complex] - numpy array""" + b = np.array([True, False, True], np.bool_) + i = np.array([2, 3, 5], np.int64) + f = np.array([1.0, 2.0, 3.0], np.float64) + c = np.array([1.1j, 2.2j, 4.1j], np.complex128) + + check(assert_type(left - b, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(left - i, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(left - f, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rsub__` cannot override. At runtime, they return + # `Index`s with the correct element type. + check(assert_type(b - left, NoReturn), pd.Index, np.complexfloating) + check(assert_type(i - left, "npt.NDArray[np.int64]"), pd.Index, np.complexfloating) + check( + assert_type(f - left, "npt.NDArray[np.float64]"), pd.Index, np.complexfloating + ) + check( + assert_type(c - left, "npt.NDArray[np.complex128]"), + pd.Index, + np.complexfloating, + ) + + +def test_sub_pd_index() -> None: + """Test pd.Index[complex] - pandas index""" + b = pd.Index([True, False, True]) + i = pd.Index([2, 3, 5]) + f = pd.Index([1.0, 2.0, 3.0]) + c = pd.Index([1.1j, 2.2j, 4.1j]) + + check(assert_type(left - b, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(left - i, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(left - f, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + check(assert_type(b - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(i - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(f - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + check(assert_type(c - left, "pd.Index[complex]"), pd.Index, np.complexfloating) diff --git a/tests/indexes/arithmetic/float/test_sub.py b/tests/indexes/arithmetic/float/test_sub.py new file mode 100644 index 000000000..57f39adf0 --- /dev/null +++ b/tests/indexes/arithmetic/float/test_sub.py @@ -0,0 +1,84 @@ +from typing import NoReturn + +import numpy as np +from numpy import typing as npt # noqa: F401 +import pandas as pd +from typing_extensions import assert_type + +from tests import check + +# left operand +left = pd.Index([1.0, 2.0, 3.0]) + + +def test_sub_py_scalar() -> None: + """Test pd.Index[float] - Python native scalars""" + b, i, f, c = True, 1, 1.0, 1j + + check(assert_type(left - b, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - i, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - f, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + check(assert_type(b - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(i - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(f - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(c - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + + +def test_sub_py_sequence() -> None: + """Test pd.Index[float] - Python native sequence""" + b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + + check(assert_type(left - b, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - i, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - f, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + check(assert_type(b - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(i - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(f - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(c - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + + +def test_sub_numpy_array() -> None: + """Test pd.Index[float] - numpy array""" + b = np.array([True, False, True], np.bool_) + i = np.array([2, 3, 5], np.int64) + f = np.array([1.0, 2.0, 3.0], np.float64) + c = np.array([1.1j, 2.2j, 4.1j], np.complex128) + + check(assert_type(left - b, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - i, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - f, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rsub__` cannot override. At runtime, they return + # `Index`s with the correct element type. + check(assert_type(b - left, NoReturn), pd.Index, np.floating) + check(assert_type(i - left, "npt.NDArray[np.int64]"), pd.Index, np.floating) + check(assert_type(f - left, "npt.NDArray[np.float64]"), pd.Index, np.floating) + check( + assert_type(c - left, "npt.NDArray[np.complex128]"), + pd.Index, + np.complexfloating, + ) + + +def test_sub_pd_index() -> None: + """Test pd.Index[float] - pandas index""" + b = pd.Index([True, False, True]) + i = pd.Index([2, 3, 5]) + f = pd.Index([1.0, 2.0, 3.0]) + c = pd.Index([1.1j, 2.2j, 4.1j]) + + check(assert_type(left - b, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - i, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - f, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + check(assert_type(b - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(i - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(f - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(c - left, "pd.Index[complex]"), pd.Index, np.complexfloating) diff --git a/tests/indexes/arithmetic/int/test_sub.py b/tests/indexes/arithmetic/int/test_sub.py new file mode 100644 index 000000000..7b8c6b01f --- /dev/null +++ b/tests/indexes/arithmetic/int/test_sub.py @@ -0,0 +1,84 @@ +from typing import NoReturn + +import numpy as np +from numpy import typing as npt # noqa: F401 +import pandas as pd +from typing_extensions import assert_type + +from tests import check + +# left operand +left = pd.Index([1, 2, 3]) + + +def test_sub_py_scalar() -> None: + """Test pd.Index[int] - Python native scalars""" + b, i, f, c = True, 1, 1.0, 1j + + check(assert_type(left - b, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(left - i, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(left - f, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + check(assert_type(b - left, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(i - left, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(f - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(c - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + + +def test_sub_py_sequence() -> None: + """Test pd.Index[int] - Python native sequence""" + b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + + check(assert_type(left - b, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(left - i, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(left - f, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + check(assert_type(b - left, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(i - left, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(f - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(c - left, "pd.Index[complex]"), pd.Index, np.complexfloating) + + +def test_sub_numpy_array() -> None: + """Test pd.Index[int] - numpy array""" + b = np.array([True, False, True], np.bool_) + i = np.array([2, 3, 5], np.int64) + f = np.array([1.0, 2.0, 3.0], np.float64) + c = np.array([1.1j, 2.2j, 4.1j], np.complex128) + + check(assert_type(left - b, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(left - i, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(left - f, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rsub__` cannot override. At runtime, they return + # `Index`s with the correct element type. + check(assert_type(b - left, NoReturn), pd.Index, np.integer) + check(assert_type(i - left, "npt.NDArray[np.int64]"), pd.Index, np.integer) + check(assert_type(f - left, "npt.NDArray[np.float64]"), pd.Index, np.floating) + check( + assert_type(c - left, "npt.NDArray[np.complex128]"), + pd.Index, + np.complexfloating, + ) + + +def test_sub_pd_index() -> None: + """Test pd.Index[int] - pandas index""" + b = pd.Index([True, False, True]) + i = pd.Index([2, 3, 5]) + f = pd.Index([1.0, 2.0, 3.0]) + c = pd.Index([1.1j, 2.2j, 4.1j]) + + check(assert_type(left - b, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(left - i, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(left - f, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(left - c, "pd.Index[complex]"), pd.Index, np.complexfloating) + + check(assert_type(b - left, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(i - left, "pd.Index[int]"), pd.Index, np.integer) + check(assert_type(f - left, "pd.Index[float]"), pd.Index, np.floating) + check(assert_type(c - left, "pd.Index[complex]"), pd.Index, np.complexfloating) diff --git a/tests/indexes/arithmetic/test_sub.py b/tests/indexes/arithmetic/test_sub.py new file mode 100644 index 000000000..4ec4719c1 --- /dev/null +++ b/tests/indexes/arithmetic/test_sub.py @@ -0,0 +1,103 @@ +from typing import NoReturn + +import numpy as np +from numpy import typing as npt # noqa: F401 +import pandas as pd +from typing_extensions import assert_type + +from tests import ( + TYPE_CHECKING_INVALID_USAGE, + check, +) + +# left operands +left_i = pd.MultiIndex.from_tuples([(1,), (2,), (3,)]).levels[0] +left_str = pd.MultiIndex.from_tuples([("1",), ("2",), ("3_",)]).levels[0] + + +def test_sub_i_py_scalar() -> None: + """Test pd.Index[Any] (int) - Python native scalars""" + b, i, f, c = True, 1, 1.0, 1j + + check(assert_type(left_i - b, pd.Index), pd.Index) + check(assert_type(left_i - i, pd.Index), pd.Index) + check(assert_type(left_i - f, pd.Index), pd.Index) + check(assert_type(left_i - c, pd.Index), pd.Index) + + check(assert_type(b - left_i, pd.Index), pd.Index) + check(assert_type(i - left_i, pd.Index), pd.Index) + check(assert_type(f - left_i, pd.Index), pd.Index) + check(assert_type(c - left_i, pd.Index), pd.Index) + + +def test_sub_i_py_sequence() -> None: + """Test pd.Index[Any] (int) - Python native sequence""" + b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + + check(assert_type(left_i - b, pd.Index), pd.Index) + check(assert_type(left_i - i, pd.Index), pd.Index) + check(assert_type(left_i - f, pd.Index), pd.Index) + check(assert_type(left_i - c, pd.Index), pd.Index) + + check(assert_type(b - left_i, pd.Index), pd.Index) + check(assert_type(i - left_i, pd.Index), pd.Index) + check(assert_type(f - left_i, pd.Index), pd.Index) + check(assert_type(c - left_i, pd.Index), pd.Index) + + +def test_sub_i_numpy_array() -> None: + """Test pd.Index[Any] (int) - numpy array""" + b = np.array([True, False, True], np.bool_) + i = np.array([2, 3, 5], np.int64) + f = np.array([1.0, 2.0, 3.0], np.float64) + c = np.array([1.1j, 2.2j, 4.1j], np.complex128) + + check(assert_type(left_i - b, pd.Index), pd.Index) + check(assert_type(left_i - i, pd.Index), pd.Index) + check(assert_type(left_i - f, pd.Index), pd.Index) + check(assert_type(left_i - c, pd.Index), pd.Index) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rsub__` cannot override. At runtime, they return + # `Index`s. + # `mypy` thinks the return types are `Any`, which is a bug. + check(assert_type(b - left_i, NoReturn), pd.Index) # type: ignore[assert-type] + check( + assert_type(i - left_i, "npt.NDArray[np.int64]"), pd.Index # type: ignore[assert-type] + ) + check( + assert_type(f - left_i, "npt.NDArray[np.float64]"), pd.Index # type: ignore[assert-type] + ) + check( + assert_type(c - left_i, "npt.NDArray[np.complex128]"), pd.Index # type: ignore[assert-type] + ) + + +def test_sub_i_pd_index() -> None: + """Test pd.Index[Any] (int) - pandas index""" + a = pd.MultiIndex.from_tuples([(1,), (2,), (3,)]).levels[0] + b = pd.Index([True, False, True]) + i = pd.Index([2, 3, 5]) + f = pd.Index([1.0, 2.0, 3.0]) + c = pd.Index([1.1j, 2.2j, 4.1j]) + + check(assert_type(left_i - a, pd.Index), pd.Index) + check(assert_type(left_i - b, pd.Index), pd.Index) + check(assert_type(left_i - i, pd.Index), pd.Index) + check(assert_type(left_i - f, pd.Index), pd.Index) + check(assert_type(left_i - c, pd.Index), pd.Index) + + check(assert_type(a - left_i, pd.Index), pd.Index) + check(assert_type(b - left_i, pd.Index), pd.Index) + check(assert_type(i - left_i, pd.Index), pd.Index) + check(assert_type(f - left_i, pd.Index), pd.Index) + check(assert_type(c - left_i, pd.Index), pd.Index) + + +def test_sub_str_py_str() -> None: + """Test pd.Series[Any] (int) - Python str""" + s = "abc" + + if TYPE_CHECKING_INVALID_USAGE: + _0 = left_i - s # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + _1 = s - left_i # type: ignore[operator] # pyright:ignore[reportOperatorIssue] diff --git a/tests/series/arithmetic/bool/test_sub.py b/tests/series/arithmetic/bool/test_sub.py index 958cb8e3b..2fc39c909 100644 --- a/tests/series/arithmetic/bool/test_sub.py +++ b/tests/series/arithmetic/bool/test_sub.py @@ -124,7 +124,40 @@ def test_sub_pd_series() -> None: f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) - # In the following two cases, mypy fails to recognise the second operand as pd.Series[bool] + if TYPE_CHECKING_INVALID_USAGE: + _0 = left - b # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(left - i, "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left - f, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + + if TYPE_CHECKING_INVALID_USAGE: + _1 = b - left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(i - left, "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(f - left, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(c - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + + if TYPE_CHECKING_INVALID_USAGE: + left.sub(b) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + check(assert_type(left.sub(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.sub(f), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.sub(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + if TYPE_CHECKING_INVALID_USAGE: + left.rsub(b) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + check(assert_type(left.rsub(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.rsub(f), "pd.Series[float]"), pd.Series, np.floating) + check( + assert_type(left.rsub(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_sub_pd_index() -> None: + """Test pd.Series[bool] - pandas index""" + b = pd.Index([True, False, True]) + i = pd.Index([2, 3, 5]) + f = pd.Index([1.0, 2.0, 3.0]) + c = pd.Index([1.1j, 2.2j, 4.1j]) + if TYPE_CHECKING_INVALID_USAGE: _0 = left - b # type: ignore[operator] # pyright: ignore[reportOperatorIssue] check(assert_type(left - i, "pd.Series[int]"), pd.Series, np.integer) From 5780e7bc9b3d25ae0783d2e0438f3e72b3d1a75a Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Tue, 2 Sep 2025 15:15:47 +0200 Subject: [PATCH 2/4] refactor(series): remove duplicated tests --- tests/indexes/test_indexes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/indexes/test_indexes.py b/tests/indexes/test_indexes.py index 990d7fa4f..ba7686876 100644 --- a/tests/indexes/test_indexes.py +++ b/tests/indexes/test_indexes.py @@ -262,11 +262,9 @@ def test_types_to_numpy() -> None: def test_index_arithmetic() -> None: # GH 287 idx = pd.Index([1, 2.2, 3], dtype=float) - check(assert_type(idx - 3, "pd.Index[float]"), pd.Index, np.float64) check(assert_type(idx * 3, "pd.Index[float]"), pd.Index, np.float64) check(assert_type(idx / 3, "pd.Index[float]"), pd.Index, np.float64) check(assert_type(idx // 3, "pd.Index[float]"), pd.Index, np.float64) - check(assert_type(3 - idx, "pd.Index[float]"), pd.Index, np.float64) check(assert_type(3 * idx, "pd.Index[float]"), pd.Index, np.float64) check(assert_type(3 / idx, "pd.Index[float]"), pd.Index, np.float64) check(assert_type(3 // idx, "pd.Index[float]"), pd.Index, np.float64) From 1877b98c701c36a80500ad587bb283f6de0d64e7 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 3 Sep 2025 00:34:05 +0200 Subject: [PATCH 3/4] fix(comment): #1360 --- pandas-stubs/core/base.pyi | 15 +++++++++ pandas-stubs/core/indexes/base.pyi | 40 +++++------------------- pandas-stubs/core/indexes/datetimes.pyi | 7 +++-- pandas-stubs/core/indexes/timedeltas.pyi | 3 +- pandas-stubs/core/series.pyi | 33 ++++++++----------- tests/indexes/arithmetic/test_sub.py | 3 +- 6 files changed, 44 insertions(+), 57 deletions(-) diff --git a/pandas-stubs/core/base.pyi b/pandas-stubs/core/base.pyi index eb42fc0e4..bc21dccea 100644 --- a/pandas-stubs/core/base.pyi +++ b/pandas-stubs/core/base.pyi @@ -1,6 +1,7 @@ from collections.abc import ( Hashable, Iterator, + Sequence, ) from typing import ( Any, @@ -34,10 +35,24 @@ from pandas._typing import ( SequenceNotStr, SupportsDType, np_1darray, + np_ndarray_anyint, + np_ndarray_bool, + np_ndarray_complex, + np_ndarray_float, ) from pandas.util._decorators import cache_readonly _ListLike: TypeAlias = ArrayLike | dict[str, np.ndarray] | SequenceNotStr[S1] +NumListLike: TypeAlias = ( + ExtensionArray + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | np_ndarray_complex + | dict[str, np.ndarray] + | Sequence[complex] + | IndexOpsMixin[complex] +) class NoNewAttributesMixin: def __setattr__(self, key: str, value: Any) -> None: ... diff --git a/pandas-stubs/core/indexes/base.pyi b/pandas-stubs/core/indexes/base.pyi index 03a78e67d..d8bb3a351 100644 --- a/pandas-stubs/core/indexes/base.pyi +++ b/pandas-stubs/core/indexes/base.pyi @@ -31,17 +31,18 @@ from pandas import ( PeriodDtype, PeriodIndex, Series, - Timedelta, TimedeltaIndex, - Timestamp, ) from pandas.core.arrays import ExtensionArray -from pandas.core.base import IndexOpsMixin +from pandas.core.base import ( + IndexOpsMixin, + NumListLike, + _ListLike, +) from pandas.core.strings.accessor import StringMethods from typing_extensions import ( Never, Self, - TypeAlias, ) from pandas._libs.interval import _OrderableT @@ -80,24 +81,11 @@ from pandas._typing import ( np_ndarray_complex, np_ndarray_float, np_ndarray_str, - np_ndarray_td, type_t, ) class InvalidIndexError(Exception): ... -_ListLike: TypeAlias = ArrayLike | dict[_str, np.ndarray] | SequenceNotStr[S1] -_NumListLike: TypeAlias = ( - ExtensionArray - | np_ndarray_bool - | np_ndarray_anyint - | np_ndarray_float - | np_ndarray_complex - | dict[_str, np.ndarray] - | Sequence[complex] - | IndexOpsMixin[complex] -) - class Index(IndexOpsMixin[S1]): __hash__: ClassVar[None] # type: ignore[assignment] # overloads with additional dtypes @@ -642,9 +630,9 @@ class Index(IndexOpsMixin[S1]): @overload def __sub__(self: Index[Never], other: DatetimeIndex) -> Never: ... @overload - def __sub__(self: Index[Never], other: complex | _NumListLike | Index) -> Index: ... + def __sub__(self: Index[Never], other: complex | NumListLike | Index) -> Index: ... @overload - def __sub__(self, other: Index[Never]) -> Index: ... # type: ignore[overload-overlap] + def __sub__(self, other: Index[Never]) -> Index: ... @overload def __sub__( self: Index[bool], @@ -709,21 +697,9 @@ class Index(IndexOpsMixin[S1]): ), ) -> Index[complex]: ... @overload - def __sub__( - self: Index[Timestamp], - other: timedelta | np.timedelta64 | np_ndarray_td | TimedeltaIndex, - ) -> DatetimeIndex: ... - @overload - def __sub__( - self: Index[Timedelta], - other: timedelta | np.timedelta64 | np_ndarray_td | TimedeltaIndex, - ) -> TimedeltaIndex: ... - @overload def __rsub__(self: Index[Never], other: DatetimeIndex) -> Never: ... # type: ignore[misc] @overload - def __rsub__( - self: Index[Never], other: complex | _NumListLike | Index - ) -> Index: ... + def __rsub__(self: Index[Never], other: complex | NumListLike | Index) -> Index: ... @overload def __rsub__(self, other: Index[Never]) -> Index: ... @overload diff --git a/pandas-stubs/core/indexes/datetimes.pyi b/pandas-stubs/core/indexes/datetimes.pyi index 31a64514b..acbaeae5e 100644 --- a/pandas-stubs/core/indexes/datetimes.pyi +++ b/pandas-stubs/core/indexes/datetimes.pyi @@ -37,6 +37,8 @@ from pandas._typing import ( IntervalClosedType, TimeUnit, TimeZones, + np_ndarray_dt, + np_ndarray_td, ) from pandas.core.dtypes.dtypes import DatetimeTZDtype @@ -71,11 +73,12 @@ class DatetimeIndex( def __sub__(self, other: TimedeltaSeries) -> TimestampSeries: ... @overload def __sub__( - self, other: timedelta | Timedelta | TimedeltaIndex | BaseOffset + self, + other: timedelta | np.timedelta64 | np_ndarray_td | TimedeltaIndex | BaseOffset, ) -> DatetimeIndex: ... @overload def __sub__( # pyright: ignore[reportIncompatibleMethodOverride] - self, other: datetime | Timestamp | DatetimeIndex + self, other: datetime | np.datetime64 | np_ndarray_dt | DatetimeIndex ) -> TimedeltaIndex: ... @final def to_series(self, index=..., name: Hashable = ...) -> TimestampSeries: ... diff --git a/pandas-stubs/core/indexes/timedeltas.pyi b/pandas-stubs/core/indexes/timedeltas.pyi index aa27d4e4c..5c998f80c 100644 --- a/pandas-stubs/core/indexes/timedeltas.pyi +++ b/pandas-stubs/core/indexes/timedeltas.pyi @@ -30,6 +30,7 @@ from pandas._libs.tslibs import BaseOffset from pandas._typing import ( AxesData, TimedeltaConvertibleTypes, + np_ndarray_td, num, ) @@ -59,7 +60,7 @@ class TimedeltaIndex( ) -> Self: ... def __radd__(self, other: dt.datetime | Timestamp | DatetimeIndex) -> DatetimeIndex: ... # type: ignore[override] def __sub__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] - self, other: dt.timedelta | Timedelta | Self + self, other: dt.timedelta | np.timedelta64 | np_ndarray_td | Self ) -> Self: ... def __mul__(self, other: num) -> Self: ... @overload # type: ignore[override] diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 67dc176fc..f31493ae9 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -54,7 +54,11 @@ from pandas.core.arrays.base import ExtensionArray from pandas.core.arrays.categorical import CategoricalAccessor from pandas.core.arrays.datetimes import DatetimeArray from pandas.core.arrays.interval import IntervalArray -from pandas.core.base import IndexOpsMixin +from pandas.core.base import ( + IndexOpsMixin, + NumListLike, + _ListLike, +) from pandas.core.frame import DataFrame from pandas.core.generic import NDFrame from pandas.core.groupby.generic import SeriesGroupBy @@ -258,20 +262,9 @@ class _LocIndexerSeries(_LocIndexer, Generic[S1]): value: S1 | ArrayLike | Series[S1] | None, ) -> None: ... -_ListLike: TypeAlias = ArrayLike | dict[_str, np.ndarray] | SequenceNotStr[S1] _ListLikeS1: TypeAlias = ( ArrayLike | dict[_str, np.ndarray] | Sequence[S1] | IndexOpsMixin[S1] ) -_NumListLike: TypeAlias = ( - ExtensionArray - | np_ndarray_bool - | np_ndarray_anyint - | np_ndarray_float - | np_ndarray_complex - | dict[_str, np.ndarray] - | Sequence[complex] - | IndexOpsMixin[complex] -) class Series(IndexOpsMixin[S1], NDFrame): # Define __index__ because mypy thinks Series follows protocol `SupportsIndex` https://github.com/pandas-dev/pandas-stubs/pull/1332#discussion_r2285648790 @@ -2125,7 +2118,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[_bool]: ... @overload def __mul__( - self: Series[Never], other: complex | _NumListLike | Series + self: Series[Never], other: complex | NumListLike | Series ) -> Series: ... @overload def __mul__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @@ -2321,7 +2314,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> TimedeltaSeries: ... @overload def __rmul__( - self: Series[Never], other: complex | _NumListLike | Series + self: Series[Never], other: complex | NumListLike | Series ) -> Series: ... @overload def __rmul__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @@ -2555,7 +2548,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Never: ... @overload def __sub__( - self: Series[Never], other: complex | _NumListLike | Index | Series + self: Series[Never], other: complex | NumListLike | Index | Series ) -> Series: ... @overload def __sub__(self, other: Index[Never] | Series[Never]) -> Series: ... # type: ignore[overload-overlap] @@ -2680,7 +2673,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[Never], - other: complex | _NumListLike | Index | Series, + other: complex | NumListLike | Index | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2834,7 +2827,7 @@ class Series(IndexOpsMixin[S1], NDFrame): def __rsub__(self: Series[Never], other: DatetimeIndex | TimestampSeries) -> Never: ... # type: ignore[misc] @overload def __rsub__( - self: Series[Never], other: complex | _NumListLike | Index | Series + self: Series[Never], other: complex | NumListLike | Index | Series ) -> Series: ... @overload def __rsub__(self, other: Index[Never] | Series[Never]) -> Series: ... @@ -2937,7 +2930,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[Never], - other: complex | _NumListLike | Index | Series, + other: complex | NumListLike | Index | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -3061,7 +3054,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[complex]: ... @overload def __truediv__( # type:ignore[overload-overlap] - self: Series[Never], other: complex | _NumListLike | Series + self: Series[Never], other: complex | NumListLike | Series ) -> Series: ... @overload def __truediv__(self, other: Series[Never]) -> Series: ... @@ -3257,7 +3250,7 @@ class Series(IndexOpsMixin[S1], NDFrame): div = truediv @overload def __rtruediv__( # type:ignore[overload-overlap] - self: Series[Never], other: complex | _NumListLike | Series + self: Series[Never], other: complex | NumListLike | Series ) -> Series: ... @overload def __rtruediv__(self, other: Series[Never]) -> Series: ... diff --git a/tests/indexes/arithmetic/test_sub.py b/tests/indexes/arithmetic/test_sub.py index 4ec4719c1..9cda57aed 100644 --- a/tests/indexes/arithmetic/test_sub.py +++ b/tests/indexes/arithmetic/test_sub.py @@ -10,9 +10,8 @@ check, ) -# left operands +# left operand left_i = pd.MultiIndex.from_tuples([(1,), (2,), (3,)]).levels[0] -left_str = pd.MultiIndex.from_tuples([("1",), ("2",), ("3_",)]).levels[0] def test_sub_i_py_scalar() -> None: From 2e60c24266aefa000948b99d805bb41d686e783d Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 3 Sep 2025 00:40:55 +0200 Subject: [PATCH 4/4] fix(ty): #1360 make it happy --- pandas-stubs/core/base.pyi | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pandas-stubs/core/base.pyi b/pandas-stubs/core/base.pyi index bc21dccea..c116a717e 100644 --- a/pandas-stubs/core/base.pyi +++ b/pandas-stubs/core/base.pyi @@ -43,16 +43,6 @@ from pandas._typing import ( from pandas.util._decorators import cache_readonly _ListLike: TypeAlias = ArrayLike | dict[str, np.ndarray] | SequenceNotStr[S1] -NumListLike: TypeAlias = ( - ExtensionArray - | np_ndarray_bool - | np_ndarray_anyint - | np_ndarray_float - | np_ndarray_complex - | dict[str, np.ndarray] - | Sequence[complex] - | IndexOpsMixin[complex] -) class NoNewAttributesMixin: def __setattr__(self, key: str, value: Any) -> None: ... @@ -175,3 +165,14 @@ class IndexOpsMixin(OpsMixin, Generic[S1, GenericT_co]): sorter: _ListLike | None = ..., ) -> np.intp: ... def drop_duplicates(self, *, keep: DropKeep = ...) -> Self: ... + +NumListLike: TypeAlias = ( + ExtensionArray + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | np_ndarray_complex + | dict[str, np.ndarray] + | Sequence[complex] + | IndexOpsMixin[complex] +)