Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
8a99cf4
refactor: only drop TimestampSeries https://github.com/pandas-dev/pan…
cmp0xff Jul 13, 2025
ed69ec5
fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274/fi…
cmp0xff Jul 28, 2025
f1cf19f
Merge branch 'main' into hotfix/cmp0xff/gh718-drop-tss
cmp0xff Aug 2, 2025
ad0ee10
less mypy ignore
cmp0xff Aug 4, 2025
2dada6d
fix: even less mypy ignores
cmp0xff Aug 5, 2025
0359fd5
refactor(comment): https://github.com/pandas-dev/pandas-stubs/pull/12…
cmp0xff Aug 5, 2025
34703d7
feat: sub
cmp0xff Aug 5, 2025
c4d657e
fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274/fi…
cmp0xff Aug 6, 2025
1eeb809
fix: pyrefly
cmp0xff Aug 6, 2025
ceff1fe
fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274/fi…
cmp0xff Aug 6, 2025
dd8baff
fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274#di…
cmp0xff Aug 6, 2025
8a07eca
fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274#di…
cmp0xff Aug 6, 2025
3458fc9
fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274#di…
cmp0xff Aug 6, 2025
d333902
fix: reduce ignore
cmp0xff Aug 6, 2025
368c3ff
refactor: explain a temporary failure
cmp0xff Aug 7, 2025
2f661ae
Merge branch 'main' into hotfix/cmp0xff/gh718-drop-tss
cmp0xff Aug 7, 2025
15996ff
fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274#di…
cmp0xff Aug 7, 2025
c9c46b0
feat: arithmetic sub
cmp0xff Aug 8, 2025
e3447cf
fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1312#di…
cmp0xff Aug 8, 2025
672009e
feat(comment): https://github.com/pandas-dev/pandas-stubs/pull/1312#d…
cmp0xff Aug 11, 2025
5dab881
Merge branch 'main' into feature/cmp0xff/arithmetic-sub
cmp0xff Aug 14, 2025
1e9b11d
fix(comment): completeness https://github.com/pandas-dev/pandas-stubs…
cmp0xff Aug 14, 2025
6484af7
feat(series): arithmetic mul
cmp0xff Aug 8, 2025
72c2bd1
Merge branch 'feature/cmp0xff/arithmetic-mul' into hotfix/cmp0xff/gh7…
cmp0xff Aug 16, 2025
0657417
Merge branch 'main' into hotfix/cmp0xff/gh718-drop-tss
cmp0xff Aug 19, 2025
7a87d18
fix: type asserts
cmp0xff Aug 20, 2025
6469196
feat: tests for addition
cmp0xff Aug 20, 2025
aac6cff
fix(mypy): attempt for python > 310
cmp0xff Aug 20, 2025
72208c7
feat: tests for subtraction
cmp0xff Aug 20, 2025
533b15d
fix: pyright
cmp0xff Aug 20, 2025
ce06dc2
fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274#di…
cmp0xff Aug 20, 2025
f855f86
refactor: revert unused design
cmp0xff Aug 20, 2025
8a01e12
Merge branch 'main' into hotfix/cmp0xff/gh718-drop-tss
cmp0xff Aug 22, 2025
0a84cd0
Merge branch 'main' into hotfix/cmp0xff/gh718-drop-tss
cmp0xff Aug 27, 2025
253e61a
Merge branch 'main' into hotfix/cmp0xff/gh718-drop-tss
cmp0xff Sep 2, 2025
f736d55
Merge branch 'main' into hotfix/cmp0xff/gh718-drop-tss
cmp0xff Sep 4, 2025
3ff2c36
Merge branch 'main' into hotfix/cmp0xff/gh718-drop-tss
cmp0xff Sep 11, 2025
f10e990
Merge branch 'main' into hotfix/cmp0xff/gh718-drop-tss
cmp0xff Sep 13, 2025
4e5e68c
feat: progressive philosophy
cmp0xff Sep 13, 2025
55fdfe9
Merge branch 'main' into hotfix/cmp0xff/gh718-drop-tss
cmp0xff Sep 14, 2025
6572abc
fix(pytest): remove invalid tests
cmp0xff Sep 15, 2025
7b380b3
fix: comment
cmp0xff Sep 15, 2025
e7705e4
fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274/fi…
cmp0xff Sep 15, 2025
b487bc9
fix(comment): https://github.com/pandas-dev/pandas-stubs/pull/1274#di…
cmp0xff Sep 15, 2025
ff37521
chore(philosophy): first draft
cmp0xff Sep 15, 2025
61a9703
fix(pyright): median
cmp0xff Sep 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 30 additions & 27 deletions docs/philosophy.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,67 +36,70 @@ 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
from typing import reveal_type

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"]))
reveal_type(s2)
td = s1 - s2
reveal_type(td)
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 an error 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.TimestampSeries"
ttest.py:6: note: Revealed type is "pandas.core.series.TimestampSeries"
ttest.py:8: note: Revealed type is "pandas.core.series.TimedeltaSeries"
ttest.py:10: note: Revealed type is "builtins.Exception"
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: Unsupported operand types for + ("Series[Timestamp]" and "Series[Timestamp]") [operator]
```

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()`.

### Generic Series have restricted arithmetic
### Progressive arithmetic typing for generic Series

Consider the following Series from a DataFrame:

