Skip to content

Commit cca96ca

Browse files
TYP: GH1654 concat / pivot_table and plotting fix for 3.0 (#1720)
* GH1654 Concat/Pivot_table and plotting fix for 3.0 * GH1654 Series.map supports engine * GH1654 Add passing a series to json_normalize * GH1654 PR Feedback * GH1654 PR Feedback
1 parent 757fad4 commit cca96ca

11 files changed

Lines changed: 147 additions & 6 deletions

File tree

pandas-stubs/core/frame.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,7 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack):
13641364
margins_name: _str = "All",
13651365
observed: _bool = True,
13661366
sort: _bool = True,
1367+
**kwargs: Any,
13671368
) -> Self: ...
13681369
def stack(
13691370
self,
@@ -1563,6 +1564,7 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack):
15631564
self,
15641565
func: Callable[..., Any],
15651566
na_action: Literal["ignore"] | None = None,
1567+
engine: Any = None,
15661568
**kwargs: Any,
15671569
) -> Self: ...
15681570
def join(

pandas-stubs/core/reshape/concat.pyi

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ def concat(
4040
sort: bool = False,
4141
) -> Never: ...
4242
@overload
43+
def concat(
44+
objs: Iterable[NDFrame | None] | Mapping[HashableT1, NDFrame | None],
45+
*,
46+
axis: Axis = 0,
47+
join: Literal["inner", "outer"] = "outer",
48+
ignore_index: Literal[True],
49+
keys: Iterable[HashableT2],
50+
levels: Sequence[list[HashableT3] | tuple[HashableT3, ...]] | None = None,
51+
names: list[HashableT4] | None = None,
52+
verify_integrity: bool = False,
53+
sort: bool = False,
54+
) -> Never: ...
55+
@overload
4356
def concat( # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload]
4457
objs: Iterable[Series[S2] | None] | Mapping[HashableT1, Series[S2] | None],
4558
*,

pandas-stubs/core/reshape/pivot.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def pivot_table(
6161
margins_name: Hashable = "All",
6262
observed: bool = True,
6363
sort: bool = True,
64+
**kwargs: Any,
6465
) -> DataFrame: ...
6566

6667
# Can only use Index or ndarray when index or columns is a Grouper
@@ -78,6 +79,7 @@ def pivot_table(
7879
margins_name: Hashable = "All",
7980
observed: bool = True,
8081
sort: bool = True,
82+
**kwargs: Any,
8183
) -> DataFrame: ...
8284
@overload
8385
def pivot_table(
@@ -93,6 +95,7 @@ def pivot_table(
9395
margins_name: Hashable = "All",
9496
observed: bool = True,
9597
sort: bool = True,
98+
**kwargs: Any,
9699
) -> DataFrame: ...
97100
def pivot(
98101
data: DataFrame,

pandas-stubs/core/series.pyi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,39 +1116,45 @@ class Series(IndexOpsMixin[S1], ElementOpsMixin[S1], NDFrame):
11161116
self,
11171117
func: Callable[Concatenate[S1, ...], S2 | NAType],
11181118
na_action: Literal["ignore"],
1119+
engine: Callable[..., Any] | None = None,
11191120
**kwargs: Any,
11201121
) -> Series[S2]: ...
11211122
@overload
11221123
def map(
11231124
self,
11241125
func: Mapping[S1, S2] | Series[S2],
11251126
na_action: Literal["ignore"],
1127+
engine: None = None,
11261128
) -> Series[S2]: ...
11271129
@overload
11281130
def map(
11291131
self,
11301132
func: Callable[Concatenate[S1 | NAType, ...], S2 | NAType],
11311133
na_action: None = None,
1134+
engine: Callable[..., Any] | None = None,
11321135
**kwargs: Any,
11331136
) -> Series[S2]: ...
11341137
@overload
11351138
def map(
11361139
self,
11371140
func: Mapping[S1, S2] | Series[S2],
11381141
na_action: None = None,
1142+
engine: None = None,
11391143
) -> Series[S2]: ...
11401144
@overload
11411145
def map(
11421146
self,
11431147
func: Callable[..., Any],
11441148
na_action: Literal["ignore"] | None = None,
1149+
engine: Callable[..., Any] | None = None,
11451150
**kwargs: Any,
11461151
) -> Series: ...
11471152
@overload
11481153
def map(
11491154
self,
11501155
func: Mapping[Any, Any] | Series,
11511156
na_action: Literal["ignore"] | None = None,
1157+
engine: None = None,
11521158
) -> Series: ...
11531159
@overload
11541160
def aggregate(

pandas-stubs/core/window/ewm.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ class ExponentialMovingWindow(BaseWindow[NDFrameT]):
5454
**kwargs: Any,
5555
) -> Series: ...
5656
@overload
57-
def aggregate( # ty: ignore[invalid-method-override] # pyright: ignore[reportIncompatibleMethodOverride]
57+
def aggregate( # pyright: ignore[reportIncompatibleMethodOverride]
5858
self: BaseWindow[DataFrame],
5959
func: str,
6060
*args: Any,
6161
**kwargs: Any,
6262
) -> DataFrame: ...
63-
agg = aggregate # type: ignore[assignment] # ty: ignore[invalid-method-override] # pyrefly: ignore[bad-override]
63+
agg = aggregate # type: ignore[assignment] # pyrefly: ignore[bad-override]
6464

6565
class ExponentialMovingWindowGroupby(
6666
BaseWindowGroupby[NDFrameT], ExponentialMovingWindow[NDFrameT]

pandas-stubs/io/json/_normalize.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
from collections.abc import Iterable
12
from typing import Any
23

34
from pandas import DataFrame
5+
from pandas.core.series import Series
46

57
from pandas._typing import IgnoreRaise
68

79
def json_normalize(
8-
data: dict[str, Any] | list[dict[str, Any]],
10+
data: dict[str, Any] | Iterable[dict[str, Any]] | Series,
911
record_path: str | list[str] | None = None,
1012
meta: str | list[str | list[str]] | None = None,
1113
meta_prefix: str | None = None,

pandas-stubs/plotting/_core.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ class PlotAccessor:
468468
| Callable[[gaussian_kde], float]
469469
| None
470470
) = ...,
471+
weights: np_ndarray_float | Series[float] | None = None,
471472
ind: np_ndarray_float | int | None = ...,
472473
*,
473474
subplots: Literal[False] | None = ...,
@@ -482,6 +483,7 @@ class PlotAccessor:
482483
| Callable[[gaussian_kde], float]
483484
| None
484485
) = ...,
486+
weights: np_ndarray_float | Series[float] | None = None,
485487
ind: np_ndarray_float | int | None = ...,
486488
*,
487489
subplots: Literal[True],

tests/frame/test_frame.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,25 @@ def test_types_pivot_table() -> None:
15271527
pd.DataFrame,
15281528
)
15291529

1530+
# test passing kwargs for the aggfunc to pivot_table
1531+
df = pd.DataFrame(
1532+
{
1533+
"A": ["good", "bad", "good", "bad", "good"],
1534+
"B": ["one", "two", "one", "three", "two"],
1535+
"X": [2, 5, 4, 20, 10],
1536+
}
1537+
)
1538+
1539+
check(
1540+
assert_type(
1541+
pd.pivot_table(
1542+
df, index="A", columns="B", values="X", aggfunc="std", ddof=2
1543+
),
1544+
pd.DataFrame,
1545+
),
1546+
pd.DataFrame,
1547+
)
1548+
15301549

15311550
def test_pivot_table_aggfunc_string_reduction(sample_df: pd.DataFrame) -> None:
15321551
"""Test string aggfunc with reduction functions from ReductionKernelType."""

tests/series/test_series.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2783,6 +2783,50 @@ def func(x: int, y: int) -> int:
27832783
)
27842784

27852785

2786+
def test_map_engine() -> None:
2787+
s: pd.Series[int] = pd.Series([1, 2, 3])
2788+
2789+
def to_str(x: int) -> str:
2790+
return str(x)
2791+
2792+
# engine=None with na_action="ignore": func takes S1, returns S2
2793+
check(
2794+
assert_type(s.map(to_str, na_action="ignore", engine=None), "pd.Series[str]"),
2795+
pd.Series,
2796+
str,
2797+
)
2798+
2799+
# engine=None with na_action=None: func must handle NAType
2800+
def nullable_to_str(x: int | NAType) -> str | NAType:
2801+
return str(x) if isinstance(x, int) else x
2802+
2803+
check(
2804+
assert_type(
2805+
s.map(nullable_to_str, na_action=None, engine=None), "pd.Series[str]"
2806+
),
2807+
pd.Series,
2808+
str,
2809+
)
2810+
2811+
# Mapping/Series: engine must be None (omitted or explicit)
2812+
mapping = {1: "a", 2: "b", 3: "c"}
2813+
check(
2814+
assert_type(s.map(mapping, engine=None), "pd.Series[str]"),
2815+
pd.Series,
2816+
str,
2817+
)
2818+
2819+
if TYPE_CHECKING:
2820+
# engine accepts any callable (e.g. a JIT decorator like numba.jit).
2821+
# Cannot be tested at runtime: pandas validates __pandas_udf__ on the engine.
2822+
def jit_decorator(f: Callable[..., Any]) -> Callable[..., Any]:
2823+
return f
2824+
2825+
assert_type(
2826+
s.map(to_str, na_action="ignore", engine=jit_decorator), "pd.Series[str]"
2827+
)
2828+
2829+
27862830
def test_case_when() -> None:
27872831
c = pd.Series([6, 7, 8, 9], name="c")
27882832
a = pd.Series([0, 0, 1, 2])

tests/test_pandas.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,15 @@ def test_concat_args() -> None:
403403
pd.DataFrame,
404404
)
405405

406+
if TYPE_CHECKING_INVALID_USAGE:
407+
408+
def _cannot_pass_ignore_index_eq_true_and_keys() -> ( # pyright: ignore[reportUnusedFunction]
409+
None
410+
):
411+
assert_type(
412+
pd.concat([df, df2], ignore_index=True, keys=["df1", "df2"]), Never
413+
)
414+
406415

407416
def test_frame_subclass_concat() -> None:
408417
"""Test concatenate subclass of DataFrame GH1396."""
@@ -445,13 +454,37 @@ def test_types_json_normalize() -> None:
445454
),
446455
pd.DataFrame,
447456
)
457+
458+
# iterable case
459+
check(
460+
assert_type(pd.json_normalize(data=(dic for dic in data1)), pd.DataFrame),
461+
pd.DataFrame,
462+
)
463+
448464
data2: dict[str, Any] = {"name": {"given": "Mose", "family": "Regner"}}
449465
check(
450466
assert_type(data2, dict[str, Any]),
451467
dict,
452468
)
453469
check(assert_type(pd.json_normalize(data=data2), pd.DataFrame), pd.DataFrame)
454470

