From c9c46b0a6fb8e90d88db60bf94c9aa89df44b93d Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Fri, 8 Aug 2025 16:32:05 +0200 Subject: [PATCH 01/15] feat: arithmetic sub --- pandas-stubs/core/series.pyi | 282 +++++++++++++++++--- tests/series/arithmetic/complex/test_sub.py | 129 +++++++++ tests/series/arithmetic/float/test_sub.py | 111 ++++++++ tests/series/arithmetic/int/test_sub.py | 111 ++++++++ tests/series/arithmetic/test_sub.py | 106 ++++++++ tests/series/test_series.py | 4 +- 6 files changed, 711 insertions(+), 32 deletions(-) create mode 100644 tests/series/arithmetic/complex/test_sub.py create mode 100644 tests/series/arithmetic/float/test_sub.py create mode 100644 tests/series/arithmetic/int/test_sub.py create mode 100644 tests/series/arithmetic/test_sub.py diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index d206adcb..c57123f4 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -2071,7 +2071,6 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[bool]: ... @overload def __ror__(self, other: int | np_ndarray_anyint | Series[int]) -> Series[int]: ... - def __rsub__(self, other: num | _ListLike | Series[S1]) -> Series: ... # ignore needed for mypy as we want different results based on the arguments @overload # type: ignore[override] def __rxor__( # pyright: ignore[reportOverlappingOverload] @@ -2080,6 +2079,47 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __rxor__(self, other: int | np_ndarray_anyint | Series[int]) -> Series[int]: ... @overload + def __sub__( + self, other: Timestamp | datetime | TimestampSeries + ) -> TimedeltaSeries: ... + @overload + def __sub__(self: Series[Never], other: complex | _ListLike | Series) -> Series: ... + @overload + def __sub__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] + @overload + def __sub__( + self: Series[int], other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX] + ) -> Series[_T_COMPLEX]: ... + @overload + def __sub__(self: Series[int], other: np_ndarray_anyint) -> Series[int]: ... + @overload + def __sub__(self: Series[int], other: np_ndarray_float) -> Series[float]: ... + @overload + def __sub__(self: Series[int], other: np_ndarray_complex) -> Series[complex]: ... + @overload + def __sub__( + self: Series[float], + other: int | Sequence[int] | np_ndarray_anyint | np_ndarray_float | Series[int], + ) -> Series[float]: ... + @overload + def __sub__( + self: Series[float], + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + ) -> Series[_T_COMPLEX]: ... + @overload + def __sub__(self: Series[float], other: np_ndarray_complex) -> Series[complex]: ... + @overload + def __sub__( + self: Series[complex], + other: ( + Sequence[_T_COMPLEX] + | Series[_T_COMPLEX] + | np_ndarray_anyint + | np_ndarray_float + | np_ndarray_complex + ), + ) -> Series[complex]: ... + @overload def __sub__( self: Series[Timestamp], other: Timedelta | TimedeltaSeries | TimedeltaIndex | np.timedelta64, @@ -2090,14 +2130,217 @@ class Series(IndexOpsMixin[S1], NDFrame): other: Timedelta | TimedeltaSeries | TimedeltaIndex | np.timedelta64, ) -> TimedeltaSeries: ... @overload - def __sub__( - self, other: Timestamp | datetime | TimestampSeries - ) -> TimedeltaSeries: ... + def __sub__(self, other: S1 | Series[S1]) -> Self: ... + @overload + def sub( + self: Series[Never], + other: Scalar | _ListLike | Series, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series: ... + @overload + def sub( + self: Series[int], + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[_T_COMPLEX]: ... + @overload + def sub( + self: Series[int], + other: np_ndarray_anyint, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[int]: ... + @overload + def sub( + self: Series[int], + other: np_ndarray_float, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[float]: ... + @overload + def sub( + self: Series[int], + other: np_ndarray_complex, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload + def sub( + self: Series[float], + other: int | Sequence[int] | np_ndarray_anyint | np_ndarray_float | Series[int], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[float]: ... + @overload + def sub( + self: Series[float], + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[_T_COMPLEX]: ... + @overload + def sub( + self: Series[float], + other: np_ndarray_complex, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload + def sub( + self: Series[complex], + other: ( + Sequence[_T_COMPLEX] + | np_ndarray_anyint + | np_ndarray_float + | np_ndarray_complex + | Series[_T_COMPLEX] + ), + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload + def sub( + self, + other: S1 | Series[S1], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Self: ... + @overload + def __rsub__(self: Series[Never], other: Scalar | _ListLike) -> Series: ... + @overload + def __rsub__( + self: Series[int], other: _T_COMPLEX | Sequence[_T_COMPLEX] + ) -> Series[_T_COMPLEX]: ... @overload - def __sub__(self, other: num | _ListLike | Series) -> Series: ... + def __rsub__(self: Series[int], other: np_ndarray_anyint) -> Series[int]: ... + @overload + def __rsub__(self: Series[int], other: np_ndarray_float) -> Series[float]: ... + @overload + def __rsub__(self: Series[int], other: np_ndarray_complex) -> Series[complex]: ... + @overload + def __rsub__( + self: Series[float], + other: int | Sequence[int] | np_ndarray_anyint | np_ndarray_float, + ) -> Series[float]: ... + @overload + def __rsub__( + self: Series[float], other: _T_COMPLEX | Sequence[_T_COMPLEX] + ) -> Series[_T_COMPLEX]: ... + @overload + def __rsub__(self: Series[float], other: np_ndarray_complex) -> Series[complex]: ... + @overload + def __rsub__( + self: Series[complex], + other: ( + np_ndarray_anyint + | np_ndarray_float + | np_ndarray_complex + | Sequence[_T_COMPLEX] + ), + ) -> Series[complex]: ... + @overload + def __rsub__(self, other: S1) -> Self: ... + @overload + def rsub( + self: Series[Never], + other: Scalar | _ListLike | Series, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series: ... + @overload + def rsub( + self: Series[int], + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[_T_COMPLEX]: ... + @overload + def rsub( + self: Series[int], + other: np_ndarray_anyint, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[int]: ... + @overload + def rsub( + self: Series[int], + other: np_ndarray_float, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[float]: ... + @overload + def rsub( + self: Series[int], + other: np_ndarray_complex, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload + def rsub( + self: Series[float], + other: int | Sequence[int] | np_ndarray_anyint | np_ndarray_float | Series[int], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[float]: ... + @overload + def rsub( + self: Series[float], + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[_T_COMPLEX]: ... + @overload + def rsub( + self: Series[float], + other: np_ndarray_complex, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload + def rsub( + self: Series[complex], + other: ( + Sequence[_T_COMPLEX] + | np_ndarray_anyint + | np_ndarray_float + | np_ndarray_complex + | Series[_T_COMPLEX] + ), + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload + def rsub( + self, + other: S1 | Series[S1], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Self: ... @overload def __truediv__( - self: Series[Never], other: Scalar | _ListLike | Series + self: Series[Never], other: complex | _ListLike | Series ) -> Series: ... @overload def __truediv__(self, other: Series[Never]) -> Series: ... @@ -2145,7 +2388,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def truediv( self: Series[Never], - other: Scalar | _ListLike | Series, + other: complex | _ListLike | Series, level: Level | None = None, fill_value: float | None = None, axis: AxisIndex = 0, @@ -2231,7 +2474,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series: ... div = truediv @overload - def __rtruediv__(self: Series[Never], other: Scalar | _ListLike) -> Series: ... + def __rtruediv__(self: Series[Never], other: complex | _ListLike) -> Series: ... @overload def __rtruediv__( self: Series[int], @@ -2274,7 +2517,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rtruediv( self: Series[Never], - other: Scalar | _ListLike | Series, + other: complex | _ListLike | Series, level: Level | None = None, fill_value: float | None = None, axis: AxisIndex = 0, @@ -2675,13 +2918,6 @@ class Series(IndexOpsMixin[S1], NDFrame): fill_value: float | None = None, axis: AxisIndex = ..., ) -> Series[S1]: ... - def rsub( - self, - other: Series[S1] | Scalar, - level: Level | None = ..., - fill_value: float | None = None, - axis: AxisIndex = ..., - ) -> Series[S1]: ... def sem( self, axis: AxisIndex | None = 0, @@ -2705,20 +2941,6 @@ class Series(IndexOpsMixin[S1], NDFrame): numeric_only: _bool = False, **kwargs: Any, ) -> float: ... - def sub( - self, - other: num | _ListLike | Series[S1], - level: Level | None = ..., - fill_value: float | None = None, - axis: AxisIndex | None = 0, - ) -> Series[S1]: ... - def subtract( - self, - other: num | _ListLike | Series[S1], - level: Level | None = ..., - fill_value: float | None = None, - axis: AxisIndex | None = 0, - ) -> Series[S1]: ... @overload def sum( self: Series[Never], diff --git a/tests/series/arithmetic/complex/test_sub.py b/tests/series/arithmetic/complex/test_sub.py new file mode 100644 index 00000000..ebe20da3 --- /dev/null +++ b/tests/series/arithmetic/complex/test_sub.py @@ -0,0 +1,129 @@ +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 = pd.Series([1j, 2j, 3j]) # left operand + + +def test_sub_py_scalar() -> None: + """Test pd.Series[complex] - Python native scalars""" + i, f, c = 1, 1.0, 1j + + check(assert_type(left - i, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left - f, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(i - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(f - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(c - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.sub(i), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(f), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check( + assert_type(left.rsub(i), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rsub(f), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rsub(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_sub_py_sequence() -> None: + """Test pd.Series[complex] - Python native sequence""" + i, f, c = [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + + check(assert_type(left - i, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left - f, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(i - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(f - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(c - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.sub(i), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(f), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check( + assert_type(left.rsub(i), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rsub(f), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rsub(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_sub_numpy_array() -> None: + """Test pd.Series[complex] - numpy array""" + 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, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left - f, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rsub__` cannot override. At runtime, they return + # `Series`s with the correct element type. + check(assert_type(i - left, "npt.NDArray[np.int64]"), pd.Series, np.complexfloating) + check( + assert_type(f - left, "npt.NDArray[np.float64]"), pd.Series, np.complexfloating + ) + check( + assert_type(c - left, "npt.NDArray[np.complex128]"), + pd.Series, + np.complexfloating, + ) + + check(assert_type(left.sub(i), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(f), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check( + assert_type(left.rsub(i), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rsub(f), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rsub(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_sub_pd_series() -> None: + """Test pd.Series[complex] - pandas series""" + i = pd.Series([2, 3, 5]) + f = pd.Series([1.0, 2.0, 3.0]) + c = pd.Series([1.1j, 2.2j, 4.1j]) + + check(assert_type(left - i, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left - f, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(i - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(f - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(c - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.sub(i), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(f), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check( + assert_type(left.rsub(i), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rsub(f), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rsub(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) diff --git a/tests/series/arithmetic/float/test_sub.py b/tests/series/arithmetic/float/test_sub.py new file mode 100644 index 00000000..da2d572e --- /dev/null +++ b/tests/series/arithmetic/float/test_sub.py @@ -0,0 +1,111 @@ +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 = pd.Series([1.0, 2.0, 3.0]) # left operand + + +def test_sub_py_scalar() -> None: + """Test pd.Series[float] - Python native scalars""" + i, f, c = 1, 1.0, 1j + + check(assert_type(left - i, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left - f, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(i - left, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(f - left, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(c - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.sub(i), "pd.Series[float]"), pd.Series, np.floating) + 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) + + check(assert_type(left.rsub(i), "pd.Series[float]"), pd.Series, np.floating) + 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_py_sequence() -> None: + """Test pd.Series[float] - Python native sequence""" + i, f, c = [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + + check(assert_type(left - i, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left - f, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(i - left, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(f - left, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(c - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.sub(i), "pd.Series[float]"), pd.Series, np.floating) + 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) + + check(assert_type(left.rsub(i), "pd.Series[float]"), pd.Series, np.floating) + 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_numpy_array() -> None: + """Test pd.Series[float] - numpy array""" + 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, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left - f, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rsub__` cannot override. At runtime, they return + # `Series`s with the correct element type. + check(assert_type(i - left, "npt.NDArray[np.int64]"), pd.Series, np.floating) + check(assert_type(f - left, "npt.NDArray[np.float64]"), pd.Series, np.floating) + check( + assert_type(c - left, "npt.NDArray[np.complex128]"), + pd.Series, + np.complexfloating, + ) + + check(assert_type(left.sub(i), "pd.Series[float]"), pd.Series, np.floating) + 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) + + check(assert_type(left.rsub(i), "pd.Series[float]"), pd.Series, np.floating) + 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_series() -> None: + """Test pd.Series[float] - pandas series""" + i = pd.Series([2, 3, 5]) + f = pd.Series([1.0, 2.0, 3.0]) + c = pd.Series([1.1j, 2.2j, 4.1j]) + + check(assert_type(left - i, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left - f, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(i - left, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(f - left, "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(c - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.sub(i), "pd.Series[float]"), pd.Series, np.floating) + 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) + + check(assert_type(left.rsub(i), "pd.Series[float]"), pd.Series, np.floating) + 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 + ) diff --git a/tests/series/arithmetic/int/test_sub.py b/tests/series/arithmetic/int/test_sub.py new file mode 100644 index 00000000..5451663d --- /dev/null +++ b/tests/series/arithmetic/int/test_sub.py @@ -0,0 +1,111 @@ +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 = pd.Series([1, 2, 3]) # left operand + + +def test_sub_py_scalar() -> None: + """Test pd.Series[int] - Python native scalars""" + i, f, c = 1, 1.0, 1j + + 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) + + 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) + + 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) + + 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_py_sequence() -> None: + """Test pd.Series[int] - Python native sequence""" + i, f, c = [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + + 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) + + 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) + + 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) + + 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_numpy_array() -> None: + """Test pd.Series[int] - numpy array""" + 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, "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) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rsub__` cannot override. At runtime, they return + # `Series`s with the correct element type. + check(assert_type(i - left, "npt.NDArray[np.int64]"), pd.Series, np.integer) + check(assert_type(f - left, "npt.NDArray[np.float64]"), pd.Series, np.floating) + check( + assert_type(c - left, "npt.NDArray[np.complex128]"), + pd.Series, + np.complexfloating, + ) + + 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) + + 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_series() -> None: + """Test pd.Series[int] - pandas series""" + i = pd.Series([2, 3, 5]) + f = pd.Series([1.0, 2.0, 3.0]) + c = pd.Series([1.1j, 2.2j, 4.1j]) + + 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) + + 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) + + 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) + + 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 + ) diff --git a/tests/series/arithmetic/test_sub.py b/tests/series/arithmetic/test_sub.py new file mode 100644 index 00000000..20e7420d --- /dev/null +++ b/tests/series/arithmetic/test_sub.py @@ -0,0 +1,106 @@ +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 = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand + + +def test_sub_py_scalar() -> None: + """Test pd.Series[Any] - Python native scalars""" + i, f, c = 1, 1.0, 1j + + check(assert_type(left - i, pd.Series), pd.Series) + check(assert_type(left - f, pd.Series), pd.Series) + check(assert_type(left - c, pd.Series), pd.Series) + + check(assert_type(i - left, pd.Series), pd.Series) + check(assert_type(f - left, pd.Series), pd.Series) + check(assert_type(c - left, pd.Series), pd.Series) + + check(assert_type(left.sub(i), pd.Series), pd.Series) + check(assert_type(left.sub(f), pd.Series), pd.Series) + check(assert_type(left.sub(c), pd.Series), pd.Series) + + check(assert_type(left.rsub(i), pd.Series), pd.Series) + check(assert_type(left.rsub(f), pd.Series), pd.Series) + check(assert_type(left.rsub(c), pd.Series), pd.Series) + + +def test_sub_py_sequence() -> None: + """Test pd.Series[Any] - Python native sequence""" + i, f, c = [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + + check(assert_type(left - i, pd.Series), pd.Series) + check(assert_type(left - f, pd.Series), pd.Series) + check(assert_type(left - c, pd.Series), pd.Series) + + check(assert_type(i - left, pd.Series), pd.Series) + check(assert_type(f - left, pd.Series), pd.Series) + check(assert_type(c - left, pd.Series), pd.Series) + + check(assert_type(left.sub(i), pd.Series), pd.Series) + check(assert_type(left.sub(f), pd.Series), pd.Series) + check(assert_type(left.sub(c), pd.Series), pd.Series) + + check(assert_type(left.rsub(i), pd.Series), pd.Series) + check(assert_type(left.rsub(f), pd.Series), pd.Series) + check(assert_type(left.rsub(c), pd.Series), pd.Series) + + +def test_sub_numpy_array() -> None: + """Test pd.Series[Any] - numpy array""" + 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, pd.Series), pd.Series) + check(assert_type(left - f, pd.Series), pd.Series) + check(assert_type(left - c, pd.Series), pd.Series) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rsub__` cannot override. At runtime, they return + # `Series`s. + # `mypy` thinks the return types are `Any`, which is a bug. + check( + assert_type(i - left, "npt.NDArray[np.int64]"), pd.Series # type: ignore[assert-type] + ) + check( + assert_type(f - left, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] + ) + check( + assert_type(c - left, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] + ) + + check(assert_type(left.sub(i), pd.Series), pd.Series) + check(assert_type(left.sub(f), pd.Series), pd.Series) + check(assert_type(left.sub(c), pd.Series), pd.Series) + + check(assert_type(left.rsub(i), pd.Series), pd.Series) + check(assert_type(left.rsub(f), pd.Series), pd.Series) + check(assert_type(left.rsub(c), pd.Series), pd.Series) + + +def test_sub_pd_series() -> None: + """Test pd.Series[Any] - pandas series""" + i = pd.Series([2, 3, 5]) + f = pd.Series([1.0, 2.0, 3.0]) + c = pd.Series([1.1j, 2.2j, 4.1j]) + + check(assert_type(left - i, pd.Series), pd.Series) + check(assert_type(left - f, pd.Series), pd.Series) + check(assert_type(left - c, pd.Series), pd.Series) + + check(assert_type(i - left, pd.Series), pd.Series) + check(assert_type(f - left, pd.Series), pd.Series) + check(assert_type(c - left, pd.Series), pd.Series) + + check(assert_type(left.sub(i), pd.Series), pd.Series) + check(assert_type(left.sub(f), pd.Series), pd.Series) + check(assert_type(left.sub(c), pd.Series), pd.Series) + + check(assert_type(left.rsub(i), pd.Series), pd.Series) + check(assert_type(left.rsub(f), pd.Series), pd.Series) + check(assert_type(left.rsub(c), pd.Series), pd.Series) diff --git a/tests/series/test_series.py b/tests/series/test_series.py index 4d1486dc..250a0e5e 100644 --- a/tests/series/test_series.py +++ b/tests/series/test_series.py @@ -819,7 +819,7 @@ def test_types_element_wise_arithmetic() -> None: check(assert_type(s.add(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) # TODO this one below should type pd.Series[int] - check(assert_type(s - s2, pd.Series), pd.Series, np.integer) + check(assert_type(s - s2, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s.sub(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) # TODO these two below should type pd.Series[int] @@ -1643,7 +1643,7 @@ def test_series_mul() -> None: sm = s * 4 check(assert_type(sm, pd.Series), pd.Series) ss = s - 4 - check(assert_type(ss, pd.Series), pd.Series) + check(assert_type(ss, "pd.Series[int]"), pd.Series, np.integer) sm2 = s * s check(assert_type(sm2, pd.Series), pd.Series) sp = s + 4 From e3447cf91a71444b2dd1f82e72b8f650c18a0161 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Fri, 8 Aug 2025 18:36:11 +0200 Subject: [PATCH 02/15] fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1312#discussion_r2263481612 --- tests/series/test_series.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/series/test_series.py b/tests/series/test_series.py index 250a0e5e..d9723588 100644 --- a/tests/series/test_series.py +++ b/tests/series/test_series.py @@ -818,7 +818,6 @@ def test_types_element_wise_arithmetic() -> None: check(assert_type(s + s2, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s.add(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - # TODO this one below should type pd.Series[int] check(assert_type(s - s2, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s.sub(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) From 672009edb82b98d52e45b86330b3c939897f72a8 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Mon, 11 Aug 2025 18:07:07 +0200 Subject: [PATCH 03/15] feat(comment): https://github.com/pandas-dev/pandas-stubs/pull/1312#discussion_r2263578761 --- pandas-stubs/core/series.pyi | 175 ++++++++++++++------ tests/series/arithmetic/bool/test_sub.py | 113 +++++++++++++ tests/series/arithmetic/complex/test_sub.py | 32 +++- tests/series/arithmetic/float/test_sub.py | 24 ++- tests/series/arithmetic/int/test_sub.py | 6 +- tests/series/arithmetic/test_sub.py | 24 ++- 6 files changed, 314 insertions(+), 60 deletions(-) create mode 100644 tests/series/arithmetic/bool/test_sub.py diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index c57123f4..507f653e 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -188,6 +188,7 @@ from pandas.core.dtypes.dtypes import CategoricalDtype from pandas.plotting import PlotAccessor +_T_NUM_NON_BOOL = TypeVar("_T_NUM_NON_BOOL", int, float, complex) _T_INT = TypeVar("_T_INT", bound=int) _T_COMPLEX = TypeVar("_T_COMPLEX", bound=complex) @@ -2087,19 +2088,38 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __sub__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @overload + def __sub__( + self: Series[bool], + other: _T_NUM_NON_BOOL | Sequence[_T_NUM_NON_BOOL] | Series[_T_NUM_NON_BOOL], + ) -> Series[_T_NUM_NON_BOOL]: ... + @overload + def __sub__(self: Series[bool], other: np_ndarray_anyint) -> Series[int]: ... + @overload + def __sub__(self: Series[bool], other: np_ndarray_float) -> Series[float]: ... + @overload + def __sub__( + self: Series[int], + other: ( + bool | Sequence[bool] | np_ndarray_bool | np_ndarray_anyint | Series[bool] + ), + ) -> Series[int]: ... + @overload def __sub__( self: Series[int], other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX] ) -> Series[_T_COMPLEX]: ... @overload - def __sub__(self: Series[int], other: np_ndarray_anyint) -> Series[int]: ... - @overload def __sub__(self: Series[int], other: np_ndarray_float) -> Series[float]: ... @overload - def __sub__(self: Series[int], other: np_ndarray_complex) -> Series[complex]: ... - @overload def __sub__( self: Series[float], - other: int | Sequence[int] | np_ndarray_anyint | np_ndarray_float | Series[int], + other: ( + _T_INT + | Sequence[_T_INT] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Series[_T_INT] + ), ) -> Series[float]: ... @overload def __sub__( @@ -2107,19 +2127,21 @@ class Series(IndexOpsMixin[S1], NDFrame): other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], ) -> Series[_T_COMPLEX]: ... @overload - def __sub__(self: Series[float], other: np_ndarray_complex) -> Series[complex]: ... - @overload def __sub__( self: Series[complex], other: ( Sequence[_T_COMPLEX] - | Series[_T_COMPLEX] + | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float - | np_ndarray_complex + | Series[_T_COMPLEX] ), ) -> Series[complex]: ... @overload + def __sub__( + self: Series[_T_COMPLEX], other: np_ndarray_complex + ) -> Series[complex]: ... + @overload def __sub__( self: Series[Timestamp], other: Timedelta | TimedeltaSeries | TimedeltaIndex | np.timedelta64, @@ -2134,22 +2156,22 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[Never], - other: Scalar | _ListLike | Series, + other: complex | _ListLike | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, ) -> Series: ... @overload def sub( - self: Series[int], - other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + self: Series[bool], + other: _T_NUM_NON_BOOL | Sequence[_T_NUM_NON_BOOL] | Series[_T_NUM_NON_BOOL], level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[_T_COMPLEX]: ... + ) -> Series[_T_NUM_NON_BOOL]: ... @overload def sub( - self: Series[int], + self: Series[bool], other: np_ndarray_anyint, level: Level | None = None, fill_value: float | None = None, @@ -2158,23 +2180,25 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[int], - other: np_ndarray_float, + other: ( + bool | Sequence[bool] | np_ndarray_bool | np_ndarray_anyint | Series[bool] + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[float]: ... + ) -> Series[int]: ... @overload def sub( self: Series[int], - other: np_ndarray_complex, + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[complex]: ... + ) -> Series[_T_COMPLEX]: ... @overload def sub( - self: Series[float], - other: int | Sequence[int] | np_ndarray_anyint | np_ndarray_float | Series[int], + self: Series[_T_INT], + other: np_ndarray_float, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2182,27 +2206,34 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[float], - other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + other: ( + _T_INT + | Sequence[_T_INT] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Series[_T_INT] + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[_T_COMPLEX]: ... + ) -> Series[float]: ... @overload def sub( self: Series[float], - other: np_ndarray_complex, + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[complex]: ... + ) -> Series[_T_COMPLEX]: ... @overload def sub( self: Series[complex], other: ( Sequence[_T_COMPLEX] + | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float - | np_ndarray_complex | Series[_T_COMPLEX] ), level: Level | None = None, @@ -2210,6 +2241,14 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> Series[complex]: ... @overload + def sub( + self: Series[_T_COMPLEX], + other: np_ndarray_complex, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload def sub( self, other: S1 | Series[S1], @@ -2218,39 +2257,54 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> Self: ... @overload - def __rsub__(self: Series[Never], other: Scalar | _ListLike) -> Series: ... + def __rsub__(self: Series[Never], other: complex | _ListLike) -> Series: ... @overload def __rsub__( - self: Series[int], other: _T_COMPLEX | Sequence[_T_COMPLEX] - ) -> Series[_T_COMPLEX]: ... + self: Series[bool], other: _T_NUM_NON_BOOL | Sequence[_T_NUM_NON_BOOL] + ) -> Series[_T_NUM_NON_BOOL]: ... @overload - def __rsub__(self: Series[int], other: np_ndarray_anyint) -> Series[int]: ... + def __rsub__(self: Series[bool], other: np_ndarray_anyint) -> Series[int]: ... @overload - def __rsub__(self: Series[int], other: np_ndarray_float) -> Series[float]: ... + def __rsub__( + self: Series[int], + other: bool | Sequence[bool] | np_ndarray_bool | np_ndarray_anyint, + ) -> Series[int]: ... @overload - def __rsub__(self: Series[int], other: np_ndarray_complex) -> Series[complex]: ... + def __rsub__( + self: Series[int], other: _T_COMPLEX | Sequence[_T_COMPLEX] + ) -> Series[_T_COMPLEX]: ... + @overload + def __rsub__(self: Series[_T_INT], other: np_ndarray_float) -> Series[float]: ... @overload def __rsub__( self: Series[float], - other: int | Sequence[int] | np_ndarray_anyint | np_ndarray_float, + other: ( + _T_INT + | Sequence[_T_INT] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + ), ) -> Series[float]: ... @overload def __rsub__( self: Series[float], other: _T_COMPLEX | Sequence[_T_COMPLEX] ) -> Series[_T_COMPLEX]: ... @overload - def __rsub__(self: Series[float], other: np_ndarray_complex) -> Series[complex]: ... - @overload def __rsub__( self: Series[complex], other: ( - np_ndarray_anyint + Sequence[_T_COMPLEX] + | np_ndarray_bool + | np_ndarray_anyint | np_ndarray_float - | np_ndarray_complex - | Sequence[_T_COMPLEX] ), ) -> Series[complex]: ... @overload + def __rsub__( + self: Series[_T_COMPLEX], other: np_ndarray_complex + ) -> Series[complex]: ... + @overload def __rsub__(self, other: S1) -> Self: ... @overload def rsub( @@ -2262,15 +2316,15 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series: ... @overload def rsub( - self: Series[int], - other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + self: Series[bool], + other: _T_NUM_NON_BOOL | Sequence[_T_NUM_NON_BOOL] | Series[_T_NUM_NON_BOOL], level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[_T_COMPLEX]: ... + ) -> Series[_T_NUM_NON_BOOL]: ... @overload def rsub( - self: Series[int], + self: Series[bool], other: np_ndarray_anyint, level: Level | None = None, fill_value: float | None = None, @@ -2279,23 +2333,25 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[int], - other: np_ndarray_float, + other: ( + bool | Sequence[bool] | np_ndarray_bool | np_ndarray_anyint | Series[bool] + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[float]: ... + ) -> Series[int]: ... @overload def rsub( self: Series[int], - other: np_ndarray_complex, + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[complex]: ... + ) -> Series[_T_COMPLEX]: ... @overload def rsub( - self: Series[float], - other: int | Sequence[int] | np_ndarray_anyint | np_ndarray_float | Series[int], + self: Series[_T_INT], + other: np_ndarray_float, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2303,27 +2359,34 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[float], - other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + other: ( + _T_INT + | Sequence[_T_INT] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Series[_T_INT] + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[_T_COMPLEX]: ... + ) -> Series[float]: ... @overload def rsub( self: Series[float], - other: np_ndarray_complex, + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[complex]: ... + ) -> Series[_T_COMPLEX]: ... @overload def rsub( self: Series[complex], other: ( Sequence[_T_COMPLEX] + | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float - | np_ndarray_complex | Series[_T_COMPLEX] ), level: Level | None = None, @@ -2331,6 +2394,14 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> Series[complex]: ... @overload + def rsub( + self: Series[_T_COMPLEX], + other: np_ndarray_complex, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload def rsub( self, other: S1 | Series[S1], diff --git a/tests/series/arithmetic/bool/test_sub.py b/tests/series/arithmetic/bool/test_sub.py new file mode 100644 index 00000000..223453cf --- /dev/null +++ b/tests/series/arithmetic/bool/test_sub.py @@ -0,0 +1,113 @@ +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 = pd.Series([True, True, False]) # left operand + + +def test_sub_py_scalar() -> None: + """Test pd.Series[bool] - Python native scalars""" + b, i, f, c = True, 1, 1.0, 1j + + 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) + + 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) + + 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) + + 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_py_sequence() -> None: + """Test pd.Series[bool] - 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, "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) + + 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) + + 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) + + 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_numpy_array() -> None: + """Test pd.Series[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) + + 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) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rsub__` cannot override. At runtime, they return + # `Series`s with the correct element type. + check(assert_type(i - left, "npt.NDArray[np.int64]"), pd.Series, np.integer) + check(assert_type(f - left, "npt.NDArray[np.float64]"), pd.Series, np.floating) + check( + assert_type(c - left, "npt.NDArray[np.complex128]"), + pd.Series, + np.complexfloating, + ) + + 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) + + 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_series() -> None: + """Test pd.Series[bool] - pandas series""" + b = pd.Series([True, False, True]) + i = pd.Series([2, 3, 5]) + f = pd.Series([1.0, 2.0, 3.0]) + c = pd.Series([1.1j, 2.2j, 4.1j]) + + 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) + + 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) + + 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) + + 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 + ) diff --git a/tests/series/arithmetic/complex/test_sub.py b/tests/series/arithmetic/complex/test_sub.py index ebe20da3..5a7cc0de 100644 --- a/tests/series/arithmetic/complex/test_sub.py +++ b/tests/series/arithmetic/complex/test_sub.py @@ -1,3 +1,5 @@ +from typing import NoReturn + import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd @@ -10,20 +12,26 @@ def test_sub_py_scalar() -> None: """Test pd.Series[complex] - Python native scalars""" - i, f, c = 1, 1.0, 1j + b, i, f, c = True, 1, 1.0, 1j + check(assert_type(left - b, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left - i, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left - f, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(b - left, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(i - left, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(f - left, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(c - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(b), "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left.sub(i), "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left.sub(f), "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left.sub(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + check( + assert_type(left.rsub(b), "pd.Series[complex]"), pd.Series, np.complexfloating + ) check( assert_type(left.rsub(i), "pd.Series[complex]"), pd.Series, np.complexfloating ) @@ -37,20 +45,26 @@ def test_sub_py_scalar() -> None: def test_sub_py_sequence() -> None: """Test pd.Series[complex] - Python native sequence""" - i, f, c = [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + 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.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left - i, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left - f, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(b - left, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(i - left, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(f - left, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(c - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(b), "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left.sub(i), "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left.sub(f), "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left.sub(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + check( + assert_type(left.rsub(b), "pd.Series[complex]"), pd.Series, np.complexfloating + ) check( assert_type(left.rsub(i), "pd.Series[complex]"), pd.Series, np.complexfloating ) @@ -64,10 +78,12 @@ def test_sub_py_sequence() -> None: def test_sub_numpy_array() -> None: """Test pd.Series[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.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left - i, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left - f, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) @@ -75,6 +91,7 @@ def test_sub_numpy_array() -> None: # `numpy` typing gives the corresponding `ndarray`s in the static type # checking, where our `__rsub__` cannot override. At runtime, they return # `Series`s with the correct element type. + check(assert_type(b - left, NoReturn), pd.Series, np.complexfloating) check(assert_type(i - left, "npt.NDArray[np.int64]"), pd.Series, np.complexfloating) check( assert_type(f - left, "npt.NDArray[np.float64]"), pd.Series, np.complexfloating @@ -85,10 +102,14 @@ def test_sub_numpy_array() -> None: np.complexfloating, ) + check(assert_type(left.sub(b), "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left.sub(i), "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left.sub(f), "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left.sub(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + check( + assert_type(left.rsub(b), "pd.Series[complex]"), pd.Series, np.complexfloating + ) check( assert_type(left.rsub(i), "pd.Series[complex]"), pd.Series, np.complexfloating ) @@ -102,22 +123,29 @@ def test_sub_numpy_array() -> None: def test_sub_pd_series() -> None: """Test pd.Series[complex] - pandas series""" + b = pd.Series([True, False, True]) i = pd.Series([2, 3, 5]) f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) + check(assert_type(left - b, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left - i, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left - f, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(b - left, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(i - left, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(f - left, "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(c - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(b), "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left.sub(i), "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left.sub(f), "pd.Series[complex]"), pd.Series, np.complexfloating) check(assert_type(left.sub(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + check( + assert_type(left.rsub(b), "pd.Series[complex]"), pd.Series, np.complexfloating + ) check( assert_type(left.rsub(i), "pd.Series[complex]"), pd.Series, np.complexfloating ) diff --git a/tests/series/arithmetic/float/test_sub.py b/tests/series/arithmetic/float/test_sub.py index da2d572e..c7095765 100644 --- a/tests/series/arithmetic/float/test_sub.py +++ b/tests/series/arithmetic/float/test_sub.py @@ -1,3 +1,5 @@ +from typing import NoReturn + import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd @@ -10,20 +12,24 @@ def test_sub_py_scalar() -> None: """Test pd.Series[float] - Python native scalars""" - i, f, c = 1, 1.0, 1j + b, i, f, c = True, 1, 1.0, 1j + check(assert_type(left - b, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left - i, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left - f, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(b - left, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(i - left, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(f - left, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(c - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(b), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.sub(i), "pd.Series[float]"), pd.Series, np.floating) 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) + check(assert_type(left.rsub(b), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.rsub(i), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.rsub(f), "pd.Series[float]"), pd.Series, np.floating) check( @@ -33,20 +39,24 @@ def test_sub_py_scalar() -> None: def test_sub_py_sequence() -> None: """Test pd.Series[float] - Python native sequence""" - i, f, c = [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + 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.Series[float]"), pd.Series, np.floating) check(assert_type(left - i, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left - f, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(b - left, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(i - left, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(f - left, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(c - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(b), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.sub(i), "pd.Series[float]"), pd.Series, np.floating) 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) + check(assert_type(left.rsub(b), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.rsub(i), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.rsub(f), "pd.Series[float]"), pd.Series, np.floating) check( @@ -56,10 +66,12 @@ def test_sub_py_sequence() -> None: def test_sub_numpy_array() -> None: """Test pd.Series[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.Series[float]"), pd.Series, np.floating) check(assert_type(left - i, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left - f, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) @@ -67,6 +79,7 @@ def test_sub_numpy_array() -> None: # `numpy` typing gives the corresponding `ndarray`s in the static type # checking, where our `__rsub__` cannot override. At runtime, they return # `Series`s with the correct element type. + check(assert_type(b - left, NoReturn), pd.Series, np.floating) check(assert_type(i - left, "npt.NDArray[np.int64]"), pd.Series, np.floating) check(assert_type(f - left, "npt.NDArray[np.float64]"), pd.Series, np.floating) check( @@ -75,10 +88,12 @@ def test_sub_numpy_array() -> None: np.complexfloating, ) + check(assert_type(left.sub(b), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.sub(i), "pd.Series[float]"), pd.Series, np.floating) 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) + check(assert_type(left.rsub(b), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.rsub(i), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.rsub(f), "pd.Series[float]"), pd.Series, np.floating) check( @@ -88,22 +103,27 @@ def test_sub_numpy_array() -> None: def test_sub_pd_series() -> None: """Test pd.Series[float] - pandas series""" + b = pd.Series([True, False, True]) i = pd.Series([2, 3, 5]) f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) + check(assert_type(left - b, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left - i, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left - f, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left - c, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(b - left, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(i - left, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(f - left, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(c - left, "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.sub(b), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.sub(i), "pd.Series[float]"), pd.Series, np.floating) 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) + check(assert_type(left.rsub(b), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.rsub(i), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.rsub(f), "pd.Series[float]"), pd.Series, np.floating) check( diff --git a/tests/series/arithmetic/int/test_sub.py b/tests/series/arithmetic/int/test_sub.py index 5451663d..addbae27 100644 --- a/tests/series/arithmetic/int/test_sub.py +++ b/tests/series/arithmetic/int/test_sub.py @@ -10,7 +10,7 @@ def test_sub_py_scalar() -> None: """Test pd.Series[int] - Python native scalars""" - i, f, c = 1, 1.0, 1j + b, i, f, c = True, 1, 1.0, 1j check(assert_type(left - i, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(left - f, "pd.Series[float]"), pd.Series, np.floating) @@ -33,7 +33,7 @@ def test_sub_py_scalar() -> None: def test_sub_py_sequence() -> None: """Test pd.Series[int] - Python native sequence""" - i, f, c = [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] check(assert_type(left - i, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(left - f, "pd.Series[float]"), pd.Series, np.floating) @@ -56,6 +56,7 @@ def test_sub_py_sequence() -> None: def test_sub_numpy_array() -> None: """Test pd.Series[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) @@ -88,6 +89,7 @@ def test_sub_numpy_array() -> None: def test_sub_pd_series() -> None: """Test pd.Series[int] - pandas series""" + b = pd.Series([True, False, True]) i = pd.Series([2, 3, 5]) f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) diff --git a/tests/series/arithmetic/test_sub.py b/tests/series/arithmetic/test_sub.py index 20e7420d..9ca8dfd3 100644 --- a/tests/series/arithmetic/test_sub.py +++ b/tests/series/arithmetic/test_sub.py @@ -1,3 +1,5 @@ +from typing import NoReturn + import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd @@ -10,20 +12,24 @@ def test_sub_py_scalar() -> None: """Test pd.Series[Any] - Python native scalars""" - i, f, c = 1, 1.0, 1j + b, i, f, c = True, 1, 1.0, 1j + check(assert_type(left - b, pd.Series), pd.Series) check(assert_type(left - i, pd.Series), pd.Series) check(assert_type(left - f, pd.Series), pd.Series) check(assert_type(left - c, pd.Series), pd.Series) + check(assert_type(b - left, pd.Series), pd.Series) check(assert_type(i - left, pd.Series), pd.Series) check(assert_type(f - left, pd.Series), pd.Series) check(assert_type(c - left, pd.Series), pd.Series) + check(assert_type(left.sub(b), pd.Series), pd.Series) check(assert_type(left.sub(i), pd.Series), pd.Series) check(assert_type(left.sub(f), pd.Series), pd.Series) check(assert_type(left.sub(c), pd.Series), pd.Series) + check(assert_type(left.rsub(b), pd.Series), pd.Series) check(assert_type(left.rsub(i), pd.Series), pd.Series) check(assert_type(left.rsub(f), pd.Series), pd.Series) check(assert_type(left.rsub(c), pd.Series), pd.Series) @@ -31,20 +37,24 @@ def test_sub_py_scalar() -> None: def test_sub_py_sequence() -> None: """Test pd.Series[Any] - Python native sequence""" - i, f, c = [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + 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.Series), pd.Series) check(assert_type(left - i, pd.Series), pd.Series) check(assert_type(left - f, pd.Series), pd.Series) check(assert_type(left - c, pd.Series), pd.Series) + check(assert_type(b - left, pd.Series), pd.Series) check(assert_type(i - left, pd.Series), pd.Series) check(assert_type(f - left, pd.Series), pd.Series) check(assert_type(c - left, pd.Series), pd.Series) + check(assert_type(left.sub(b), pd.Series), pd.Series) check(assert_type(left.sub(i), pd.Series), pd.Series) check(assert_type(left.sub(f), pd.Series), pd.Series) check(assert_type(left.sub(c), pd.Series), pd.Series) + check(assert_type(left.rsub(b), pd.Series), pd.Series) check(assert_type(left.rsub(i), pd.Series), pd.Series) check(assert_type(left.rsub(f), pd.Series), pd.Series) check(assert_type(left.rsub(c), pd.Series), pd.Series) @@ -52,10 +62,12 @@ def test_sub_py_sequence() -> None: def test_sub_numpy_array() -> None: """Test pd.Series[Any] - 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.Series), pd.Series) check(assert_type(left - i, pd.Series), pd.Series) check(assert_type(left - f, pd.Series), pd.Series) check(assert_type(left - c, pd.Series), pd.Series) @@ -64,6 +76,7 @@ def test_sub_numpy_array() -> None: # checking, where our `__rsub__` cannot override. At runtime, they return # `Series`s. # `mypy` thinks the return types are `Any`, which is a bug. + check(assert_type(b - left, NoReturn), pd.Series) # type: ignore[assert-type] check( assert_type(i - left, "npt.NDArray[np.int64]"), pd.Series # type: ignore[assert-type] ) @@ -74,10 +87,12 @@ def test_sub_numpy_array() -> None: assert_type(c - left, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] ) + check(assert_type(left.sub(b), pd.Series), pd.Series) check(assert_type(left.sub(i), pd.Series), pd.Series) check(assert_type(left.sub(f), pd.Series), pd.Series) check(assert_type(left.sub(c), pd.Series), pd.Series) + check(assert_type(left.rsub(b), pd.Series), pd.Series) check(assert_type(left.rsub(i), pd.Series), pd.Series) check(assert_type(left.rsub(f), pd.Series), pd.Series) check(assert_type(left.rsub(c), pd.Series), pd.Series) @@ -85,22 +100,27 @@ def test_sub_numpy_array() -> None: def test_sub_pd_series() -> None: """Test pd.Series[Any] - pandas series""" + b = pd.Series([True, False, True]) i = pd.Series([2, 3, 5]) f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) + check(assert_type(left - b, pd.Series), pd.Series) check(assert_type(left - i, pd.Series), pd.Series) check(assert_type(left - f, pd.Series), pd.Series) check(assert_type(left - c, pd.Series), pd.Series) + check(assert_type(b - left, pd.Series), pd.Series) check(assert_type(i - left, pd.Series), pd.Series) check(assert_type(f - left, pd.Series), pd.Series) check(assert_type(c - left, pd.Series), pd.Series) + check(assert_type(left.sub(b), pd.Series), pd.Series) check(assert_type(left.sub(i), pd.Series), pd.Series) check(assert_type(left.sub(f), pd.Series), pd.Series) check(assert_type(left.sub(c), pd.Series), pd.Series) + check(assert_type(left.rsub(b), pd.Series), pd.Series) check(assert_type(left.rsub(i), pd.Series), pd.Series) check(assert_type(left.rsub(f), pd.Series), pd.Series) check(assert_type(left.rsub(c), pd.Series), pd.Series) From 1e9b11d406ca20accc96c898473e7e38b3acd75a Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Thu, 14 Aug 2025 11:24:59 +0200 Subject: [PATCH 04/15] fix(comment): completeness https://github.com/pandas-dev/pandas-stubs/pull/1312#pullrequestreview-3106652818 --- pandas-stubs/core/series.pyi | 145 ++++++++++++++--------- tests/series/arithmetic/bool/test_sub.py | 42 ++++++- tests/series/arithmetic/int/test_sub.py | 18 +++ 3 files changed, 148 insertions(+), 57 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 71efa2ae..5db811b6 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -188,7 +188,6 @@ from pandas.core.dtypes.dtypes import CategoricalDtype from pandas.plotting import PlotAccessor -_T_NUM_NON_BOOL = TypeVar("_T_NUM_NON_BOOL", int, float, complex) _T_INT = TypeVar("_T_INT", bound=int) _T_COMPLEX = TypeVar("_T_COMPLEX", bound=complex) @@ -2072,14 +2071,19 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __sub__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @overload + def __sub__(self: Series[bool], other: bool | Sequence[bool]) -> Never: ... + @overload def __sub__( - self: Series[bool], - other: _T_NUM_NON_BOOL | Sequence[_T_NUM_NON_BOOL] | Series[_T_NUM_NON_BOOL], - ) -> Series[_T_NUM_NON_BOOL]: ... + self: Series[bool], other: _T_COMPLEX | Sequence[_T_COMPLEX] + ) -> Series[_T_COMPLEX]: ... @overload - def __sub__(self: Series[bool], other: np_ndarray_anyint) -> Series[int]: ... + def __sub__( + self: Series[bool], other: np_ndarray_anyint | Series[int] + ) -> Series[int]: ... @overload - def __sub__(self: Series[bool], other: np_ndarray_float) -> Series[float]: ... + def __sub__( + self: Series[bool], other: np_ndarray_float | Series[float] + ) -> Series[float]: ... @overload def __sub__( self: Series[int], @@ -2097,8 +2101,8 @@ class Series(IndexOpsMixin[S1], NDFrame): def __sub__( self: Series[float], other: ( - _T_INT - | Sequence[_T_INT] + int + | Sequence[int] | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float @@ -2114,7 +2118,8 @@ class Series(IndexOpsMixin[S1], NDFrame): def __sub__( self: Series[complex], other: ( - Sequence[_T_COMPLEX] + _T_COMPLEX + | Sequence[_T_COMPLEX] | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float @@ -2123,7 +2128,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[complex]: ... @overload def __sub__( - self: Series[_T_COMPLEX], other: np_ndarray_complex + self: Series[_T_COMPLEX], other: np_ndarray_complex | Series[complex] ) -> Series[complex]: ... @overload def __sub__( @@ -2136,8 +2141,6 @@ class Series(IndexOpsMixin[S1], NDFrame): other: Timedelta | TimedeltaSeries | TimedeltaIndex | np.timedelta64, ) -> TimedeltaSeries: ... @overload - def __sub__(self, other: S1 | Series[S1]) -> Self: ... - @overload def sub( self: Series[Never], other: complex | _ListLike | Series, @@ -2148,20 +2151,36 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[bool], - other: _T_NUM_NON_BOOL | Sequence[_T_NUM_NON_BOOL] | Series[_T_NUM_NON_BOOL], + other: bool | Sequence[bool], level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[_T_NUM_NON_BOOL]: ... + ) -> Never: ... @overload def sub( self: Series[bool], - other: np_ndarray_anyint, + other: _T_COMPLEX | Sequence[_T_COMPLEX], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[_T_COMPLEX]: ... + @overload + def sub( + self: Series[bool], + other: np_ndarray_anyint | Series[int], level: Level | None = None, fill_value: float | None = None, axis: int = 0, ) -> Series[int]: ... @overload + def sub( + self: Series[bool], + other: np_ndarray_float | Series[float], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[float]: ... + @overload def sub( self: Series[int], other: ( @@ -2191,8 +2210,8 @@ class Series(IndexOpsMixin[S1], NDFrame): def sub( self: Series[float], other: ( - _T_INT - | Sequence[_T_INT] + int + | Sequence[int] | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float @@ -2214,7 +2233,8 @@ class Series(IndexOpsMixin[S1], NDFrame): def sub( self: Series[complex], other: ( - Sequence[_T_COMPLEX] + _T_COMPLEX + | Sequence[_T_COMPLEX] | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float @@ -2227,27 +2247,31 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[_T_COMPLEX], - other: np_ndarray_complex, + other: np_ndarray_complex | Series[complex], level: Level | None = None, fill_value: float | None = None, axis: int = 0, ) -> Series[complex]: ... @overload - def sub( - self, - other: S1 | Series[S1], - level: Level | None = None, - fill_value: float | None = None, - axis: int = 0, - ) -> Self: ... + def __rsub__( + self: Series[Never], other: complex | _ListLike | Series + ) -> Series: ... @overload - def __rsub__(self: Series[Never], other: complex | _ListLike) -> Series: ... + def __rsub__(self, other: Series[Never]) -> Series: ... + @overload + def __rsub__(self: Series[bool], other: bool | Sequence[bool]) -> Never: ... + @overload + def __rsub__( + self: Series[bool], other: _T_COMPLEX | Sequence[_T_COMPLEX] + ) -> Series[_T_COMPLEX]: ... @overload def __rsub__( - self: Series[bool], other: _T_NUM_NON_BOOL | Sequence[_T_NUM_NON_BOOL] - ) -> Series[_T_NUM_NON_BOOL]: ... + self: Series[bool], other: np_ndarray_anyint | Series[int] + ) -> Series[int]: ... @overload - def __rsub__(self: Series[bool], other: np_ndarray_anyint) -> Series[int]: ... + def __rsub__( + self: Series[bool], other: np_ndarray_float | Series[float] + ) -> Series[float]: ... @overload def __rsub__( self: Series[int], @@ -2255,7 +2279,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[int]: ... @overload def __rsub__( - self: Series[int], other: _T_COMPLEX | Sequence[_T_COMPLEX] + self: Series[int], other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX] ) -> Series[_T_COMPLEX]: ... @overload def __rsub__(self: Series[_T_INT], other: np_ndarray_float) -> Series[float]: ... @@ -2263,37 +2287,39 @@ class Series(IndexOpsMixin[S1], NDFrame): def __rsub__( self: Series[float], other: ( - _T_INT - | Sequence[_T_INT] + int + | Sequence[int] | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float + | Series[_T_INT] ), ) -> Series[float]: ... @overload def __rsub__( - self: Series[float], other: _T_COMPLEX | Sequence[_T_COMPLEX] + self: Series[float], + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], ) -> Series[_T_COMPLEX]: ... @overload def __rsub__( self: Series[complex], other: ( - Sequence[_T_COMPLEX] + _T_COMPLEX + | Sequence[_T_COMPLEX] | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float + | Series[_T_COMPLEX] ), ) -> Series[complex]: ... @overload def __rsub__( - self: Series[_T_COMPLEX], other: np_ndarray_complex + self: Series[_T_COMPLEX], other: np_ndarray_complex | Series[complex] ) -> Series[complex]: ... @overload - def __rsub__(self, other: S1) -> Self: ... - @overload def rsub( self: Series[Never], - other: Scalar | _ListLike | Series, + other: complex | _ListLike | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2301,20 +2327,36 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[bool], - other: _T_NUM_NON_BOOL | Sequence[_T_NUM_NON_BOOL] | Series[_T_NUM_NON_BOOL], + other: bool | Sequence[bool], level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[_T_NUM_NON_BOOL]: ... + ) -> Never: ... @overload def rsub( self: Series[bool], - other: np_ndarray_anyint, + other: _T_COMPLEX | Sequence[_T_COMPLEX], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[_T_COMPLEX]: ... + @overload + def rsub( + self: Series[bool], + other: np_ndarray_anyint | Series[int], level: Level | None = None, fill_value: float | None = None, axis: int = 0, ) -> Series[int]: ... @overload + def rsub( + self: Series[bool], + other: np_ndarray_float | Series[float], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[float]: ... + @overload def rsub( self: Series[int], other: ( @@ -2344,8 +2386,8 @@ class Series(IndexOpsMixin[S1], NDFrame): def rsub( self: Series[float], other: ( - _T_INT - | Sequence[_T_INT] + int + | Sequence[int] | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float @@ -2367,7 +2409,8 @@ class Series(IndexOpsMixin[S1], NDFrame): def rsub( self: Series[complex], other: ( - Sequence[_T_COMPLEX] + _T_COMPLEX + | Sequence[_T_COMPLEX] | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float @@ -2380,20 +2423,12 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[_T_COMPLEX], - other: np_ndarray_complex, + other: np_ndarray_complex | Series[complex], level: Level | None = None, fill_value: float | None = None, axis: int = 0, ) -> Series[complex]: ... @overload - def rsub( - self, - other: S1 | Series[S1], - level: Level | None = None, - fill_value: float | None = None, - axis: int = 0, - ) -> Self: ... - @overload def __truediv__( self: Series[Never], other: complex | _ListLike | Series ) -> Series: ... @@ -3248,7 +3283,7 @@ class TimestampSeries(Series[Timestamp]): self, other: Timestamp | datetime | TimestampSeries ) -> TimedeltaSeries: ... @overload - def __sub__( # pyright: ignore[reportIncompatibleMethodOverride] + def __sub__( self, other: ( timedelta | TimedeltaSeries | TimedeltaIndex | np.timedelta64 | BaseOffset diff --git a/tests/series/arithmetic/bool/test_sub.py b/tests/series/arithmetic/bool/test_sub.py index 223453cf..a0813ed1 100644 --- a/tests/series/arithmetic/bool/test_sub.py +++ b/tests/series/arithmetic/bool/test_sub.py @@ -1,9 +1,15 @@ import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd -from typing_extensions import assert_type +from typing_extensions import ( + Never, + assert_type, +) -from tests import check +from tests import ( + TYPE_CHECKING_INVALID_USAGE, + check, +) left = pd.Series([True, True, False]) # left operand @@ -12,18 +18,26 @@ def test_sub_py_scalar() -> None: """Test pd.Series[bool] - Python native scalars""" b, i, f, c = True, 1, 1.0, 1j + if TYPE_CHECKING_INVALID_USAGE: + assert_type(left - b, Never) 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: + assert_type(b - left, Never) 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: + assert_type(left.sub(b), Never) 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: + assert_type(left.rsub(b), Never) 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( @@ -35,18 +49,26 @@ def test_sub_py_sequence() -> None: """Test pd.Series[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: + assert_type(left - b, Never) 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: + assert_type(b - left, Never) 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: + assert_type(left.sub(b), Never) 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: + assert_type(left.rsub(b), Never) 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( @@ -61,6 +83,8 @@ def test_sub_numpy_array() -> None: 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.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) @@ -68,6 +92,8 @@ def test_sub_numpy_array() -> None: # `numpy` typing gives the corresponding `ndarray`s in the static type # checking, where our `__rsub__` cannot override. At runtime, they return # `Series`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.Series, np.integer) check(assert_type(f - left, "npt.NDArray[np.float64]"), pd.Series, np.floating) check( @@ -76,10 +102,14 @@ def test_sub_numpy_array() -> None: 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( @@ -94,18 +124,26 @@ def test_sub_pd_series() -> None: f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) + if TYPE_CHECKING_INVALID_USAGE: + _ = left - b # 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: + _ = b - left # 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( diff --git a/tests/series/arithmetic/int/test_sub.py b/tests/series/arithmetic/int/test_sub.py index addbae27..57056e93 100644 --- a/tests/series/arithmetic/int/test_sub.py +++ b/tests/series/arithmetic/int/test_sub.py @@ -1,3 +1,5 @@ +from typing import NoReturn + import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd @@ -12,18 +14,22 @@ def test_sub_py_scalar() -> None: """Test pd.Series[int] - Python native scalars""" b, i, f, c = True, 1, 1.0, 1j + check(assert_type(left - b, "pd.Series[int]"), pd.Series, np.integer) 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) + check(assert_type(b - left, "pd.Series[int]"), pd.Series, np.integer) 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) + check(assert_type(left.sub(b), "pd.Series[int]"), pd.Series, np.integer) 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) + check(assert_type(left.rsub(b), "pd.Series[int]"), pd.Series, np.integer) 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( @@ -35,18 +41,22 @@ def test_sub_py_sequence() -> None: """Test pd.Series[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.Series[int]"), pd.Series, np.integer) 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) + check(assert_type(b - left, "pd.Series[int]"), pd.Series, np.integer) 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) + check(assert_type(left.sub(b), "pd.Series[int]"), pd.Series, np.integer) 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) + check(assert_type(left.rsub(b), "pd.Series[int]"), pd.Series, np.integer) 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( @@ -61,6 +71,7 @@ def test_sub_numpy_array() -> None: 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.Series[int]"), pd.Series, np.integer) 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) @@ -68,6 +79,7 @@ def test_sub_numpy_array() -> None: # `numpy` typing gives the corresponding `ndarray`s in the static type # checking, where our `__rsub__` cannot override. At runtime, they return # `Series`s with the correct element type. + check(assert_type(b - left, NoReturn), pd.Series, np.integer) check(assert_type(i - left, "npt.NDArray[np.int64]"), pd.Series, np.integer) check(assert_type(f - left, "npt.NDArray[np.float64]"), pd.Series, np.floating) check( @@ -76,10 +88,12 @@ def test_sub_numpy_array() -> None: np.complexfloating, ) + check(assert_type(left.sub(b), "pd.Series[int]"), pd.Series, np.integer) 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) + check(assert_type(left.rsub(b), "pd.Series[int]"), pd.Series, np.integer) 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( @@ -94,18 +108,22 @@ def test_sub_pd_series() -> None: f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) + check(assert_type(left - b, "pd.Series[int]"), pd.Series, np.integer) 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) + check(assert_type(b - left, "pd.Series[int]"), pd.Series, np.integer) 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) + check(assert_type(left.sub(b), "pd.Series[int]"), pd.Series, np.integer) 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) + check(assert_type(left.rsub(b), "pd.Series[int]"), pd.Series, np.integer) 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( From c8e3ab25b940bef8dcaa51f672a2373842dd994c Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Sat, 16 Aug 2025 11:58:16 +0200 Subject: [PATCH 05/15] fix: pd.Series[bool] - pd.Series[bool] --- tests/series/arithmetic/bool/test_sub.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/series/arithmetic/bool/test_sub.py b/tests/series/arithmetic/bool/test_sub.py index a0813ed1..29c59e2d 100644 --- a/tests/series/arithmetic/bool/test_sub.py +++ b/tests/series/arithmetic/bool/test_sub.py @@ -1,3 +1,5 @@ +from typing import cast + import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd @@ -124,14 +126,15 @@ 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: - _ = left - b # pyright: ignore[reportOperatorIssue] + _ = left - cast("pd.Series[bool]", b) # type: ignore[redundant-cast,operator] # pyright: ignore[reportUnnecessaryCast,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: - _ = b - left # pyright: ignore[reportOperatorIssue] + _ = b - cast("pd.Series[bool]", left) # type: ignore[redundant-cast,operator] # pyright: ignore[reportUnnecessaryCast,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) From 0a8689794b47fb7523ce9c2870551779063d6f12 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Mon, 18 Aug 2025 09:16:09 +0200 Subject: [PATCH 06/15] fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1312#discussion_r2280666916 --- tests/series/arithmetic/bool/test_sub.py | 6 ++---- tests/series/arithmetic/bool/test_truediv.py | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/series/arithmetic/bool/test_sub.py b/tests/series/arithmetic/bool/test_sub.py index 29c59e2d..a0028066 100644 --- a/tests/series/arithmetic/bool/test_sub.py +++ b/tests/series/arithmetic/bool/test_sub.py @@ -1,5 +1,3 @@ -from typing import cast - import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd @@ -128,13 +126,13 @@ def test_sub_pd_series() -> None: # In the following two cases, mypy fails to recognise the second operand as pd.Series[bool] if TYPE_CHECKING_INVALID_USAGE: - _ = left - cast("pd.Series[bool]", b) # type: ignore[redundant-cast,operator] # pyright: ignore[reportUnnecessaryCast,reportOperatorIssue] + _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: - _ = b - cast("pd.Series[bool]", left) # type: ignore[redundant-cast,operator] # pyright: ignore[reportUnnecessaryCast,reportOperatorIssue] + _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) diff --git a/tests/series/arithmetic/bool/test_truediv.py b/tests/series/arithmetic/bool/test_truediv.py index caf61d0e..0253d384 100644 --- a/tests/series/arithmetic/bool/test_truediv.py +++ b/tests/series/arithmetic/bool/test_truediv.py @@ -179,13 +179,13 @@ def test_truediv_pd_series() -> None: c = pd.Series([1.1j, 2.2j, 4.1j]) if TYPE_CHECKING_INVALID_USAGE: - _ = left / b # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _0 = left / b # type: ignore[operator] # pyright: ignore[reportOperatorIssue] check(assert_type(left / i, "pd.Series[float]"), pd.Series, np.floating) 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: - _ = b / left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _1 = b / left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] check(assert_type(i / left, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(f / left, "pd.Series[float]"), pd.Series, np.floating) check(assert_type(c / left, "pd.Series[complex]"), pd.Series, np.complexfloating) From 5c2a5924fdbbb5c5fee0086fa82d32ad14981c88 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Mon, 18 Aug 2025 10:22:15 +0200 Subject: [PATCH 07/15] fix(comment): reduce Never https://github.com/pandas-dev/pandas-stubs/pull/1312#pullrequestreview-3126128971 --- pandas-stubs/_typing.pyi | 12 + pandas-stubs/core/series.pyi | 253 +++++++++---------- tests/series/arithmetic/bool/test_sub.py | 16 +- tests/series/arithmetic/bool/test_truediv.py | 12 +- 4 files changed, 144 insertions(+), 149 deletions(-) diff --git a/pandas-stubs/_typing.pyi b/pandas-stubs/_typing.pyi index f14c393f..16c8038b 100644 --- a/pandas-stubs/_typing.pyi +++ b/pandas-stubs/_typing.pyi @@ -15,12 +15,14 @@ from re import Pattern import sys from typing import ( Any, + Generic, Literal, Protocol, SupportsIndex, TypedDict, Union, overload, + override, ) import numpy as np @@ -1038,4 +1040,14 @@ DictConvertible: TypeAlias = FulldatetimeDict | DataFrame # where it is the only acceptable type. Incomplete: TypeAlias = Any +# differentiating between bool and int/float/complex +# https://github.com/pandas-dev/pandas-stubs/pull/1312#pullrequestreview-3126128971 +class Just(Protocol, Generic[T]): + @property # type: ignore[override] + @override + def __class__(self, /) -> type[T]: ... + @__class__.setter + @override + def __class__(self, t: type[T], /) -> None: ... + __all__ = ["npt", "type_t"] diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 5db811b6..75682ef3 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -143,6 +143,7 @@ from pandas._typing import ( JoinHow, JSONSerializable, JsonSeriesOrient, + Just, Label, Level, ListLike, @@ -2071,50 +2072,47 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __sub__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @overload - def __sub__(self: Series[bool], other: bool | Sequence[bool]) -> Never: ... - @overload - def __sub__( - self: Series[bool], other: _T_COMPLEX | Sequence[_T_COMPLEX] - ) -> Series[_T_COMPLEX]: ... - @overload def __sub__( - self: Series[bool], other: np_ndarray_anyint | Series[int] + self: Series[bool], + other: Just[int] | Sequence[Just[int]] | np_ndarray_anyint | Series[int], ) -> Series[int]: ... @overload def __sub__( - self: Series[bool], other: np_ndarray_float | Series[float] + self: Series[bool], + other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], ) -> Series[float]: ... @overload def __sub__( self: Series[int], other: ( - bool | Sequence[bool] | np_ndarray_bool | np_ndarray_anyint | Series[bool] + int + | Sequence[int] + | np_ndarray_bool + | np_ndarray_anyint + | Series[bool] + | Series[int] ), ) -> Series[int]: ... @overload def __sub__( - self: Series[int], other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX] - ) -> Series[_T_COMPLEX]: ... - @overload - def __sub__(self: Series[int], other: np_ndarray_float) -> Series[float]: ... + self: Series[int], + other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], + ) -> Series[float]: ... @overload def __sub__( self: Series[float], other: ( - int - | Sequence[int] + float + | Sequence[float] | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float - | Series[_T_INT] + | Series[bool] + | Series[int] + | Series[float] ), ) -> Series[float]: ... @overload - def __sub__( - self: Series[float], - other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], - ) -> Series[_T_COMPLEX]: ... - @overload def __sub__( self: Series[complex], other: ( @@ -2128,7 +2126,13 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[complex]: ... @overload def __sub__( - self: Series[_T_COMPLEX], other: np_ndarray_complex | Series[complex] + self: Series[_T_COMPLEX], + other: ( + Just[complex] + | Sequence[Just[complex]] + | np_ndarray_complex + | Series[complex] + ), ) -> Series[complex]: ... @overload def __sub__( @@ -2150,24 +2154,16 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series: ... @overload def sub( - self: Series[bool], - other: bool | Sequence[bool], - level: Level | None = None, - fill_value: float | None = None, - axis: int = 0, - ) -> Never: ... - @overload - def sub( - self: Series[bool], - other: _T_COMPLEX | Sequence[_T_COMPLEX], + self, + other: Series[Never], level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[_T_COMPLEX]: ... + ) -> Series: ... @overload def sub( self: Series[bool], - other: np_ndarray_anyint | Series[int], + other: Just[int] | Sequence[Just[int]] | np_ndarray_anyint | Series[int], level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2175,7 +2171,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[bool], - other: np_ndarray_float | Series[float], + other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2184,7 +2180,12 @@ class Series(IndexOpsMixin[S1], NDFrame): def sub( self: Series[int], other: ( - bool | Sequence[bool] | np_ndarray_bool | np_ndarray_anyint | Series[bool] + int + | Sequence[int] + | np_ndarray_bool + | np_ndarray_anyint + | Series[bool] + | Series[int] ), level: Level | None = None, fill_value: float | None = None, @@ -2193,15 +2194,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[int], - other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], - level: Level | None = None, - fill_value: float | None = None, - axis: int = 0, - ) -> Series[_T_COMPLEX]: ... - @overload - def sub( - self: Series[_T_INT], - other: np_ndarray_float, + other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2210,26 +2203,20 @@ class Series(IndexOpsMixin[S1], NDFrame): def sub( self: Series[float], other: ( - int - | Sequence[int] + float + | Sequence[float] | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float - | Series[_T_INT] + | Series[bool] + | Series[int] + | Series[float] ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, ) -> Series[float]: ... @overload - def sub( - self: Series[float], - other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], - level: Level | None = None, - fill_value: float | None = None, - axis: int = 0, - ) -> Series[_T_COMPLEX]: ... - @overload def sub( self: Series[complex], other: ( @@ -2247,7 +2234,12 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[_T_COMPLEX], - other: np_ndarray_complex | Series[complex], + other: ( + Just[complex] + | Sequence[Just[complex]] + | np_ndarray_complex + | Series[complex] + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2259,48 +2251,47 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __rsub__(self, other: Series[Never]) -> Series: ... @overload - def __rsub__(self: Series[bool], other: bool | Sequence[bool]) -> Never: ... - @overload - def __rsub__( - self: Series[bool], other: _T_COMPLEX | Sequence[_T_COMPLEX] - ) -> Series[_T_COMPLEX]: ... - @overload def __rsub__( - self: Series[bool], other: np_ndarray_anyint | Series[int] + self: Series[bool], + other: Just[int] | Sequence[Just[int]] | np_ndarray_anyint | Series[int], ) -> Series[int]: ... @overload def __rsub__( - self: Series[bool], other: np_ndarray_float | Series[float] + self: Series[bool], + other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], ) -> Series[float]: ... @overload def __rsub__( self: Series[int], - other: bool | Sequence[bool] | np_ndarray_bool | np_ndarray_anyint, + other: ( + int + | Sequence[int] + | np_ndarray_bool + | np_ndarray_anyint + | Series[bool] + | Series[int] + ), ) -> Series[int]: ... @overload def __rsub__( - self: Series[int], other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX] - ) -> Series[_T_COMPLEX]: ... - @overload - def __rsub__(self: Series[_T_INT], other: np_ndarray_float) -> Series[float]: ... + self: Series[int], + other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], + ) -> Series[float]: ... @overload def __rsub__( self: Series[float], other: ( - int - | Sequence[int] + float + | Sequence[float] | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float - | Series[_T_INT] + | Series[bool] + | Series[int] + | Series[float] ), ) -> Series[float]: ... @overload - def __rsub__( - self: Series[float], - other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], - ) -> Series[_T_COMPLEX]: ... - @overload def __rsub__( self: Series[complex], other: ( @@ -2314,7 +2305,13 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[complex]: ... @overload def __rsub__( - self: Series[_T_COMPLEX], other: np_ndarray_complex | Series[complex] + self: Series[_T_COMPLEX], + other: ( + Just[complex] + | Sequence[Just[complex]] + | np_ndarray_complex + | Series[complex] + ), ) -> Series[complex]: ... @overload def rsub( @@ -2327,23 +2324,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[bool], - other: bool | Sequence[bool], - level: Level | None = None, - fill_value: float | None = None, - axis: int = 0, - ) -> Never: ... - @overload - def rsub( - self: Series[bool], - other: _T_COMPLEX | Sequence[_T_COMPLEX], - level: Level | None = None, - fill_value: float | None = None, - axis: int = 0, - ) -> Series[_T_COMPLEX]: ... - @overload - def rsub( - self: Series[bool], - other: np_ndarray_anyint | Series[int], + other: Just[int] | Sequence[Just[int]] | np_ndarray_anyint | Series[int], level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2351,7 +2332,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[bool], - other: np_ndarray_float | Series[float], + other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2360,7 +2341,12 @@ class Series(IndexOpsMixin[S1], NDFrame): def rsub( self: Series[int], other: ( - bool | Sequence[bool] | np_ndarray_bool | np_ndarray_anyint | Series[bool] + int + | Sequence[int] + | np_ndarray_bool + | np_ndarray_anyint + | Series[bool] + | Series[int] ), level: Level | None = None, fill_value: float | None = None, @@ -2369,15 +2355,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[int], - other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], - level: Level | None = None, - fill_value: float | None = None, - axis: int = 0, - ) -> Series[_T_COMPLEX]: ... - @overload - def rsub( - self: Series[_T_INT], - other: np_ndarray_float, + other: Just[float] | Sequence[Just[float]] | np_ndarray_float | Series[float], level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2386,26 +2364,20 @@ class Series(IndexOpsMixin[S1], NDFrame): def rsub( self: Series[float], other: ( - int - | Sequence[int] + float + | Sequence[float] | np_ndarray_bool | np_ndarray_anyint | np_ndarray_float - | Series[_T_INT] + | Series[bool] + | Series[int] + | Series[float] ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, ) -> Series[float]: ... @overload - def rsub( - self: Series[float], - other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], - level: Level | None = None, - fill_value: float | None = None, - axis: int = 0, - ) -> Series[_T_COMPLEX]: ... - @overload def rsub( self: Series[complex], other: ( @@ -2423,7 +2395,12 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[_T_COMPLEX], - other: np_ndarray_complex | Series[complex], + other: ( + Just[complex] + | Sequence[Just[complex]] + | np_ndarray_complex + | Series[complex] + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2435,12 +2412,13 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __truediv__(self, other: Series[Never]) -> Series: ... @overload - def __truediv__(self: Series[bool], other: bool | np_ndarray_bool) -> Never: ... + def __truediv__(self: Series[bool], other: np_ndarray_bool) -> Never: ... @overload - def __truediv__( # pyright: ignore[reportOverlappingOverload] + def __truediv__( self: Series[bool], other: ( - float + Just[int] + | Just[float] | Sequence[float] | np_ndarray_anyint | np_ndarray_float @@ -2450,7 +2428,8 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[float]: ... @overload def __truediv__( - self: Series[bool], other: complex | Sequence[complex] | Series[complex] + self: Series[bool], + other: Just[complex] | Sequence[Just[complex]] | Series[complex], ) -> Series[complex]: ... @overload def __truediv__( @@ -2523,16 +2502,17 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def truediv( self: Series[bool], - other: bool | np_ndarray_bool, + other: np_ndarray_bool, level: Level | None = None, fill_value: float | None = None, axis: AxisIndex = 0, ) -> Never: ... @overload - def truediv( # pyright: ignore[reportOverlappingOverload] + def truediv( self: Series[bool], other: ( - float + Just[int] + | Just[float] | Sequence[float] | np_ndarray_anyint | np_ndarray_float @@ -2546,7 +2526,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def truediv( self: Series[bool], - other: complex | Sequence[complex] | Series[complex], + other: Just[complex] | Sequence[Just[complex]] | Series[complex], level: Level | None = None, fill_value: float | None = None, axis: AxisIndex = 0, @@ -2636,12 +2616,13 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __rtruediv__(self, other: Series[Never]) -> Series: ... @overload - def __rtruediv__(self: Series[bool], other: bool | np_ndarray_bool) -> Never: ... + def __rtruediv__(self: Series[bool], other: np_ndarray_bool) -> Never: ... @overload - def __rtruediv__( # pyright: ignore[reportOverlappingOverload] + def __rtruediv__( self: Series[bool], other: ( - float + Just[int] + | Just[float] | Sequence[float] | np_ndarray_anyint | np_ndarray_float @@ -2651,7 +2632,8 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[float]: ... @overload def __rtruediv__( - self: Series[bool], other: complex | Sequence[complex] | Series[complex] + self: Series[bool], + other: Just[complex] | Sequence[Just[complex]] | Series[complex], ) -> Series[complex]: ... @overload def __rtruediv__( @@ -2723,16 +2705,17 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rtruediv( self: Series[bool], - other: bool | np_ndarray_bool, + other: np_ndarray_bool, level: Level | None = None, fill_value: float | None = None, axis: AxisIndex = 0, ) -> Never: ... @overload - def rtruediv( # pyright: ignore[reportOverlappingOverload] + def rtruediv( self: Series[bool], other: ( - float + Just[int] + | Just[float] | Sequence[float] | np_ndarray_anyint | np_ndarray_float @@ -2746,7 +2729,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rtruediv( self: Series[bool], - other: complex | Sequence[complex] | Series[complex], + other: Just[complex] | Sequence[Just[complex]] | Series[complex], level: Level | None = None, fill_value: float | None = None, axis: AxisIndex = 0, diff --git a/tests/series/arithmetic/bool/test_sub.py b/tests/series/arithmetic/bool/test_sub.py index a0028066..958cb8e3 100644 --- a/tests/series/arithmetic/bool/test_sub.py +++ b/tests/series/arithmetic/bool/test_sub.py @@ -19,25 +19,25 @@ def test_sub_py_scalar() -> None: b, i, f, c = True, 1, 1.0, 1j if TYPE_CHECKING_INVALID_USAGE: - assert_type(left - b, Never) + _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: - assert_type(b - left, Never) + _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: - assert_type(left.sub(b), Never) + 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: - assert_type(left.rsub(b), Never) + 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( @@ -50,25 +50,25 @@ def test_sub_py_sequence() -> None: b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] if TYPE_CHECKING_INVALID_USAGE: - assert_type(left - b, Never) + _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: - assert_type(b - left, Never) + _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: - assert_type(left.sub(b), Never) + 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: - assert_type(left.rsub(b), Never) + 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( diff --git a/tests/series/arithmetic/bool/test_truediv.py b/tests/series/arithmetic/bool/test_truediv.py index 0253d384..e7e04658 100644 --- a/tests/series/arithmetic/bool/test_truediv.py +++ b/tests/series/arithmetic/bool/test_truediv.py @@ -19,19 +19,19 @@ def test_truediv_py_scalar() -> None: b, i, f, c = True, 1, 1.0, 1j if TYPE_CHECKING_INVALID_USAGE: - assert_type(left / b, Never) + _0 = left / b # type: ignore[operator] # pyright: ignore[reportOperatorIssue] check(assert_type(left / i, "pd.Series[float]"), pd.Series, np.floating) 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: - assert_type(b / left, Never) + _1 = b / left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] check(assert_type(i / left, "pd.Series[float]"), pd.Series, np.floating) 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: - assert_type(left.truediv(b), Never) + left.truediv(b) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] check(assert_type(left.truediv(i), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.truediv(f), "pd.Series[float]"), pd.Series, np.floating) check( @@ -41,13 +41,13 @@ def test_truediv_py_scalar() -> None: ) if TYPE_CHECKING_INVALID_USAGE: - assert_type(left.div(b), Never) + left.div(b) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] check(assert_type(left.div(i), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.div(f), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.div(c), "pd.Series[complex]"), pd.Series, np.complexfloating) if TYPE_CHECKING_INVALID_USAGE: - assert_type(left.rtruediv(b), Never) + left.rtruediv(b) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] check(assert_type(left.rtruediv(i), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.rtruediv(f), "pd.Series[float]"), pd.Series, np.floating) check( @@ -57,7 +57,7 @@ def test_truediv_py_scalar() -> None: ) if TYPE_CHECKING_INVALID_USAGE: - assert_type(left.rdiv(b), Never) + left.rdiv(b) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] check(assert_type(left.rdiv(i), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.rdiv(f), "pd.Series[float]"), pd.Series, np.floating) check( From 0c7b5feeb90e4989c293e4dd467908cc27e6e275 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Mon, 18 Aug 2025 14:50:23 +0200 Subject: [PATCH 08/15] fix: py310 and py311 --- pandas-stubs/_typing.pyi | 2 +- pyproject.toml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas-stubs/_typing.pyi b/pandas-stubs/_typing.pyi index 16c8038b..2d043aac 100644 --- a/pandas-stubs/_typing.pyi +++ b/pandas-stubs/_typing.pyi @@ -22,7 +22,6 @@ from typing import ( TypedDict, Union, overload, - override, ) import numpy as np @@ -39,6 +38,7 @@ from typing_extensions import ( ParamSpec, TypeAlias, TypeVar, + override, ) from pandas._libs.interval import Interval diff --git a/pyproject.toml b/pyproject.toml index 0de7fe6c..46999ea1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,6 +134,8 @@ args = [ { name = "nightly", positional = false, default = false, type = "boolean", required = false, help = "Compare against pandas nightly (off by default)" }, ] +[tool.ruff] +target-version = 'py310' [tool.black] target-version = ['py310'] From f300b5d97444efc3e1627f649dea41b118a94831 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Mon, 18 Aug 2025 15:04:46 +0200 Subject: [PATCH 09/15] fix(ty): ignore --- pandas-stubs/core/groupby/groupby.pyi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas-stubs/core/groupby/groupby.pyi b/pandas-stubs/core/groupby/groupby.pyi index 1490e7d1..9a620a45 100644 --- a/pandas-stubs/core/groupby/groupby.pyi +++ b/pandas-stubs/core/groupby/groupby.pyi @@ -74,9 +74,9 @@ from pandas._typing import ( from pandas.plotting import PlotAccessor _ResamplerGroupBy: TypeAlias = ( - DatetimeIndexResamplerGroupby[NDFrameT] - | PeriodIndexResamplerGroupby[NDFrameT] - | TimedeltaIndexResamplerGroupby[NDFrameT] + DatetimeIndexResamplerGroupby[NDFrameT] # ty: ignore[invalid-argument-type] + | PeriodIndexResamplerGroupby[NDFrameT] # ty: ignore[invalid-argument-type] + | TimedeltaIndexResamplerGroupby[NDFrameT] # ty: ignore[invalid-argument-type] ) class GroupBy(BaseGroupBy[NDFrameT]): From 2c5c14d32cec5e1db81decc256cb81bbe34a3994 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Mon, 18 Aug 2025 15:40:44 +0200 Subject: [PATCH 10/15] fix(ruff): revert changes --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 46999ea1..0de7fe6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,8 +134,6 @@ args = [ { name = "nightly", positional = false, default = false, type = "boolean", required = false, help = "Compare against pandas nightly (off by default)" }, ] -[tool.ruff] -target-version = 'py310' [tool.black] target-version = ['py310'] From 44d0be02795c6329320eb83c04083ee76264ab64 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Mon, 18 Aug 2025 16:49:55 +0200 Subject: [PATCH 11/15] fix: comments --- pandas-stubs/core/series.pyi | 50 +++++++++++--------- tests/series/arithmetic/bool/test_truediv.py | 8 ++-- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 320f6b83..72c65e3a 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -2071,10 +2071,6 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __rxor__(self, other: int | np_ndarray_anyint | Series[int]) -> Series[int]: ... @overload - def __sub__( - self, other: Timestamp | datetime | TimestampSeries - ) -> TimedeltaSeries: ... - @overload def __sub__(self: Series[Never], other: complex | _ListLike | Series) -> Series: ... @overload def __sub__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @@ -2144,12 +2140,12 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __sub__( self: Series[Timestamp], - other: Timedelta | TimedeltaSeries | TimedeltaIndex | np.timedelta64, + other: timedelta | np.timedelta64 | TimedeltaSeries | TimedeltaIndex, ) -> TimestampSeries: ... @overload def __sub__( self: Series[Timedelta], - other: Timedelta | TimedeltaSeries | TimedeltaIndex | np.timedelta64, + other: timedelta | np.timedelta64 | TimedeltaSeries | TimedeltaIndex, ) -> TimedeltaSeries: ... @overload def sub( @@ -2239,6 +2235,22 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> Series[complex]: ... @overload + def sub( + self: Series[Timestamp], + other: timedelta | np.timedelta64 | TimedeltaSeries | TimedeltaIndex, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload + def sub( + self: Series[Timedelta], + other: timedelta | np.timedelta64 | TimedeltaSeries | TimedeltaIndex, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload def sub( self: Series[_T_COMPLEX], other: ( @@ -2329,6 +2341,14 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> Series: ... @overload + def rsub( + self, + other: Series[Never], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series: ... + @overload def rsub( self: Series[bool], other: Just[int] | Sequence[Just[int]] | np_ndarray_anyint | Series[int], @@ -2507,14 +2527,6 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: AxisIndex = 0, ) -> Series: ... @overload - def truediv( - self: Series[bool], - other: np_ndarray_bool, - level: Level | None = None, - fill_value: float | None = None, - axis: AxisIndex = 0, - ) -> Never: ... - @overload def truediv( self: Series[bool], other: ( @@ -2710,14 +2722,6 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: AxisIndex = 0, ) -> Series: ... @overload - def rtruediv( - self: Series[bool], - other: np_ndarray_bool, - level: Level | None = None, - fill_value: float | None = None, - axis: AxisIndex = 0, - ) -> Never: ... - @overload def rtruediv( self: Series[bool], other: ( @@ -3292,7 +3296,7 @@ class TimestampSeries(_SeriesSubclassBase[Timestamp, np.datetime64]): self, other: Timestamp | datetime | TimestampSeries ) -> TimedeltaSeries: ... @overload - def __sub__( + def __sub__( # pyright: ignore[reportIncompatibleMethodOverride] self, other: ( timedelta | TimedeltaSeries | TimedeltaIndex | np.timedelta64 | BaseOffset diff --git a/tests/series/arithmetic/bool/test_truediv.py b/tests/series/arithmetic/bool/test_truediv.py index e7e04658..a96e712b 100644 --- a/tests/series/arithmetic/bool/test_truediv.py +++ b/tests/series/arithmetic/bool/test_truediv.py @@ -137,7 +137,7 @@ def test_truediv_numpy_array() -> None: ) if TYPE_CHECKING_INVALID_USAGE: - assert_type(left.truediv(b), Never) + left.truediv(b) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] check(assert_type(left.truediv(i), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.truediv(f), "pd.Series[float]"), pd.Series, np.floating) check( @@ -147,13 +147,13 @@ def test_truediv_numpy_array() -> None: ) if TYPE_CHECKING_INVALID_USAGE: - assert_type(left.div(b), Never) + left.div(b) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] check(assert_type(left.div(i), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.div(f), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.div(c), "pd.Series[complex]"), pd.Series, np.complexfloating) if TYPE_CHECKING_INVALID_USAGE: - assert_type(left.rtruediv(b), Never) + left.rtruediv(b) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] check(assert_type(left.rtruediv(i), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.rtruediv(f), "pd.Series[float]"), pd.Series, np.floating) check( @@ -163,7 +163,7 @@ def test_truediv_numpy_array() -> None: ) if TYPE_CHECKING_INVALID_USAGE: - assert_type(left.rdiv(b), Never) + left.rdiv(b) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] check(assert_type(left.rdiv(i), "pd.Series[float]"), pd.Series, np.floating) check(assert_type(left.rdiv(f), "pd.Series[float]"), pd.Series, np.floating) check( From 61de1a13d80f9ae9af7fc0d5cb530ab8cc76a96f Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Tue, 19 Aug 2025 13:32:24 +0200 Subject: [PATCH 12/15] fix: comments --- pandas-stubs/_typing.pyi | 2 + pandas-stubs/core/series.pyi | 47 +++++-- tests/series/arithmetic/test_sub.py | 204 ++++++++++++++++++---------- 3 files changed, 174 insertions(+), 79 deletions(-) diff --git a/pandas-stubs/_typing.pyi b/pandas-stubs/_typing.pyi index be608a95..e2c8bedd 100644 --- a/pandas-stubs/_typing.pyi +++ b/pandas-stubs/_typing.pyi @@ -820,6 +820,8 @@ np_ndarray_float: TypeAlias = npt.NDArray[np.floating] np_ndarray_complex: TypeAlias = npt.NDArray[np.complexfloating] np_ndarray_bool: TypeAlias = npt.NDArray[np.bool_] np_ndarray_str: TypeAlias = npt.NDArray[np.str_] +np_ndarray_dt: TypeAlias = npt.NDArray[np.datetime64] +np_ndarray_td: TypeAlias = npt.NDArray[np.timedelta64] # Define shape and generic type variables with defaults similar to numpy GenericT = TypeVar("GenericT", bound=np.generic, default=Any) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 72c65e3a..5ffc73ca 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -186,6 +186,7 @@ from pandas._typing import ( np_ndarray_anyint, np_ndarray_bool, np_ndarray_complex, + np_ndarray_dt, np_ndarray_float, npt, num, @@ -2071,6 +2072,11 @@ 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: datetime | np.datetime64 | np_ndarray_dt | TimestampSeries, + ) -> TimedeltaSeries: ... + @overload def __sub__(self: Series[Never], other: complex | _ListLike | Series) -> Series: ... @overload def __sub__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @@ -2148,6 +2154,14 @@ class Series(IndexOpsMixin[S1], NDFrame): other: timedelta | np.timedelta64 | TimedeltaSeries | TimedeltaIndex, ) -> TimedeltaSeries: ... @overload + def sub( + self: Series[Never], + other: datetime | np.datetime64 | np_ndarray_dt | TimestampSeries, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> TimedeltaSeries: ... + @overload def sub( self: Series[Never], other: complex | _ListLike | Series, @@ -2236,15 +2250,20 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[complex]: ... @overload def sub( - self: Series[Timestamp], - other: timedelta | np.timedelta64 | TimedeltaSeries | TimedeltaIndex, + self: Series[_T_COMPLEX], + other: ( + Just[complex] + | Sequence[Just[complex]] + | np_ndarray_complex + | Series[complex] + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, ) -> Series[complex]: ... @overload def sub( - self: Series[Timedelta], + self: Series[Timestamp], other: timedelta | np.timedelta64 | TimedeltaSeries | TimedeltaIndex, level: Level | None = None, fill_value: float | None = None, @@ -2252,18 +2271,18 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[complex]: ... @overload def sub( - self: Series[_T_COMPLEX], - other: ( - Just[complex] - | Sequence[Just[complex]] - | np_ndarray_complex - | Series[complex] - ), + self: Series[Timedelta], + other: timedelta | np.timedelta64 | TimedeltaSeries | TimedeltaIndex, level: Level | None = None, fill_value: float | None = None, axis: int = 0, ) -> Series[complex]: ... @overload + def __rsub__( # type: ignore[misc] + self: Series[Never], + other: datetime | np.datetime64 | np_ndarray_dt | TimestampSeries, + ) -> TimedeltaSeries: ... + @overload def __rsub__( self: Series[Never], other: complex | _ListLike | Series ) -> Series: ... @@ -2333,6 +2352,14 @@ class Series(IndexOpsMixin[S1], NDFrame): ), ) -> Series[complex]: ... @overload + def rsub( + self: Series[Never], + other: datetime | np.datetime64 | np_ndarray_dt | TimestampSeries, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> TimedeltaSeries: ... + @overload def rsub( self: Series[Never], other: complex | _ListLike | Series, diff --git a/tests/series/arithmetic/test_sub.py b/tests/series/arithmetic/test_sub.py index 9ca8dfd3..cb8b314f 100644 --- a/tests/series/arithmetic/test_sub.py +++ b/tests/series/arithmetic/test_sub.py @@ -1,4 +1,11 @@ -from typing import NoReturn +from datetime import ( + datetime, + timedelta, +) +from typing import ( + TYPE_CHECKING, + NoReturn, +) import numpy as np from numpy import typing as npt # noqa: F401 @@ -7,57 +14,60 @@ from tests import check -left = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand +if TYPE_CHECKING: + from pandas.core.series import TimedeltaSeries # noqa: F401 + +left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand def test_sub_py_scalar() -> None: """Test pd.Series[Any] - Python native scalars""" b, i, f, c = True, 1, 1.0, 1j - check(assert_type(left - b, pd.Series), pd.Series) - check(assert_type(left - i, pd.Series), pd.Series) - check(assert_type(left - f, pd.Series), pd.Series) - check(assert_type(left - c, pd.Series), pd.Series) + check(assert_type(left_i - b, pd.Series), pd.Series) + check(assert_type(left_i - i, pd.Series), pd.Series) + check(assert_type(left_i - f, pd.Series), pd.Series) + check(assert_type(left_i - c, pd.Series), pd.Series) - check(assert_type(b - left, pd.Series), pd.Series) - check(assert_type(i - left, pd.Series), pd.Series) - check(assert_type(f - left, pd.Series), pd.Series) - check(assert_type(c - left, pd.Series), pd.Series) + check(assert_type(b - left_i, pd.Series), pd.Series) + check(assert_type(i - left_i, pd.Series), pd.Series) + check(assert_type(f - left_i, pd.Series), pd.Series) + check(assert_type(c - left_i, pd.Series), pd.Series) - check(assert_type(left.sub(b), pd.Series), pd.Series) - check(assert_type(left.sub(i), pd.Series), pd.Series) - check(assert_type(left.sub(f), pd.Series), pd.Series) - check(assert_type(left.sub(c), pd.Series), pd.Series) + check(assert_type(left_i.sub(b), pd.Series), pd.Series) + check(assert_type(left_i.sub(i), pd.Series), pd.Series) + check(assert_type(left_i.sub(f), pd.Series), pd.Series) + check(assert_type(left_i.sub(c), pd.Series), pd.Series) - check(assert_type(left.rsub(b), pd.Series), pd.Series) - check(assert_type(left.rsub(i), pd.Series), pd.Series) - check(assert_type(left.rsub(f), pd.Series), pd.Series) - check(assert_type(left.rsub(c), pd.Series), pd.Series) + check(assert_type(left_i.rsub(b), pd.Series), pd.Series) + check(assert_type(left_i.rsub(i), pd.Series), pd.Series) + check(assert_type(left_i.rsub(f), pd.Series), pd.Series) + check(assert_type(left_i.rsub(c), pd.Series), pd.Series) def test_sub_py_sequence() -> None: """Test pd.Series[Any] - 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.Series), pd.Series) - check(assert_type(left - i, pd.Series), pd.Series) - check(assert_type(left - f, pd.Series), pd.Series) - check(assert_type(left - c, pd.Series), pd.Series) + check(assert_type(left_i - b, pd.Series), pd.Series) + check(assert_type(left_i - i, pd.Series), pd.Series) + check(assert_type(left_i - f, pd.Series), pd.Series) + check(assert_type(left_i - c, pd.Series), pd.Series) - check(assert_type(b - left, pd.Series), pd.Series) - check(assert_type(i - left, pd.Series), pd.Series) - check(assert_type(f - left, pd.Series), pd.Series) - check(assert_type(c - left, pd.Series), pd.Series) + check(assert_type(b - left_i, pd.Series), pd.Series) + check(assert_type(i - left_i, pd.Series), pd.Series) + check(assert_type(f - left_i, pd.Series), pd.Series) + check(assert_type(c - left_i, pd.Series), pd.Series) - check(assert_type(left.sub(b), pd.Series), pd.Series) - check(assert_type(left.sub(i), pd.Series), pd.Series) - check(assert_type(left.sub(f), pd.Series), pd.Series) - check(assert_type(left.sub(c), pd.Series), pd.Series) + check(assert_type(left_i.sub(b), pd.Series), pd.Series) + check(assert_type(left_i.sub(i), pd.Series), pd.Series) + check(assert_type(left_i.sub(f), pd.Series), pd.Series) + check(assert_type(left_i.sub(c), pd.Series), pd.Series) - check(assert_type(left.rsub(b), pd.Series), pd.Series) - check(assert_type(left.rsub(i), pd.Series), pd.Series) - check(assert_type(left.rsub(f), pd.Series), pd.Series) - check(assert_type(left.rsub(c), pd.Series), pd.Series) + check(assert_type(left_i.rsub(b), pd.Series), pd.Series) + check(assert_type(left_i.rsub(i), pd.Series), pd.Series) + check(assert_type(left_i.rsub(f), pd.Series), pd.Series) + check(assert_type(left_i.rsub(c), pd.Series), pd.Series) def test_sub_numpy_array() -> None: @@ -67,35 +77,35 @@ def test_sub_numpy_array() -> None: 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.Series), pd.Series) - check(assert_type(left - i, pd.Series), pd.Series) - check(assert_type(left - f, pd.Series), pd.Series) - check(assert_type(left - c, pd.Series), pd.Series) + check(assert_type(left_i - b, pd.Series), pd.Series) + check(assert_type(left_i - i, pd.Series), pd.Series) + check(assert_type(left_i - f, pd.Series), pd.Series) + check(assert_type(left_i - c, pd.Series), pd.Series) # `numpy` typing gives the corresponding `ndarray`s in the static type # checking, where our `__rsub__` cannot override. At runtime, they return # `Series`s. # `mypy` thinks the return types are `Any`, which is a bug. - check(assert_type(b - left, NoReturn), pd.Series) # type: ignore[assert-type] + check(assert_type(b - left_i, NoReturn), pd.Series) # type: ignore[assert-type] check( - assert_type(i - left, "npt.NDArray[np.int64]"), pd.Series # type: ignore[assert-type] + assert_type(i - left_i, "npt.NDArray[np.int64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(f - left, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] + assert_type(f - left_i, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(c - left, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] + assert_type(c - left_i, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] ) - check(assert_type(left.sub(b), pd.Series), pd.Series) - check(assert_type(left.sub(i), pd.Series), pd.Series) - check(assert_type(left.sub(f), pd.Series), pd.Series) - check(assert_type(left.sub(c), pd.Series), pd.Series) + check(assert_type(left_i.sub(b), pd.Series), pd.Series) + check(assert_type(left_i.sub(i), pd.Series), pd.Series) + check(assert_type(left_i.sub(f), pd.Series), pd.Series) + check(assert_type(left_i.sub(c), pd.Series), pd.Series) - check(assert_type(left.rsub(b), pd.Series), pd.Series) - check(assert_type(left.rsub(i), pd.Series), pd.Series) - check(assert_type(left.rsub(f), pd.Series), pd.Series) - check(assert_type(left.rsub(c), pd.Series), pd.Series) + check(assert_type(left_i.rsub(b), pd.Series), pd.Series) + check(assert_type(left_i.rsub(i), pd.Series), pd.Series) + check(assert_type(left_i.rsub(f), pd.Series), pd.Series) + check(assert_type(left_i.rsub(c), pd.Series), pd.Series) def test_sub_pd_series() -> None: @@ -105,22 +115,78 @@ def test_sub_pd_series() -> None: f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) - check(assert_type(left - b, pd.Series), pd.Series) - check(assert_type(left - i, pd.Series), pd.Series) - check(assert_type(left - f, pd.Series), pd.Series) - check(assert_type(left - c, pd.Series), pd.Series) - - check(assert_type(b - left, pd.Series), pd.Series) - check(assert_type(i - left, pd.Series), pd.Series) - check(assert_type(f - left, pd.Series), pd.Series) - check(assert_type(c - left, pd.Series), pd.Series) - - check(assert_type(left.sub(b), pd.Series), pd.Series) - check(assert_type(left.sub(i), pd.Series), pd.Series) - check(assert_type(left.sub(f), pd.Series), pd.Series) - check(assert_type(left.sub(c), pd.Series), pd.Series) - - check(assert_type(left.rsub(b), pd.Series), pd.Series) - check(assert_type(left.rsub(i), pd.Series), pd.Series) - check(assert_type(left.rsub(f), pd.Series), pd.Series) - check(assert_type(left.rsub(c), pd.Series), pd.Series) + check(assert_type(left_i - b, pd.Series), pd.Series) + check(assert_type(left_i - i, pd.Series), pd.Series) + check(assert_type(left_i - f, pd.Series), pd.Series) + check(assert_type(left_i - c, pd.Series), pd.Series) + + check(assert_type(b - left_i, pd.Series), pd.Series) + check(assert_type(i - left_i, pd.Series), pd.Series) + check(assert_type(f - left_i, pd.Series), pd.Series) + check(assert_type(c - left_i, pd.Series), pd.Series) + + check(assert_type(left_i.sub(b), pd.Series), pd.Series) + check(assert_type(left_i.sub(i), pd.Series), pd.Series) + check(assert_type(left_i.sub(f), pd.Series), pd.Series) + check(assert_type(left_i.sub(c), pd.Series), pd.Series) + + check(assert_type(left_i.rsub(b), pd.Series), pd.Series) + check(assert_type(left_i.rsub(i), pd.Series), pd.Series) + check(assert_type(left_i.rsub(f), pd.Series), pd.Series) + check(assert_type(left_i.rsub(c), pd.Series), pd.Series) + + +anchor = datetime(2025, 8, 18) +left_ts = pd.DataFrame({"a": [anchor + timedelta(hours=h + 1) for h in range(3)]})["a"] + + +def test_sub_py_datetime() -> None: + """Test pd.Series[Any] - Python native datetime(s)""" + s = anchor + + check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) + + check(assert_type(s - left_ts, "TimedeltaSeries"), pd.Series, pd.Timedelta) + + check(assert_type(left_ts.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + + check(assert_type(left_ts.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + + +def test_sub_numpy_datetime() -> None: + """Test pd.Series[Any] - numpy datetime(s)""" + s = np.datetime64(anchor) + a = np.array([s + np.timedelta64(m, "m") for m in range(3)]) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rsub__` cannot override. At runtime, they return + # `Series`s. + check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left_ts - a, "TimedeltaSeries"), pd.Series, pd.Timedelta) # type: ignore[assert-type] + + check(assert_type(s - left_ts, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(a - left_ts, "npt.NDArray[np.datetime64]"), pd.Series, pd.Timedelta) # type: ignore[assert-type] + + check(assert_type(left_ts.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left_ts.sub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) # type: ignore[assert-type] + + check(assert_type(left_ts.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left_ts.rsub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) # type: ignore[assert-type] + + +def test_sub_pd_datetime() -> None: + """Test pd.Series[Any] - Pandas datetime(s)""" + s = pd.Timestamp(anchor) + a = pd.Series([s + pd.Timedelta(minutes=m) for m in range(3)]) + + check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left_ts - a, "TimedeltaSeries"), pd.Series, pd.Timedelta) + + check(assert_type(s - left_ts, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(a - left_ts, "TimedeltaSeries"), pd.Series, pd.Timedelta) + + check(assert_type(left_ts.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left_ts.sub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) + + check(assert_type(left_ts.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left_ts.rsub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) From c4482de23dda5a2830fae0489b9048625aa0838c Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Tue, 19 Aug 2025 13:39:48 +0200 Subject: [PATCH 13/15] fix: typing --- pandas-stubs/core/series.pyi | 39 +++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 5ffc73ca..eddb77f2 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -188,6 +188,7 @@ from pandas._typing import ( np_ndarray_complex, np_ndarray_dt, np_ndarray_float, + np_ndarray_td, npt, num, ) @@ -2146,12 +2147,24 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __sub__( self: Series[Timestamp], - other: timedelta | np.timedelta64 | TimedeltaSeries | TimedeltaIndex, + other: ( + timedelta + | np.timedelta64 + | np_ndarray_td + | TimedeltaSeries + | TimedeltaIndex + ), ) -> TimestampSeries: ... @overload def __sub__( self: Series[Timedelta], - other: timedelta | np.timedelta64 | TimedeltaSeries | TimedeltaIndex, + other: ( + timedelta + | np.timedelta64 + | np_ndarray_td + | TimedeltaSeries + | TimedeltaIndex + ), ) -> TimedeltaSeries: ... @overload def sub( @@ -2170,7 +2183,7 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> Series: ... @overload - def sub( + def sub( # type: ignore[overload-overlap] self, other: Series[Never], level: Level | None = None, @@ -2264,19 +2277,31 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[Timestamp], - other: timedelta | np.timedelta64 | TimedeltaSeries | TimedeltaIndex, + other: ( + timedelta + | np.timedelta64 + | np_ndarray_td + | TimedeltaSeries + | TimedeltaIndex + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[complex]: ... + ) -> TimestampSeries: ... @overload def sub( self: Series[Timedelta], - other: timedelta | np.timedelta64 | TimedeltaSeries | TimedeltaIndex, + other: ( + timedelta + | np.timedelta64 + | np_ndarray_td + | TimedeltaSeries + | TimedeltaIndex + ), level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> Series[complex]: ... + ) -> TimedeltaSeries: ... @overload def __rsub__( # type: ignore[misc] self: Series[Never], From 1140a6dd62c11b4d4e91eda2e150fbd872e48282 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Tue, 19 Aug 2025 14:49:58 +0200 Subject: [PATCH 14/15] fix: reduce ignore --- tests/series/arithmetic/test_sub.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/series/arithmetic/test_sub.py b/tests/series/arithmetic/test_sub.py index cb8b314f..d13c3cb3 100644 --- a/tests/series/arithmetic/test_sub.py +++ b/tests/series/arithmetic/test_sub.py @@ -141,7 +141,7 @@ def test_sub_pd_series() -> None: def test_sub_py_datetime() -> None: - """Test pd.Series[Any] - Python native datetime(s)""" + """Test pd.Series[Any] - Python native datetime""" s = anchor check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) @@ -156,22 +156,22 @@ def test_sub_py_datetime() -> None: def test_sub_numpy_datetime() -> None: """Test pd.Series[Any] - numpy datetime(s)""" s = np.datetime64(anchor) - a = np.array([s + np.timedelta64(m, "m") for m in range(3)]) + a = np.array([s + np.timedelta64(m, "m") for m in range(3)], dtype=np.datetime64) - # `numpy` typing gives the corresponding `ndarray`s in the static type - # checking, where our `__rsub__` cannot override. At runtime, they return - # `Series`s. check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(left_ts - a, "TimedeltaSeries"), pd.Series, pd.Timedelta) # type: ignore[assert-type] + check(assert_type(left_ts - a, "TimedeltaSeries"), pd.Series, pd.Timedelta) check(assert_type(s - left_ts, "TimedeltaSeries"), pd.Series, pd.Timedelta) + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rsub__` cannot override. At runtime, they return + # `Series`s. check(assert_type(a - left_ts, "npt.NDArray[np.datetime64]"), pd.Series, pd.Timedelta) # type: ignore[assert-type] check(assert_type(left_ts.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(left_ts.sub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) # type: ignore[assert-type] + check(assert_type(left_ts.sub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) check(assert_type(left_ts.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(left_ts.rsub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) # type: ignore[assert-type] + check(assert_type(left_ts.rsub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) def test_sub_pd_datetime() -> None: From d0bd63a2d186e57bfd9d720ce5d1982511565af1 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Tue, 19 Aug 2025 14:50:39 +0200 Subject: [PATCH 15/15] fix: reduce keyword argument --- tests/series/arithmetic/test_sub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/series/arithmetic/test_sub.py b/tests/series/arithmetic/test_sub.py index d13c3cb3..636aa242 100644 --- a/tests/series/arithmetic/test_sub.py +++ b/tests/series/arithmetic/test_sub.py @@ -156,7 +156,7 @@ def test_sub_py_datetime() -> None: def test_sub_numpy_datetime() -> None: """Test pd.Series[Any] - numpy datetime(s)""" s = np.datetime64(anchor) - a = np.array([s + np.timedelta64(m, "m") for m in range(3)], dtype=np.datetime64) + a = np.array([s + np.timedelta64(m, "m") for m in range(3)], np.datetime64) check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) check(assert_type(left_ts - a, "TimedeltaSeries"), pd.Series, pd.Timedelta)