```python
import pandas as pd
from typing_extensions import reveal_type
from typing import TYPE_CHECKING, cast

if TYPE_CHECKING:
from pandas.core.series import TimestampSeries # noqa: F401


frame = pd.DataFrame({"timestamp": [pd.Timestamp(2025, 8, 26)], "tag": ["one"], "value": [1.0]})
frame = pd.DataFrame({"timestamp": [pd.Timestamp(2025, 9, 15)], "tag": ["one"], "value": [1.0]})
values = frame["value"]
reveal_type(values) # type checker: Series[Any], runtime: Series
new_values = values + 2

timestamps = frame["timestamp"]
reveal_type(timestamps) # type checker: Series[Any], runtime: Series
reveal_type(timestamps - pd.Timestamp(2025, 7, 12)) # type checker: Unknown and error, runtime: Series
reveal_type(cast("TimestampSeries", timestamps) - pd.Timestamp(2025, 7, 12)) # type checker: TimedeltaSeries, runtime: Series
reveal_type(timestamps - pd.Timestamp(2025, 7, 12)) # type checker: TimedeltaSeries, runtime: Series

tags = frame["tag"]
reveal_type("suffix" + tags) # type checker: Never, runtime: Series
reveal_type("suffix" + tags) # type checker: Series[str], runtime: Series
```

Since they are taken from a DataFrame, all three of them, `values`, `timestamps`
Since these Series are taken from a DataFrame, all three of them, `values`, `timestamps`
and `tags`, are recognized by type checkers as `Series[Any]`. The code snippet
runs fine at runtime. In the stub for type checking, however, we restrict
generic Series to perform arithmetic operations only with numeric types, and
give `Series[Any]` for the results. For `Timedelta`, `Timestamp`, `str`, etc.,
arithmetic is restricted to `Series[Any]` and the result is either undefined,
showing `Unknown` and errors, or `Never`. Users are encouraged to cast such
generic Series to ones with concrete types, so that type checkers can provide
meaningful results.
runs fine at runtime. In the stub for type checking, when there is only one
valid outcome, we provide the typing of this outcome as the result. For
example, if a `Timestamp` is subtracted from a `Series[Any]`, or a `str`
is added to a `Series[Any]`, valid outcomes can only be `TimedeltaSeries` and
`Series[str]`, respectively, which will be realized when the left operands
are actually `Series[Timestamp]` and `Series[str]`, respectively.

Note that static type checkers cannot determine the contents of a `Series[Any]`
at runtime. Users are invited to verify the results provided progressivly by the
type checkers and be warned if they are unreasonable.

When there are several possible valid outcomes of an arithmetic expression,
for example numeric types `Series[bool]`, `Series[int]`, etc., `Series[Any]`
will be given as the resulting typing.

### Interval is Generic

Expand Down
11 changes: 4 additions & 7 deletions pandas-stubs/_libs/interval.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -176,7 +173,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: ...
Expand All @@ -187,7 +184,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: ...
Expand All @@ -198,7 +195,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: ...
Expand Down
7 changes: 2 additions & 5 deletions pandas-stubs/_libs/tslibs/timedeltas.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -170,7 +167,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
Expand Down
21 changes: 9 additions & 12 deletions pandas-stubs/_libs/tslibs/timestamps.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ from pandas.core.indexes.base import Index
from pandas.core.series import (
Series,
TimedeltaSeries,
TimestampSeries,
)
from typing_extensions import (
Never,
Expand Down Expand Up @@ -187,7 +186,7 @@ class Timestamp(datetime, SupportsIndex):
self, other: np_ndarray[ShapeT, np.datetime64]
) -> np_ndarray[ShapeT, np.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
Expand All @@ -197,7 +196,7 @@ class Timestamp(datetime, SupportsIndex):
self, other: np_ndarray[ShapeT, np.datetime64]
) -> np_ndarray[ShapeT, np.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
Expand All @@ -207,7 +206,7 @@ class Timestamp(datetime, SupportsIndex):
self, other: np_ndarray[ShapeT, np.datetime64]
) -> np_ndarray[ShapeT, np.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
Expand All @@ -217,7 +216,7 @@ class Timestamp(datetime, SupportsIndex):
self, other: np_ndarray[ShapeT, np.datetime64]
) -> np_ndarray[ShapeT, np.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__(
Expand All @@ -226,7 +225,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
Expand All @@ -239,23 +238,21 @@ class Timestamp(datetime, SupportsIndex):
) -> np_ndarray[ShapeT, np.datetime64]: ...
# TODO: test dt64
@overload # type: ignore[override]
def __sub__(self, other: Timestamp | datetime | np.datetime64) -> Timedelta: ...
def __sub__(self, other: datetime | np.datetime64) -> Timedelta: ...
@overload
def __sub__(self, other: timedelta | np.timedelta64 | Tick) -> Self: ...
@overload
def __sub__(self, other: TimedeltaIndex) -> DatetimeIndex: ...
@overload
def __sub__(self, other: TimedeltaSeries) -> TimestampSeries: ...
@overload
def __sub__(self, other: TimestampSeries) -> TimedeltaSeries: ...
def __sub__(self, other: TimedeltaSeries) -> Series[Timestamp]: ...
@overload
def __sub__(
self, other: np_ndarray[ShapeT, np.timedelta64]
) -> np_ndarray[ShapeT, np.datetime64]: ...
@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: Index) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
@overload
Expand All @@ -265,7 +262,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: Index) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
@overload
Expand Down
Loading
Loading