471+
# series case
472+
data = [
473+
{
474+
"id": 1,
475+
"name": "Cole Volk",
476+
"fitness": {"height": 130, "weight": 60},
477+
},
478+
{"name": "Mark Reg", "fitness": {"height": 130, "weight": 60}},
479+
{
480+
"id": 2,
481+
"name": "Faye Raker",
482+
"fitness": {"height": 130, "weight": 60},
483+
},
484+
]
485+
series = pd.Series(data, index=pd.Index(["a", "b", "c"]))
486+
check(assert_type(pd.json_normalize(series), pd.DataFrame), pd.DataFrame)
487+
455488

456489
def test_isna() -> None:
457490
# https://github.com/pandas-dev/pandas-stubs/issues/264
@@ -2128,6 +2161,24 @@ def g(x: pd.Series) -> int:
21282161
index=idx,
21292162
)
21302163

2164+
df = pd.DataFrame(
2165+
{
2166+
"A": ["good", "bad", "good", "bad", "good"],
2167+
"B": ["one", "two", "one", "three", "two"],
2168+
"X": [2, 5, 4, 20, 10],
2169+
}
2170+
)
2171+
2172+
check(
2173+
assert_type(
2174+
pd.pivot_table(
2175+
df, index="A", columns="B", values="X", aggfunc="std", ddof=2
2176+
),
2177+
pd.DataFrame,
2178+
),
2179+
pd.DataFrame,
2180+
)
2181+
21312182

21322183
def test_pivot_table_aggfunc_string_reduction(sample_df: pd.DataFrame) -> None:
21332184
"""Test string aggfunc with reduction functions from ReductionKernelType."""

0 commit comments

Comments
 (0)