From 514eefe8e210b172479be01dee9e80a0a042c4b0 Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Fri, 10 Jan 2025 17:44:23 -0500 Subject: [PATCH 01/15] GH1089 Migrate frame/series tests to new framework --- pandas-stubs/core/frame.pyi | 2 +- tests/test_frame.py | 10 ++- tests/test_series.py | 174 +++++++++++++++++++++++------------- 3 files changed, 118 insertions(+), 68 deletions(-) diff --git a/pandas-stubs/core/frame.pyi b/pandas-stubs/core/frame.pyi index aa69c5d80..f4bd8e416 100644 --- a/pandas-stubs/core/frame.pyi +++ b/pandas-stubs/core/frame.pyi @@ -246,7 +246,7 @@ if sys.version_info >= (3, 12): @overload def __getitem__(self, key: Scalar | tuple[Hashable, ...]) -> Series: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] @overload - def __getitem__( # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] + def __getitem__( # pyright: ignore[reportOverlappingOverload] self, key: Iterable[Hashable] | slice ) -> Self: ... @overload diff --git a/tests/test_frame.py b/tests/test_frame.py index ad7bb6450..4d6c138f0 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -2409,7 +2409,7 @@ def test_indexslice_getitem(): ind = pd.Index([2, 3]) check( assert_type( - pd.IndexSlice[ind, :], tuple["pd.Index[int]", "slice[None, None, None]"] + pd.IndexSlice[ind, :], tuple["pd.Index[int]", "slice[None, None, None]"] # type: ignore[type-arg] ), tuple, ) @@ -2451,10 +2451,10 @@ def test_sum_get_add() -> None: def test_getset_untyped() -> None: - result: int = 10 df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]}) # Tests that Dataframe.__getitem__ needs to return untyped series. - result = df["x"].max() + # TODO this typecheck is actually bogus as the right part is "Unknown" + result: pd.Series = df["x"].max() def test_getmultiindex_columns() -> None: @@ -2965,7 +2965,9 @@ def sum_mean(x: pd.DataFrame) -> float: pd.Series, ) - lfunc: Callable[[pd.DataFrame], float] = lambda x: x.sum().mean() + def lfunc(x: pd.DataFrame) -> float: + return x.sum().mean() + with pytest_warns_bounded( DeprecationWarning, "DataFrameGroupBy.apply operated on the grouping columns.", diff --git a/tests/test_series.py b/tests/test_series.py index d11ab74db..0a7705319 100644 --- a/tests/test_series.py +++ b/tests/test_series.py @@ -145,20 +145,20 @@ def test_types_all() -> None: def test_types_csv() -> None: s = pd.Series(data=[1, 2, 3]) - csv_df: str = s.to_csv() + check(assert_type(s.to_csv(), str), str) with ensure_clean() as path: s.to_csv(path) - s2: pd.DataFrame = pd.read_csv(path) + check(assert_type(pd.read_csv(path), pd.DataFrame), pd.DataFrame) with ensure_clean() as path: s.to_csv(Path(path)) - s3: pd.DataFrame = pd.read_csv(Path(path)) + check(assert_type(pd.read_csv(Path(path)), pd.DataFrame), pd.DataFrame) # This keyword was added in 1.1.0 https://pandas.pydata.org/docs/whatsnew/v1.1.0.html with ensure_clean() as path: s.to_csv(path, errors="replace") - s4: pd.DataFrame = pd.read_csv(path) + check(assert_type(pd.read_csv(path), pd.DataFrame), pd.DataFrame) def test_types_copy() -> None: @@ -229,11 +229,11 @@ def test_types_boolean_indexing() -> None: def test_types_df_to_df_comparison() -> None: s = pd.Series(data={"col1": [1, 2]}) s2 = pd.Series(data={"col1": [3, 2]}) - res_gt: pd.Series = s > s2 - res_ge: pd.Series = s >= s2 - res_lt: pd.Series = s < s2 - res_le: pd.Series = s <= s2 - res_e: pd.Series = s == s2 + check(assert_type(s > s2, "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(s >= s2, "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(s < s2, "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(s <= s2, "pd.Series[bool]"), pd.Series, np.bool_) + check(assert_type(s == s2, "pd.Series[bool]"), pd.Series, np.bool_) def test_types_head_tail() -> None: @@ -309,7 +309,11 @@ def test_types_drop_multilevel() -> None: codes=[[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 1, 2]], ) s = pd.Series(data=[1, 2, 3, 4, 5, 6], index=index) - res: pd.Series = s.drop(labels="first", level=1) + check( + assert_type(s.drop(labels="first", level=1), "pd.Series[int]"), + pd.Series, + np.int64, + ) def test_types_drop_duplicates() -> None: @@ -382,7 +386,11 @@ def test_types_sort_index() -> None: # This was added in 1.1.0 https://pandas.pydata.org/docs/whatsnew/v1.1.0.html def test_types_sort_index_with_key() -> None: s = pd.Series([1, 2, 3], index=["a", "B", "c"]) - res: pd.Series = s.sort_index(key=lambda k: k.str.lower()) + check( + assert_type(s.sort_index(key=lambda k: k.str.lower()), "pd.Series[int]"), + pd.Series, + np.int64, + ) def test_types_sort_values() -> None: @@ -412,7 +420,11 @@ def test_types_sort_values() -> None: # This was added in 1.1.0 https://pandas.pydata.org/docs/whatsnew/v1.1.0.html def test_types_sort_values_with_key() -> None: s = pd.Series([1, 2, 3], index=[2, 3, 1]) - res: pd.Series = s.sort_values(key=lambda k: -k) + check( + assert_type(s.sort_values(key=lambda k: -k), "pd.Series[int]"), + pd.Series, + np.integer, + ) def test_types_shift() -> None: @@ -435,18 +447,32 @@ def test_types_rank() -> None: def test_types_mean() -> None: s = pd.Series([1, 2, 3, np.nan]) - f1: float = s.mean() - s1: pd.Series = s.groupby(level=0).mean() - f2: float = s.mean(skipna=False) - f3: float = s.mean(numeric_only=False) + check(assert_type(s.mean(), float), float) + check( + assert_type( + s.groupby(level=0).mean(), # pyright: ignore[reportAssertTypeFailure] + "pd.Series[float]", + ), + pd.Series, + float, + ) + check(assert_type(s.mean(skipna=False), float), float) + check(assert_type(s.mean(numeric_only=False), float), float) def test_types_median() -> None: s = pd.Series([1, 2, 3, np.nan]) - f1: float = s.median() - s1: pd.Series = s.groupby(level=0).median() - f2: float = s.median(skipna=False) - f3: float = s.median(numeric_only=False) + check(assert_type(s.median(), float), float) + check( + assert_type( + s.groupby(level=0).median(), # pyright: ignore[reportAssertTypeFailure] + "pd.Series[float]", + ), + pd.Series, + float, + ) + check(assert_type(s.median(skipna=False), float), float) + check(assert_type(s.median(numeric_only=False), float), float) def test_types_sum() -> None: @@ -624,26 +650,36 @@ def test_types_element_wise_arithmetic() -> None: s = pd.Series([0, 1, -10]) s2 = pd.Series([7, -5, 10]) - res_add1: pd.Series = s + s2 - res_add2: pd.Series = s.add(s2, fill_value=0) + 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) - res_sub: pd.Series = s - s2 - res_sub2: pd.Series = s.sub(s2, fill_value=0) + check(assert_type(s - s2, pd.Series), pd.Series, np.integer) + check(assert_type(s.sub(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - res_mul: pd.Series = s * s2 - res_mul2: pd.Series = s.mul(s2, fill_value=0) + check(assert_type(s * s2, pd.Series), pd.Series, np.integer) + check(assert_type(s.mul(s2, fill_value=0), pd.Series), pd.Series, np.integer) - res_div: pd.Series = s / s2 - res_div2: pd.Series = s.div(s2, fill_value=0) + check(assert_type(s / s2, pd.Series), pd.Series, np.float64) + check( + assert_type(s.div(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) - res_floordiv: pd.Series = s // s2 - res_floordiv2: pd.Series = s.floordiv(s2, fill_value=0) + check(assert_type(s // s2, "pd.Series[int]"), pd.Series, np.integer) + check( + assert_type(s.floordiv(s2, fill_value=0), "pd.Series[int]"), + pd.Series, + np.integer, + ) - res_mod: pd.Series = s % s2 - res_mod2: pd.Series = s.mod(s2, fill_value=0) + check(assert_type(s % s2, "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(s.mod(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - res_pow: pd.Series = s ** s2.abs() - res_pow2: pd.Series = s.pow(s2.abs(), fill_value=0) + check(assert_type(s ** s2.abs(), "pd.Series[int]"), pd.Series, np.integer) + check( + assert_type(s.pow(s2.abs(), fill_value=0), "pd.Series[int]"), + pd.Series, + np.integer, + ) check(assert_type(divmod(s, s2), tuple["pd.Series[int]", "pd.Series[int]"]), tuple) @@ -651,36 +687,42 @@ def test_types_element_wise_arithmetic() -> None: def test_types_scalar_arithmetic() -> None: s = pd.Series([0, 1, -10]) - res_add1: pd.Series = s + 1 - res_add2: pd.Series = s.add(1, fill_value=0) + check(assert_type(s + 1, "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(s.add(1, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - res_sub: pd.Series = s - 1 - res_sub2: pd.Series = s.sub(1, fill_value=0) + check(assert_type(s - 1, pd.Series), pd.Series, np.integer) + check(assert_type(s.sub(1, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - res_mul: pd.Series = s * 2 - res_mul2: pd.Series = s.mul(2, fill_value=0) + check(assert_type(s * 2, pd.Series), pd.Series, np.integer) + check(assert_type(s.mul(2, fill_value=0), pd.Series), pd.Series, np.integer) - res_div: pd.Series = s / 2 - res_div2: pd.Series = s.div(2, fill_value=0) + check(assert_type(s / 2, pd.Series), pd.Series, np.float64) + check( + assert_type(s.div(2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) - res_floordiv: pd.Series = s // 2 - res_floordiv2: pd.Series = s.floordiv(2, fill_value=0) + check(assert_type(s // 2, "pd.Series[int]"), pd.Series, np.integer) + check( + assert_type(s.floordiv(2, fill_value=0), "pd.Series[int]"), + pd.Series, + np.integer, + ) - res_mod: pd.Series = s % 2 - res_mod2: pd.Series = s.mod(2, fill_value=0) + check(assert_type(s % 2, "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(s.mod(2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - res_pow: pd.Series = s**2 - res_pow1: pd.Series = s**0 - res_pow2: pd.Series = s**0.213 - res_pow3: pd.Series = s.pow(0.5) + check(assert_type(s**2, "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(s**0, "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(s**0.213, "pd.Series[int]"), pd.Series, np.float64) + check(assert_type(s.pow(0.5), "pd.Series[int]"), pd.Series, np.float64) # GH 103 def test_types_complex_arithmetic() -> None: c = 1 + 1j s = pd.Series([1.0, 2.0, 3.0]) - x = s + c - y = s - c + check(assert_type(s + c, pd.Series), pd.Series) + check(assert_type(s - c, pd.Series), pd.Series) def test_types_groupby() -> None: @@ -1105,8 +1147,8 @@ def test_types_getitem() -> None: s = pd.Series({"key": [0, 1, 2, 3]}) key: list[int] = s["key"] s2 = pd.Series([0, 1, 2, 3]) - value: int = s2[0] - s3: pd.Series = s[:2] + check(assert_type(s2[0], int), np.integer) + check(assert_type(s[:2], pd.Series), pd.Series) def test_types_getitem_by_timestamp() -> None: @@ -1117,9 +1159,9 @@ def test_types_getitem_by_timestamp() -> None: def test_types_eq() -> None: s1 = pd.Series([1, 2, 3]) - res1: pd.Series = s1 == 1 + check(assert_type(s1 == 1, "pd.Series[bool]"), pd.Series, np.bool_) s2 = pd.Series([1, 2, 4]) - res2: pd.Series = s1 == s2 + check(assert_type(s1 == s2, "pd.Series[bool]"), pd.Series, np.bool_) def test_types_rename_axis() -> None: @@ -1317,7 +1359,7 @@ def test_series_multiindex_getitem() -> None: s = pd.Series( [1, 2, 3, 4], index=pd.MultiIndex.from_product([["a", "b"], ["x", "y"]]) ) - s1: pd.Series = s["a", :] + check(assert_type(s["a", :], "pd.Series[int]"), pd.Series, np.integer) def test_series_mul() -> None: @@ -1408,13 +1450,19 @@ def test_cat_accessor() -> None: def test_cat_ctor_values() -> None: - c1 = pd.Categorical(["a", "b", "a"]) + check(assert_type(pd.Categorical(["a", "b", "a"]), pd.Categorical), pd.Categorical) # GH 95 - c2 = pd.Categorical(pd.Series(["a", "b", "a"])) + check( + assert_type(pd.Categorical(pd.Series(["a", "b", "a"])), pd.Categorical), + pd.Categorical, + ) s: Sequence = cast(Sequence, ["a", "b", "a"]) - c3 = pd.Categorical(s) + check(assert_type(pd.Categorical(s), pd.Categorical), pd.Categorical) # GH 107 - c4 = pd.Categorical(np.array([1, 2, 3, 1, 1])) + check( + assert_type(pd.Categorical(np.array([1, 2, 3, 1, 1])), pd.Categorical), + pd.Categorical, + ) def test_iloc_getitem_ndarray() -> None: @@ -2768,7 +2816,7 @@ def test_astype_other() -> None: def test_all_astype_args_tested() -> None: """Check that all relevant numpy type aliases are tested.""" - NUMPY_ALIASES: set[str] = {k for k in np.sctypeDict} + NUMPY_ALIASES: set[str | int] = {k for k in np.sctypeDict} EXCLUDED_ALIASES = { "datetime64", "m", @@ -2889,7 +2937,7 @@ def test_convert_dtypes_dtype_backend() -> None: def test_apply_returns_none() -> None: # GH 557 s = pd.Series([1, 2, 3]) - check(assert_type(s.apply(lambda x: None), pd.Series), pd.Series) + check(assert_type(s.apply(lambda _: None), pd.Series), pd.Series) def test_loc_callable() -> None: From f86df3938eacb76e9363b4620ae0c84d9a352627 Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Fri, 10 Jan 2025 19:56:47 -0500 Subject: [PATCH 02/15] Fix test --- tests/test_frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_frame.py b/tests/test_frame.py index 4d6c138f0..5cfc95ad7 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -2409,7 +2409,7 @@ def test_indexslice_getitem(): ind = pd.Index([2, 3]) check( assert_type( - pd.IndexSlice[ind, :], tuple["pd.Index[int]", "slice[None, None, None]"] # type: ignore[type-arg] + pd.IndexSlice[ind, :], tuple["pd.Index[int]", "slice[None, None, None]"] ), tuple, ) From dc97df1f2e0163c75d2317afd6ea593979150df0 Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Fri, 10 Jan 2025 20:00:49 -0500 Subject: [PATCH 03/15] Fix test --- pandas-stubs/core/frame.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas-stubs/core/frame.pyi b/pandas-stubs/core/frame.pyi index f4bd8e416..aa69c5d80 100644 --- a/pandas-stubs/core/frame.pyi +++ b/pandas-stubs/core/frame.pyi @@ -246,7 +246,7 @@ if sys.version_info >= (3, 12): @overload def __getitem__(self, key: Scalar | tuple[Hashable, ...]) -> Series: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] @overload - def __getitem__( # pyright: ignore[reportOverlappingOverload] + def __getitem__( # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] self, key: Iterable[Hashable] | slice ) -> Self: ... @overload From b8331c8d984e18ef47326f0e6f8614f69d1b3627 Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Tue, 14 Jan 2025 07:27:48 -0500 Subject: [PATCH 04/15] GH1089 PR Feedback --- tests/test_frame.py | 6 ++---- tests/test_series.py | 12 +++++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_frame.py b/tests/test_frame.py index 5cfc95ad7..f9015985b 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -2453,8 +2453,7 @@ def test_sum_get_add() -> None: def test_getset_untyped() -> None: df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]}) # Tests that Dataframe.__getitem__ needs to return untyped series. - # TODO this typecheck is actually bogus as the right part is "Unknown" - result: pd.Series = df["x"].max() + check(assert_type(df["x"].max(), Any), np.integer) def test_getmultiindex_columns() -> None: @@ -2965,8 +2964,7 @@ def sum_mean(x: pd.DataFrame) -> float: pd.Series, ) - def lfunc(x: pd.DataFrame) -> float: - return x.sum().mean() + lfunc: Callable[[pd.DataFrame], float] = lambda x: x.sum().mean() with pytest_warns_bounded( DeprecationWarning, diff --git a/tests/test_series.py b/tests/test_series.py index 0a7705319..e8996eb47 100644 --- a/tests/test_series.py +++ b/tests/test_series.py @@ -312,7 +312,7 @@ def test_types_drop_multilevel() -> None: check( assert_type(s.drop(labels="first", level=1), "pd.Series[int]"), pd.Series, - np.int64, + np.integer, ) @@ -389,7 +389,7 @@ def test_types_sort_index_with_key() -> None: check( assert_type(s.sort_index(key=lambda k: k.str.lower()), "pd.Series[int]"), pd.Series, - np.int64, + np.integer, ) @@ -1147,8 +1147,10 @@ def test_types_getitem() -> None: s = pd.Series({"key": [0, 1, 2, 3]}) key: list[int] = s["key"] s2 = pd.Series([0, 1, 2, 3]) + check(assert_type(s[0], Any), np.integer) check(assert_type(s2[0], int), np.integer) check(assert_type(s[:2], pd.Series), pd.Series) + check(assert_type(s2[:2], "pd.Series[int]"), pd.Series, np.integer) def test_types_getitem_by_timestamp() -> None: @@ -1306,9 +1308,9 @@ def test_types_dot() -> None: s2 = pd.Series([-1, 2, -3, 4]) df1 = pd.DataFrame([[0, 1], [-2, 3], [4, -5], [6, 7]]) n1 = np.array([[0, 1], [1, 2], [-1, -1], [2, 0]]) - check(assert_type(s1.dot(s2), Scalar), np.int64) - check(assert_type(s1 @ s2, Scalar), np.int64) - check(assert_type(s1.dot(df1), "pd.Series[int]"), pd.Series, np.int64) + check(assert_type(s1.dot(s2), Scalar), np.integer) + check(assert_type(s1 @ s2, Scalar), np.integer) + check(assert_type(s1.dot(df1), "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s1 @ df1, pd.Series), pd.Series) check(assert_type(s1.dot(n1), np.ndarray), np.ndarray) check(assert_type(s1 @ n1, np.ndarray), np.ndarray) From 6e496ae495d6919d82b2b8ebd4299c0b08735089 Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Tue, 14 Jan 2025 07:33:20 -0500 Subject: [PATCH 05/15] GH1089 PR Feedback --- tests/test_series.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_series.py b/tests/test_series.py index e8996eb47..7c163e2be 100644 --- a/tests/test_series.py +++ b/tests/test_series.py @@ -1147,7 +1147,6 @@ def test_types_getitem() -> None: s = pd.Series({"key": [0, 1, 2, 3]}) key: list[int] = s["key"] s2 = pd.Series([0, 1, 2, 3]) - check(assert_type(s[0], Any), np.integer) check(assert_type(s2[0], int), np.integer) check(assert_type(s[:2], pd.Series), pd.Series) check(assert_type(s2[:2], "pd.Series[int]"), pd.Series, np.integer) From 3c6f490dfa3d64d5ee4b515fffd9d3a9f1ecdd72 Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Tue, 14 Jan 2025 18:27:16 -0500 Subject: [PATCH 06/15] GH1089 Fix pow operator on Series[int] with float exponent --- pandas-stubs/core/series.pyi | 9 +++++++++ tests/test_series.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 718da55d1..acf5a78c3 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1894,6 +1894,15 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: AxisIndex = ..., ) -> Series[_bool]: ... def nunique(self, dropna: _bool = ...) -> int: ... + @overload + def pow( + self: Series[int], + other: float, + level: Level | None = ..., + fill_value: float | None = ..., + axis: AxisIndex | None = ..., + ) -> Series[float]: ... + @overload def pow( self, other: num | _ListLike | Series[S1], diff --git a/tests/test_series.py b/tests/test_series.py index 7c163e2be..86b277df5 100644 --- a/tests/test_series.py +++ b/tests/test_series.py @@ -714,7 +714,7 @@ def test_types_scalar_arithmetic() -> None: check(assert_type(s**2, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s**0, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s**0.213, "pd.Series[int]"), pd.Series, np.float64) - check(assert_type(s.pow(0.5), "pd.Series[int]"), pd.Series, np.float64) + check(assert_type(s.pow(0.5), "pd.Series[float]"), pd.Series, np.float64) # GH 103 From 01130e1753ac8778d467e438d13c7e4f9419c2a8 Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Wed, 15 Jan 2025 18:29:23 -0500 Subject: [PATCH 07/15] GH1089 Partial typehinting --- pandas-stubs/core/series.pyi | 57 ++++++++++++++++++++++++++++++++++++ tests/test_series.py | 14 +++++---- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 770ad7767..b20c5c118 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1609,6 +1609,11 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __add__(self, other: S1 | Self) -> Self: ... @overload + def __add__( + self, + other: complex, + ) -> Series[complex]: ... + @overload def __add__( self, other: num | _str | timedelta | Timedelta | _ListLike | Series | np.timedelta64, @@ -1716,6 +1721,16 @@ class Series(IndexOpsMixin[S1], NDFrame): self, other: Timestamp | datetime | TimestampSeries ) -> TimedeltaSeries: ... @overload + def __sub__( + self: Series[int], + other: int, + ) -> Series[int]: ... + @overload + def __sub__( + self, + other: complex, + ) -> Series[complex]: ... + @overload def __sub__(self, other: num | _ListLike | Series) -> Series: ... def __truediv__(self, other: num | _ListLike | Series[S1] | Path) -> Series: ... # ignore needed for mypy as we want different results based on the arguments @@ -1742,6 +1757,23 @@ class Series(IndexOpsMixin[S1], NDFrame): @property def loc(self) -> _LocIndexerSeries[S1]: ... # Methods + @overload + def add( + self: Series[int], + other: int, + level: Level | None = ..., + fill_value: float | None = ..., + axis: int = ..., + ) -> Series[int]: ... + @overload + def add( + self, + other: complex, + level: Level | None = ..., + fill_value: float | None = ..., + axis: int = ..., + ) -> Series[complex]: ... + @overload def add( self, other: Series[S1] | Scalar, @@ -2085,6 +2117,31 @@ class Series(IndexOpsMixin[S1], NDFrame): numeric_only: _bool = ..., **kwargs, ) -> float: ... + @overload + def sub( + self: Series[int], + other: int, + level: Level | None = ..., + fill_value: float | None = ..., + axis: AxisIndex | None = ..., + ) -> Series[int]: ... + @overload + def sub( + self: Series[int], + other: float, + level: Level | None = ..., + fill_value: float | None = ..., + axis: AxisIndex | None = ..., + ) -> Series[int]: ... + @overload + def sub( + self, + other: complex, + level: Level | None = ..., + fill_value: float | None = ..., + axis: AxisIndex | None = ..., + ) -> Series[complex]: ... + @overload def sub( self, other: num | _ListLike | Series[S1], diff --git a/tests/test_series.py b/tests/test_series.py index ddbb5e733..4f6c02fb3 100644 --- a/tests/test_series.py +++ b/tests/test_series.py @@ -450,7 +450,7 @@ def test_types_mean() -> None: check(assert_type(s.mean(), float), float) check( assert_type( - s.groupby(level=0).mean(), # pyright: ignore[reportAssertTypeFailure] + s.groupby(level=0).mean(), "pd.Series[float]", ), pd.Series, @@ -465,7 +465,7 @@ def test_types_median() -> None: check(assert_type(s.median(), float), float) check( assert_type( - s.groupby(level=0).median(), # pyright: ignore[reportAssertTypeFailure] + s.groupby(level=0).median(), "pd.Series[float]", ), pd.Series, @@ -690,7 +690,7 @@ def test_types_scalar_arithmetic() -> None: check(assert_type(s + 1, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s.add(1, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - check(assert_type(s - 1, pd.Series), pd.Series, np.integer) + check(assert_type(s - 1, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s.sub(1, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s * 2, pd.Series), pd.Series, np.integer) @@ -721,8 +721,10 @@ def test_types_scalar_arithmetic() -> None: def test_types_complex_arithmetic() -> None: c = 1 + 1j s = pd.Series([1.0, 2.0, 3.0]) - check(assert_type(s + c, pd.Series), pd.Series) - check(assert_type(s - c, pd.Series), pd.Series) + check(assert_type(s + c, "pd.Series[complex]"), pd.Series, complex) + check(assert_type(s.add(c), "pd.Series[complex]"), pd.Series, complex) + check(assert_type(s - c, "pd.Series[complex]"), pd.Series, complex) + check(assert_type(s.sub(c), "pd.Series[complex]"), pd.Series, complex) def test_types_groupby() -> None: @@ -1368,7 +1370,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 4c60287261f3b81b55f13992e20c48d3935438a9 Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Wed, 15 Jan 2025 18:56:43 -0500 Subject: [PATCH 08/15] GH1089 Partial typehinting --- pandas-stubs/core/series.pyi | 36 +++++++++++++++++++++++++++++++++++- tests/test_series.py | 21 +++++++++++++-------- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index b20c5c118..9b5a3ff2d 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -857,7 +857,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def dot(self, other: Series[S1]) -> Scalar: ... @overload - def dot(self, other: DataFrame) -> Series[S1]: ... + def dot(self, other: DataFrame) -> Series: ... @overload def dot( self, other: ArrayLike | dict[_str, np.ndarray] | Sequence[S1] | Index[S1] @@ -1628,6 +1628,11 @@ class Series(IndexOpsMixin[S1], NDFrame): self, other: int | np_ndarray_anyint | Series[int] ) -> Series[int]: ... # def __array__(self, dtype: Optional[_bool] = ...) -> _np_ndarray + @overload + def __div__(self: Series[int], other: Series[int]) -> Series[float]: ... + @overload + def __div__(self: Series[int], other: int) -> Series[float]: ... + @overload def __div__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... def __eq__(self, other: object) -> Series[_bool]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] def __floordiv__(self, other: num | _ListLike | Series[S1]) -> Series[int]: ... @@ -1648,6 +1653,14 @@ class Series(IndexOpsMixin[S1], NDFrame): self, other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64 ) -> TimedeltaSeries: ... @overload + def __mul__(self: Series[int], other: int) -> Series[int]: ... + @overload + def __mul__(self: Series[int], other: Series[int]) -> Series[int]: ... + @overload + def __mul__(self: Series[int], other: Series[float]) -> Series[float]: ... + @overload + def __mul__(self: Series[Any], other: Series[Any]) -> Series: ... + @overload def __mul__(self, other: num | _ListLike | Series) -> Series: ... def __mod__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... def __ne__(self, other: object) -> Series[_bool]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] @@ -1674,6 +1687,11 @@ class Series(IndexOpsMixin[S1], NDFrame): def __rand__( # pyright: ignore[reportIncompatibleMethodOverride] self, other: int | np_ndarray_anyint | Series[int] ) -> Series[int]: ... + @overload + def __rdiv__(self: Series[int], other: int) -> Series[float]: ... + @overload + def __rdiv__(self: Series[int], other: Series[int]) -> Series[float]: ... + @overload 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] def __rfloordiv__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... @@ -1936,6 +1954,22 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: AxisIndex | None = ..., ) -> Series[S1]: ... @overload + def mul( + self: Series[int], + other: Series[int], + level: Level | None = ..., + fill_value: float | None = ..., + axis: AxisIndex | None = ..., + ) -> Series[int]: ... + @overload + def mul( + self: Series[int], + other: Series[float], + level: Level | None = ..., + fill_value: float | None = ..., + axis: AxisIndex | None = ..., + ) -> Series[float]: ... + @overload def mul( self, other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64, diff --git a/tests/test_series.py b/tests/test_series.py index 4f6c02fb3..c8d8402ba 100644 --- a/tests/test_series.py +++ b/tests/test_series.py @@ -656,10 +656,12 @@ def test_types_element_wise_arithmetic() -> None: check(assert_type(s - s2, pd.Series), pd.Series, np.integer) check(assert_type(s.sub(s2, fill_value=0), "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), pd.Series, np.integer) + check(assert_type(s * s2, "pd.Series[int]"), 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), pd.Series, np.float64) + # GH1089 should be the following + # check(assert_type(s / s2, "pd.Series[float]"), pd.Series, np.float64) + check(assert_type(s / s2, "pd.Series"), pd.Series, np.float64) check( assert_type(s.div(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 ) @@ -693,9 +695,11 @@ def test_types_scalar_arithmetic() -> None: check(assert_type(s - 1, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s.sub(1, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - check(assert_type(s * 2, pd.Series), pd.Series, np.integer) + check(assert_type(s * 2, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s.mul(2, fill_value=0), pd.Series), pd.Series, np.integer) + # GH1089 should be + # check(assert_type(s / 2, "pd.Series[float]"), pd.Series, np.float64) check(assert_type(s / 2, pd.Series), pd.Series, np.float64) check( assert_type(s.div(2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 @@ -1311,7 +1315,7 @@ def test_types_dot() -> None: n1 = np.array([[0, 1], [1, 2], [-1, -1], [2, 0]]) check(assert_type(s1.dot(s2), Scalar), np.integer) check(assert_type(s1 @ s2, Scalar), np.integer) - check(assert_type(s1.dot(df1), "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(s1.dot(df1), pd.Series), pd.Series, np.integer) check(assert_type(s1 @ df1, pd.Series), pd.Series) check(assert_type(s1.dot(n1), np.ndarray), np.ndarray) check(assert_type(s1 @ n1, np.ndarray), np.ndarray) @@ -1333,7 +1337,8 @@ def test_series_min_max_sub_axis() -> None: sd = s1 / 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) + # TODO GH1089 This should not match to Series[int] + check(assert_type(sm, pd.Series), pd.Series) # pyright: ignore check(assert_type(sd, pd.Series), pd.Series) @@ -1368,11 +1373,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[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) From 94919e0d7aad5c5dcd12ec52f067a2cf6a56d5ca Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Thu, 16 Jan 2025 07:40:59 -0500 Subject: [PATCH 09/15] GH1089 Partial typehinting --- tests/test_series.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_series.py b/tests/test_series.py index c8d8402ba..656f8d6e9 100644 --- a/tests/test_series.py +++ b/tests/test_series.py @@ -1338,7 +1338,8 @@ def test_series_min_max_sub_axis() -> None: check(assert_type(sa, pd.Series), pd.Series) check(assert_type(ss, pd.Series), pd.Series) # TODO GH1089 This should not match to Series[int] - check(assert_type(sm, pd.Series), pd.Series) # pyright: ignore + # check(assert_type(sm, pd.Series), pd.Series) + check(assert_type(sm, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(sd, pd.Series), pd.Series) From ec20f776f0e6f3a99fcaafd2a19ca1002f7e2b63 Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Thu, 16 Jan 2025 07:53:18 -0500 Subject: [PATCH 10/15] GH1089 Partial typehinting --- tests/test_series.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_series.py b/tests/test_series.py index 656f8d6e9..a373296f3 100644 --- a/tests/test_series.py +++ b/tests/test_series.py @@ -1338,8 +1338,7 @@ def test_series_min_max_sub_axis() -> None: check(assert_type(sa, pd.Series), pd.Series) check(assert_type(ss, pd.Series), pd.Series) # TODO GH1089 This should not match to Series[int] - # check(assert_type(sm, pd.Series), pd.Series) - check(assert_type(sm, "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(sm, pd.Series), pd.Series, np.integer) # pyright: ignore[reportAssertTypeFailure] check(assert_type(sd, pd.Series), pd.Series) From 3e56625e41b78366b8f8d0e70a3016f589fdef96 Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Thu, 16 Jan 2025 20:14:16 -0500 Subject: [PATCH 11/15] GH1089 Partial typehinting --- pandas-stubs/core/series.pyi | 43 ++++++++++++++++++------------------ tests/test_series.py | 26 +++++++++++++--------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 9b5a3ff2d..6850ca4cd 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1628,12 +1628,6 @@ class Series(IndexOpsMixin[S1], NDFrame): self, other: int | np_ndarray_anyint | Series[int] ) -> Series[int]: ... # def __array__(self, dtype: Optional[_bool] = ...) -> _np_ndarray - @overload - def __div__(self: Series[int], other: Series[int]) -> Series[float]: ... - @overload - def __div__(self: Series[int], other: int) -> Series[float]: ... - @overload - def __div__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... def __eq__(self, other: object) -> Series[_bool]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] def __floordiv__(self, other: num | _ListLike | Series[S1]) -> Series[int]: ... def __ge__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] @@ -1649,15 +1643,19 @@ class Series(IndexOpsMixin[S1], NDFrame): self, other: S1 | _ListLike | Series[S1] | datetime | timedelta | date ) -> Series[_bool]: ... @overload + def __mul__( # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] + self, other: S1 | Self + ) -> Self: ... + @overload def __mul__( self, other: timedelta | Timedelta | TimedeltaSeries | np.timedelta64 ) -> TimedeltaSeries: ... @overload - def __mul__(self: Series[int], other: int) -> Series[int]: ... - @overload - def __mul__(self: Series[int], other: Series[int]) -> Series[int]: ... + def __mul__( # pyright: ignore[reportOverlappingOverload] + self: Series[int], other: Series[int] | int + ) -> Series[int]: ... @overload - def __mul__(self: Series[int], other: Series[float]) -> Series[float]: ... + def __mul__(self: Series[int], other: Series[float] | float) -> Series[float]: ... @overload def __mul__(self: Series[Any], other: Series[Any]) -> Series: ... @overload @@ -1687,12 +1685,6 @@ class Series(IndexOpsMixin[S1], NDFrame): def __rand__( # pyright: ignore[reportIncompatibleMethodOverride] self, other: int | np_ndarray_anyint | Series[int] ) -> Series[int]: ... - @overload - def __rdiv__(self: Series[int], other: int) -> Series[float]: ... - @overload - def __rdiv__(self: Series[int], other: Series[int]) -> Series[float]: ... - @overload - 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] def __rfloordiv__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... def __rmod__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... @@ -1741,7 +1733,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __sub__( self: Series[int], - other: int, + other: int | Series[int], ) -> Series[int]: ... @overload def __sub__( @@ -1749,8 +1741,15 @@ class Series(IndexOpsMixin[S1], NDFrame): other: complex, ) -> Series[complex]: ... @overload + def __sub__(self, other: S1 | Self) -> Self: ... + @overload def __sub__(self, other: num | _ListLike | Series) -> Series: ... - def __truediv__(self, other: num | _ListLike | Series[S1] | Path) -> Series: ... + @overload + def __truediv__(self: Series[int], other: Series[int] | int) -> Series[float]: ... + @overload + def __truediv__( + self, other: num | _ListLike | Series[S1] | Path + ) -> Series | Self: ... # ignore needed for mypy as we want different results based on the arguments @overload # type: ignore[override] def __xor__( # pyright: ignore[reportOverlappingOverload] @@ -1956,9 +1955,9 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def mul( self: Series[int], - other: Series[int], + other: Series[int] | int, level: Level | None = ..., - fill_value: float | None = ..., + fill_value: int | None = ..., axis: AxisIndex | None = ..., ) -> Series[int]: ... @overload @@ -2152,7 +2151,7 @@ class Series(IndexOpsMixin[S1], NDFrame): **kwargs, ) -> float: ... @overload - def sub( + def sub( # pyright: ignore[reportOverlappingOverload] self: Series[int], other: int, level: Level | None = ..., @@ -2166,7 +2165,7 @@ class Series(IndexOpsMixin[S1], NDFrame): level: Level | None = ..., fill_value: float | None = ..., axis: AxisIndex | None = ..., - ) -> Series[int]: ... + ) -> Series[float]: ... @overload def sub( self, diff --git a/tests/test_series.py b/tests/test_series.py index a373296f3..cfd9d7681 100644 --- a/tests/test_series.py +++ b/tests/test_series.py @@ -653,15 +653,14 @@ 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) - 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) check(assert_type(s * s2, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s.mul(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) # GH1089 should be the following - # check(assert_type(s / s2, "pd.Series[float]"), pd.Series, np.float64) - check(assert_type(s / s2, "pd.Series"), pd.Series, np.float64) + 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 ) @@ -696,11 +695,9 @@ def test_types_scalar_arithmetic() -> None: check(assert_type(s.sub(1, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s * 2, "pd.Series[int]"), pd.Series, np.integer) - check(assert_type(s.mul(2, fill_value=0), pd.Series), pd.Series, np.integer) + check(assert_type(s.mul(2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - # GH1089 should be - # check(assert_type(s / 2, "pd.Series[float]"), pd.Series, np.float64) - check(assert_type(s / 2, pd.Series), pd.Series, np.float64) + check(assert_type(s / 2, "pd.Series[float]"), pd.Series, np.float64) check( assert_type(s.div(2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 ) @@ -1312,10 +1309,12 @@ def test_types_dot() -> None: s1 = pd.Series([0, 1, 2, 3]) s2 = pd.Series([-1, 2, -3, 4]) df1 = pd.DataFrame([[0, 1], [-2, 3], [4, -5], [6, 7]]) + df2 = pd.DataFrame([[0.0, 1.0], [-2.0, 3.0], [4.0, -5.0], [6.0, 7.0]]) n1 = np.array([[0, 1], [1, 2], [-1, -1], [2, 0]]) check(assert_type(s1.dot(s2), Scalar), np.integer) check(assert_type(s1 @ s2, Scalar), np.integer) check(assert_type(s1.dot(df1), pd.Series), pd.Series, np.integer) + check(assert_type(s1.dot(df2), pd.Series), pd.Series, np.float64) check(assert_type(s1 @ df1, pd.Series), pd.Series) check(assert_type(s1.dot(n1), np.ndarray), np.ndarray) check(assert_type(s1 @ n1, np.ndarray), np.ndarray) @@ -1336,10 +1335,15 @@ def test_series_min_max_sub_axis() -> None: sm = s1 * s2 sd = s1 / s2 check(assert_type(sa, pd.Series), pd.Series) - check(assert_type(ss, pd.Series), pd.Series) - # TODO GH1089 This should not match to Series[int] - check(assert_type(sm, pd.Series), pd.Series, np.integer) # pyright: ignore[reportAssertTypeFailure] - check(assert_type(sd, pd.Series), pd.Series) + check( + assert_type(ss, pd.Series), # pyright: ignore[reportAssertTypeFailure] + pd.Series, + ) + check(assert_type(sm, pd.Series), pd.Series) + check( + assert_type(sd, pd.Series), # pyright: ignore[reportAssertTypeFailure] + pd.Series, + ) def test_series_index_isin() -> None: From fcd829f5053e5e0c035f23e9b0d27483584457c4 Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Fri, 17 Jan 2025 18:28:33 -0500 Subject: [PATCH 12/15] GH1089 Partial typehinting --- pandas-stubs/core/series.pyi | 146 +++++++++++++++++++++--- tests/test_series.py | 51 +-------- tests/test_series_arithmetic.py | 191 ++++++++++++++++++++++++++++++++ 3 files changed, 327 insertions(+), 61 deletions(-) create mode 100644 tests/test_series_arithmetic.py diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index 6850ca4cd..c8d412e41 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1609,6 +1609,16 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __add__(self, other: S1 | Self) -> Self: ... @overload + def __add__( + self: Series[int] | Series[float], + other: float | Series[float], + ) -> Series[float]: ... + @overload + def __add__( + self: Series[float], + other: int | Series[int], + ) -> Series[float]: ... + @overload def __add__( self, other: complex, @@ -1629,6 +1639,9 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[int]: ... # def __array__(self, dtype: Optional[_bool] = ...) -> _np_ndarray def __eq__(self, other: object) -> Series[_bool]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] + @overload + def __floordiv__(self: Series[float], other: Series[float] | Series[int] | float) -> Series[float]: ... # type: ignore[overload-overlap] + @overload def __floordiv__(self, other: num | _ListLike | Series[S1]) -> Series[int]: ... def __ge__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] self, other: S1 | _ListLike | Series[S1] | datetime | timedelta | date @@ -1657,11 +1670,29 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __mul__(self: Series[int], other: Series[float] | float) -> Series[float]: ... @overload + def __mul__(self: Series[float], other: Series[int] | int) -> Series[float]: ... + @overload def __mul__(self: Series[Any], other: Series[Any]) -> Series: ... @overload def __mul__(self, other: num | _ListLike | Series) -> Series: ... + @overload + def __mod__( # pyright: ignore[reportOverlappingOverload] + self: Series[int], other: int | Series[int] + ) -> Series[int]: ... + @overload + def __mod__(self: Series[int], other: float | Series[float]) -> Series[float]: ... + @overload + def __mod__( + self: Series[float], other: float | Series[int] | Series[float] + ) -> Series[float]: ... + @overload def __mod__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... def __ne__(self, other: object) -> Series[_bool]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] + @overload + def __pow__(self: Series[int], other: Series[int] | int) -> Series[int]: ... + @overload + def __pow__(self, other: Series[float] | float) -> Series[float]: ... + @overload def __pow__(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] @@ -1686,7 +1717,12 @@ class Series(IndexOpsMixin[S1], NDFrame): self, other: int | np_ndarray_anyint | Series[int] ) -> Series[int]: ... 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]: ... + @overload + def __rfloordiv__(self: Series[float], other: float | Series[int] | Series[float]) -> Series[float]: ... # type: ignore[misc] + @overload + def __rfloordiv__( + self, other: num | _ListLike | Series[float] + ) -> Series[float]: ... def __rmod__(self, other: num | _ListLike | Series[S1]) -> Series[S1]: ... @overload def __rmul__( @@ -1731,25 +1767,35 @@ class Series(IndexOpsMixin[S1], NDFrame): self, other: Timestamp | datetime | TimestampSeries ) -> TimedeltaSeries: ... @overload - def __sub__( + def __sub__(self, other: S1 | Self) -> Self: ... + @overload + def __sub__( # pyright: ignore[reportOverlappingOverload] self: Series[int], other: int | Series[int], ) -> Series[int]: ... @overload + def __sub__( + self: Series[int], + other: float | Series[float], + ) -> Series[float]: ... + @overload + def __sub__( + self: Series[float], + other: int | Series[int], + ) -> Series[float]: ... + @overload def __sub__( self, other: complex, ) -> Series[complex]: ... @overload - def __sub__(self, other: S1 | Self) -> Self: ... - @overload def __sub__(self, other: num | _ListLike | Series) -> Series: ... @overload def __truediv__(self: Series[int], other: Series[int] | int) -> Series[float]: ... @overload - def __truediv__( - self, other: num | _ListLike | Series[S1] | Path - ) -> Series | Self: ... + def __truediv__(self, other: Path) -> Series: ... + @overload + def __truediv__(self, other: num | _ListLike | Series[S1]) -> Series[float]: ... # ignore needed for mypy as we want different results based on the arguments @overload # type: ignore[override] def __xor__( # pyright: ignore[reportOverlappingOverload] @@ -1773,7 +1819,7 @@ class Series(IndexOpsMixin[S1], NDFrame): def iloc(self) -> _iLocIndexerSeries[S1]: ... @property def loc(self) -> _LocIndexerSeries[S1]: ... - # Methods + # Met @overload @overload def add( self: Series[int], @@ -1783,6 +1829,22 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = ..., ) -> Series[int]: ... @overload + def add( # pyright: ignore[reportOverlappingOverload] + self, + other: float | Series[float], + level: Level | None = ..., + fill_value: float | None = ..., + axis: int = ..., + ) -> Series[float]: ... + @overload + def add( + self: Series[float], + other: int | Series[int], + level: Level | None = ..., + fill_value: float | None = ..., + axis: int = ..., + ) -> Series[float]: ... + @overload def add( self, other: complex, @@ -1798,6 +1860,14 @@ class Series(IndexOpsMixin[S1], NDFrame): fill_value: float | None = ..., axis: int = ..., ) -> Series[S1]: ... + @overload + def add( + self, + other: S1 | Self, + level: Level | None = ..., + fill_value: float | None = ..., + axis: int = ..., + ) -> Series: ... def all( self, axis: AxisIndex = ..., @@ -1861,13 +1931,30 @@ class Series(IndexOpsMixin[S1], NDFrame): min_periods: int = ..., method: CalculationMethod = ..., ) -> Expanding[Series]: ... + @overload + def floordiv( # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] + self: Series[int], + other: Series[int] | int, + level: Level | None = ..., + fill_value: float | None = ..., + axis: AxisIndex | None = ..., + ) -> Series[int]: ... + @overload + def floordiv( + self: Series[int] | Series[float], + other: num | _ListLike | Series[S1], + level: Level | None = ..., + fill_value: float | None = ..., + axis: AxisIndex | None = ..., + ) -> Series[float]: ... + @overload def floordiv( self, other: num | _ListLike | Series[S1], level: Level | None = ..., fill_value: float | None = ..., axis: AxisIndex | None = ..., - ) -> Series[int]: ... + ) -> Series[S1]: ... def ge( self, other: Scalar | Series[S1], @@ -1945,6 +2032,31 @@ class Series(IndexOpsMixin[S1], NDFrame): numeric_only: _bool = ..., **kwargs, ) -> S1: ... + @overload + def mod( # pyright: ignore[reportOverlappingOverload] + self: Series[int], + other: Series[int] | int, + level: Level | None = ..., + fill_value: float | None = ..., + axis: AxisIndex | None = ..., + ) -> Series[int]: ... + @overload + def mod( + self: Series[int], + other: Series[float] | float, + level: Level | None = ..., + fill_value: float | None = ..., + axis: AxisIndex | None = ..., + ) -> Series[float]: ... + @overload + def mod( + self: Series[float], + other: Series[int] | Series[float] | float, + level: Level | None = ..., + fill_value: float | None = ..., + axis: AxisIndex | None = ..., + ) -> Series[float]: ... + @overload def mod( self, other: num | _ListLike | Series[S1], @@ -1953,7 +2065,7 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: AxisIndex | None = ..., ) -> Series[S1]: ... @overload - def mul( + def mul( # pyright: ignore[reportOverlappingOverload] self: Series[int], other: Series[int] | int, level: Level | None = ..., @@ -1961,9 +2073,17 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: AxisIndex | None = ..., ) -> Series[int]: ... @overload + def mul( + self: Series[float], + other: Series[int] | Series[float] | int, + level: Level | None = ..., + fill_value: float | None = ..., + axis: AxisIndex | None = ..., + ) -> Series[float]: ... + @overload def mul( self: Series[int], - other: Series[float], + other: Series[float] | float, level: Level | None = ..., fill_value: float | None = ..., axis: AxisIndex | None = ..., @@ -2002,7 +2122,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def pow( self: Series[int], - other: float, + other: float | Series[float], level: Level | None = ..., fill_value: float | None = ..., axis: AxisIndex | None = ..., @@ -2161,7 +2281,7 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[int], - other: float, + other: float | Series[float], level: Level | None = ..., fill_value: float | None = ..., axis: AxisIndex | None = ..., diff --git a/tests/test_series.py b/tests/test_series.py index cfd9d7681..36966318c 100644 --- a/tests/test_series.py +++ b/tests/test_series.py @@ -646,45 +646,6 @@ def get_depth(url: str) -> int: check(assert_type(s.apply(lambda x: pd.NA), pd.Series), pd.Series, NAType) -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) - - check(assert_type(s * s2, "pd.Series[int]"), pd.Series, np.integer) - check(assert_type(s.mul(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - - # GH1089 should be the following - 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 - ) - - check(assert_type(s // s2, "pd.Series[int]"), pd.Series, np.integer) - check( - assert_type(s.floordiv(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.mod(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - - check(assert_type(s ** s2.abs(), "pd.Series[int]"), pd.Series, np.integer) - check( - assert_type(s.pow(s2.abs(), fill_value=0), "pd.Series[int]"), - pd.Series, - np.integer, - ) - - check(assert_type(divmod(s, s2), tuple["pd.Series[int]", "pd.Series[int]"]), tuple) - - def test_types_scalar_arithmetic() -> None: s = pd.Series([0, 1, -10]) @@ -714,7 +675,7 @@ def test_types_scalar_arithmetic() -> None: check(assert_type(s**2, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s**0, "pd.Series[int]"), pd.Series, np.integer) - check(assert_type(s**0.213, "pd.Series[int]"), pd.Series, np.float64) + check(assert_type(s**0.213, "pd.Series[float]"), pd.Series, np.float64) check(assert_type(s.pow(0.5), "pd.Series[float]"), pd.Series, np.float64) @@ -1335,15 +1296,9 @@ def test_series_min_max_sub_axis() -> None: sm = s1 * s2 sd = s1 / s2 check(assert_type(sa, pd.Series), pd.Series) - check( - assert_type(ss, pd.Series), # pyright: ignore[reportAssertTypeFailure] - pd.Series, - ) + check(assert_type(ss, pd.Series), pd.Series) check(assert_type(sm, pd.Series), pd.Series) - check( - assert_type(sd, pd.Series), # pyright: ignore[reportAssertTypeFailure] - pd.Series, - ) + check(assert_type(sd, "pd.Series[float]"), pd.Series) def test_series_index_isin() -> None: diff --git a/tests/test_series_arithmetic.py b/tests/test_series_arithmetic.py new file mode 100644 index 000000000..5f3de54af --- /dev/null +++ b/tests/test_series_arithmetic.py @@ -0,0 +1,191 @@ +"""Test module for arithmetic operations on Series.""" + +from typing import assert_type + +import numpy as np +import pandas as pd + +from tests import check + + +def test_element_wise_int_int() -> 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) + + check(assert_type(s * s2, "pd.Series[int]"), pd.Series, np.integer) + check(assert_type(s.mul(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) + + # GH1089 should be the following + 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 + ) + + check(assert_type(s // s2, "pd.Series[int]"), pd.Series, np.integer) + check( + assert_type(s.floordiv(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.mod(s2, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) + + check(assert_type(s ** s2.abs(), "pd.Series[int]"), pd.Series, np.integer) + check( + assert_type(s.pow(s2.abs(), fill_value=0), "pd.Series[int]"), + pd.Series, + np.integer, + ) + + check(assert_type(divmod(s, s2), tuple["pd.Series[int]", "pd.Series[int]"]), tuple) + + +def test_element_wise_int_float() -> None: + s = pd.Series([0, 1, -10]) + s2 = pd.Series([7.0, -5.5, 10.4]) + + check(assert_type(s + s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.add(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) + + check(assert_type(s - s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.sub(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) + + check(assert_type(s * s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.mul(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) + + # GH1089 should be the following + 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 + ) + + check( + assert_type(s.floordiv(s2, fill_value=0), "pd.Series[float]"), + pd.Series, + np.float64, + ) + + check(assert_type(s % s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.mod(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) + + check(assert_type(s ** s2.abs(), "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.pow(s2.abs(), fill_value=0), "pd.Series[float]"), + pd.Series, + np.float64, + ) + + check(assert_type(divmod(s, s2), tuple["pd.Series[int]", "pd.Series[int]"]), tuple) + + +def test_element_wise_float_int() -> None: + s = pd.Series([0.0, 1.4, -10.25]) + s2 = pd.Series([7, -5, 10]) + + check(assert_type(s + s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.add(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) + + check(assert_type(s - s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.sub(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) + + check(assert_type(s * s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.mul(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) + + # GH1089 should be the following + 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 + ) + + check(assert_type(s // s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.floordiv(s2, fill_value=0), "pd.Series[float]"), + pd.Series, + np.float64, + ) + + check(assert_type(s % s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.mod(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) + + check(assert_type(s ** s2.abs(), "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.pow(s2.abs(), fill_value=0), "pd.Series[float]"), + pd.Series, + np.float64, + ) + + check( + assert_type(divmod(s, s2), tuple["pd.Series[float]", "pd.Series[float]"]), tuple + ) + + +def test_element_wise_float_float() -> None: + s = pd.Series([0.439, 1.43829, -10.432]) + s2 = pd.Series([7.4389, -5.543, 10.432]) + + check(assert_type(s + s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.add(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) + + check(assert_type(s - s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.sub(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) + + check(assert_type(s * s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.mul(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) + + # GH1089 should be the following + 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 + ) + + check(assert_type(s // s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.floordiv(s2, fill_value=0), "pd.Series[float]"), + pd.Series, + np.float64, + ) + + check(assert_type(s % s2, "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.mod(s2, fill_value=0), "pd.Series[float]"), pd.Series, np.float64 + ) + + check(assert_type(s ** s2.abs(), "pd.Series[float]"), pd.Series, np.float64) + check( + assert_type(s.pow(s2.abs(), fill_value=0), "pd.Series[float]"), + pd.Series, + np.float64, + ) + + check( + assert_type(divmod(s, s2), tuple["pd.Series[float]", "pd.Series[float]"]), tuple + ) From 5ee1dcc166b06eba72ae093225aefe1d3a18cad9 Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Fri, 17 Jan 2025 20:46:56 -0500 Subject: [PATCH 13/15] GH1089 Fix import --- tests/test_series_arithmetic.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_series_arithmetic.py b/tests/test_series_arithmetic.py index 5f3de54af..846b50be4 100644 --- a/tests/test_series_arithmetic.py +++ b/tests/test_series_arithmetic.py @@ -1,9 +1,8 @@ """Test module for arithmetic operations on Series.""" -from typing import assert_type - import numpy as np import pandas as pd +from typing_extensions import assert_type from tests import check From 41f019b56605650d8d8e00b1c13a674fc2ab4f3f Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Sat, 18 Jan 2025 11:23:38 -0500 Subject: [PATCH 14/15] GH1089 Fix import --- pandas-stubs/core/series.pyi | 12 ++++++++++-- tests/test_series_arithmetic.py | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index c8d412e41..a23225a95 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1954,7 +1954,7 @@ class Series(IndexOpsMixin[S1], NDFrame): level: Level | None = ..., fill_value: float | None = ..., axis: AxisIndex | None = ..., - ) -> Series[S1]: ... + ) -> Self: ... def ge( self, other: Scalar | Series[S1], @@ -2065,6 +2065,14 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: AxisIndex | None = ..., ) -> Series[S1]: ... @overload + def mul( # type: ignore[overload-overlap] + self, + other: Series[S1] | Self, + level: Level | None = ..., + fill_value: int | None = ..., + axis: AxisIndex | None = ..., + ) -> Self: ... + @overload def mul( # pyright: ignore[reportOverlappingOverload] self: Series[int], other: Series[int] | int, @@ -2103,7 +2111,7 @@ class Series(IndexOpsMixin[S1], NDFrame): level: Level | None = ..., fill_value: float | None = ..., axis: AxisIndex | None = ..., - ) -> Series: ... + ) -> Self: ... def multiply( self, other: num | _ListLike | Series[S1], diff --git a/tests/test_series_arithmetic.py b/tests/test_series_arithmetic.py index 846b50be4..f54c17ccf 100644 --- a/tests/test_series_arithmetic.py +++ b/tests/test_series_arithmetic.py @@ -1,5 +1,7 @@ """Test module for arithmetic operations on Series.""" +from typing import cast + import numpy as np import pandas as pd from typing_extensions import assert_type @@ -188,3 +190,21 @@ def test_element_wise_float_float() -> None: check( assert_type(divmod(s, s2), tuple["pd.Series[float]", "pd.Series[float]"]), tuple ) + + +def test_element_wise_int_unknown() -> None: + s = cast(pd.Series, pd.Series([7, -5, 10])) + s2 = pd.Series([0, 1, -105]) + + check(assert_type(s + s2, pd.Series), pd.Series) + check(assert_type(s.add(s2, fill_value=0), "pd.Series[float]"), pd.Series) + + check(assert_type(s - s2, pd.Series), pd.Series) + check(assert_type(s.sub(s2, fill_value=0), pd.Series), pd.Series) + + check(assert_type(s * s2, pd.Series), pd.Series) + check(assert_type(s.mul(s2, fill_value=0), pd.Series), pd.Series) + + # GH1089 should be the following + check(assert_type(s / s2, "pd.Series[float]"), pd.Series) + check(assert_type(s.div(s2, fill_value=0), "pd.Series[float]"), pd.Series) From 2971f0144641369f513c337d457a62e784bc8b93 Mon Sep 17 00:00:00 2001 From: Loic Diridollou Date: Thu, 6 Feb 2025 09:47:59 +0100 Subject: [PATCH 15/15] GH1089 Add more tests --- pandas-stubs/core/series.pyi | 32 ++++++++----- tests/test_series_arithmetic.py | 79 +++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 16 deletions(-) diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index a23225a95..8eeb225c0 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -1822,15 +1822,23 @@ class Series(IndexOpsMixin[S1], NDFrame): # Met @overload @overload def add( + self, + other: S1 | Self, + level: Level | None = ..., + fill_value: float | None = ..., + axis: int = ..., + ) -> Self: ... + @overload + def add( # pyright: ignore[reportOverlappingOverload] self: Series[int], - other: int, + other: Series[int] | int, level: Level | None = ..., fill_value: float | None = ..., axis: int = ..., ) -> Series[int]: ... @overload - def add( # pyright: ignore[reportOverlappingOverload] - self, + def add( + self: Series[int] | Series[float], other: float | Series[float], level: Level | None = ..., fill_value: float | None = ..., @@ -1846,7 +1854,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series[float]: ... @overload def add( - self, + self: Series[complex], other: complex, level: Level | None = ..., fill_value: float | None = ..., @@ -1860,14 +1868,6 @@ class Series(IndexOpsMixin[S1], NDFrame): fill_value: float | None = ..., axis: int = ..., ) -> Series[S1]: ... - @overload - def add( - self, - other: S1 | Self, - level: Level | None = ..., - fill_value: float | None = ..., - axis: int = ..., - ) -> Series: ... def all( self, axis: AxisIndex = ..., @@ -2279,6 +2279,14 @@ class Series(IndexOpsMixin[S1], NDFrame): **kwargs, ) -> float: ... @overload + def sub( + self, + other: S1 | Self, + level: Level | None = ..., + fill_value: float | None = ..., + axis: int = ..., + ) -> Self: ... + @overload def sub( # pyright: ignore[reportOverlappingOverload] self: Series[int], other: int, diff --git a/tests/test_series_arithmetic.py b/tests/test_series_arithmetic.py index f54c17ccf..59b728da3 100644 --- a/tests/test_series_arithmetic.py +++ b/tests/test_series_arithmetic.py @@ -1,7 +1,5 @@ """Test module for arithmetic operations on Series.""" -from typing import cast - import numpy as np import pandas as pd from typing_extensions import assert_type @@ -193,11 +191,12 @@ def test_element_wise_float_float() -> None: def test_element_wise_int_unknown() -> None: - s = cast(pd.Series, pd.Series([7, -5, 10])) + df = pd.DataFrame({"a": [7, -5, 10]}) + s = df["a"] s2 = pd.Series([0, 1, -105]) check(assert_type(s + s2, pd.Series), pd.Series) - check(assert_type(s.add(s2, fill_value=0), "pd.Series[float]"), pd.Series) + check(assert_type(s.add(s2, fill_value=0), pd.Series), pd.Series) check(assert_type(s - s2, pd.Series), pd.Series) check(assert_type(s.sub(s2, fill_value=0), pd.Series), pd.Series) @@ -208,3 +207,75 @@ def test_element_wise_int_unknown() -> None: # GH1089 should be the following check(assert_type(s / s2, "pd.Series[float]"), pd.Series) check(assert_type(s.div(s2, fill_value=0), "pd.Series[float]"), pd.Series) + + +def test_element_wise_unknown_int() -> None: + df = pd.DataFrame({"a": [7, -5, 10]}) + s = pd.Series([0, 1, -105]) + s2 = df["a"] + + check(assert_type(s + s2, pd.Series), pd.Series) + check(assert_type(s.add(s2, fill_value=0), pd.Series), pd.Series) + + check(assert_type(s - s2, pd.Series), pd.Series) + check(assert_type(s.sub(s2, fill_value=0), pd.Series), pd.Series) + + check(assert_type(s * s2, pd.Series), pd.Series) + check(assert_type(s.mul(s2, fill_value=0), pd.Series), pd.Series) + + check(assert_type(s / s2, "pd.Series[float]"), pd.Series) + check(assert_type(s.div(s2, fill_value=0), "pd.Series[float]"), pd.Series) + + +def test_element_wise_unknown_unknown() -> None: + df = pd.DataFrame({"a": [7, -5, 10]}) + s = df["a"] + s2 = df["a"] + + check(assert_type(s + s2, pd.Series), pd.Series) + check(assert_type(s.add(s2, fill_value=0), pd.Series), pd.Series) + + check(assert_type(s - s2, pd.Series), pd.Series) + check(assert_type(s.sub(s2, fill_value=0), pd.Series), pd.Series) + + check(assert_type(s * s2, pd.Series), pd.Series) + check(assert_type(s.mul(s2, fill_value=0), pd.Series), pd.Series) + + check(assert_type(s / s2, "pd.Series[float]"), pd.Series) + check(assert_type(s.div(s2, fill_value=0), "pd.Series[float]"), pd.Series) + + +def test_element_wise_float_unknown() -> None: + df = pd.DataFrame({"a": [7, -5, 10]}) + s = pd.Series([1.3, 2.5, 4.5]) + s2 = df["a"] + + check(assert_type(s + s2, pd.Series), pd.Series) + check(assert_type(s.add(s2, fill_value=0), pd.Series), pd.Series) + + check(assert_type(s - s2, pd.Series), pd.Series) + check(assert_type(s.sub(s2, fill_value=0), pd.Series), pd.Series) + + check(assert_type(s * s2, pd.Series), pd.Series) + check(assert_type(s.mul(s2, fill_value=0), pd.Series), pd.Series) + + check(assert_type(s / s2, "pd.Series[float]"), pd.Series) + check(assert_type(s.div(s2, fill_value=0), "pd.Series[float]"), pd.Series) + + +def test_element_wise_unknown_float() -> None: + df = pd.DataFrame({"a": [7, -5, 10]}) + s = df["a"] + s2 = pd.Series([1.3, 2.5, 4.5]) + + check(assert_type(s + s2, pd.Series), pd.Series) + check(assert_type(s.add(s2, fill_value=0), pd.Series), pd.Series) + + check(assert_type(s - s2, pd.Series), pd.Series) + check(assert_type(s.sub(s2, fill_value=0), pd.Series), pd.Series) + + check(assert_type(s * s2, pd.Series), pd.Series) + check(assert_type(s.mul(s2, fill_value=0), pd.Series), pd.Series) + + check(assert_type(s / s2, "pd.Series[float]"), pd.Series) + check(assert_type(s.div(s2, fill_value=0), "pd.Series[float]"), pd.Series)