From 8a99cf4f4af9b04f90d52a494a918fbb9c85f211 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Sun, 13 Jul 2025 08:23:15 +0200 Subject: [PATCH 01/27] refactor: only drop TimestampSeries https://github.com/pandas-dev/pandas-stubs/pull/1273#pullrequestreview-3013726071 --- docs/philosophy.md | 16 +- pandas-stubs/_libs/interval.pyi | 11 +- pandas-stubs/_libs/tslibs/timedeltas.pyi | 7 +- pandas-stubs/_libs/tslibs/timestamps.pyi | 19 +- pandas-stubs/core/indexes/accessors.pyi | 66 +++++- pandas-stubs/core/indexes/datetimes.pyi | 8 +- pandas-stubs/core/indexes/interval.pyi | 3 +- pandas-stubs/core/reshape/tile.pyi | 13 +- pandas-stubs/core/series.pyi | 252 ++++++++++++++++------- pandas-stubs/core/tools/datetimes.pyi | 7 +- pyproject.toml | 2 +- tests/test_frame.py | 14 +- tests/test_pandas.py | 20 +- tests/test_scalars.py | 24 ++- tests/test_series.py | 134 ++++++------ tests/test_timefuncs.py | 183 ++++++++++------ 16 files changed, 494 insertions(+), 285 deletions(-) diff --git a/docs/philosophy.md b/docs/philosophy.md index 853c516e1..c19f1e1b9 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -36,6 +36,8 @@ This also allows type checking for operations on series that contain date/time d the following example that creates two series of datetimes with corresponding arithmetic. ```python +import pandas as pd + s1 = pd.Series(pd.to_datetime(["2022-05-01", "2022-06-01"])) reveal_type(s1) s2 = pd.Series(pd.to_datetime(["2022-05-15", "2022-06-15"])) @@ -46,19 +48,21 @@ ssum = s1 + s2 reveal_type(ssum) ``` -The above code (without the `reveal_type()` statements) will raise an `Exception` on the computation of `ssum` because it is +The above code (without the `reveal_type()` statements) will get a `Never` +on the computation of `ssum` because it is inappropriate to add two series containing `Timestamp` values. The types will be revealed as follows: ```text -ttest.py:4: note: Revealed type is "pandas.core.series.TimestampSeries" -ttest.py:6: note: Revealed type is "pandas.core.series.TimestampSeries" +ttest.py:4: note: Revealed type is "pandas.core.series.Series[pandas._libs.tslibs.timestamps.Timestamp]" +ttest.py:6: note: Revealed type is "pandas.core.series.Series[pandas._libs.tslibs.timestamps.Timestamp]" ttest.py:8: note: Revealed type is "pandas.core.series.TimedeltaSeries" -ttest.py:10: note: Revealed type is "builtins.Exception" +ttest.py:9: error: Need type annotation for "ssum" [var-annotated] +ttest.py:10: note: Revealed type is "Never" ``` -The type `TimestampSeries` is the result of creating a series from `pd.to_datetime()`, while -the type `TimedeltaSeries` is the result of subtracting two `TimestampSeries` as well as +The type `Series[Timestamp]` is the result of creating a series from `pd.to_datetime()`, while +the type `TimedeltaSeries` is the result of subtracting two `Series[Timestamp]` as well as the result of `pd.to_timedelta()`. ### Interval is Generic diff --git a/pandas-stubs/_libs/interval.pyi b/pandas-stubs/_libs/interval.pyi index fdb0a398c..613eb06ba 100644 --- a/pandas-stubs/_libs/interval.pyi +++ b/pandas-stubs/_libs/interval.pyi @@ -13,10 +13,7 @@ from pandas import ( Timedelta, Timestamp, ) -from pandas.core.series import ( - TimedeltaSeries, - TimestampSeries, -) +from pandas.core.series import TimedeltaSeries from pandas._typing import ( IntervalClosedType, @@ -174,7 +171,7 @@ class Interval(IntervalMixin, Generic[_OrderableT]): @overload def __gt__( self, - other: Series[int] | Series[float] | TimestampSeries | TimedeltaSeries, + other: Series[int] | Series[float] | Series[Timestamp] | TimedeltaSeries, ) -> Series[bool]: ... @overload def __lt__(self, other: Interval[_OrderableT]) -> bool: ... @@ -183,7 +180,7 @@ class Interval(IntervalMixin, Generic[_OrderableT]): @overload def __lt__( self, - other: Series[int] | Series[float] | TimestampSeries | TimedeltaSeries, + other: Series[int] | Series[float] | Series[Timestamp] | TimedeltaSeries, ) -> Series[bool]: ... @overload def __ge__(self, other: Interval[_OrderableT]) -> bool: ... @@ -192,7 +189,7 @@ class Interval(IntervalMixin, Generic[_OrderableT]): @overload def __ge__( self, - other: Series[int] | Series[float] | TimestampSeries | TimedeltaSeries, + other: Series[int] | Series[float] | Series[Timestamp] | TimedeltaSeries, ) -> Series[bool]: ... @overload def __le__(self, other: Interval[_OrderableT]) -> bool: ... diff --git a/pandas-stubs/_libs/tslibs/timedeltas.pyi b/pandas-stubs/_libs/tslibs/timedeltas.pyi index 01a17e1df..798b87a9d 100644 --- a/pandas-stubs/_libs/tslibs/timedeltas.pyi +++ b/pandas-stubs/_libs/tslibs/timedeltas.pyi @@ -17,10 +17,7 @@ from pandas import ( Series, TimedeltaIndex, ) -from pandas.core.series import ( - TimedeltaSeries, - TimestampSeries, -) +from pandas.core.series import TimedeltaSeries from typing_extensions import ( Self, TypeAlias, @@ -167,7 +164,7 @@ class Timedelta(timedelta): other: TimedeltaSeries, ) -> TimedeltaSeries: ... @overload - def __add__(self, other: TimestampSeries) -> TimestampSeries: ... + def __add__(self, other: Series[Timestamp]) -> Series[Timestamp]: ... @overload def __radd__(self, other: np.datetime64) -> Timestamp: ... @overload diff --git a/pandas-stubs/_libs/tslibs/timestamps.pyi b/pandas-stubs/_libs/tslibs/timestamps.pyi index b986c186a..3b991d7ef 100644 --- a/pandas-stubs/_libs/tslibs/timestamps.pyi +++ b/pandas-stubs/_libs/tslibs/timestamps.pyi @@ -25,7 +25,6 @@ from pandas import ( from pandas.core.series import ( Series, TimedeltaSeries, - TimestampSeries, ) from typing_extensions import ( Never, @@ -172,7 +171,7 @@ class Timestamp(datetime, SupportsIndex): self, other: DatetimeIndex | npt.NDArray[np.datetime64] ) -> np_ndarray_bool: ... @overload - def __le__(self, other: TimestampSeries) -> Series[bool]: ... + def __le__(self, other: Series[Timestamp]) -> Series[bool]: ... @overload # type: ignore[override] def __lt__(self, other: Timestamp | datetime | np.datetime64) -> bool: ... # type: ignore[misc] @overload @@ -180,7 +179,7 @@ class Timestamp(datetime, SupportsIndex): self, other: DatetimeIndex | npt.NDArray[np.datetime64] ) -> np_ndarray_bool: ... @overload - def __lt__(self, other: TimestampSeries) -> Series[bool]: ... + def __lt__(self, other: Series[Timestamp]) -> Series[bool]: ... @overload # type: ignore[override] def __ge__(self, other: Timestamp | datetime | np.datetime64) -> bool: ... # type: ignore[misc] @overload @@ -188,7 +187,7 @@ class Timestamp(datetime, SupportsIndex): self, other: DatetimeIndex | npt.NDArray[np.datetime64] ) -> np_ndarray_bool: ... @overload - def __ge__(self, other: TimestampSeries) -> Series[bool]: ... + def __ge__(self, other: Series[Timestamp]) -> Series[bool]: ... @overload # type: ignore[override] def __gt__(self, other: Timestamp | datetime | np.datetime64) -> bool: ... # type: ignore[misc] @overload @@ -196,7 +195,7 @@ class Timestamp(datetime, SupportsIndex): self, other: DatetimeIndex | npt.NDArray[np.datetime64] ) -> np_ndarray_bool: ... @overload - def __gt__(self, other: TimestampSeries) -> Series[bool]: ... + def __gt__(self, other: Series[Timestamp]) -> Series[bool]: ... # error: Signature of "__add__" incompatible with supertype "date"/"datetime" @overload # type: ignore[override] def __add__( @@ -205,7 +204,7 @@ class Timestamp(datetime, SupportsIndex): @overload def __add__(self, other: timedelta | np.timedelta64 | Tick) -> Self: ... @overload - def __add__(self, other: TimedeltaSeries) -> TimestampSeries: ... + def __add__(self, other: TimedeltaSeries) -> Series[Timestamp]: ... @overload def __add__(self, other: TimedeltaIndex) -> DatetimeIndex: ... @overload @@ -224,9 +223,9 @@ class Timestamp(datetime, SupportsIndex): @overload def __sub__(self, other: TimedeltaIndex) -> DatetimeIndex: ... @overload - def __sub__(self, other: TimedeltaSeries) -> TimestampSeries: ... + def __sub__(self, other: TimedeltaSeries) -> Series[Timestamp]: ... @overload - def __sub__(self, other: TimestampSeries) -> TimedeltaSeries: ... + def __sub__(self, other: Series[Timestamp]) -> TimedeltaSeries: ... @overload def __sub__( self, other: npt.NDArray[np.timedelta64] @@ -234,7 +233,7 @@ class Timestamp(datetime, SupportsIndex): @overload def __eq__(self, other: Timestamp | datetime | np.datetime64) -> bool: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] @overload - def __eq__(self, other: TimestampSeries) -> Series[bool]: ... # type: ignore[overload-overlap] + def __eq__(self, other: Series[Timestamp]) -> Series[bool]: ... # type: ignore[overload-overlap] @overload def __eq__(self, other: npt.NDArray[np.datetime64] | Index) -> np_ndarray_bool: ... # type: ignore[overload-overlap] @overload @@ -242,7 +241,7 @@ class Timestamp(datetime, SupportsIndex): @overload def __ne__(self, other: Timestamp | datetime | np.datetime64) -> bool: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] @overload - def __ne__(self, other: TimestampSeries) -> Series[bool]: ... # type: ignore[overload-overlap] + def __ne__(self, other: Series[Timestamp]) -> Series[bool]: ... # type: ignore[overload-overlap] @overload def __ne__(self, other: npt.NDArray[np.datetime64] | Index) -> np_ndarray_bool: ... # type: ignore[overload-overlap] @overload diff --git a/pandas-stubs/core/indexes/accessors.pyi b/pandas-stubs/core/indexes/accessors.pyi index b5db84dba..70ce8e090 100644 --- a/pandas-stubs/core/indexes/accessors.pyi +++ b/pandas-stubs/core/indexes/accessors.pyi @@ -4,9 +4,11 @@ from datetime import ( tzinfo as _tzinfo, ) from typing import ( + Any, Generic, Literal, TypeVar, + overload, ) import numpy as np @@ -17,6 +19,7 @@ from pandas import ( PeriodIndex, Timedelta, TimedeltaIndex, + Timestamp, ) from pandas.core.accessor import PandasDelegate from pandas.core.arrays import ( @@ -29,12 +32,13 @@ from pandas.core.series import ( PeriodSeries, Series, TimedeltaSeries, - TimestampSeries, ) +from typing_extensions import Never from pandas._libs.tslibs import BaseOffset from pandas._libs.tslibs.offsets import DateOffset from pandas._typing import ( + S1, TimeAmbiguous, TimeNonexistent, TimestampConvention, @@ -155,14 +159,13 @@ class _DatetimeLikeOps( ], ): ... -# Ideally, the rounding methods would return TimestampSeries when `Series.dt.method` +# Ideally, the rounding methods would return Series[Timestamp] when `Series.dt.method` # is invoked, but because of how Series.dt is hooked in and that we may not know the # type of the series, we don't know which kind of series was ...ed # in to the dt accessor _DTTimestampTimedeltaReturnType = TypeVar( - "_DTTimestampTimedeltaReturnType", - bound=Series | TimestampSeries | TimedeltaSeries | DatetimeIndex | TimedeltaIndex, + "_DTTimestampTimedeltaReturnType", bound=Series | DatetimeIndex | TimedeltaIndex ) class _DatetimeRoundingMethods(Generic[_DTTimestampTimedeltaReturnType]): @@ -198,7 +201,7 @@ class _DatetimeRoundingMethods(Generic[_DTTimestampTimedeltaReturnType]): ) -> _DTTimestampTimedeltaReturnType: ... _DTNormalizeReturnType = TypeVar( - "_DTNormalizeReturnType", TimestampSeries, DatetimeIndex + "_DTNormalizeReturnType", Series[Timestamp], DatetimeIndex ) _DTStrKindReturnType = TypeVar("_DTStrKindReturnType", bound=Series[str] | Index) _DTToPeriodReturnType = TypeVar( @@ -320,7 +323,7 @@ class TimedeltaProperties( def as_unit(self, unit: TimeUnit) -> TimedeltaSeries: ... _PeriodDTReturnTypes = TypeVar( - "_PeriodDTReturnTypes", bound=TimestampSeries | DatetimeIndex + "_PeriodDTReturnTypes", bound=Series[Timestamp] | DatetimeIndex ) _PeriodIntReturnTypes = TypeVar("_PeriodIntReturnTypes", bound=Series[int] | Index[int]) _PeriodStrReturnTypes = TypeVar("_PeriodStrReturnTypes", bound=Series[str] | Index) @@ -363,7 +366,7 @@ class PeriodIndexFieldOps( class PeriodProperties( Properties, _PeriodProperties[ - TimestampSeries, Series[int], Series[str], DatetimeArray, PeriodArray + Series[Timestamp], Series[int], Series[str], DatetimeArray, PeriodArray ], _DatetimeFieldOps[Series[int]], _IsLeapYearProperty, @@ -377,7 +380,7 @@ class CombinedDatetimelikeProperties( Series[dt.date], Series[dt.time], str, - TimestampSeries, + Series[Timestamp], Series[str], PeriodSeries, ], @@ -388,11 +391,11 @@ class TimestampProperties( DatetimeProperties[ Series[int], Series[bool], - TimestampSeries, + Series[Timestamp], Series[dt.date], Series[dt.time], str, - TimestampSeries, + Series[Timestamp], Series[str], PeriodSeries, ] @@ -427,3 +430,46 @@ class TimedeltaIndexProperties( _TimedeltaPropertiesNoRounding[Index, Index], _DatetimeRoundingMethods[TimedeltaIndex], ): ... + +class _dtDescriptor(CombinedDatetimelikeProperties, Generic[S1]): + @overload + def __get__(self, instance: Series[Never], owner: Any) -> Never: ... + @overload + def __get__( + self, instance: Series[Timestamp], owner: Any + ) -> TimestampProperties: ... + @overload + def __get__( + self, instance: Series[S1], owner: Any + ) -> CombinedDatetimelikeProperties: ... + def round( + self, + freq: str | BaseOffset | None, + ambiguous: Literal["raise", "infer", "NaT"] | bool | np_ndarray_bool = ..., + nonexistent: ( + Literal["shift_forward", "shift_backward", "NaT", "raise"] + | timedelta + | Timedelta + ) = ..., + ) -> Series[S1]: ... + def floor( + self, + freq: str | BaseOffset | None, + ambiguous: Literal["raise", "infer", "NaT"] | bool | np_ndarray_bool = ..., + nonexistent: ( + Literal["shift_forward", "shift_backward", "NaT", "raise"] + | timedelta + | Timedelta + ) = ..., + ) -> Series[S1]: ... + def ceil( + self, + freq: str | BaseOffset | None, + ambiguous: Literal["raise", "infer", "NaT"] | bool | np_ndarray_bool = ..., + nonexistent: ( + Literal["shift_forward", "shift_backward", "NaT", "raise"] + | timedelta + | Timedelta + ) = ..., + ) -> Series[S1]: ... + def as_unit(self, unit: TimeUnit) -> Series[S1]: ... diff --git a/pandas-stubs/core/indexes/datetimes.pyi b/pandas-stubs/core/indexes/datetimes.pyi index 40a7102cd..589390856 100644 --- a/pandas-stubs/core/indexes/datetimes.pyi +++ b/pandas-stubs/core/indexes/datetimes.pyi @@ -23,8 +23,8 @@ from pandas import ( from pandas.core.indexes.accessors import DatetimeIndexProperties from pandas.core.indexes.datetimelike import DatetimeTimedeltaMixin from pandas.core.series import ( + Series, TimedeltaSeries, - TimestampSeries, ) from typing_extensions import Self @@ -60,13 +60,13 @@ class DatetimeIndex(DatetimeTimedeltaMixin[Timestamp], DatetimeIndexProperties): # various ignores needed for mypy, as we do want to restrict what can be used in # arithmetic for these types @overload - def __add__(self, other: TimedeltaSeries) -> TimestampSeries: ... + def __add__(self, other: TimedeltaSeries) -> Series[Timestamp]: ... @overload def __add__( self, other: timedelta | Timedelta | TimedeltaIndex | BaseOffset ) -> DatetimeIndex: ... @overload - def __sub__(self, other: TimedeltaSeries) -> TimestampSeries: ... + def __sub__(self, other: TimedeltaSeries) -> Series[Timestamp]: ... @overload def __sub__( self, other: timedelta | Timedelta | TimedeltaIndex | BaseOffset @@ -76,7 +76,7 @@ class DatetimeIndex(DatetimeTimedeltaMixin[Timestamp], DatetimeIndexProperties): self, other: datetime | Timestamp | DatetimeIndex ) -> TimedeltaIndex: ... @final - def to_series(self, index=..., name: Hashable = ...) -> TimestampSeries: ... + def to_series(self, index=..., name: Hashable = ...) -> Series[Timestamp]: ... def snap(self, freq: str = ...): ... def slice_indexer(self, start=..., end=..., step=...): ... def searchsorted(self, value, side: str = ..., sorter=...): ... diff --git a/pandas-stubs/core/indexes/interval.pyi b/pandas-stubs/core/indexes/interval.pyi index 026c4f2af..cc53b668c 100644 --- a/pandas-stubs/core/indexes/interval.pyi +++ b/pandas-stubs/core/indexes/interval.pyi @@ -15,7 +15,6 @@ from pandas import Index from pandas.core.indexes.extension import ExtensionIndex from pandas.core.series import ( TimedeltaSeries, - TimestampSeries, ) from typing_extensions import TypeAlias @@ -53,7 +52,7 @@ _EdgesFloat: TypeAlias = ( _EdgesTimestamp: TypeAlias = ( Sequence[DatetimeLike] | npt.NDArray[np.datetime64] - | TimestampSeries + | pd.Series[pd.Timestamp] | pd.DatetimeIndex ) _EdgesTimedelta: TypeAlias = ( diff --git a/pandas-stubs/core/reshape/tile.pyi b/pandas-stubs/core/reshape/tile.pyi index 336426cc9..da7d83f16 100644 --- a/pandas-stubs/core/reshape/tile.pyi +++ b/pandas-stubs/core/reshape/tile.pyi @@ -12,10 +12,9 @@ from pandas import ( Index, Interval, IntervalIndex, - Series, Timestamp, ) -from pandas.core.series import TimestampSeries +from pandas.core.series import Series from pandas._typing import ( IntervalT, @@ -51,10 +50,10 @@ def cut( ) -> tuple[npt.NDArray[np.intp], IntervalIndex[IntervalT]]: ... @overload def cut( # pyright: ignore[reportOverlappingOverload] - x: TimestampSeries, + x: Series[Timestamp], bins: ( int - | TimestampSeries + | Series[Timestamp] | DatetimeIndex | Sequence[Timestamp] | Sequence[np.datetime64] @@ -70,7 +69,7 @@ def cut( # pyright: ignore[reportOverlappingOverload] ) -> tuple[Series, DatetimeIndex]: ... @overload def cut( - x: TimestampSeries, + x: Series[Timestamp], bins: IntervalIndex[Interval[Timestamp]], right: bool = ..., labels: Sequence[Label] | None = ..., @@ -156,10 +155,10 @@ def cut( ) -> npt.NDArray[np.intp]: ... @overload def cut( - x: TimestampSeries, + x: Series[Timestamp], bins: ( int - | TimestampSeries + | Series[Timestamp] | DatetimeIndex | Sequence[Timestamp] | Sequence[np.datetime64] diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 3a3ba8c41..834a96938 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -25,6 +25,7 @@ from typing import ( Generic, Literal, NoReturn, + TypeVar, final, overload, ) @@ -59,10 +60,9 @@ from pandas.core.groupby.generic import SeriesGroupBy from pandas.core.groupby.groupby import BaseGroupBy from pandas.core.indexers import BaseIndexer from pandas.core.indexes.accessors import ( - CombinedDatetimelikeProperties, PeriodProperties, TimedeltaProperties, - TimestampProperties, + _dtDescriptor, ) from pandas.core.indexes.category import CategoricalIndex from pandas.core.indexes.datetimes import DatetimeIndex @@ -184,6 +184,30 @@ from pandas.core.dtypes.dtypes import CategoricalDtype from pandas.plotting import PlotAccessor +_T_FLOAT = TypeVar("_T_FLOAT", bound=float) +_T_COMPLEX = TypeVar("_T_COMPLEX", bound=complex) + +_scalar_timestamp: TypeAlias = datetime | np.datetime64 | Timestamp +_vector_timestamp: TypeAlias = ( + Sequence[datetime] + | Sequence[np.datetime64] + | Sequence[Timestamp] + | np.typing.NDArray[np.datetime64] + | DatetimeIndex +) +_nonseries_timestamp: TypeAlias = _scalar_timestamp | _vector_timestamp + +_scalar_timedelta: TypeAlias = timedelta | np.timedelta64 | BaseOffset | Timedelta +_vector_timedelta: TypeAlias = ( + Sequence[timedelta] + | Sequence[np.timedelta64] + | Sequence[Timedelta] + | np.typing.NDArray[np.timedelta64] + | TimedeltaIndex +) +_nonseries_timedelta: TypeAlias = _scalar_timedelta | _vector_timedelta +_T_TIMESTAMP = TypeVar("_T_TIMESTAMP", bound=Timestamp) + class _iLocIndexerSeries(_iLocIndexer, Generic[S1]): # get item @overload @@ -301,7 +325,7 @@ class Series(IndexOpsMixin[S1], NDFrame): dtype: TimestampDtypeArg = ..., name: Hashable = ..., copy: bool = ..., - ) -> TimestampSeries: ... + ) -> Series[Timestamp]: ... @overload def __new__( cls, @@ -311,7 +335,7 @@ class Series(IndexOpsMixin[S1], NDFrame): dtype: TimestampDtypeArg, name: Hashable = ..., copy: bool = ..., - ) -> TimestampSeries: ... + ) -> Series[Timestamp]: ... @overload def __new__( cls, @@ -754,6 +778,13 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def count(self, level: Hashable) -> Series[S1]: ... def mode(self, dropna=...) -> Series[S1]: ... + @overload + def unique(self: Series[Never]) -> np.ndarray: ... # type: ignore[overload-overlap] + @overload + def unique(self: Series[Timestamp]) -> DatetimeArray: ... # type: ignore[overload-overlap] + @overload + def unique(self: Series[Timedelta]) -> TimedeltaArray: ... # type: ignore[overload-overlap] + @overload def unique(self) -> np.ndarray: ... @overload def drop_duplicates( @@ -811,6 +842,8 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def diff(self: Series[_str], periods: int = ...) -> Never: ... @overload + def diff(self: Series[Timestamp], periods: int = ...) -> TimedeltaSeries: ... # type: ignore[overload-overlap] + @overload def diff(self, periods: int = ...) -> Series[float]: ... def autocorr(self, lag: int = ...) -> float: ... @overload @@ -1194,7 +1227,7 @@ class Series(IndexOpsMixin[S1], NDFrame): Series[type[object]], ]: ... @property - def dt(self) -> CombinedDatetimelikeProperties: ... + def dt(self) -> _dtDescriptor[S1]: ... @property def plot(self) -> PlotAccessor: ... sparse = ... @@ -1315,7 +1348,7 @@ class Series(IndexOpsMixin[S1], NDFrame): dtype: TimestampDtypeArg, copy: _bool = ..., errors: IgnoreRaise = ..., - ) -> TimestampSeries: ... + ) -> Series[Timestamp]: ... @overload def astype( self, @@ -1617,6 +1650,26 @@ class Series(IndexOpsMixin[S1], NDFrame): # just failed to generate these so I couldn't match # them up. @overload + def __add__( + self: Series[Never], + other: num | _str | timedelta | Timedelta | _ListLike | Series | np.timedelta64, + ) -> Series: ... + @overload + def __add__( + self: Series[Timestamp], other: _nonseries_timestamp | Series[Timestamp] + ) -> Never: ... + @overload + def __add__( + self: Series[Timestamp], + other: _nonseries_timedelta | Series[Timedelta] | TimedeltaSeries, + ) -> Series[Timestamp]: ... + @overload + def __add__(self: Series[Timedelta], other: Period) -> PeriodSeries: ... + @overload + def __add__( + self: Series[Timedelta], other: _nonseries_timestamp | Series[Timestamp] + ) -> Series[Timestamp]: ... + @overload def __add__(self, other: S1 | Self) -> Self: ... @overload def __add__( @@ -1633,6 +1686,16 @@ class Series(IndexOpsMixin[S1], NDFrame): # def __array__(self, dtype: Optional[_bool] = ...) -> _np_ndarray def __div__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... def __eq__(self, other: object) -> Series[_bool]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] + @overload + def __floordiv__( + self: Series[Timedelta], other: float | Sequence[float] + ) -> TimedeltaSeries: ... + @overload + def __floordiv__( + self: Series[Timedelta], + other: _nonseries_timedelta | Series[Timedelta], + ) -> Series[int]: ... + @overload def __floordiv__(self, other: num | _ListLike | Series[S1]) -> Series[int]: ... def __ge__( # type: ignore[override] self, other: S1 | _ListLike | Series[S1] | datetime | timedelta | date @@ -1647,8 +1710,27 @@ class Series(IndexOpsMixin[S1], NDFrame): self, other: S1 | _ListLike | Series[S1] | datetime | timedelta | date ) -> Series[_bool]: ... @overload + def __mul__(self: Series[Never], other: num | _ListLike | Series) -> Series: ... + @overload + def __mul__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] + @overload def __mul__( - self, other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64 + self: Series[int], other: _T_COMPLEX | Series[_T_COMPLEX] + ) -> Series[_T_COMPLEX]: ... + @overload + def __mul__( + self: Series[Timestamp], + other: ( + num | Sequence[num] | Series[int] | Series[float] | float | Sequence[float] + ), + ) -> Series[Timestamp]: ... + @overload + def __mul__( + self: Series[Timestamp], other: _nonseries_timedelta | TimedeltaSeries + ) -> Never: ... + @overload + def __mul__( + self: Series[_T_FLOAT], other: _nonseries_timedelta | TimedeltaSeries ) -> TimedeltaSeries: ... @overload def __mul__(self, other: num | _ListLike | Series) -> Series: ... @@ -1663,6 +1745,18 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __or__(self, other: int | np_ndarray_anyint | Series[int]) -> Series[int]: ... @overload + def __radd__( + self: Series[Never], other: num | _str | _ListLike | Series + ) -> Series: ... + @overload + def __radd__( + self: Series[Timestamp], other: _nonseries_timedelta | Series[Timedelta] + ) -> Series[Timestamp]: ... + @overload + def __radd__( + self: Series[Timedelta], other: datetime | Timestamp | Series[Timestamp] + ) -> Series[Timestamp]: ... + @overload def __radd__(self, other: S1 | Series[S1]) -> Self: ... @overload def __radd__(self, other: num | _str | _ListLike | Series) -> Series: ... @@ -1675,6 +1769,11 @@ class Series(IndexOpsMixin[S1], NDFrame): def __rand__(self, other: int | np_ndarray_anyint | Series[int]) -> Series[int]: ... def __rdiv__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... def __rdivmod__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] + @overload + def __rfloordiv__( + self: Series[Timedelta], other: _nonseries_timedelta | Series[Timedelta] + ) -> Series[int]: ... + @overload def __rfloordiv__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... def __rmod__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... @overload @@ -1693,6 +1792,11 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __ror__(self, other: int | np_ndarray_anyint | Series[int]) -> Series[int]: ... def __rsub__(self, other: num | _ListLike | Series[S1]) -> Series: ... + @overload + def __rtruediv__( + self: Series[Timedelta], other: _nonseries_timedelta | Series[Timedelta] + ) -> Series[float]: ... + @overload def __rtruediv__(self, other: num | _ListLike | Series[S1] | Path) -> Series: ... # ignore needed for mypy as we want different results based on the arguments @overload # type: ignore[override] @@ -1702,21 +1806,33 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __rxor__(self, other: int | np_ndarray_anyint | Series[int]) -> Series[int]: ... @overload - def __sub__( - self: Series[Timestamp], - other: Timedelta | TimedeltaSeries | TimedeltaIndex | np.timedelta64, - ) -> TimestampSeries: ... + def __sub__(self: Series[Never], other: num | _ListLike | Series) -> Series: ... @overload def __sub__( - self: Series[Timedelta], - other: Timedelta | TimedeltaSeries | TimedeltaIndex | np.timedelta64, - ) -> TimedeltaSeries: ... + self: Series[Timestamp], other: _nonseries_timedelta | TimedeltaSeries + ) -> Series[Timestamp]: ... @overload def __sub__( - self, other: Timestamp | datetime | TimestampSeries + self: Series[Timestamp], other: _nonseries_timestamp | Series[_T_TIMESTAMP] ) -> TimedeltaSeries: ... @overload + def __sub__(self, other: S1 | Self) -> Self: ... + @overload def __sub__(self, other: num | _ListLike | Series) -> Series: ... + @overload + def __truediv__( + self: Series[Never], other: num | _ListLike | Series[S1] | Path + ) -> Series: ... + @overload + def __truediv__( + self: Series[Timestamp], + other: float | Series[int] | Series[float] | Sequence[float], + ) -> Series[Timestamp]: ... + @overload + def __truediv__( + self: Series[Timedelta], other: _nonseries_timedelta | Series[Timedelta] + ) -> Series[float]: ... + @overload def __truediv__(self, other: num | _ListLike | Series[S1] | Path) -> Series: ... # ignore needed for mypy as we want different results based on the arguments @overload # type: ignore[override] @@ -1786,6 +1902,14 @@ class Series(IndexOpsMixin[S1], NDFrame): **kwargs: Any, ) -> Never: ... @overload + def cumprod( + self: Series[Timestamp], + axis: AxisIndex = ..., + skipna: _bool = ..., + *args: Any, + **kwargs: Any, + ) -> Never: ... + @overload def cumprod( self, axis: AxisIndex = ..., @@ -1899,6 +2023,25 @@ class Series(IndexOpsMixin[S1], NDFrame): numeric_only: _bool = ..., **kwargs: Any, ) -> S1: ... + @overload + def mean( + self: Series[Never], + axis: AxisIndex | None = ..., + skipna: _bool = ..., + level: None = ..., + numeric_only: _bool = ..., + **kwargs: Any, + ) -> float: ... + @overload + def mean( + self: Series[Timestamp], + axis: AxisIndex | None = ..., + skipna: _bool = ..., + level: None = ..., + numeric_only: _bool = ..., + **kwargs: Any, + ) -> Timestamp: ... + @overload def mean( self, axis: AxisIndex | None = ..., @@ -1914,7 +2057,7 @@ class Series(IndexOpsMixin[S1], NDFrame): level: None = ..., numeric_only: _bool = ..., **kwargs: Any, - ) -> float: ... + ) -> S1: ... def min( self, axis: AxisIndex | None = ..., @@ -2091,6 +2234,17 @@ class Series(IndexOpsMixin[S1], NDFrame): numeric_only: _bool = ..., **kwargs: Any, ) -> Scalar: ... + @overload + def std( + self: Series[Timestamp], + axis: AxisIndex | None = ..., + skipna: _bool | None = ..., + level: None = ..., + ddof: int = ..., + numeric_only: _bool = ..., + **kwargs: Any, + ) -> Timedelta: ... + @overload def std( self, axis: AxisIndex | None = ..., @@ -2217,71 +2371,19 @@ class Series(IndexOpsMixin[S1], NDFrame): @final def __bool__(self) -> NoReturn: ... -class TimestampSeries(Series[Timestamp]): - @property - def dt(self) -> TimestampProperties: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] - def __add__(self, other: TimedeltaSeries | np.timedelta64 | timedelta | BaseOffset) -> TimestampSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] - def __radd__(self, other: TimedeltaSeries | np.timedelta64 | timedelta) -> TimestampSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] - @overload # type: ignore[override] - def __sub__( - self, other: Timestamp | datetime | TimestampSeries - ) -> TimedeltaSeries: ... - @overload - def __sub__( # pyright: ignore[reportIncompatibleMethodOverride] - self, - other: ( - timedelta | TimedeltaSeries | TimedeltaIndex | np.timedelta64 | BaseOffset - ), - ) -> TimestampSeries: ... - def __mul__(self, other: float | Series[int] | Series[float] | Sequence[float]) -> TimestampSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] - def __truediv__(self, other: float | Series[int] | Series[float] | Sequence[float]) -> TimestampSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] - def unique(self) -> DatetimeArray: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] - def mean( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] - self, - axis: AxisIndex | None = ..., - skipna: _bool = ..., - level: None = ..., - numeric_only: _bool = ..., - **kwargs: Any, - ) -> Timestamp: ... - def median( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] - self, - axis: AxisIndex | None = ..., - skipna: _bool = ..., - level: None = ..., - numeric_only: _bool = ..., - **kwargs: Any, - ) -> Timestamp: ... - def std( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] - self, - axis: AxisIndex | None = ..., - skipna: _bool | None = ..., - ddof: int = ..., - numeric_only: _bool = ..., - **kwargs: Any, - ) -> Timedelta: ... - def diff(self, periods: int = ...) -> TimedeltaSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] - def cumprod( - self, - axis: AxisIndex = ..., - skipna: _bool = ..., - *args: Any, - **kwargs: Any, - ) -> Never: ... - class TimedeltaSeries(Series[Timedelta]): # ignores needed because of mypy @overload # type: ignore[override] def __add__(self, other: Period) -> PeriodSeries: ... @overload def __add__( - self, other: datetime | Timestamp | TimestampSeries | DatetimeIndex - ) -> TimestampSeries: ... + self, other: datetime | Timestamp | Series[Timestamp] | DatetimeIndex + ) -> Series[Timestamp]: ... @overload def __add__( # pyright: ignore[reportIncompatibleMethodOverride] self, other: timedelta | Timedelta | np.timedelta64 ) -> TimedeltaSeries: ... - def __radd__(self, other: datetime | Timestamp | TimestampSeries) -> TimestampSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] + def __radd__(self, other: datetime | Timestamp | Series[Timestamp]) -> Series[Timestamp]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] def __mul__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] self, other: num | Sequence[num] | Series[int] | Series[float] ) -> TimedeltaSeries: ... @@ -2348,7 +2450,7 @@ class TimedeltaSeries(Series[Timedelta]): numeric_only: _bool = ..., **kwargs: Any, ) -> Timedelta: ... - def median( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] + def median( self, axis: AxisIndex | None = ..., skipna: _bool = ..., @@ -2373,7 +2475,7 @@ class TimedeltaSeries(Series[Timedelta]): *args: Any, **kwargs: Any, ) -> TimedeltaSeries: ... - def cumprod( + def cumprod( # pyrefly: ignore self, axis: AxisIndex = ..., skipna: _bool = ..., @@ -2401,7 +2503,7 @@ class OffsetSeries(Series[BaseOffset]): def __radd__( # pyright: ignore[reportIncompatibleMethodOverride] self, other: BaseOffset ) -> OffsetSeries: ... - def cumprod( + def cumprod( # pyrefly: ignore self, axis: AxisIndex = ..., skipna: _bool = ..., @@ -2412,4 +2514,4 @@ class OffsetSeries(Series[BaseOffset]): class IntervalSeries(Series[Interval[_OrderableT]], Generic[_OrderableT]): @property def array(self) -> IntervalArray: ... - def diff(self, periods: int = ...) -> Never: ... + def diff(self, periods: int = ...) -> Never: ... # pyrefly: ignore diff --git a/pandas-stubs/core/tools/datetimes.pyi b/pandas-stubs/core/tools/datetimes.pyi index 6d45eef4d..f040023bf 100644 --- a/pandas-stubs/core/tools/datetimes.pyi +++ b/pandas-stubs/core/tools/datetimes.pyi @@ -16,10 +16,7 @@ from pandas import ( ) from pandas.core.arrays import ExtensionArray from pandas.core.indexes.datetimes import DatetimeIndex -from pandas.core.series import ( - Series, - TimestampSeries, -) +from pandas.core.series import Series from typing_extensions import TypeAlias from pandas._libs.tslibs import NaTType @@ -94,7 +91,7 @@ def to_datetime( unit: str | None = ..., origin: Literal["julian", "unix"] | TimestampConvertibleTypes = ..., cache: bool = ..., -) -> TimestampSeries: ... +) -> Series[Timestamp]: ... @overload def to_datetime( arg: ( diff --git a/pyproject.toml b/pyproject.toml index a60a4f6ba..0de7fe6ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ mypy = "1.17.0" pandas = "2.3.1" pyarrow = ">=10.0.1" pytest = ">=7.1.2" -pyright = ">=1.1.400" +pyright = ">=1.1.403" ty = "^0.0.1a8" pyrefly = "^0.21.0" poethepoet = ">=0.16.5" diff --git a/tests/test_frame.py b/tests/test_frame.py index 2973d25cb..5a3ffb67c 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -31,7 +31,6 @@ import numpy as np import numpy.typing as npt import pandas as pd -from pandas import Timestamp from pandas.api.typing import NAType from pandas.core.resample import ( DatetimeIndexResampler, @@ -63,10 +62,8 @@ if TYPE_CHECKING: from pandas.core.frame import _PandasNamedTuple - from pandas.core.series import TimestampSeries else: _PandasNamedTuple: TypeAlias = tuple - TimestampSeries: TypeAlias = pd.Series DF = pd.DataFrame(data={"col1": [1, 2], "col2": [3, 4]}) @@ -2535,9 +2532,9 @@ def test_types_regressions() -> None: sseries + pd.Timedelta(1, "d") check( - assert_type(sseries + pd.Timedelta(1, "D"), TimestampSeries), + assert_type(sseries + pd.Timedelta(1, "D"), "pd.Series[pd.Timestamp]"), pd.Series, - Timestamp, + pd.Timestamp, ) # https://github.com/microsoft/pylance-release/issues/2133 @@ -2819,9 +2816,9 @@ def test_sum_get_add() -> None: summer = df.sum(axis=1) check(assert_type(summer, pd.Series), pd.Series) - check(assert_type(s + summer, pd.Series), pd.Series) - check(assert_type(s + df["y"], pd.Series), pd.Series) - check(assert_type(summer + summer, pd.Series), pd.Series) + check(assert_type(s + summer, pd.Series), pd.Series) # type: ignore[assert-type] + check(assert_type(s + df["y"], pd.Series), pd.Series) # type: ignore[assert-type] + check(assert_type(summer + summer, pd.Series), pd.Series) # type: ignore[assert-type] def test_getset_untyped() -> None: @@ -3479,6 +3476,7 @@ def test_groupby_apply() -> None: df = pd.DataFrame({"col1": [1, 2, 3], "col2": [4, 5, 6]}) def sum_mean(x: pd.DataFrame) -> float: + x.sum() return x.sum().mean() with pytest_warns_bounded( diff --git a/tests/test_pandas.py b/tests/test_pandas.py index 068254c3a..1807a3c50 100644 --- a/tests/test_pandas.py +++ b/tests/test_pandas.py @@ -23,7 +23,6 @@ import pytest from typing_extensions import ( Never, - TypeAlias, assert_type, ) @@ -36,18 +35,19 @@ pytest_warns_bounded, ) -if TYPE_CHECKING: - from pandas.core.series import TimestampSeries -else: - TimestampSeries: TypeAlias = pd.Series - def test_types_to_datetime() -> None: df = pd.DataFrame({"year": [2015, 2016], "month": [2, 3], "day": [4, 5]}) - check(assert_type(pd.to_datetime(df), TimestampSeries), pd.Series, pd.Timestamp) + check( + assert_type(pd.to_datetime(df), "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, + ) check( - assert_type(pd.to_datetime(df, unit="s", origin="unix"), TimestampSeries), + assert_type( + pd.to_datetime(df, unit="s", origin="unix"), "pd.Series[pd.Timestamp]" + ), pd.Series, pd.Timestamp, ) @@ -56,7 +56,7 @@ def test_types_to_datetime() -> None: pd.to_datetime( df, unit="ns", dayfirst=True, utc=False, format="%M:%D", exact=False ), - TimestampSeries, + "pd.Series[pd.Timestamp]", ), pd.Series, pd.Timestamp, @@ -92,7 +92,7 @@ def test_types_to_datetime() -> None: check( assert_type( pd.to_datetime({"year": [2015, 2016], "month": [2, 3], "day": [4, 5]}), - TimestampSeries, + "pd.Series[pd.Timestamp]", ), pd.Series, pd.Timestamp, diff --git a/tests/test_scalars.py b/tests/test_scalars.py index bfc78ae65..0a782ddbd 100644 --- a/tests/test_scalars.py +++ b/tests/test_scalars.py @@ -39,12 +39,10 @@ OffsetSeries, PeriodSeries, TimedeltaSeries, - TimestampSeries, ) else: TimedeltaSeries: TypeAlias = pd.Series - TimestampSeries: TypeAlias = pd.Series PeriodSeries: TypeAlias = pd.Series OffsetSeries: TypeAlias = pd.Series @@ -1215,10 +1213,14 @@ def test_timestamp_add_sub() -> None: check(assert_type(as_timedelta_index + ts, pd.DatetimeIndex), pd.DatetimeIndex) check( - assert_type(ts + as_timedelta_series, TimestampSeries), pd.Series, pd.Timestamp + assert_type(ts + as_timedelta_series, "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, ) check( - assert_type(as_timedelta_series + ts, TimestampSeries), pd.Series, pd.Timestamp + assert_type(as_timedelta_series + ts, "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, ) check( @@ -1241,7 +1243,9 @@ def test_timestamp_add_sub() -> None: check(assert_type(ts - as_offset, pd.Timestamp), pd.Timestamp) check(assert_type(ts - as_timedelta_index, pd.DatetimeIndex), pd.DatetimeIndex) check( - assert_type(ts - as_timedelta_series, TimestampSeries), pd.Series, pd.Timestamp + assert_type(ts - as_timedelta_series, "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, ) check( assert_type(ts - as_np_ndarray_td64, npt.NDArray[np.datetime64]), @@ -1264,9 +1268,13 @@ def test_timestamp_cmp() -> None: # DatetimeIndex, but the type checker detects it to be UnknownIndex. c_unknown_index = pd.DataFrame({"a": [1]}, index=c_datetimeindex).index c_np_ndarray_dt64 = np_dt64_arr - c_series_dt64: TimestampSeries = pd.Series([1, 2, 3], dtype="datetime64[ns]") + c_series_dt64 = pd.Series([1, 2, 3], dtype="datetime64[ns]") c_series_timestamp = pd.Series(pd.DatetimeIndex(["2000-1-1"])) - check(assert_type(c_series_timestamp, TimestampSeries), pd.Series, pd.Timestamp) + check( + assert_type(c_series_timestamp, "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, + ) # Use xor to ensure one is True and the other is False # Correctness ensures since tested to be bools gt = check(assert_type(ts > c_timestamp, bool), bool) @@ -1725,7 +1733,7 @@ def test_types_timestamp_series_comparisons() -> None: data = pd.date_range("2022-01-01", "2022-01-31", freq="D") s = pd.Series(data) ts2 = pd.Timestamp("2022-01-15") - check(assert_type(s, TimestampSeries), pd.Series, pd.Timestamp) + check(assert_type(s, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) check(assert_type(ts2 <= s, "pd.Series[bool]"), pd.Series, np.bool_) check(assert_type(ts2 >= s, "pd.Series[bool]"), pd.Series, np.bool_) check(assert_type(ts2 < s, "pd.Series[bool]"), pd.Series, np.bool_) diff --git a/tests/test_series.py b/tests/test_series.py index f58d651f4..bda68d692 100644 --- a/tests/test_series.py +++ b/tests/test_series.py @@ -68,7 +68,6 @@ from pandas.core.series import ( OffsetSeries, TimedeltaSeries, - TimestampSeries, ) from tests import ( @@ -89,7 +88,6 @@ else: TimedeltaSeries: TypeAlias = pd.Series - TimestampSeries: TypeAlias = pd.Series OffsetSeries: TypeAlias = pd.Series @@ -811,13 +809,11 @@ 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), 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] - # check(assert_type(s * s2, "pd.Series[int]"), pd.Series, np.integer ) - check(assert_type(s * s2, pd.Series), pd.Series, np.integer) + check(assert_type(s * s2, "pd.Series[int]"), pd.Series, np.integer) + # TODO this below should type pd.Series[int] # check(assert_type(s.mul(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s.mul(s2, fill_value=0), pd.Series), pd.Series, np.integer) @@ -1604,8 +1600,8 @@ def test_series_min_max_sub_axis() -> None: ss = s1 - s2 sm = s1 * s2 sd = s1 / s2 - check(assert_type(sa, pd.Series), pd.Series) - check(assert_type(ss, pd.Series), pd.Series) + check(assert_type(sa, pd.Series), pd.Series) # type: ignore[assert-type] + check(assert_type(ss, pd.Series), pd.Series) # type: ignore[assert-type] check(assert_type(sm, pd.Series), pd.Series) check(assert_type(sd, pd.Series), pd.Series) @@ -1641,11 +1637,11 @@ def test_series_multiindex_getitem() -> None: def test_series_mul() -> None: s = pd.Series([1, 2, 3]) sm = s * 4 - check(assert_type(sm, pd.Series), pd.Series) + check(assert_type(sm, "pd.Series[int]"), pd.Series, np.integer) 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) + check(assert_type(sm2, "pd.Series[int]"), pd.Series, np.integer) sp = s + 4 check(assert_type(sp, "pd.Series[int]"), pd.Series, np.integer) @@ -2791,64 +2787,64 @@ def test_astype_timestamp(cast_arg: TimestampDtypeArg, target_type: type) -> Non if cast_arg in ("date32[pyarrow]", "date64[pyarrow]"): x = pd.Series(pd.date_range("2000-01-01", "2000-02-01")) - check(x.astype(cast_arg), TimestampSeries, target_type) + check(x.astype(cast_arg), pd.Series, target_type) else: - check(s.astype(cast_arg), TimestampSeries, target_type) + check(s.astype(cast_arg), pd.Series, target_type) if TYPE_CHECKING: # numpy datetime64 - assert_type(s.astype("datetime64[Y]"), TimestampSeries) - assert_type(s.astype("datetime64[M]"), TimestampSeries) - assert_type(s.astype("datetime64[W]"), TimestampSeries) - assert_type(s.astype("datetime64[D]"), TimestampSeries) - assert_type(s.astype("datetime64[h]"), TimestampSeries) - assert_type(s.astype("datetime64[m]"), TimestampSeries) - assert_type(s.astype("datetime64[s]"), TimestampSeries) - assert_type(s.astype("datetime64[ms]"), TimestampSeries) - assert_type(s.astype("datetime64[us]"), TimestampSeries) - assert_type(s.astype("datetime64[μs]"), TimestampSeries) - assert_type(s.astype("datetime64[ns]"), TimestampSeries) - assert_type(s.astype("datetime64[ps]"), TimestampSeries) - assert_type(s.astype("datetime64[fs]"), TimestampSeries) - assert_type(s.astype("datetime64[as]"), TimestampSeries) + assert_type(s.astype("datetime64[Y]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("datetime64[M]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("datetime64[W]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("datetime64[D]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("datetime64[h]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("datetime64[m]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("datetime64[s]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("datetime64[ms]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("datetime64[us]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("datetime64[μs]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("datetime64[ns]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("datetime64[ps]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("datetime64[fs]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("datetime64[as]"), "pd.Series[pd.Timestamp]") # numpy datetime64 type codes - assert_type(s.astype("M8[Y]"), TimestampSeries) - assert_type(s.astype("M8[M]"), TimestampSeries) - assert_type(s.astype("M8[W]"), TimestampSeries) - assert_type(s.astype("M8[D]"), TimestampSeries) - assert_type(s.astype("M8[h]"), TimestampSeries) - assert_type(s.astype("M8[m]"), TimestampSeries) - assert_type(s.astype("M8[s]"), TimestampSeries) - assert_type(s.astype("M8[ms]"), TimestampSeries) - assert_type(s.astype("M8[us]"), TimestampSeries) - assert_type(s.astype("M8[μs]"), TimestampSeries) - assert_type(s.astype("M8[ns]"), TimestampSeries) - assert_type(s.astype("M8[ps]"), TimestampSeries) - assert_type(s.astype("M8[fs]"), TimestampSeries) - assert_type(s.astype("M8[as]"), TimestampSeries) + assert_type(s.astype("M8[Y]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("M8[M]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("M8[W]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("M8[D]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("M8[h]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("M8[m]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("M8[s]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("M8[ms]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("M8[us]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("M8[μs]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("M8[ns]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("M8[ps]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("M8[fs]"), "pd.Series[pd.Timestamp]") + assert_type(s.astype("M8[as]"), "pd.Series[pd.Timestamp]") # numpy datetime64 type codes - assert_type(s.astype(" None: def test_timedeltaseries_operators() -> None: series = pd.Series([pd.Timedelta(days=1)]) check( - assert_type(series + datetime.datetime.now(), TimestampSeries), + assert_type(series + datetime.datetime.now(), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) @@ -3363,7 +3359,7 @@ def test_timedeltaseries_operators() -> None: pd.Timedelta, ) check( - assert_type(datetime.datetime.now() + series, TimestampSeries), + assert_type(datetime.datetime.now() + series, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) @@ -3377,13 +3373,13 @@ def test_timedeltaseries_operators() -> None: def test_timestamp_series() -> None: series = pd.Series([pd.Timestamp(2024, 4, 4)]) check( - assert_type(series + YearEnd(0), TimestampSeries), - TimestampSeries, + assert_type(series + YearEnd(0), "pd.Series[pd.Timestamp]"), + pd.Series, pd.Timestamp, ) check( - assert_type(series - YearEnd(0), TimestampSeries), - TimestampSeries, + assert_type(series - YearEnd(0), "pd.Series[pd.Timestamp]"), + pd.Series, pd.Timestamp, ) @@ -3915,7 +3911,7 @@ def test_cumsum_timedelta() -> None: s = pd.Series(pd.to_timedelta([1, 2, 3], "h")) check(assert_type(s.cumsum(), "TimedeltaSeries"), pd.Series, pd.Timedelta) check( - assert_type(pd.Timestamp(0) + s.cumsum(), "TimestampSeries"), + assert_type(pd.Timestamp(0) + s.cumsum(), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) diff --git a/tests/test_timefuncs.py b/tests/test_timefuncs.py index 4843c5139..67de2b7ae 100644 --- a/tests/test_timefuncs.py +++ b/tests/test_timefuncs.py @@ -51,7 +51,6 @@ if TYPE_CHECKING: from pandas.core.series import PeriodSeries # noqa: F401 from pandas.core.series import TimedeltaSeries # noqa: F401 - from pandas.core.series import TimestampSeries # noqa: F401 from tests import np_ndarray_bool @@ -125,7 +124,7 @@ def test_types_timestamp_series_comparisons() -> None: data = pd.date_range("2022-01-01", "2022-01-31", freq="D") s = pd.Series(data) ts2 = pd.Timestamp("2022-01-15") - check(assert_type(s, "TimestampSeries"), pd.Series, pd.Timestamp) + check(assert_type(s, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) check(assert_type(ts2 <= s, "pd.Series[bool]"), pd.Series, np.bool_) check(assert_type(ts2 >= s, "pd.Series[bool]"), pd.Series, np.bool_) check(assert_type(ts2 < s, "pd.Series[bool]"), pd.Series, np.bool_) @@ -216,7 +215,7 @@ def test_datetimeindex_plus_timedelta() -> None: check( assert_type( pd.Series([pd.Timestamp("2022-03-05"), pd.Timestamp("2022-03-06")]), - "TimestampSeries", + "pd.Series[pd.Timestamp]", ), pd.Series, pd.Timestamp, @@ -225,13 +224,13 @@ def test_datetimeindex_plus_timedelta() -> None: td_s = pd.to_timedelta(pd.Series([10, 20]), "minutes") dti_td_s = dti + td_s check( - assert_type(dti_td_s, "TimestampSeries"), + assert_type(dti_td_s, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) td_dti_s = td_s + dti check( - assert_type(td_dti_s, "TimestampSeries"), + assert_type(td_dti_s, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) @@ -251,7 +250,7 @@ def test_datetimeindex_minus_timedelta() -> None: check( assert_type( pd.Series([pd.Timestamp("2022-03-05"), pd.Timestamp("2022-03-06")]), - "TimestampSeries", + "pd.Series[pd.Timestamp]", ), pd.Series, pd.Timestamp, @@ -260,7 +259,7 @@ def test_datetimeindex_minus_timedelta() -> None: td_s = pd.to_timedelta(pd.Series([10, 20]), "minutes") dti_td_s = dti - td_s check( - assert_type(dti_td_s, "TimestampSeries"), + assert_type(dti_td_s, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) @@ -277,7 +276,7 @@ def test_timestamp_plus_timedelta_series() -> None: check( assert_type( pd.Series([pd.Timestamp("2022-03-05"), pd.Timestamp("2022-03-06")]), - "TimestampSeries", + "pd.Series[pd.Timestamp]", ), pd.Series, pd.Timestamp, @@ -285,9 +284,9 @@ def test_timestamp_plus_timedelta_series() -> None: ts = pd.Timestamp("2022-03-05") td = pd.to_timedelta(pd.Series([10, 20]), "minutes") r3 = td + ts - check(assert_type(r3, "TimestampSeries"), pd.Series, pd.Timestamp) + check(assert_type(r3, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) r4 = ts + td - check(assert_type(r4, "TimestampSeries"), pd.Series, pd.Timestamp) + check(assert_type(r4, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) def test_timedelta_series_mult() -> None: @@ -321,9 +320,9 @@ def test_fail_on_adding_two_timestamps() -> None: s1 = pd.Series(pd.to_datetime(["2022-05-01", "2022-06-01"])) s2 = pd.Series(pd.to_datetime(["2022-05-15", "2022-06-15"])) if TYPE_CHECKING_INVALID_USAGE: - ssum: pd.Series = s1 + s2 # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + ssum = s1 + s2 # type: ignore[var-annotated] ts = pd.Timestamp("2022-06-30") - tsum: pd.Series = s1 + ts # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + tsum = s1 + ts # type: ignore[var-annotated] def test_dtindex_tzinfo() -> None: @@ -378,7 +377,9 @@ def test_series_dt_accessors() -> None: i0 = pd.date_range(start="2022-06-01", periods=10) check(assert_type(i0, pd.DatetimeIndex), pd.DatetimeIndex, pd.Timestamp) - check(assert_type(i0.to_series(), "TimestampSeries"), pd.Series, pd.Timestamp) + check( + assert_type(i0.to_series(), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp + ) s0 = pd.Series(i0) @@ -425,94 +426,125 @@ def test_series_dt_accessors() -> None: ) s0_local = s0.dt.tz_localize("UTC") check( - assert_type(s0_local, "TimestampSeries"), + assert_type(s0_local, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) check( - assert_type(s0.dt.tz_localize(None), "TimestampSeries"), pd.Series, pd.Timestamp + assert_type(s0.dt.tz_localize(None), "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, ) check( assert_type( s0.dt.tz_localize(pytz.UTC, nonexistent=dt.timedelta(0)), - "TimestampSeries", + "pd.Series[pd.Timestamp]", ), pd.Series, pd.Timestamp, ) check( - assert_type(s0.dt.tz_localize(pytz.timezone("US/Eastern")), "TimestampSeries"), + assert_type( + s0.dt.tz_localize(pytz.timezone("US/Eastern")), "pd.Series[pd.Timestamp]" + ), pd.Series, pd.Timestamp, ) check( - assert_type(s0_local.dt.tz_convert("EST"), "TimestampSeries"), + assert_type(s0_local.dt.tz_convert("EST"), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) check( - assert_type(s0_local.dt.tz_convert(None), "TimestampSeries"), + assert_type(s0_local.dt.tz_convert(None), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) check( - assert_type(s0_local.dt.tz_convert(pytz.UTC), "TimestampSeries"), + assert_type(s0_local.dt.tz_convert(pytz.UTC), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) check( - assert_type(s0_local.dt.tz_convert(1), "TimestampSeries"), + assert_type(s0_local.dt.tz_convert(1), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) check( assert_type( - s0_local.dt.tz_convert(pytz.timezone("US/Eastern")), "TimestampSeries" + s0_local.dt.tz_convert(pytz.timezone("US/Eastern")), + "pd.Series[pd.Timestamp]", ), pd.Series, pd.Timestamp, ) check(assert_type(s0.dt.tz, Optional[dt.tzinfo]), type(None)) check(assert_type(s0_local.dt.tz, Optional[dt.tzinfo]), dt.tzinfo) - check(assert_type(s0.dt.normalize(), "TimestampSeries"), pd.Series, pd.Timestamp) + check( + assert_type(s0.dt.normalize(), "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, + ) check(assert_type(s0.dt.strftime("%Y"), "pd.Series[str]"), pd.Series, str) check( - assert_type(s0.dt.round("D", nonexistent=dt.timedelta(1)), "TimestampSeries"), + assert_type( + s0.dt.round("D", nonexistent=dt.timedelta(1)), "pd.Series[pd.Timestamp]" + ), pd.Series, pd.Timestamp, ) check( - assert_type(s0.dt.round("D", ambiguous=False), "TimestampSeries"), + assert_type(s0.dt.round("D", ambiguous=False), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) check( - assert_type(s0.dt.floor("D", nonexistent=dt.timedelta(1)), "TimestampSeries"), + assert_type( + s0.dt.floor("D", nonexistent=dt.timedelta(1)), "pd.Series[pd.Timestamp]" + ), pd.Series, pd.Timestamp, ) check( - assert_type(s0.dt.floor("D", ambiguous=False), "TimestampSeries"), + assert_type(s0.dt.floor("D", ambiguous=False), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) check( - assert_type(s0.dt.ceil("D", nonexistent=dt.timedelta(1)), "TimestampSeries"), + assert_type( + s0.dt.ceil("D", nonexistent=dt.timedelta(1)), "pd.Series[pd.Timestamp]" + ), pd.Series, pd.Timestamp, ) check( - assert_type(s0.dt.ceil("D", ambiguous=False), "TimestampSeries"), + assert_type(s0.dt.ceil("D", ambiguous=False), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) check(assert_type(s0.dt.month_name(), "pd.Series[str]"), pd.Series, str) check(assert_type(s0.dt.day_name(), "pd.Series[str]"), pd.Series, str) check(assert_type(s0.dt.unit, TimeUnit), str) - check(assert_type(s0.dt.as_unit("s"), "TimestampSeries"), pd.Series, pd.Timestamp) - check(assert_type(s0.dt.as_unit("ms"), "TimestampSeries"), pd.Series, pd.Timestamp) - check(assert_type(s0.dt.as_unit("us"), "TimestampSeries"), pd.Series, pd.Timestamp) - check(assert_type(s0.dt.as_unit("ns"), "TimestampSeries"), pd.Series, pd.Timestamp) + check( + assert_type(s0.dt.as_unit("s"), "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, + ) + check( + assert_type(s0.dt.as_unit("ms"), "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, + ) + check( + assert_type(s0.dt.as_unit("us"), "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, + ) + check( + assert_type(s0.dt.as_unit("ns"), "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, + ) i1 = pd.period_range(start="2022-06-01", periods=10) @@ -523,8 +555,14 @@ def test_series_dt_accessors() -> None: s1 = pd.Series(i1) check(assert_type(s1.dt.qyear, "pd.Series[int]"), pd.Series, np.integer) - check(assert_type(s1.dt.start_time, "TimestampSeries"), pd.Series, pd.Timestamp) - check(assert_type(s1.dt.end_time, "TimestampSeries"), pd.Series, pd.Timestamp) + check( + assert_type(s1.dt.start_time, "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, + ) + check( + assert_type(s1.dt.end_time, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp + ) i2 = pd.timedelta_range(start="1 day", periods=10) check(assert_type(i2, pd.TimedeltaIndex), pd.TimedeltaIndex) @@ -551,18 +589,31 @@ def test_series_dt_accessors() -> None: check(assert_type(s2.dt.as_unit("us"), "TimedeltaSeries"), pd.Series, pd.Timedelta) check(assert_type(s2.dt.as_unit("ns"), "TimedeltaSeries"), pd.Series, pd.Timedelta) - # Checks for general Series other than TimestampSeries and TimedeltaSeries + # Checks for general Series other than Series[Timestamp] and TimedeltaSeries - s4 = cast( - "pd.Series[pd.Timestamp]", - pd.Series([pd.Timestamp("2024-01-01"), pd.Timestamp("2024-01-02")]), - ) + s4 = pd.Series([pd.Timestamp("2024-01-01"), pd.Timestamp("2024-01-02")]) check(assert_type(s4.dt.unit, TimeUnit), str) - check(assert_type(s4.dt.as_unit("s"), pd.Series), pd.Series, pd.Timestamp) - check(assert_type(s4.dt.as_unit("ms"), pd.Series), pd.Series, pd.Timestamp) - check(assert_type(s4.dt.as_unit("us"), pd.Series), pd.Series, pd.Timestamp) - check(assert_type(s4.dt.as_unit("ns"), pd.Series), pd.Series, pd.Timestamp) + check( + assert_type(s4.dt.as_unit("s"), "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, + ) + check( + assert_type(s4.dt.as_unit("ms"), "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, + ) + check( + assert_type(s4.dt.as_unit("us"), "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, + ) + check( + assert_type(s4.dt.as_unit("ns"), "pd.Series[pd.Timestamp]"), + pd.Series, + pd.Timestamp, + ) s5 = cast( "pd.Series[pd.Timedelta]", @@ -570,10 +621,26 @@ def test_series_dt_accessors() -> None: ) check(assert_type(s5.dt.unit, TimeUnit), str) - check(assert_type(s5.dt.as_unit("s"), pd.Series), pd.Series, pd.Timedelta) - check(assert_type(s5.dt.as_unit("ms"), pd.Series), pd.Series, pd.Timedelta) - check(assert_type(s5.dt.as_unit("us"), pd.Series), pd.Series, pd.Timedelta) - check(assert_type(s5.dt.as_unit("ns"), pd.Series), pd.Series, pd.Timedelta) + check( + assert_type(s5.dt.as_unit("s"), "pd.Series[pd.Timedelta]"), # type: ignore[assert-type] + pd.Series, + pd.Timedelta, + ) + check( + assert_type(s5.dt.as_unit("ms"), "pd.Series[pd.Timedelta]"), # type: ignore[assert-type] + pd.Series, + pd.Timedelta, + ) + check( + assert_type(s5.dt.as_unit("us"), "pd.Series[pd.Timedelta]"), # type: ignore[assert-type] + pd.Series, + pd.Timedelta, + ) + check( + assert_type(s5.dt.as_unit("ns"), "pd.Series[pd.Timedelta]"), # type: ignore[assert-type] + pd.Series, + pd.Timedelta, + ) def test_datetimeindex_accessors() -> None: @@ -1068,7 +1135,7 @@ def test_to_datetime_scalar_extended() -> None: def test_to_datetime_series() -> None: s = pd.Series(["2000-01-01", "2000-01-02"]) - check(assert_type(pd.to_datetime(s), "TimestampSeries"), pd.Series) + check(assert_type(pd.to_datetime(s), "pd.Series[pd.Timestamp]"), pd.Series) d: FulldatetimeDict = { "year": [2000, 2000, 2000], "month": [1, 1, 1], @@ -1089,9 +1156,9 @@ def test_to_datetime_series() -> None: "us": [1, 1, 1], "ns": [1, 1, 1], } - check(assert_type(pd.to_datetime(df), "TimestampSeries"), pd.Series) - check(assert_type(pd.to_datetime(d), "TimestampSeries"), pd.Series) - check(assert_type(pd.to_datetime(d_ex), "TimestampSeries"), pd.Series) + check(assert_type(pd.to_datetime(df), "pd.Series[pd.Timestamp]"), pd.Series) + check(assert_type(pd.to_datetime(d), "pd.Series[pd.Timestamp]"), pd.Series) + check(assert_type(pd.to_datetime(d_ex), "pd.Series[pd.Timestamp]"), pd.Series) def test_to_datetime_array() -> None: @@ -1334,19 +1401,19 @@ def test_timedelta64_and_arithmatic_operator() -> None: s3 = s2 - s1 check(assert_type(s3, "TimedeltaSeries"), pd.Series, pd.Timedelta) td1 = pd.Timedelta(1, "D") - check(assert_type(s2 - td1, "TimestampSeries"), pd.Series, pd.Timestamp) + check(assert_type(s2 - td1, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) # GH 758 s4 = s1.astype(object) - check(assert_type(s4 - td1, "TimestampSeries"), pd.Series, pd.Timestamp) + check(assert_type(s4 - td1, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) td = np.timedelta64(1, "D") - check(assert_type((s1 - td), "TimestampSeries"), pd.Series, pd.Timestamp) - check(assert_type((s1 + td), "TimestampSeries"), pd.Series, pd.Timestamp) + check(assert_type((s1 - td), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + check(assert_type((s1 + td), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) check(assert_type((s3 - td), "TimedeltaSeries"), pd.Series, pd.Timedelta) check(assert_type((s3 + td), "TimedeltaSeries"), pd.Series, pd.Timedelta) check(assert_type((s3 / td), "pd.Series[float]"), pd.Series, float) if TYPE_CHECKING_INVALID_USAGE: - r1 = s1 * td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + r1 = s1 * td # type: ignore[var-annotated] r2 = s1 / td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] r3 = s3 * td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] @@ -1355,7 +1422,7 @@ def test_timedeltaseries_add_timestampseries() -> None: tds = pd.Series(pd.timedelta_range(start="1 day", periods=10)) tss = pd.Series(pd.date_range(start="2012-01-01", periods=10, freq="W-MON")) plus = tds + tss - check(assert_type(plus, "TimestampSeries"), pd.Series, pd.Timestamp) + check(assert_type(plus, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) def test_mean_median_std() -> None: From ed69ec596d09989815da1c8251d5476e9e862aad Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Mon, 28 Jul 2025 17:05:33 +0200 Subject: [PATCH 02/27] fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274/files#r2229555145 --- docs/philosophy.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/philosophy.md b/docs/philosophy.md index c19f1e1b9..0c54e6833 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -37,6 +37,7 @@ the following example that creates two series of datetimes with corresponding ar ```python import pandas as pd +from typing import reveal_type s1 = pd.Series(pd.to_datetime(["2022-05-01", "2022-06-01"])) reveal_type(s1) @@ -51,14 +52,14 @@ reveal_type(ssum) The above code (without the `reveal_type()` statements) will get a `Never` on the computation of `ssum` because it is inappropriate to add two series containing `Timestamp` values. The types will be -revealed as follows: +revealed by `mypy` as follows: ```text -ttest.py:4: note: Revealed type is "pandas.core.series.Series[pandas._libs.tslibs.timestamps.Timestamp]" -ttest.py:6: note: Revealed type is "pandas.core.series.Series[pandas._libs.tslibs.timestamps.Timestamp]" -ttest.py:8: note: Revealed type is "pandas.core.series.TimedeltaSeries" -ttest.py:9: error: Need type annotation for "ssum" [var-annotated] -ttest.py:10: note: Revealed type is "Never" +ttest.py:5: note: Revealed type is "pandas.core.series.Series[pandas._libs.tslibs.timestamps.Timestamp]" +ttest.py:7: note: Revealed type is "pandas.core.series.Series[pandas._libs.tslibs.timestamps.Timestamp]" +ttest.py:9: note: Revealed type is "pandas.core.series.TimedeltaSeries" +ttest.py:10: error: Need type annotation for "ssum" [var-annotated] +ttest.py:11: note: Revealed type is "Never" ``` The type `Series[Timestamp]` is the result of creating a series from `pd.to_datetime()`, while From ad0ee10e14bc22ec7455c88ebcfde529615f2108 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Mon, 4 Aug 2025 23:44:01 +0200 Subject: [PATCH 03/27] less mypy ignore --- pandas-stubs/core/series.pyi | 4 ++-- tests/series/test_series.py | 2 +- tests/test_frame.py | 6 +++--- tests/test_timefuncs.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index e268e8922..14cb751f5 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1636,7 +1636,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __add__(self: Series[Never], other: Scalar | _ListLike | Series) -> Series: ... # type: ignore[overload-overlap] @overload - def __add__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] + def __add__(self, other: Series[Never]) -> Series: ... @overload def __add__( self: Series[int], other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX] @@ -1673,7 +1673,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[complex]: ... @overload def __add__( - self: Series[Timestamp], other: _nonseries_timestamp | Series[Timestamp] + self: Series[Timestamp], other: _nonseries_timestamp # | Series[Timestamp] ) -> Never: ... @overload def __add__( diff --git a/tests/series/test_series.py b/tests/series/test_series.py index 8d2336dee..37821aeca 100644 --- a/tests/series/test_series.py +++ b/tests/series/test_series.py @@ -1605,7 +1605,7 @@ def test_series_min_max_sub_axis() -> None: ss = s1 - s2 sm = s1 * s2 sd = s1 / s2 - check(assert_type(sa, pd.Series), pd.Series) # type: ignore[assert-type] + check(assert_type(sa, pd.Series), pd.Series) check(assert_type(ss, pd.Series), pd.Series) # type: ignore[assert-type] check(assert_type(sm, pd.Series), pd.Series) check(assert_type(sd, pd.Series), pd.Series) diff --git a/tests/test_frame.py b/tests/test_frame.py index d6f4e1963..97427b307 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -2828,9 +2828,9 @@ def test_sum_get_add() -> None: summer = df.sum(axis=1) check(assert_type(summer, pd.Series), pd.Series) - check(assert_type(s + summer, pd.Series), pd.Series) # type: ignore[assert-type] - check(assert_type(s + df["y"], pd.Series), pd.Series) # type: ignore[assert-type] - check(assert_type(summer + summer, pd.Series), pd.Series) # type: ignore[assert-type] + check(assert_type(s + summer, pd.Series), pd.Series) + check(assert_type(s + df["y"], pd.Series), pd.Series) + check(assert_type(summer + summer, pd.Series), pd.Series) def test_getset_untyped() -> None: diff --git a/tests/test_timefuncs.py b/tests/test_timefuncs.py index 67de2b7ae..f5ff41053 100644 --- a/tests/test_timefuncs.py +++ b/tests/test_timefuncs.py @@ -320,7 +320,7 @@ def test_fail_on_adding_two_timestamps() -> None: s1 = pd.Series(pd.to_datetime(["2022-05-01", "2022-06-01"])) s2 = pd.Series(pd.to_datetime(["2022-05-15", "2022-06-15"])) if TYPE_CHECKING_INVALID_USAGE: - ssum = s1 + s2 # type: ignore[var-annotated] + ssum = s1 + s2 ts = pd.Timestamp("2022-06-30") tsum = s1 + ts # type: ignore[var-annotated] From 2dada6dd39baad648458fe150a3932846e8751b7 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Tue, 5 Aug 2025 17:31:32 +0200 Subject: [PATCH 04/27] fix: even less mypy ignores --- pandas-stubs/_typing.pyi | 18 ++++++++++++------ pandas-stubs/core/series.pyi | 32 +++++++++++++++----------------- tests/test_frame.py | 1 - tests/test_timefuncs.py | 4 ++-- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/pandas-stubs/_typing.pyi b/pandas-stubs/_typing.pyi index f14c393fe..5d94555a0 100644 --- a/pandas-stubs/_typing.pyi +++ b/pandas-stubs/_typing.pyi @@ -824,23 +824,29 @@ MaskType: TypeAlias = Series[bool] | np_ndarray_bool | list[bool] # Scratch types for generics -SeriesDType: TypeAlias = ( +# Closure upon add, sub, mul +SeriesDTypeClosure: TypeAlias = ( str | bytes - | datetime.date - | datetime.time | bool | int | float | complex | Dtype + | Interval + | CategoricalDtype + | list[str] +) +S1C = TypeVar("S1C", bound=SeriesDTypeClosure, default=Any) + +SeriesDType: TypeAlias = ( + SeriesDTypeClosure + | datetime.date + | datetime.time | datetime.datetime # includes pd.Timestamp | datetime.timedelta # includes pd.Timedelta | Period - | Interval - | CategoricalDtype | BaseOffset - | list[str] ) S1 = TypeVar("S1", bound=SeriesDType, default=Any) # Like S1, but without `default=Any`. diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 14cb751f5..b14475333 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -104,6 +104,7 @@ from pandas._libs.tslibs.nattype import NaTType from pandas._libs.tslibs.offsets import DateOffset from pandas._typing import ( S1, + S1C, S2, AggFuncTypeBase, AggFuncTypeDictFrame, @@ -186,29 +187,26 @@ from pandas.core.dtypes.dtypes import CategoricalDtype from pandas.plotting import PlotAccessor -_T_FLOAT = TypeVar("_T_FLOAT", bound=float) _T_COMPLEX = TypeVar("_T_COMPLEX", bound=complex) -_scalar_timestamp: TypeAlias = datetime | np.datetime64 | Timestamp +_scalar_timestamp: TypeAlias = date | datetime | np.datetime64 _vector_timestamp: TypeAlias = ( - Sequence[datetime] + Sequence[date] + | Sequence[datetime] | Sequence[np.datetime64] - | Sequence[Timestamp] | np.typing.NDArray[np.datetime64] | DatetimeIndex ) _nonseries_timestamp: TypeAlias = _scalar_timestamp | _vector_timestamp -_scalar_timedelta: TypeAlias = timedelta | np.timedelta64 | BaseOffset | Timedelta +_scalar_timedelta: TypeAlias = timedelta | np.timedelta64 | BaseOffset _vector_timedelta: TypeAlias = ( Sequence[timedelta] | Sequence[np.timedelta64] - | Sequence[Timedelta] | np.typing.NDArray[np.timedelta64] | TimedeltaIndex ) _nonseries_timedelta: TypeAlias = _scalar_timedelta | _vector_timedelta -_T_TIMESTAMP = TypeVar("_T_TIMESTAMP", bound=Timestamp) class _iLocIndexerSeries(_iLocIndexer, Generic[S1]): # get item @@ -1634,9 +1632,9 @@ class Series(IndexOpsMixin[S1], NDFrame): # just failed to generate these so I couldn't match # them up. @overload - def __add__(self: Series[Never], other: Scalar | _ListLike | Series) -> Series: ... # type: ignore[overload-overlap] + def __add__(self: Series[Never], other: Scalar | _ListLike | Series) -> Series: ... @overload - def __add__(self, other: Series[Never]) -> Series: ... + def __add__(self: Series[S1C], other: Series[Never]) -> Series: ... @overload def __add__( self: Series[int], other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX] @@ -1671,10 +1669,10 @@ class Series(IndexOpsMixin[S1], NDFrame): | np_ndarray_complex ), ) -> Series[complex]: ... - @overload - def __add__( - self: Series[Timestamp], other: _nonseries_timestamp # | Series[Timestamp] - ) -> Never: ... + # @overload + # def __add__( + # self: Series[Timestamp], other: _nonseries_timestamp # | Series[Timestamp] + # ) -> Never: ... @overload def __add__( self: Series[Timestamp], @@ -1687,7 +1685,7 @@ class Series(IndexOpsMixin[S1], NDFrame): self: Series[Timedelta], other: _nonseries_timestamp | Series[Timestamp] ) -> Series[Timestamp]: ... @overload - def __add__(self, other: S1 | Series[S1]) -> Self: ... + def __add__(self: Series[S1C], other: S1C | Series[S1C]) -> Series[S1C]: ... @overload def add( self: Series[Never], @@ -1937,7 +1935,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __mul__(self: Series[Never], other: num | _ListLike | Series) -> Series: ... @overload - def __mul__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] + def __mul__(self: Series[S1C], other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @overload def __mul__( self: Series[int], other: _T_COMPLEX | Series[_T_COMPLEX] @@ -1955,7 +1953,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Never: ... @overload def __mul__( - self: Series[_T_FLOAT], other: _nonseries_timedelta | TimedeltaSeries + self, other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64 ) -> TimedeltaSeries: ... @overload def __mul__(self, other: num | _ListLike | Series) -> Series: ... @@ -2014,7 +2012,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[Timestamp]: ... @overload def __sub__( - self: Series[Timestamp], other: _nonseries_timestamp | Series[_T_TIMESTAMP] + self: Series[Timestamp], other: _nonseries_timestamp | Series[Timestamp] ) -> TimedeltaSeries: ... @overload def __sub__(self, other: S1 | Self) -> Self: ... diff --git a/tests/test_frame.py b/tests/test_frame.py index 97427b307..9755096a2 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -3488,7 +3488,6 @@ def test_groupby_apply() -> None: df = pd.DataFrame({"col1": [1, 2, 3], "col2": [4, 5, 6]}) def sum_mean(x: pd.DataFrame) -> float: - x.sum() return x.sum().mean() with pytest_warns_bounded( diff --git a/tests/test_timefuncs.py b/tests/test_timefuncs.py index f5ff41053..853b41f62 100644 --- a/tests/test_timefuncs.py +++ b/tests/test_timefuncs.py @@ -320,9 +320,9 @@ def test_fail_on_adding_two_timestamps() -> None: s1 = pd.Series(pd.to_datetime(["2022-05-01", "2022-06-01"])) s2 = pd.Series(pd.to_datetime(["2022-05-15", "2022-06-15"])) if TYPE_CHECKING_INVALID_USAGE: - ssum = s1 + s2 + ssum: pd.Series = s1 + s2 # type: ignore[operator] # pyright: ignore[reportOperatorIssue] ts = pd.Timestamp("2022-06-30") - tsum = s1 + ts # type: ignore[var-annotated] + tsum: pd.Series = s1 + ts # type: ignore[operator] # pyright: ignore[reportOperatorIssue] def test_dtindex_tzinfo() -> None: From 0359fd5c34f889a824a03e3c35134fd1c24cb324 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Tue, 5 Aug 2025 17:35:40 +0200 Subject: [PATCH 05/27] refactor(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274/files#r2229550572 --- docs/philosophy.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/philosophy.md b/docs/philosophy.md index 0c54e6833..ec916d4aa 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -49,8 +49,7 @@ ssum = s1 + s2 reveal_type(ssum) ``` -The above code (without the `reveal_type()` statements) will get a `Never` -on the computation of `ssum` because it is +The above code (without the `reveal_type()` statements) will get an error on the computation of `ssum` because it is inappropriate to add two series containing `Timestamp` values. The types will be revealed by `mypy` as follows: @@ -58,8 +57,8 @@ revealed by `mypy` as follows: ttest.py:5: note: Revealed type is "pandas.core.series.Series[pandas._libs.tslibs.timestamps.Timestamp]" ttest.py:7: note: Revealed type is "pandas.core.series.Series[pandas._libs.tslibs.timestamps.Timestamp]" ttest.py:9: note: Revealed type is "pandas.core.series.TimedeltaSeries" -ttest.py:10: error: Need type annotation for "ssum" [var-annotated] -ttest.py:11: note: Revealed type is "Never" +ttest.py:10: error: Unsupported operand types for + ("Series[Timestamp]" and "Series[Timestamp]") [operator] +ttest.py:11: note: Revealed type is "pandas.core.series.Series[pandas._libs.tslibs.timestamps.Timestamp]" ``` The type `Series[Timestamp]` is the result of creating a series from `pd.to_datetime()`, while From 34703d7117124695707727720193059384b4eb45 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Tue, 5 Aug 2025 18:18:24 +0200 Subject: [PATCH 06/27] feat: sub --- pandas-stubs/core/series.pyi | 44 +++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index b14475333..502f6ab61 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1669,10 +1669,6 @@ class Series(IndexOpsMixin[S1], NDFrame): | np_ndarray_complex ), ) -> Series[complex]: ... - # @overload - # def __add__( - # self: Series[Timestamp], other: _nonseries_timestamp # | Series[Timestamp] - # ) -> Never: ... @overload def __add__( self: Series[Timestamp], @@ -2007,6 +2003,42 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __sub__(self: Series[Never], other: num | _ListLike | Series) -> Series: ... @overload + def __sub__(self: Series[S1C], other: Series[Never]) -> Series: ... + @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: ( + _T_COMPLEX + | Sequence[_T_COMPLEX] + | Series[_T_COMPLEX] + | np_ndarray_anyint + | np_ndarray_float + | np_ndarray_complex + ), + ) -> Series[complex]: ... + @overload def __sub__( self: Series[Timestamp], other: _nonseries_timedelta | TimedeltaSeries ) -> Series[Timestamp]: ... @@ -2015,10 +2047,6 @@ class Series(IndexOpsMixin[S1], NDFrame): self: Series[Timestamp], other: _nonseries_timestamp | Series[Timestamp] ) -> TimedeltaSeries: ... @overload - def __sub__(self, other: S1 | Self) -> Self: ... - @overload - def __sub__(self, other: num | _ListLike | Series) -> Series: ... - @overload def __truediv__( self: Series[Never], other: Scalar | _ListLike | Series ) -> Series: ... From c4d657ef0dc0baef8195c8aae121c6e27b856cbd Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 6 Aug 2025 09:24:04 +0200 Subject: [PATCH 07/27] fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274/files#r2229581983 --- pandas-stubs/_typing.pyi | 2 +- pandas-stubs/core/indexes/accessors.pyi | 6 ++++- pandas-stubs/core/series.pyi | 8 ++++--- tests/test_timefuncs.py | 32 ++++++++++++++++++------- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/pandas-stubs/_typing.pyi b/pandas-stubs/_typing.pyi index 5d94555a0..bc0325407 100644 --- a/pandas-stubs/_typing.pyi +++ b/pandas-stubs/_typing.pyi @@ -824,7 +824,7 @@ MaskType: TypeAlias = Series[bool] | np_ndarray_bool | list[bool] # Scratch types for generics -# Closure upon add, sub, mul +# Closure upon add and mul SeriesDTypeClosure: TypeAlias = ( str | bytes diff --git a/pandas-stubs/core/indexes/accessors.pyi b/pandas-stubs/core/indexes/accessors.pyi index b4195879a..e3dbeadc1 100644 --- a/pandas-stubs/core/indexes/accessors.pyi +++ b/pandas-stubs/core/indexes/accessors.pyi @@ -319,7 +319,7 @@ class TimedeltaProperties( ): @property def unit(self) -> TimeUnit: ... - def as_unit(self, unit: TimeUnit) -> TimedeltaSeries: ... + def as_unit(self, unit: TimeUnit) -> Series[Timedelta]: ... _PeriodDTReturnTypes = TypeVar( "_PeriodDTReturnTypes", bound=Series[Timestamp] | DatetimeIndex @@ -438,6 +438,10 @@ class _dtDescriptor(CombinedDatetimelikeProperties, Generic[S1]): self, instance: Series[Timestamp], owner: Any ) -> TimestampProperties: ... @overload + def __get__( + self, instance: Series[Timedelta], owner: Any + ) -> TimedeltaProperties: ... + @overload def __get__( self, instance: Series[S1], owner: Any ) -> CombinedDatetimelikeProperties: ... diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 502f6ab61..a3ab76f05 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -2831,7 +2831,7 @@ class TimedeltaSeries(Series[Timedelta]): self, other: num | Sequence[num] | Series[int] | Series[float] ) -> TimedeltaSeries: ... def unique(self) -> TimedeltaArray: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] - def __sub__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] + def __sub__( # type: ignore[override] self, other: ( timedelta | Timedelta | TimedeltaSeries | TimedeltaIndex | np.timedelta64 @@ -2889,7 +2889,9 @@ class TimedeltaSeries(Series[Timedelta]): ), ) -> Series[int]: ... @property - def dt(self) -> TimedeltaProperties: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] + def dt( + self, + ) -> TimedeltaProperties: ... # pyright: ignore[reportIncompatibleMethodOverride] def mean( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] self, axis: AxisIndex | None = ..., @@ -2934,7 +2936,7 @@ class TimedeltaSeries(Series[Timedelta]): class PeriodSeries(Series[Period]): @property def dt(self) -> PeriodProperties: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] - def __sub__(self, other: PeriodSeries) -> OffsetSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] + def __sub__(self, other: PeriodSeries) -> OffsetSeries: ... # type: ignore[override] def diff(self, periods: int = ...) -> OffsetSeries: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] def cumprod( self, diff --git a/tests/test_timefuncs.py b/tests/test_timefuncs.py index 853b41f62..44dc0527b 100644 --- a/tests/test_timefuncs.py +++ b/tests/test_timefuncs.py @@ -584,10 +584,26 @@ def test_series_dt_accessors() -> None: check(assert_type(s2.dt.to_pytimedelta(), np.ndarray), np.ndarray) check(assert_type(s2.dt.total_seconds(), "pd.Series[float]"), pd.Series, float) check(assert_type(s2.dt.unit, TimeUnit), str) - check(assert_type(s2.dt.as_unit("s"), "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(s2.dt.as_unit("ms"), "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(s2.dt.as_unit("us"), "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(s2.dt.as_unit("ns"), "TimedeltaSeries"), pd.Series, pd.Timedelta) + check( + assert_type(s2.dt.as_unit("s"), "pd.Series[pd.Timedelta]"), + pd.Series, + pd.Timedelta, + ) + check( + assert_type(s2.dt.as_unit("ms"), "pd.Series[pd.Timedelta]"), + pd.Series, + pd.Timedelta, + ) + check( + assert_type(s2.dt.as_unit("us"), "pd.Series[pd.Timedelta]"), + pd.Series, + pd.Timedelta, + ) + check( + assert_type(s2.dt.as_unit("ns"), "pd.Series[pd.Timedelta]"), + pd.Series, + pd.Timedelta, + ) # Checks for general Series other than Series[Timestamp] and TimedeltaSeries @@ -622,22 +638,22 @@ def test_series_dt_accessors() -> None: check(assert_type(s5.dt.unit, TimeUnit), str) check( - assert_type(s5.dt.as_unit("s"), "pd.Series[pd.Timedelta]"), # type: ignore[assert-type] + assert_type(s5.dt.as_unit("s"), "pd.Series[pd.Timedelta]"), pd.Series, pd.Timedelta, ) check( - assert_type(s5.dt.as_unit("ms"), "pd.Series[pd.Timedelta]"), # type: ignore[assert-type] + assert_type(s5.dt.as_unit("ms"), "pd.Series[pd.Timedelta]"), pd.Series, pd.Timedelta, ) check( - assert_type(s5.dt.as_unit("us"), "pd.Series[pd.Timedelta]"), # type: ignore[assert-type] + assert_type(s5.dt.as_unit("us"), "pd.Series[pd.Timedelta]"), pd.Series, pd.Timedelta, ) check( - assert_type(s5.dt.as_unit("ns"), "pd.Series[pd.Timedelta]"), # type: ignore[assert-type] + assert_type(s5.dt.as_unit("ns"), "pd.Series[pd.Timedelta]"), pd.Series, pd.Timedelta, ) From 1eeb809e7c26389a11710d426eb939a955790203 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 6 Aug 2025 09:31:43 +0200 Subject: [PATCH 08/27] fix: pyrefly --- pandas-stubs/core/series.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index a3ab76f05..5703669c1 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -2889,9 +2889,9 @@ class TimedeltaSeries(Series[Timedelta]): ), ) -> Series[int]: ... @property - def dt( + def dt( # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore self, - ) -> TimedeltaProperties: ... # pyright: ignore[reportIncompatibleMethodOverride] + ) -> TimedeltaProperties: ... def mean( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] self, axis: AxisIndex | None = ..., From ceff1fedaf3ef4750ba136aa3513acd5b74b6432 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 6 Aug 2025 17:55:11 +0200 Subject: [PATCH 09/27] fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274/files#r2229580369 --- tests/test_timefuncs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_timefuncs.py b/tests/test_timefuncs.py index 44dc0527b..8d83450da 100644 --- a/tests/test_timefuncs.py +++ b/tests/test_timefuncs.py @@ -609,6 +609,7 @@ def test_series_dt_accessors() -> None: s4 = pd.Series([pd.Timestamp("2024-01-01"), pd.Timestamp("2024-01-02")]) + check(assert_type(s4, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) check(assert_type(s4.dt.unit, TimeUnit), str) check( assert_type(s4.dt.as_unit("s"), "pd.Series[pd.Timestamp]"), From dd8bafff0bf7303c5ee458f4adf4b536820cee5a Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 6 Aug 2025 17:59:03 +0200 Subject: [PATCH 10/27] fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274#discussion_r2229575174 --- tests/test_scalars.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_scalars.py b/tests/test_scalars.py index 0a782ddbd..83e9b2d60 100644 --- a/tests/test_scalars.py +++ b/tests/test_scalars.py @@ -1269,6 +1269,9 @@ def test_timestamp_cmp() -> None: c_unknown_index = pd.DataFrame({"a": [1]}, index=c_datetimeindex).index c_np_ndarray_dt64 = np_dt64_arr c_series_dt64 = pd.Series([1, 2, 3], dtype="datetime64[ns]") + check( + assert_type(c_series_dt64, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp + ) c_series_timestamp = pd.Series(pd.DatetimeIndex(["2000-1-1"])) check( assert_type(c_series_timestamp, "pd.Series[pd.Timestamp]"), From 8a07ecaabf17bef6a63366c738f925f758f3249a Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 6 Aug 2025 18:12:57 +0200 Subject: [PATCH 11/27] fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274#discussion_r2229583234 --- pandas-stubs/core/series.pyi | 13 ++++++++++--- tests/test_timefuncs.py | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 5703669c1..769e7d593 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1945,11 +1945,18 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[Timestamp]: ... @overload def __mul__( - self: Series[Timestamp], other: _nonseries_timedelta | TimedeltaSeries - ) -> Never: ... + self: Series[int], + other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64, + ) -> TimedeltaSeries: ... @overload def __mul__( - self, other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64 + self: Series[float], + other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64, + ) -> TimedeltaSeries: ... + @overload + def __mul__( + self: Series[bool], + other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64, ) -> TimedeltaSeries: ... @overload def __mul__(self, other: num | _ListLike | Series) -> Series: ... diff --git a/tests/test_timefuncs.py b/tests/test_timefuncs.py index 8d83450da..9c29e8084 100644 --- a/tests/test_timefuncs.py +++ b/tests/test_timefuncs.py @@ -1430,7 +1430,7 @@ def test_timedelta64_and_arithmatic_operator() -> None: check(assert_type((s3 + td), "TimedeltaSeries"), pd.Series, pd.Timedelta) check(assert_type((s3 / td), "pd.Series[float]"), pd.Series, float) if TYPE_CHECKING_INVALID_USAGE: - r1 = s1 * td # type: ignore[var-annotated] + r1 = s1 * td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] r2 = s1 / td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] r3 = s3 * td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] From 3458fc963fd0e130094609aead89db24c1174fed Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 6 Aug 2025 18:20:18 +0200 Subject: [PATCH 12/27] fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274#discussion_r2229597172 --- pandas-stubs/core/series.pyi | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 769e7d593..89539b9ab 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -2521,14 +2521,24 @@ class Series(IndexOpsMixin[S1], NDFrame): numeric_only: _bool = ..., **kwargs: Any, ) -> float: ... + @overload def median( - self, + self: Series[float], axis: AxisIndex | None = ..., skipna: _bool = ..., level: None = ..., numeric_only: _bool = ..., **kwargs: Any, - ) -> S1: ... + ) -> float: ... + @overload + def median( + self: Series[Timestamp], + axis: AxisIndex | None = ..., + skipna: _bool = ..., + level: None = ..., + numeric_only: _bool = ..., + **kwargs: Any, + ) -> Timestamp: ... def min( self, axis: AxisIndex | None = ..., @@ -2907,7 +2917,7 @@ class TimedeltaSeries(Series[Timedelta]): numeric_only: _bool = ..., **kwargs: Any, ) -> Timedelta: ... - def median( + def median( # mypy: ignore[override] self, axis: AxisIndex | None = ..., skipna: _bool = ..., From d3339027f5b8862f33d4b3c2e89d9953728b3574 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 6 Aug 2025 18:27:24 +0200 Subject: [PATCH 13/27] fix: reduce ignore --- pandas-stubs/_typing.pyi | 1 + pandas-stubs/core/series.pyi | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pandas-stubs/_typing.pyi b/pandas-stubs/_typing.pyi index bc0325407..47033e1df 100644 --- a/pandas-stubs/_typing.pyi +++ b/pandas-stubs/_typing.pyi @@ -838,6 +838,7 @@ SeriesDTypeClosure: TypeAlias = ( | list[str] ) S1C = TypeVar("S1C", bound=SeriesDTypeClosure, default=Any) +S2C = TypeVar("S2C", bound=SeriesDTypeClosure) SeriesDType: TypeAlias = ( SeriesDTypeClosure diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 89539b9ab..a22a68258 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_COMPLEX = TypeVar("_T_COMPLEX", bound=complex) +_T_TIMESTAMP = TypeVar("_T_TIMESTAMP", bound=Timestamp) _scalar_timestamp: TypeAlias = date | datetime | np.datetime64 _vector_timestamp: TypeAlias = ( @@ -2051,7 +2052,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[Timestamp]: ... @overload def __sub__( - self: Series[Timestamp], other: _nonseries_timestamp | Series[Timestamp] + self: Series[Timestamp], other: _nonseries_timestamp | Series[_T_TIMESTAMP] ) -> TimedeltaSeries: ... @overload def __truediv__( @@ -2917,7 +2918,7 @@ class TimedeltaSeries(Series[Timedelta]): numeric_only: _bool = ..., **kwargs: Any, ) -> Timedelta: ... - def median( # mypy: ignore[override] + def median( # type: ignore[override] self, axis: AxisIndex | None = ..., skipna: _bool = ..., From 368c3ff560ad4e7ee114f9ebc3b7a4c46ec85cd3 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Thu, 7 Aug 2025 11:01:00 +0200 Subject: [PATCH 14/27] refactor: explain a temporary failure --- pandas-stubs/core/series.pyi | 10 ++++++++-- tests/series/test_series.py | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index a22a68258..d28411a80 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_COMPLEX = TypeVar("_T_COMPLEX", bound=complex) -_T_TIMESTAMP = TypeVar("_T_TIMESTAMP", bound=Timestamp) _scalar_timestamp: TypeAlias = date | datetime | np.datetime64 _vector_timestamp: TypeAlias = ( @@ -2052,7 +2051,14 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[Timestamp]: ... @overload def __sub__( - self: Series[Timestamp], other: _nonseries_timestamp | Series[_T_TIMESTAMP] + self: Series[Timestamp], other: _nonseries_timestamp + ) -> TimedeltaSeries: ... + # The following overload confuses mypy and disable it from recognising + # Series[Any] - Series[Any] should give Series[Any]. It will be fixed + # after removing TimedeltaSeries. + @overload + def __sub__( + self: Series[Timestamp], other: Series[Timestamp] ) -> TimedeltaSeries: ... @overload def __truediv__( diff --git a/tests/series/test_series.py b/tests/series/test_series.py index 37821aeca..1cc0ba161 100644 --- a/tests/series/test_series.py +++ b/tests/series/test_series.py @@ -1606,6 +1606,7 @@ def test_series_min_max_sub_axis() -> None: sm = s1 * s2 sd = s1 / s2 check(assert_type(sa, pd.Series), pd.Series) + # Will be fixed after removing TimedeltaSeries, see Series.__sub__ in series.pyi check(assert_type(ss, pd.Series), pd.Series) # type: ignore[assert-type] check(assert_type(sm, pd.Series), pd.Series) check(assert_type(sd, pd.Series), pd.Series) From 15996ff5fae405ecee0fb5d5e76ebd3c19a09aab Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Thu, 7 Aug 2025 17:45:51 +0200 Subject: [PATCH 15/27] fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274#discussion_r2260671432 --- docs/philosophy.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/philosophy.md b/docs/philosophy.md index ec916d4aa..f6650c4f9 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -46,7 +46,6 @@ reveal_type(s2) td = s1 - s2 reveal_type(td) ssum = s1 + s2 -reveal_type(ssum) ``` The above code (without the `reveal_type()` statements) will get an error on the computation of `ssum` because it is @@ -58,7 +57,6 @@ ttest.py:5: note: Revealed type is "pandas.core.series.Series[pandas._libs.tslib ttest.py:7: note: Revealed type is "pandas.core.series.Series[pandas._libs.tslibs.timestamps.Timestamp]" ttest.py:9: note: Revealed type is "pandas.core.series.TimedeltaSeries" ttest.py:10: error: Unsupported operand types for + ("Series[Timestamp]" and "Series[Timestamp]") [operator] -ttest.py:11: note: Revealed type is "pandas.core.series.Series[pandas._libs.tslibs.timestamps.Timestamp]" ``` The type `Series[Timestamp]` is the result of creating a series from `pd.to_datetime()`, while From c9c46b0a6fb8e90d88db60bf94c9aa89df44b93d Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Fri, 8 Aug 2025 16:32:05 +0200 Subject: [PATCH 16/27] 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 d206adcb6..c57123f4f 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 000000000..ebe20da3e --- /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 000000000..da2d572e7 --- /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 000000000..5451663d4 --- /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 000000000..20e7420df --- /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 4d1486dcc..250a0e5e1 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 17/27] 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 250a0e5e1..d97235888 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 18/27] 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 c57123f4f..507f653e1 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 000000000..223453cff --- /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 ebe20da3e..5a7cc0de6 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 da2d572e7..c7095765e 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 5451663d4..addbae274 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 20e7420df..9ca8dfd31 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 19/27] 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 71efa2ae0..5db811b6a 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 223453cff..a0813ed14 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 addbae274..57056e939 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 6484af773a1c90e2bf148348907fbbb23573ddb0 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Fri, 8 Aug 2025 17:44:31 +0200 Subject: [PATCH 20/27] feat(series): arithmetic mul --- pandas-stubs/core/series.pyi | 430 +++++++++++++++++--- tests/series/arithmetic/bool/test_mul.py | 129 ++++++ tests/series/arithmetic/complex/test_mul.py | 155 +++++++ tests/series/arithmetic/float/test_mul.py | 129 ++++++ tests/series/arithmetic/int/test_mul.py | 129 ++++++ tests/series/arithmetic/test_mul.py | 127 ++++++ tests/series/test_series.py | 21 +- 7 files changed, 1054 insertions(+), 66 deletions(-) create mode 100644 tests/series/arithmetic/bool/test_mul.py create mode 100644 tests/series/arithmetic/complex/test_mul.py create mode 100644 tests/series/arithmetic/float/test_mul.py create mode 100644 tests/series/arithmetic/int/test_mul.py create mode 100644 tests/series/arithmetic/test_mul.py diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 5db811b6a..61d7f42aa 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -2016,11 +2016,394 @@ class Series(IndexOpsMixin[S1], NDFrame): self, other: S1 | _ListLike | Series[S1] | datetime | timedelta | date ) -> Series[_bool]: ... @overload + def __mul__(self: Series[Never], other: complex | _ListLike | Series) -> Series: ... + @overload + def __mul__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] + @overload + def __mul__( + self: Series[bool], + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + ) -> Series[_T_COMPLEX]: ... + @overload + def __mul__(self: Series[bool], other: np_ndarray_bool) -> Series[bool]: ... + @overload + def __mul__(self: Series[bool], other: np_ndarray_anyint) -> Series[int]: ... + @overload + def __mul__(self: Series[bool], other: np_ndarray_float) -> Series[float]: ... + @overload + def __mul__( + self: Series[int], + other: ( + bool | Sequence[bool] | np_ndarray_bool | np_ndarray_anyint | Series[bool] + ), + ) -> Series[int]: ... + @overload + def __mul__( + self: Series[int], + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + ) -> Series[_T_COMPLEX]: ... + @overload + def __mul__(self: Series[int], other: np_ndarray_float) -> Series[float]: ... + @overload + def __mul__( + self: Series[float], + other: ( + int + | Sequence[int] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Series[_T_INT] + ), + ) -> Series[float]: ... + @overload + def __mul__( + self: Series[float], + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + ) -> Series[_T_COMPLEX]: ... + @overload + def __mul__( + self: Series[complex], + other: ( + _T_COMPLEX + | Sequence[_T_COMPLEX] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Series[_T_COMPLEX] + ), + ) -> Series[complex]: ... + @overload def __mul__( + self: Series[_T_COMPLEX], other: np_ndarray_complex + ) -> Series[complex]: ... + @overload + def __mul__( + self, other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64 + ) -> TimedeltaSeries: ... + @overload + def mul( + self: Series[Never], + other: complex | _ListLike | Series, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series: ... + @overload + def mul( # type: ignore[overload-overlap] + self, + other: Series[Never], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series: ... + @overload + def mul( + self: Series[bool], + 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 mul( + self: Series[bool], + other: np_ndarray_bool, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[bool]: ... + @overload + def mul( + self: Series[bool], + other: np_ndarray_anyint, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[int]: ... + @overload + def mul( + self: Series[bool], + other: np_ndarray_float, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[float]: ... + @overload + def mul( + self: Series[int], + 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[int]: ... + @overload + def mul( + 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 mul( + self: Series[int], + other: np_ndarray_float, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[float]: ... + @overload + def mul( + self: Series[float], + other: ( + int + | Sequence[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[float]: ... + @overload + def mul( + 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 mul( + self: Series[complex], + other: ( + _T_COMPLEX + | Sequence[_T_COMPLEX] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Series[_T_COMPLEX] + ), + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload + def mul( + self: Series[_T_COMPLEX], + other: np_ndarray_complex, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload + def mul( + self, + other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64, + level: Level | None = ..., + fill_value: float | None = None, + axis: AxisIndex | None = 0, + ) -> TimedeltaSeries: ... + @overload + def __rmul__( + self: Series[Never], other: complex | _ListLike | Series + ) -> Series: ... + @overload + def __rmul__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] + @overload + def __rmul__( + self: Series[bool], + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + ) -> Series[_T_COMPLEX]: ... + @overload + def __rmul__(self: Series[bool], other: np_ndarray_bool) -> Series[bool]: ... + @overload + def __rmul__(self: Series[bool], other: np_ndarray_anyint) -> Series[int]: ... + @overload + def __rmul__(self: Series[bool], other: np_ndarray_float) -> Series[float]: ... + @overload + def __rmul__( + self: Series[int], + other: ( + bool | Sequence[bool] | np_ndarray_bool | np_ndarray_anyint | Series[bool] + ), + ) -> Series[int]: ... + @overload + def __rmul__( + self: Series[int], other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX] + ) -> Series[_T_COMPLEX]: ... + @overload + def __rmul__(self: Series[int], other: np_ndarray_float) -> Series[float]: ... + @overload + def __rmul__( + self: Series[float], + other: ( + int + | Sequence[int] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Series[_T_INT] + ), + ) -> Series[float]: ... + @overload + def __rmul__( + self: Series[float], + other: _T_COMPLEX | Sequence[_T_COMPLEX] | Series[_T_COMPLEX], + ) -> Series[_T_COMPLEX]: ... + @overload + def __rmul__( + self: Series[complex], + other: ( + _T_COMPLEX + | Sequence[_T_COMPLEX] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Series[_T_COMPLEX] + ), + ) -> Series[complex]: ... + @overload + def __rmul__( + self: Series[_T_COMPLEX], other: np_ndarray_complex + ) -> Series[complex]: ... + @overload + def __rmul__( self, other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64 ) -> TimedeltaSeries: ... @overload - def __mul__(self, other: num | _ListLike | Series) -> Series: ... + def rmul( + self: Series[Never], + other: complex | _ListLike | Series, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series: ... + @overload + def rmul( # type: ignore[overload-overlap] + self, + other: Series[Never], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series: ... + @overload + def rmul( + self: Series[bool], + 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 rmul( + self: Series[bool], + other: np_ndarray_bool, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[bool]: ... + @overload + def rmul( + self: Series[bool], + other: np_ndarray_anyint, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[int]: ... + @overload + def rmul( + self: Series[bool], + other: np_ndarray_float, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[float]: ... + @overload + def rmul( + self: Series[int], + 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[int]: ... + @overload + def rmul( + 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 rmul( + self: Series[int], + other: np_ndarray_float, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[float]: ... + @overload + def rmul( + self: Series[float], + other: ( + int + | Sequence[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[float]: ... + @overload + def rmul( + 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 rmul( + self: Series[complex], + other: ( + _T_COMPLEX + | Sequence[_T_COMPLEX] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Series[_T_COMPLEX] + ), + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload + def rmul( + self: Series[_T_COMPLEX], + other: np_ndarray_complex, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series[complex]: ... + @overload + def rmul( + self, + other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64, + level: Level | None = ..., + fill_value: float | None = None, + axis: AxisIndex = ..., + ) -> TimedeltaSeries: ... def __mod__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... def __ne__(self, other: object) -> Series[_bool]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] def __pow__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... @@ -2041,12 +2424,6 @@ class Series(IndexOpsMixin[S1], NDFrame): def __rdivmod__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] def __rfloordiv__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... def __rmod__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... - @overload - def __rmul__( - self, other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64 - ) -> TimedeltaSeries: ... - @overload - def __rmul__(self, other: num | _ListLike | Series) -> Series: ... def __rpow__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... # ignore needed for mypy as we want different results based on the arguments @overload # type: ignore[override] @@ -3028,29 +3405,6 @@ class Series(IndexOpsMixin[S1], NDFrame): fill_value: float | None = None, axis: AxisIndex | None = 0, ) -> Series[S1]: ... - @overload - def mul( - self, - other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64, - level: Level | None = ..., - fill_value: float | None = None, - axis: AxisIndex | None = 0, - ) -> TimedeltaSeries: ... - @overload - def mul( - self, - other: num | _ListLike | Series, - level: Level | None = ..., - fill_value: float | None = None, - axis: AxisIndex | None = 0, - ) -> Series: ... - def multiply( - self, - other: num | _ListLike | Series[S1], - level: Level | None = ..., - fill_value: float | None = None, - axis: AxisIndex | None = 0, - ) -> Series[S1]: ... def ne( self, other: Scalar | Series[S1], @@ -3105,22 +3459,6 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: AxisIndex = ..., ) -> Series[S1]: ... @overload - def rmul( - self, - other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64, - level: Level | None = ..., - fill_value: float | None = None, - axis: AxisIndex = ..., - ) -> TimedeltaSeries: ... - @overload - def rmul( - self, - other: num | _ListLike | Series, - level: Level | None = ..., - fill_value: float | None = None, - axis: AxisIndex = ..., - ) -> Series: ... - @overload def rolling( self, window: int | _str | timedelta | BaseOffset | BaseIndexer, diff --git a/tests/series/arithmetic/bool/test_mul.py b/tests/series/arithmetic/bool/test_mul.py new file mode 100644 index 000000000..0728b6e80 --- /dev/null +++ b/tests/series/arithmetic/bool/test_mul.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([True, True, False]) # left operand + + +def test_mul_py_scalar() -> None: + """Test pd.Series[bool] * Python native scalars""" + b, i, f, c = True, 1, 1.0, 1j + + check(assert_type(left * b, "pd.Series[bool]"), pd.Series, np.bool_) + 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[bool]"), pd.Series, np.bool_) + 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.mul(b), "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(left.mul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.mul(f), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.rmul(b), "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(left.rmul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.rmul(f), "pd.Series[float]"), pd.Series, np.floating) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_mul_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 * b, "pd.Series[bool]"), pd.Series, np.bool_) + 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[bool]"), pd.Series, np.bool_) + 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.mul(b), "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(left.mul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.mul(f), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.rmul(b), "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(left.rmul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.rmul(f), "pd.Series[float]"), pd.Series, np.floating) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_mul_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 * b, "pd.Series[bool]"), pd.Series, np.bool_) + 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 `__rmul__` cannot override. At runtime, they return + # `Series`s with the correct element type. + check(assert_type(b * left, "npt.NDArray[np.bool_]"), pd.Series, np.bool_) + 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.mul(b), "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(left.mul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.mul(f), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.rmul(b), "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(left.rmul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.rmul(f), "pd.Series[float]"), pd.Series, np.floating) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_mul_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 * b, "pd.Series[bool]"), pd.Series, np.bool_) + 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[bool]"), pd.Series, np.bool_) + 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.mul(b), "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(left.mul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.mul(f), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.rmul(b), "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(left.rmul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.rmul(f), "pd.Series[float]"), pd.Series, np.floating) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) diff --git a/tests/series/arithmetic/complex/test_mul.py b/tests/series/arithmetic/complex/test_mul.py new file mode 100644 index 000000000..d89c7654f --- /dev/null +++ b/tests/series/arithmetic/complex/test_mul.py @@ -0,0 +1,155 @@ +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_mul_py_scalar() -> None: + """Test pd.Series[complex] * Python native scalars""" + 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.mul(b), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.mul(i), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.mul(f), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check( + assert_type(left.rmul(b), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rmul(i), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rmul(f), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_mul_py_sequence() -> None: + """Test pd.Series[complex] * Python native sequence""" + b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + + check(assert_type(left * b, "pd.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.mul(b), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.mul(i), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.mul(f), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check( + assert_type(left.rmul(b), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rmul(i), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rmul(f), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_mul_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) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rmul__` cannot override. At runtime, they return + # `Series`s with the correct element type. + check(assert_type(b * left, "npt.NDArray[np.bool_]"), 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 + ) + check( + assert_type(c * left, "npt.NDArray[np.complex128]"), + pd.Series, + np.complexfloating, + ) + + check(assert_type(left.mul(b), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.mul(i), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.mul(f), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check( + assert_type(left.rmul(b), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rmul(i), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rmul(f), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_mul_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.mul(b), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.mul(i), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.mul(f), "pd.Series[complex]"), pd.Series, np.complexfloating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check( + assert_type(left.rmul(b), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rmul(i), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rmul(f), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) diff --git a/tests/series/arithmetic/float/test_mul.py b/tests/series/arithmetic/float/test_mul.py new file mode 100644 index 000000000..80cb8aa6b --- /dev/null +++ b/tests/series/arithmetic/float/test_mul.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([1.0, 2.0, 3.0]) # left operand + + +def test_mul_py_scalar() -> None: + """Test pd.Series[float] * Python native scalars""" + 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.mul(b), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(i), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(f), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.rmul(b), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.rmul(i), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.rmul(f), "pd.Series[float]"), pd.Series, np.floating) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_mul_py_sequence() -> None: + """Test pd.Series[float] * Python native sequence""" + b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] + + check(assert_type(left * b, "pd.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.mul(b), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(i), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(f), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.rmul(b), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.rmul(i), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.rmul(f), "pd.Series[float]"), pd.Series, np.floating) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_mul_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) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rmul__` cannot override. At runtime, they return + # `Series`s with the correct element type. + check(assert_type(b * left, "npt.NDArray[np.bool_]"), 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( + assert_type(c * left, "npt.NDArray[np.complex128]"), + pd.Series, + np.complexfloating, + ) + + check(assert_type(left.mul(b), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(i), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(f), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.rmul(b), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.rmul(i), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.rmul(f), "pd.Series[float]"), pd.Series, np.floating) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_mul_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.mul(b), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(i), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(f), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.rmul(b), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.rmul(i), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.rmul(f), "pd.Series[float]"), pd.Series, np.floating) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) diff --git a/tests/series/arithmetic/int/test_mul.py b/tests/series/arithmetic/int/test_mul.py new file mode 100644 index 000000000..028829b77 --- /dev/null +++ b/tests/series/arithmetic/int/test_mul.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([1, 2, 3]) # left operand + + +def test_mul_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.mul(b), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.mul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.mul(f), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.rmul(b), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.rmul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.rmul(f), "pd.Series[float]"), pd.Series, np.floating) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_mul_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.mul(b), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.mul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.mul(f), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.rmul(b), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.rmul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.rmul(f), "pd.Series[float]"), pd.Series, np.floating) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_mul_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) + + 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) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rmul__` cannot override. At runtime, they return + # `Series`s with the correct element type. + check(assert_type(b * left, "npt.NDArray[np.bool_]"), 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( + assert_type(c * left, "npt.NDArray[np.complex128]"), + pd.Series, + np.complexfloating, + ) + + check(assert_type(left.mul(b), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.mul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.mul(f), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.rmul(b), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.rmul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.rmul(f), "pd.Series[float]"), pd.Series, np.floating) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) + + +def test_mul_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]) + + 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.mul(b), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.mul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.mul(f), "pd.Series[float]"), pd.Series, np.floating) + check(assert_type(left.mul(c), "pd.Series[complex]"), pd.Series, np.complexfloating) + + check(assert_type(left.rmul(b), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.rmul(i), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(left.rmul(f), "pd.Series[float]"), pd.Series, np.floating) + check( + assert_type(left.rmul(c), "pd.Series[complex]"), pd.Series, np.complexfloating + ) diff --git a/tests/series/arithmetic/test_mul.py b/tests/series/arithmetic/test_mul.py new file mode 100644 index 000000000..00ffe0681 --- /dev/null +++ b/tests/series/arithmetic/test_mul.py @@ -0,0 +1,127 @@ +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_mul_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(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.mul(b), pd.Series), pd.Series) + check(assert_type(left.mul(i), pd.Series), pd.Series) + check(assert_type(left.mul(f), pd.Series), pd.Series) + check(assert_type(left.mul(c), pd.Series), pd.Series) + + check(assert_type(left.rmul(b), pd.Series), pd.Series) + check(assert_type(left.rmul(i), pd.Series), pd.Series) + check(assert_type(left.rmul(f), pd.Series), pd.Series) + check(assert_type(left.rmul(c), pd.Series), pd.Series) + + +def test_mul_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) + + # `mypy` thinks the return types are `list[_T]` + check(assert_type(b * left, pd.Series), pd.Series) # type: ignore[assert-type] + check(assert_type(i * left, pd.Series), pd.Series) # type: ignore[assert-type] + check(assert_type(f * left, pd.Series), pd.Series) # type: ignore[assert-type] + check(assert_type(c * left, pd.Series), pd.Series) # type: ignore[assert-type] + + check(assert_type(left.mul(b), pd.Series), pd.Series) + check(assert_type(left.mul(i), pd.Series), pd.Series) + check(assert_type(left.mul(f), pd.Series), pd.Series) + check(assert_type(left.mul(c), pd.Series), pd.Series) + + check(assert_type(left.rmul(b), pd.Series), pd.Series) + check(assert_type(left.rmul(i), pd.Series), pd.Series) + check(assert_type(left.rmul(f), pd.Series), pd.Series) + check(assert_type(left.rmul(c), pd.Series), pd.Series) + + +def test_mul_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) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__rmul__` cannot override. At runtime, they return + # `Series`s. + # `mypy` thinks the return types are `Any`, which is a bug. + check( + assert_type(b * left, "npt.NDArray[np.bool_]"), pd.Series # type: ignore[assert-type] + ) + 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.mul(b), pd.Series), pd.Series) + check(assert_type(left.mul(i), pd.Series), pd.Series) + check(assert_type(left.mul(f), pd.Series), pd.Series) + check(assert_type(left.mul(c), pd.Series), pd.Series) + + check(assert_type(left.rmul(b), pd.Series), pd.Series) + check(assert_type(left.rmul(i), pd.Series), pd.Series) + check(assert_type(left.rmul(f), pd.Series), pd.Series) + check(assert_type(left.rmul(c), pd.Series), pd.Series) + + +def test_mul_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.mul(b), pd.Series), pd.Series) + check(assert_type(left.mul(i), pd.Series), pd.Series) + check(assert_type(left.mul(f), pd.Series), pd.Series) + check(assert_type(left.mul(c), pd.Series), pd.Series) + + check(assert_type(left.rmul(b), pd.Series), pd.Series) + check(assert_type(left.rmul(i), pd.Series), pd.Series) + check(assert_type(left.rmul(f), pd.Series), pd.Series) + check(assert_type(left.rmul(c), pd.Series), pd.Series) diff --git a/tests/series/test_series.py b/tests/series/test_series.py index d97235888..8df9c123c 100644 --- a/tests/series/test_series.py +++ b/tests/series/test_series.py @@ -815,19 +815,12 @@ def test_types_element_wise_arithmetic() -> None: s = pd.Series([0, 1, -10]) s2 = pd.Series([7, -5, 10]) - 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) - 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] - # check(assert_type(s * s2, "pd.Series[int]"), pd.Series, np.integer ) - check(assert_type(s * s2, pd.Series), pd.Series, np.integer) - # check(assert_type(s.mul(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - check(assert_type(s.mul(s2, fill_value=0), pd.Series), pd.Series, np.integer) + check(assert_type(s.mul(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - check(assert_type(s / s2, "pd.Series[float]"), pd.Series, np.float64) check( assert_type(s.div(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 ) @@ -1637,18 +1630,6 @@ def test_series_multiindex_getitem() -> None: s1: pd.Series = s["a", :] -def test_series_mul() -> None: - s = pd.Series([1, 2, 3]) - sm = s * 4 - check(assert_type(sm, pd.Series), pd.Series) - ss = s - 4 - 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 - check(assert_type(sp, "pd.Series[int]"), pd.Series, np.integer) - - def test_reset_index() -> None: s = pd.Series( [1, 2, 3, 4], From 7a87d18e1b24c7d6b61d11fd577186d801d9a5c0 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 20 Aug 2025 17:07:58 +0200 Subject: [PATCH 21/27] fix: type asserts --- attempt.py | 48 ++++++++++++ pandas-stubs/_libs/tslibs/timestamps.pyi | 4 +- pandas-stubs/core/series.pyi | 99 +++++++++++++++++++----- tests/series/arithmetic/test_sub.py | 2 +- tests/series/test_series.py | 1 + tests/test_timefuncs.py | 8 +- 6 files changed, 137 insertions(+), 25 deletions(-) create mode 100644 attempt.py diff --git a/attempt.py b/attempt.py new file mode 100644 index 000000000..96d9c11e3 --- /dev/null +++ b/attempt.py @@ -0,0 +1,48 @@ +from typing import assert_type, reveal_type +import pandas as pd +from pandas.core.series import TimedeltaSeries # noqa: F401 +import numpy as np +import datetime as dt + +from tests import check + + +df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [5, 4, 3, 2, 1]}) +s1 = df.min(axis=1) +s2 = df.max(axis=1) +sa = s1 + s2 +ss = s1 - s2 +sm = s1 * s2 +sd = s1 / s2 +check(assert_type(sa, pd.Series), pd.Series) +reveal_type(s1.__sub__(s2)) +reveal_type(s2.__rsub__(s1)) +check(assert_type(ss, pd.Series), pd.Series) +check(assert_type(sm, pd.Series), pd.Series) +check(assert_type(sd, pd.Series), pd.Series) + +ts1 = pd.to_datetime(pd.Series(["2022-03-05", "2022-03-06"])) +assert isinstance(ts1.iloc[0], pd.Timestamp) +td1 = pd.to_timedelta([2, 3], "seconds") +ts2 = pd.to_datetime(pd.Series(["2022-03-08", "2022-03-10"])) +r1 = ts1 - ts2 +check(assert_type(r1, "TimedeltaSeries"), pd.Series, pd.Timedelta) +r2 = r1 / td1 +check(assert_type(r2, "pd.Series[float]"), pd.Series, float) +r3 = r1 - td1 +check(assert_type(r3, "TimedeltaSeries"), pd.Series, pd.Timedelta) +r4 = pd.Timedelta(5, "days") / r1 +check(assert_type(r4, "pd.Series[float]"), pd.Series, float) +sb = pd.Series([1, 2]) == pd.Series([1, 3]) +check(assert_type(sb, "pd.Series[bool]"), pd.Series, np.bool_) +r5 = sb * r1 +check(assert_type(r5, "TimedeltaSeries"), pd.Series, pd.Timedelta) +r6 = r1 * 4 +check(assert_type(r6, "TimedeltaSeries"), pd.Series, pd.Timedelta) + +tsp1 = pd.Timestamp("2022-03-05") +dt1 = dt.datetime(2022, 9, 1, 12, 5, 30) +r7 = ts1 - tsp1 +check(assert_type(r7, "TimedeltaSeries"), pd.Series, pd.Timedelta) +r8 = ts1 - dt1 +check(assert_type(r8, "TimedeltaSeries"), pd.Series, pd.Timedelta) \ No newline at end of file diff --git a/pandas-stubs/_libs/tslibs/timestamps.pyi b/pandas-stubs/_libs/tslibs/timestamps.pyi index a5a324eaa..9bd35199f 100644 --- a/pandas-stubs/_libs/tslibs/timestamps.pyi +++ b/pandas-stubs/_libs/tslibs/timestamps.pyi @@ -246,9 +246,9 @@ class Timestamp(datetime, SupportsIndex): @overload def __sub__(self, other: TimedeltaSeries) -> Series[Timestamp]: ... @overload - def __sub__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] + def __sub__(self, other: Series[Never]) -> Series: ... @overload - def __sub__(self, other: Series[Timestamp]) -> TimedeltaSeries: ... + def __sub__(self, other: Series[Timestamp]) -> Series[Timedelta]: ... @overload def __sub__( self, other: np_ndarray[ShapeT, np.timedelta64] diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 3c868ea24..b2ebd25ba 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1624,9 +1624,9 @@ class Series(IndexOpsMixin[S1], NDFrame): # just failed to generate these so I couldn't match # them up. @overload - def __add__(self: Series[Never], other: Scalar | _ListLike | Series) -> Series: ... + def __add__(self: Series[Never], other: Scalar | _ListLike | Series) -> Series: ... # type: ignore[overload-overlap] @overload - def __add__(self, other: Series[Never]) -> Series: ... + def __add__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @overload def __add__( self: Series[bool], @@ -1709,6 +1709,15 @@ class Series(IndexOpsMixin[S1], NDFrame): other: datetime | np.datetime64 | np_ndarray_dt | Series[Timestamp], ) -> Series[Timestamp]: ... @overload + def __add__( + self: Series[Timedelta], + other: timedelta | np.timedelta64 | np_ndarray_td | TimedeltaSeries, + ) -> TimedeltaSeries: ... + @overload + def __add__( + self: Series[Timedelta], other: Series[Timedelta] + ) -> Series[Timedelta]: ... + @overload def __add__(self: Series[Timedelta], other: Period) -> PeriodSeries: ... @overload def add( @@ -2235,16 +2244,38 @@ class Series(IndexOpsMixin[S1], NDFrame): other: timedelta | np.timedelta64 | np_ndarray_td | TimedeltaSeries, ) -> TimedeltaSeries: ... @overload + def __mul__(self: Series[bool], other: Series[Timedelta]) -> Series[Timedelta]: ... # type: ignore[overload-overlap] + @overload def __mul__( self: Series[int], other: timedelta | np.timedelta64 | np_ndarray_td | TimedeltaSeries, ) -> TimedeltaSeries: ... @overload + def __mul__(self: Series[int], other: Series[Timedelta]) -> Series[Timedelta]: ... + @overload def __mul__( self: Series[float], other: timedelta | np.timedelta64 | np_ndarray_td | TimedeltaSeries, ) -> TimedeltaSeries: ... @overload + def __mul__(self: Series[float], other: Series[Timedelta]) -> Series[Timedelta]: ... + @overload + def __mul__( + self: Series[Timedelta], + other: ( + float + | Sequence[float] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + ), + ) -> TimedeltaSeries: ... + @overload + def __mul__( + self: Series[Timedelta], + other: Series[bool] | Series[int] | Series[float], + ) -> Series[Timedelta]: ... + @overload def mul( self: Series[Never], other: complex | _ListLike | Series, @@ -2436,19 +2467,49 @@ class Series(IndexOpsMixin[S1], NDFrame): self: Series[_T_COMPLEX], other: np_ndarray_complex ) -> Series[complex]: ... @overload - def __rmul__( + def __rmul__( # type: ignore[misc] self: Series[bool], - other: timedelta | np.timedelta64 | np_ndarray_td | TimedeltaSeries, + other: ( + timedelta + | np.timedelta64 + | np_ndarray_td + | Series[Timedelta] + | TimedeltaSeries + ), ) -> TimedeltaSeries: ... @overload - def __rmul__( + def __rmul__( # type: ignore[misc] self: Series[int], - other: timedelta | np.timedelta64 | np_ndarray_td | TimedeltaSeries, + other: ( + timedelta + | np.timedelta64 + | np_ndarray_td + | Series[Timedelta] + | TimedeltaSeries + ), ) -> TimedeltaSeries: ... @overload - def __rmul__( + def __rmul__( # type: ignore[misc] self: Series[float], - other: timedelta | np.timedelta64 | np_ndarray_td | TimedeltaSeries, + other: ( + timedelta + | np.timedelta64 + | np_ndarray_td + | Series[Timedelta] + | TimedeltaSeries + ), + ) -> TimedeltaSeries: ... + @overload + def __rmul__( + self: Series[Timedelta], + other: ( + float + | Sequence[float] + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | Series[_T_INT] + ), ) -> TimedeltaSeries: ... @overload def rmul( @@ -2627,16 +2688,17 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __rxor__(self, other: int | np_ndarray_anyint | Series[int]) -> Series[int]: ... @overload - def __sub__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] + def __sub__(self: Series[Never], other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @overload def __sub__( - self: Series[Never], - other: datetime | np.datetime64 | np_ndarray_dt | Series[Timestamp], + self: Series[Never], other: datetime | np.datetime64 | np_ndarray_dt ) -> TimedeltaSeries: ... @overload - def __sub__( # type: ignore[overload-overlap] - self: Series[Never], other: complex | _ListLike | Series - ) -> Series: ... + def __sub__(self: Series[Never], other: Series[Timestamp]) -> Series[Timedelta]: ... + @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[bool], @@ -2702,10 +2764,13 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[complex]: ... @overload def __sub__( - self: Series[Timestamp], - other: datetime | np.datetime64 | np_ndarray_dt | Series[Timestamp], + self: Series[Timestamp], other: datetime | np.datetime64 | np_ndarray_dt ) -> TimedeltaSeries: ... @overload + def __sub__( + self: Series[Timestamp], other: Series[Timestamp] + ) -> Series[Timedelta]: ... + @overload def __sub__( self: Series[Timestamp], other: ( @@ -2713,7 +2778,6 @@ class Series(IndexOpsMixin[S1], NDFrame): | np.timedelta64 | np_ndarray_td | TimedeltaIndex - | Series[Timedelta] | TimedeltaSeries | BaseOffset ), @@ -2726,7 +2790,6 @@ class Series(IndexOpsMixin[S1], NDFrame): | np.timedelta64 | np_ndarray_td | TimedeltaIndex - | Series[Timedelta] | TimedeltaSeries ), ) -> TimedeltaSeries: ... diff --git a/tests/series/arithmetic/test_sub.py b/tests/series/arithmetic/test_sub.py index 942877e2a..497c1ef25 100644 --- a/tests/series/arithmetic/test_sub.py +++ b/tests/series/arithmetic/test_sub.py @@ -180,7 +180,7 @@ def test_sub_pd_datetime() -> None: 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(left_ts - a, "pd.Series[pd.Timedelta]"), pd.Series, pd.Timedelta) check(assert_type(s - left_ts, pd.Series), pd.Series, pd.Timedelta) check(assert_type(a - left_ts, pd.Series), pd.Series, pd.Timedelta) diff --git a/tests/series/test_series.py b/tests/series/test_series.py index 4c047db16..f0df2f007 100644 --- a/tests/series/test_series.py +++ b/tests/series/test_series.py @@ -1598,6 +1598,7 @@ def test_series_min_max_sub_axis() -> None: ss = s1 - s2 sm = s1 * s2 sd = s1 / s2 + s1.__sub__(s2) check(assert_type(sa, pd.Series), pd.Series) check(assert_type(ss, pd.Series), pd.Series) check(assert_type(sm, pd.Series), pd.Series) diff --git a/tests/test_timefuncs.py b/tests/test_timefuncs.py index cb3346e8c..04dc9c81f 100644 --- a/tests/test_timefuncs.py +++ b/tests/test_timefuncs.py @@ -191,7 +191,7 @@ def test_timestamp_timedelta_series_arithmetic() -> None: td1 = pd.to_timedelta([2, 3], "seconds") ts2 = pd.to_datetime(pd.Series(["2022-03-08", "2022-03-10"])) r1 = ts1 - ts2 - check(assert_type(r1, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(r1, "pd.Series[pd.Timedelta]"), pd.Series, pd.Timedelta) r2 = r1 / td1 check(assert_type(r2, "pd.Series[float]"), pd.Series, float) r3 = r1 - td1 @@ -201,7 +201,7 @@ def test_timestamp_timedelta_series_arithmetic() -> None: sb = pd.Series([1, 2]) == pd.Series([1, 3]) check(assert_type(sb, "pd.Series[bool]"), pd.Series, np.bool_) r5 = sb * r1 - check(assert_type(r5, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(r5, "pd.Series[pd.Timedelta]"), pd.Series, pd.Timedelta) r6 = r1 * 4 check(assert_type(r6, "TimedeltaSeries"), pd.Series, pd.Timedelta) @@ -1655,7 +1655,7 @@ def test_timedelta64_and_arithmatic_operator() -> None: s1 = pd.Series(data=pd.date_range("1/1/2020", "2/1/2020")) s2 = pd.Series(data=pd.date_range("1/1/2021", "2/1/2021")) s3 = s2 - s1 - check(assert_type(s3, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(s3, "pd.Series[pd.Timedelta]"), pd.Series, pd.Timedelta) td1 = pd.Timedelta(1, "D") check(assert_type(s2 - td1, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) # GH 758 @@ -1808,7 +1808,7 @@ def test_timestamp_sub_series() -> None: ts1 = pd.to_datetime(pd.Series(["2022-03-05", "2022-03-06"])) one_ts = ts1.iloc[0] check(assert_type(ts1.iloc[0], pd.Timestamp), pd.Timestamp) - check(assert_type(one_ts - ts1, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(one_ts - ts1, "pd.Series[pd.Timedelta]"), pd.Series, pd.Timedelta) def test_creating_date_range() -> None: From 64691966ef6bea55ffbec737da3c86ce45e499e1 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 20 Aug 2025 19:27:51 +0200 Subject: [PATCH 22/27] feat: tests for addition --- attempt.py | 13 +- pandas-stubs/core/series.pyi | 13 +- tests/series/arithmetic/str/__init__.py | 0 tests/series/arithmetic/str/test_add.py | 69 ++++++++ tests/series/arithmetic/timestamp/__init__.py | 0 tests/series/arithmetic/timestamp/test_add.py | 151 ++++++++++++++++++ tests/series/test_series.py | 6 - tests/test_timefuncs.py | 2 +- 8 files changed, 238 insertions(+), 16 deletions(-) create mode 100644 tests/series/arithmetic/str/__init__.py create mode 100644 tests/series/arithmetic/str/test_add.py create mode 100644 tests/series/arithmetic/timestamp/__init__.py create mode 100644 tests/series/arithmetic/timestamp/test_add.py diff --git a/attempt.py b/attempt.py index 96d9c11e3..a1d0cf84c 100644 --- a/attempt.py +++ b/attempt.py @@ -1,12 +1,15 @@ -from typing import assert_type, reveal_type +import datetime as dt +from typing import ( + assert_type, + reveal_type, +) + +import numpy as np import pandas as pd from pandas.core.series import TimedeltaSeries # noqa: F401 -import numpy as np -import datetime as dt from tests import check - df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [5, 4, 3, 2, 1]}) s1 = df.min(axis=1) s2 = df.max(axis=1) @@ -45,4 +48,4 @@ r7 = ts1 - tsp1 check(assert_type(r7, "TimedeltaSeries"), pd.Series, pd.Timedelta) r8 = ts1 - dt1 -check(assert_type(r8, "TimedeltaSeries"), pd.Series, pd.Timedelta) \ No newline at end of file +check(assert_type(r8, "TimedeltaSeries"), pd.Series, pd.Timedelta) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index b2ebd25ba..9747fa465 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -187,6 +187,7 @@ from pandas._typing import ( np_ndarray_complex, np_ndarray_dt, np_ndarray_float, + np_ndarray_str, np_ndarray_td, npt, num, @@ -1688,9 +1689,11 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[complex]: ... @overload def __add__( - self: Series[_str], other: _str | Sequence[_str] | Series[_str] + self: Series[_str], other: _str | Sequence[_str] | np_ndarray_str | Series[_str] ) -> Series[_str]: ... @overload + def __add__(self: Series[Timestamp], other: np_ndarray_dt) -> Never: ... + @overload def __add__( self: Series[Timestamp], other: ( @@ -1842,7 +1845,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def add( self: Series[_str], - other: _str | Sequence[_str] | Series[_str], + other: _str | Sequence[_str] | np_ndarray_str | Series[_str], level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -1949,9 +1952,11 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[complex]: ... @overload def __radd__( - self: Series[_str], other: _str | Sequence[_str] | Series[_str] + self: Series[_str], other: _str | Sequence[_str] | np_ndarray_str | Series[_str] ) -> Series[_str]: ... @overload + def __radd__(self: Series[Timestamp], other: np_ndarray_dt) -> Never: ... + @overload def __radd__( self: Series[Timestamp], other: ( @@ -2094,7 +2099,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def radd( self: Series[_str], - other: _str | Sequence[_str] | Series[_str], + other: _str | Sequence[_str] | np_ndarray_str | Series[_str], level: Level | None = None, fill_value: float | None = None, axis: int = 0, diff --git a/tests/series/arithmetic/str/__init__.py b/tests/series/arithmetic/str/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/series/arithmetic/str/test_add.py b/tests/series/arithmetic/str/test_add.py new file mode 100644 index 000000000..1f7f7d210 --- /dev/null +++ b/tests/series/arithmetic/str/test_add.py @@ -0,0 +1,69 @@ +from typing import Any + +import numpy as np +import pandas as pd +from typing_extensions import assert_type + +from tests import check + +left = pd.Series(["1", "23", "456"]) # left operand + + +def test_add_py_scalar() -> None: + """Test pd.Series[str] + Python native str""" + r0 = "right" + + check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) + + check(assert_type(r0 + left, "pd.Series[str]"), pd.Series, str) + + check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) + + check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) + + +def test_add_py_sequence() -> None: + """Test pd.Series[str] + Python native sequence""" + r0 = ["a", "bc", "def"] + r1 = tuple(r0) + + check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) + check(assert_type(left + r1, "pd.Series[str]"), pd.Series, str) + + check(assert_type(r0 + left, "pd.Series[str]"), pd.Series, str) + check(assert_type(r1 + left, "pd.Series[str]"), pd.Series, str) + + check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) + check(assert_type(left.add(r1), "pd.Series[str]"), pd.Series, str) + + check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) + check(assert_type(left.radd(r1), "pd.Series[str]"), pd.Series, str) + + +def test_add_numpy_array() -> None: + """Test pd.Series[str] + numpy array""" + r0 = np.array(["a", "bc", "def"], np.str_) + + check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) + + # `numpy` typing gives `Any` in the static type + # checking, where our `__radd__` cannot override. At runtime, they return + # `Series`s. + check(assert_type(r0 + left, Any), pd.Series, str) + + check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) + + check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) + + +def test_add_pd_series() -> None: + """Test pd.Series[str] + pandas series""" + r0 = pd.Series(["a", "bc", "def"]) + + check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) + + check(assert_type(r0 + left, "pd.Series[str]"), pd.Series, str) + + check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) + + check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) diff --git a/tests/series/arithmetic/timestamp/__init__.py b/tests/series/arithmetic/timestamp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/series/arithmetic/timestamp/test_add.py b/tests/series/arithmetic/timestamp/test_add.py new file mode 100644 index 000000000..9d78fdf32 --- /dev/null +++ b/tests/series/arithmetic/timestamp/test_add.py @@ -0,0 +1,151 @@ +from datetime import ( + datetime, + timedelta, +) + +import numpy as np +from numpy import typing as npt # noqa: F401 +import pandas as pd +from typing_extensions import ( + Never, + assert_type, +) + +from tests import ( + TYPE_CHECKING_INVALID_USAGE, + check, +) + +left = pd.Series([pd.Timestamp(2025, 8, 20)]) + + +def test_add_py_scalar() -> None: + """Test pd.Series[pd.Timestamp] + Python native scalars""" + s = datetime(2025, 8, 20) + d = timedelta(seconds=1) + + if TYPE_CHECKING_INVALID_USAGE: + _0 = left + s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(left + d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + _1 = s + left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(d + left, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + left.add(s) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + check(assert_type(left.add(d), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + left.radd(s) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + check(assert_type(left.radd(d), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + +def test_add_numpy_scalar() -> None: + """Test pd.Series[pd.Timestamp] + numpy scalars""" + s = np.datetime64("2025-08-20") + d = np.timedelta64(1, "s") + + if TYPE_CHECKING_INVALID_USAGE: + _0 = left + s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(left + d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + _1 = s + left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(d + left, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + left.add(s) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + check(assert_type(left.add(d), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + left.radd(s) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + check(assert_type(left.radd(d), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + +def test_add_pd_scalar() -> None: + """Test pd.Series[pd.Timestamp] + pandas scalars""" + s = pd.Timestamp("2025-08-20") + d = pd.Timedelta(seconds=1) + + if TYPE_CHECKING_INVALID_USAGE: + _0 = left + s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(left + d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + _1 = s + left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(d + left, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + left.add(s) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + check(assert_type(left.add(d), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + left.radd(s) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + check(assert_type(left.radd(d), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + +def test_add_py_sequence() -> None: + """Test pd.Series[pd.Timestamp] + Python native sequence""" + s = [datetime(2025, 8, 20)] + d = [timedelta(seconds=1)] + + if TYPE_CHECKING_INVALID_USAGE: + _0 = left + s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _a = left + d # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + + _1 = s + left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _b = d + left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + + left.add(s) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + left.add(d) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + + left.radd(s) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + left.radd(d) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + + +def test_add_numpy_array() -> None: + """Test pd.Series[pd.Timestamp] + numpy array""" + s = np.array([np.datetime64("2025-08-20")], np.datetime64) + d = np.array([np.timedelta64(1, "s")], np.timedelta64) + + if TYPE_CHECKING_INVALID_USAGE: + assert_type(left + s, Never) + check(assert_type(left + d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__radd__` cannot override. At runtime, they return + # `Series`s. + if TYPE_CHECKING_INVALID_USAGE: + assert_type(s + left, "npt.NDArray[np.datetime64]") + check(assert_type(d + left, "npt.NDArray[np.timedelta64]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + left.add(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + check(assert_type(left.add(d), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + left.radd(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + check(assert_type(left.radd(d), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + +def test_add_pd_series() -> None: + """Test pd.Series[pd.Timestamp] + pandas Series""" + s = pd.Series([pd.Timestamp("2025-08-20")]) + d = pd.Series([pd.Timedelta(seconds=1)]) + + if TYPE_CHECKING_INVALID_USAGE: + _0 = left + s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(left + d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + _1 = s + left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + check(assert_type(d + left, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + left.add(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + check(assert_type(left.add(d), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + if TYPE_CHECKING_INVALID_USAGE: + left.radd(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + check(assert_type(left.radd(d), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) diff --git a/tests/series/test_series.py b/tests/series/test_series.py index f0df2f007..72af9a1c6 100644 --- a/tests/series/test_series.py +++ b/tests/series/test_series.py @@ -1653,12 +1653,6 @@ def test_reset_index() -> None: assert assert_type(s.reset_index(inplace=True, drop=True), None) is None -def test_series_add_str() -> None: - s = pd.Series(["abc", "def"]) - check(assert_type(s + "x", "pd.Series[str]"), pd.Series, str) - check(assert_type("x" + s, "pd.Series[str]"), pd.Series, str) - - def test_series_dtype() -> None: s = pd.Series(["abc", "def"], dtype=str) check(assert_type(s, "pd.Series[str]"), pd.Series, str) diff --git a/tests/test_timefuncs.py b/tests/test_timefuncs.py index 04dc9c81f..a4b401007 100644 --- a/tests/test_timefuncs.py +++ b/tests/test_timefuncs.py @@ -1836,7 +1836,7 @@ def test_timestamp_to_list_add() -> None: sseries + pd.Timedelta(1, "d") check( - assert_type(sseries + pd.Timedelta(1, "D"), pd.Series[pd.Timestamp]), + assert_type(sseries + pd.Timedelta(1, "D"), "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp, ) From aac6cff0272606abba8742012a776a1fb8e30072 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 20 Aug 2025 20:19:07 +0200 Subject: [PATCH 23/27] fix(mypy): attempt for python > 310 --- tests/series/arithmetic/str/test_add.py | 49 +++++++++++++------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/tests/series/arithmetic/str/test_add.py b/tests/series/arithmetic/str/test_add.py index 1f7f7d210..e4beff596 100644 --- a/tests/series/arithmetic/str/test_add.py +++ b/tests/series/arithmetic/str/test_add.py @@ -1,3 +1,4 @@ +from builtins import str as _str from typing import Any import numpy as np @@ -10,60 +11,60 @@ def test_add_py_scalar() -> None: - """Test pd.Series[str] + Python native str""" + """Testpd.Series[str]+ Python native str""" r0 = "right" - check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) + check(assert_type(left + r0, "pd.Series[_str]"), pd.Series, _str) - check(assert_type(r0 + left, "pd.Series[str]"), pd.Series, str) + check(assert_type(r0 + left, "pd.Series[_str]"), pd.Series, _str) - check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) + check(assert_type(left.add(r0), "pd.Series[_str]"), pd.Series, _str) - check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) + check(assert_type(left.radd(r0), "pd.Series[_str]"), pd.Series, _str) def test_add_py_sequence() -> None: - """Test pd.Series[str] + Python native sequence""" + """Testpd.Series[str]+ Python native sequence""" r0 = ["a", "bc", "def"] r1 = tuple(r0) - check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) - check(assert_type(left + r1, "pd.Series[str]"), pd.Series, str) + check(assert_type(left + r0, "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left + r1, "pd.Series[_str]"), pd.Series, _str) - check(assert_type(r0 + left, "pd.Series[str]"), pd.Series, str) - check(assert_type(r1 + left, "pd.Series[str]"), pd.Series, str) + check(assert_type(r0 + left, "pd.Series[_str]"), pd.Series, _str) + check(assert_type(r1 + left, "pd.Series[_str]"), pd.Series, _str) - check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) - check(assert_type(left.add(r1), "pd.Series[str]"), pd.Series, str) + check(assert_type(left.add(r0), "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left.add(r1), "pd.Series[_str]"), pd.Series, _str) - check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) - check(assert_type(left.radd(r1), "pd.Series[str]"), pd.Series, str) + check(assert_type(left.radd(r0), "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left.radd(r1), "pd.Series[_str]"), pd.Series, _str) def test_add_numpy_array() -> None: - """Test pd.Series[str] + numpy array""" + """Testpd.Series[str]+ numpy array""" r0 = np.array(["a", "bc", "def"], np.str_) - check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) + check(assert_type(left + r0, "pd.Series[_str]"), pd.Series, _str) # `numpy` typing gives `Any` in the static type # checking, where our `__radd__` cannot override. At runtime, they return # `Series`s. - check(assert_type(r0 + left, Any), pd.Series, str) + check(assert_type(r0 + left, Any), pd.Series, _str) - check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) + check(assert_type(left.add(r0), "pd.Series[_str]"), pd.Series, _str) - check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) + check(assert_type(left.radd(r0), "pd.Series[_str]"), pd.Series, _str) def test_add_pd_series() -> None: - """Test pd.Series[str] + pandas series""" + """Testpd.Series[str]+ pandas series""" r0 = pd.Series(["a", "bc", "def"]) - check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) + check(assert_type(left + r0, "pd.Series[_str]"), pd.Series, _str) - check(assert_type(r0 + left, "pd.Series[str]"), pd.Series, str) + check(assert_type(r0 + left, "pd.Series[_str]"), pd.Series, _str) - check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) + check(assert_type(left.add(r0), "pd.Series[_str]"), pd.Series, _str) - check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) + check(assert_type(left.radd(r0), "pd.Series[_str]"), pd.Series, _str) From 72208c7dd45ff05b32b2bc1c310502b1873fd560 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 20 Aug 2025 22:06:16 +0200 Subject: [PATCH 24/27] feat: tests for subtraction --- pandas-stubs/core/series.pyi | 13 +- pyproject.toml | 2 +- tests/series/arithmetic/str/test_add.py | 55 ++++--- tests/series/arithmetic/timestamp/test_add.py | 2 +- tests/series/arithmetic/timestamp/test_sub.py | 142 ++++++++++++++++++ 5 files changed, 188 insertions(+), 26 deletions(-) create mode 100644 tests/series/arithmetic/timestamp/test_sub.py diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 9747fa465..cf45022a9 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -2799,7 +2799,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ), ) -> TimedeltaSeries: ... @overload - def sub( # type: ignore[overload-overlap] + def sub( self: Series[Never], other: datetime | np.datetime64 | np_ndarray_dt | Series[Timestamp], level: Level | None = None, @@ -2907,6 +2907,14 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> Series[complex]: ... @overload + def sub( + self: Series[Timestamp], + other: datetime | np.datetime64 | np_ndarray_dt | Series[Timestamp], + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> TimedeltaSeries: ... + @overload def sub( self: Series[Timestamp], other: ( @@ -2914,7 +2922,6 @@ class Series(IndexOpsMixin[S1], NDFrame): | np.timedelta64 | np_ndarray_td | TimedeltaIndex - | Series[Never] | TimedeltaSeries | BaseOffset ), @@ -3008,6 +3015,8 @@ class Series(IndexOpsMixin[S1], NDFrame): ), ) -> Series[complex]: ... @overload + def __rsub__(self: Series[Timestamp], other: np_ndarray_td) -> Never: ... + @overload def __rsub__( self: Series[Timestamp], other: datetime | np.datetime64 | np_ndarray_dt ) -> TimedeltaSeries: ... diff --git a/pyproject.toml b/pyproject.toml index 0de7fe6ce..046abe579 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ mypy = "1.17.0" pandas = "2.3.1" pyarrow = ">=10.0.1" pytest = ">=7.1.2" -pyright = ">=1.1.403" +pyright = ">=1.1.404" ty = "^0.0.1a8" pyrefly = "^0.21.0" poethepoet = ">=0.16.5" diff --git a/tests/series/arithmetic/str/test_add.py b/tests/series/arithmetic/str/test_add.py index e4beff596..f7e2f5cfb 100644 --- a/tests/series/arithmetic/str/test_add.py +++ b/tests/series/arithmetic/str/test_add.py @@ -1,7 +1,8 @@ -from builtins import str as _str +import sys from typing import Any import numpy as np +from numpy import typing as npt # noqa: F401 import pandas as pd from typing_extensions import assert_type @@ -14,13 +15,13 @@ def test_add_py_scalar() -> None: """Testpd.Series[str]+ Python native str""" r0 = "right" - check(assert_type(left + r0, "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) - check(assert_type(r0 + left, "pd.Series[_str]"), pd.Series, _str) + check(assert_type(r0 + left, "pd.Series[str]"), pd.Series, str) - check(assert_type(left.add(r0), "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) - check(assert_type(left.radd(r0), "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) def test_add_py_sequence() -> None: @@ -28,43 +29,53 @@ def test_add_py_sequence() -> None: r0 = ["a", "bc", "def"] r1 = tuple(r0) - check(assert_type(left + r0, "pd.Series[_str]"), pd.Series, _str) - check(assert_type(left + r1, "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) + check(assert_type(left + r1, "pd.Series[str]"), pd.Series, str) - check(assert_type(r0 + left, "pd.Series[_str]"), pd.Series, _str) - check(assert_type(r1 + left, "pd.Series[_str]"), pd.Series, _str) + check(assert_type(r0 + left, "pd.Series[str]"), pd.Series, str) + check(assert_type(r1 + left, "pd.Series[str]"), pd.Series, str) - check(assert_type(left.add(r0), "pd.Series[_str]"), pd.Series, _str) - check(assert_type(left.add(r1), "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) + check(assert_type(left.add(r1), "pd.Series[str]"), pd.Series, str) - check(assert_type(left.radd(r0), "pd.Series[_str]"), pd.Series, _str) - check(assert_type(left.radd(r1), "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) + check(assert_type(left.radd(r1), "pd.Series[str]"), pd.Series, str) def test_add_numpy_array() -> None: """Testpd.Series[str]+ numpy array""" r0 = np.array(["a", "bc", "def"], np.str_) - check(assert_type(left + r0, "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) - # `numpy` typing gives `Any` in the static type + # `numpy` typing gives `npt.NDArray[np.str_]` in the static type # checking, where our `__radd__` cannot override. At runtime, they return # `Series`s. - check(assert_type(r0 + left, Any), pd.Series, _str) + if sys.version_info >= (3, 11): + check( + assert_type( + r0 + left, # pyright: ignore[reportAssertTypeFailure] + "npt.NDArray[np.str_]", + ), + pd.Series, + str, + ) + else: + check(assert_type(r0 + left, Any), pd.Series, str) - check(assert_type(left.add(r0), "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) - check(assert_type(left.radd(r0), "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) def test_add_pd_series() -> None: """Testpd.Series[str]+ pandas series""" r0 = pd.Series(["a", "bc", "def"]) - check(assert_type(left + r0, "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left + r0, "pd.Series[str]"), pd.Series, str) - check(assert_type(r0 + left, "pd.Series[_str]"), pd.Series, _str) + check(assert_type(r0 + left, "pd.Series[str]"), pd.Series, str) - check(assert_type(left.add(r0), "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) - check(assert_type(left.radd(r0), "pd.Series[_str]"), pd.Series, _str) + check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) diff --git a/tests/series/arithmetic/timestamp/test_add.py b/tests/series/arithmetic/timestamp/test_add.py index 9d78fdf32..689b04811 100644 --- a/tests/series/arithmetic/timestamp/test_add.py +++ b/tests/series/arithmetic/timestamp/test_add.py @@ -16,7 +16,7 @@ check, ) -left = pd.Series([pd.Timestamp(2025, 8, 20)]) +left = pd.Series([pd.Timestamp(2025, 8, 20)]) # left operand def test_add_py_scalar() -> None: diff --git a/tests/series/arithmetic/timestamp/test_sub.py b/tests/series/arithmetic/timestamp/test_sub.py new file mode 100644 index 000000000..ab9c02a2d --- /dev/null +++ b/tests/series/arithmetic/timestamp/test_sub.py @@ -0,0 +1,142 @@ +from datetime import ( + datetime, + timedelta, +) +from typing import TYPE_CHECKING + +import numpy as np +from numpy import typing as npt # noqa: F401 +import pandas as pd +from typing_extensions import assert_type + +from tests import ( + TYPE_CHECKING_INVALID_USAGE, + check, +) + +if TYPE_CHECKING: + from pandas.core.series import TimedeltaSeries # noqa: F401 + +left = pd.Series([pd.Timestamp(2025, 8, 20)]) # left operand + + +def test_sub_py_scalar() -> None: + """Test pd.Series[pd.Timestamp] - Python native scalars""" + s = datetime(2025, 8, 20) + d = timedelta(seconds=1) + + check(assert_type(left - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left - d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + check(assert_type(s - left, "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + _ = d - left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + + check(assert_type(left.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left - d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + check(assert_type(left.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + left.rsub(d) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + + +def test_sub_numpy_scalar() -> None: + """Test pd.Series[pd.Timestamp] - numpy scalars""" + s = np.datetime64("2025-08-20") + d = np.timedelta64(1, "s") + + check(assert_type(left - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left - d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + check(assert_type(s - left, "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + _ = d - left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + + check(assert_type(left.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left - d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + check(assert_type(left.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + left.rsub(d) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + + +def test_sub_pd_scalar() -> None: + """Test pd.Series[pd.Timestamp] - pandas scalars""" + s = pd.Timestamp("2025-08-20") + d = pd.Timedelta(seconds=1) + + check(assert_type(left - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left - d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + check(assert_type(s - left, "pd.Series[pd.Timedelta]"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + _ = d - left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + + check(assert_type(left.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left - d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + check(assert_type(left.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + left.rsub(d) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + + +def test_sub_py_sequence() -> None: + """Test pd.Series[pd.Timestamp] - Python native sequence""" + s = [datetime(2025, 8, 20)] + d = [timedelta(seconds=1)] + + if TYPE_CHECKING_INVALID_USAGE: + _0 = left - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _a = left - d # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + + _1 = s - left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _b = d - left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + + left.sub(s) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + left.sub(d) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + + left.rsub(s) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + left.rsub(d) # type: ignore[call-overload] # pyright: ignore[reportArgumentType,reportCallIssue] + + +def test_sub_numpy_array() -> None: + """Test pd.Series[pd.Timestamp] - numpy array""" + s = np.array([np.datetime64("2025-08-20")], np.datetime64) + d = np.array([np.timedelta64(1, "s")], np.timedelta64) + + check(assert_type(left - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left - d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + # `numpy` typing gives the corresponding `ndarray`s in the static type + # checking, where our `__radd__` cannot override. At runtime, they return + # `Series`s. + check(assert_type(s - left, "npt.NDArray[np.datetime64]"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + assert_type(d - left, "npt.NDArray[np.timedelta64]") + + check(assert_type(left.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left - d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + check(assert_type(left.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + left.rsub(d) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + + +def test_sub_pd_series() -> None: + """Test pd.Series[pd.Timestamp] - pandas Series""" + s = pd.Series([pd.Timestamp("2025-08-20")]) + d = pd.Series([pd.Timedelta(seconds=1)]) + + check(assert_type(left - s, "pd.Series[pd.Timedelta]"), pd.Series, pd.Timedelta) + check(assert_type(left - d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + check(assert_type(s - left, "pd.Series[pd.Timedelta]"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + _ = d - left # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + + check(assert_type(left.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + check(assert_type(left - d, "pd.Series[pd.Timestamp]"), pd.Series, pd.Timestamp) + + check(assert_type(left.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + left.rsub(d) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] From 533b15d986bcad1a810a9c1524b4d38a3540c915 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 20 Aug 2025 22:14:42 +0200 Subject: [PATCH 25/27] fix: pyright --- tests/series/arithmetic/str/test_add.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/series/arithmetic/str/test_add.py b/tests/series/arithmetic/str/test_add.py index f7e2f5cfb..ddde0dd58 100644 --- a/tests/series/arithmetic/str/test_add.py +++ b/tests/series/arithmetic/str/test_add.py @@ -52,14 +52,7 @@ def test_add_numpy_array() -> None: # checking, where our `__radd__` cannot override. At runtime, they return # `Series`s. if sys.version_info >= (3, 11): - check( - assert_type( - r0 + left, # pyright: ignore[reportAssertTypeFailure] - "npt.NDArray[np.str_]", - ), - pd.Series, - str, - ) + check(assert_type(r0 + left, "npt.NDArray[np.str_]"), pd.Series, str) else: check(assert_type(r0 + left, Any), pd.Series, str) From ce06dc245d64679e044929589e800509ef97d485 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 20 Aug 2025 22:44:20 +0200 Subject: [PATCH 26/27] fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274#discussion_r2261157886 --- pandas-stubs/core/indexes/accessors.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas-stubs/core/indexes/accessors.pyi b/pandas-stubs/core/indexes/accessors.pyi index 803d14e1b..d0cca4a58 100644 --- a/pandas-stubs/core/indexes/accessors.pyi +++ b/pandas-stubs/core/indexes/accessors.pyi @@ -164,7 +164,8 @@ class _DatetimeLikeOps( # in to the dt accessor _DTTimestampTimedeltaReturnType = TypeVar( - "_DTTimestampTimedeltaReturnType", bound=Series | DatetimeIndex | TimedeltaIndex + "_DTTimestampTimedeltaReturnType", + bound=Series | Series[Timestamp] | TimedeltaSeries | DatetimeIndex | TimedeltaIndex, ) class _DatetimeRoundingMethods(Generic[_DTTimestampTimedeltaReturnType]): From f855f8655fcbc0c13879f404926034621641bc58 Mon Sep 17 00:00:00 2001 From: cmp0xff Date: Wed, 20 Aug 2025 22:58:43 +0200 Subject: [PATCH 27/27] refactor: revert unused design --- pandas-stubs/_typing.pyi | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/pandas-stubs/_typing.pyi b/pandas-stubs/_typing.pyi index b81202487..e2c8bedd7 100644 --- a/pandas-stubs/_typing.pyi +++ b/pandas-stubs/_typing.pyi @@ -845,30 +845,23 @@ MaskType: TypeAlias = Series[bool] | np_ndarray_bool | list[bool] # Scratch types for generics -# Closure upon add and mul -SeriesDTypeClosure: TypeAlias = ( +SeriesDType: TypeAlias = ( str | bytes + | datetime.date + | datetime.time | bool | int | float | complex | Dtype - | Interval - | CategoricalDtype - | list[str] -) -S1C = TypeVar("S1C", bound=SeriesDTypeClosure, default=Any) -S2C = TypeVar("S2C", bound=SeriesDTypeClosure) - -SeriesDType: TypeAlias = ( - SeriesDTypeClosure - | datetime.date - | datetime.time | datetime.datetime # includes pd.Timestamp | datetime.timedelta # includes pd.Timedelta | Period + | Interval + | CategoricalDtype | BaseOffset + | list[str] ) S1 = TypeVar("S1", bound=SeriesDType, default=Any) # Like S1, but without `default=Any`.