Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
2 changes: 2 additions & 0 deletions pandas-stubs/_typing.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ from pandas._libs.tslibs import (
Timedelta,
Timestamp,
)
from pandas._libs.tslibs.nattype import NaTType

from pandas.core.dtypes.dtypes import (
CategoricalDtype,
Expand Down Expand Up @@ -134,6 +135,7 @@ _IndexIterScalar: TypeAlias = (
Scalar: TypeAlias = (
_IndexIterScalar | complex | np.integer | np.floating | np.complexfloating
)
ScalarOrNA: TypeAlias = Scalar | NAType | NaTType | None
IntStrT = TypeVar("IntStrT", int, str)

# timestamp and timedelta convertible types
Expand Down
192 changes: 39 additions & 153 deletions pandas-stubs/core/frame.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ import xarray as xr
from pandas._libs.lib import _NoDefaultDoNotUse
from pandas._libs.missing import NAType
from pandas._libs.tslibs import BaseOffset
from pandas._libs.tslibs.nattype import NaTType
from pandas._typing import (
S2,
AggFuncTypeBase,
Expand Down Expand Up @@ -146,6 +145,7 @@ from pandas._typing import (
Renamer,
ReplaceValue,
Scalar,
ScalarOrNA,
ScalarT,
SequenceNotStr,
SeriesByT,
Expand Down Expand Up @@ -181,6 +181,26 @@ _T_MUTABLE_MAPPING_co = TypeVar(
"_T_MUTABLE_MAPPING_co", bound=MutableMapping, covariant=True
)

_iLocSetItemKey: TypeAlias = (
int
| IndexType
| tuple[int, int]
| tuple[IndexType, int]
| tuple[IndexType, IndexType]
| tuple[int, IndexType]
)
_LocSetItemKey: TypeAlias = (
MaskType | Hashable | _IndexSliceTuple | Iterable[Scalar] | IndexingInt | slice
)
_SetItemValueNotDataFrame: TypeAlias = (
ScalarOrNA
| Sequence[ScalarOrNA]
| Sequence[Sequence[ScalarOrNA]]
| Mapping[Any, ScalarOrNA]
| ArrayLike
| IndexOpsMixin
)

class _iLocIndexerFrame(_iLocIndexer, Generic[_T]):
@overload
def __getitem__(self, key: tuple[int, int]) -> Scalar: ...
Expand All @@ -203,26 +223,7 @@ class _iLocIndexerFrame(_iLocIndexer, Generic[_T]):

# Keep in sync with `DataFrame.__setitem__`
def __setitem__(
self,
key: (
int
| IndexType
| tuple[int, int]
| tuple[IndexType, int]
| tuple[IndexType, IndexType]
| tuple[int, IndexType]
),
value: (
Scalar
| IndexOpsMixin
| Sequence[Scalar]
| DataFrame
| np_ndarray
| NAType
| NaTType
| Mapping[Hashable, Scalar | NAType | NaTType]
| None
),
self, key: _iLocSetItemKey, value: _SetItemValueNotDataFrame | DataFrame
) -> None: ...

class _LocIndexerFrame(_LocIndexer, Generic[_T]):
Expand Down Expand Up @@ -283,52 +284,16 @@ class _LocIndexerFrame(_LocIndexer, Generic[_T]):
# Keep in sync with `DataFrame.__setitem__`
@overload
def __setitem__(
self,
key: tuple[_IndexSliceTuple, Hashable],
value: (
Scalar
| NAType
| NaTType
| ArrayLike
| IndexOpsMixin
| Sequence[Scalar]
| Sequence[Sequence[Scalar]]
| Mapping[Hashable, Scalar | NAType | NaTType]
| None
),
self, key: tuple[_IndexSliceTuple, Hashable], value: _SetItemValueNotDataFrame
) -> None: ...
@overload
def __setitem__(
self,
key: (
MaskType
| Hashable
| _IndexSliceTuple
| Iterable[Scalar]
| IndexingInt
| slice
),
value: (
Scalar
| NAType
| NaTType
| ArrayLike
| IndexOpsMixin
| Sequence[Scalar]
| Sequence[Sequence[Scalar]]
| DataFrame
| Mapping[Hashable, Scalar | NAType | NaTType]
| None
),
self, key: _LocSetItemKey, value: _SetItemValueNotDataFrame | DataFrame
) -> None: ...

class _iAtIndexerFrame(_iAtIndexer):
def __getitem__(self, key: tuple[int, int]) -> Scalar: ...
def __setitem__(
self,
key: tuple[int, int],
value: Scalar | NAType | NaTType | None,
) -> None: ...
def __setitem__(self, key: tuple[int, int], value: ScalarOrNA) -> None: ...

class _AtIndexerFrame(_AtIndexer):
def __getitem__(
Expand All @@ -347,42 +312,26 @@ class _AtIndexerFrame(_AtIndexer):
key: (
MaskType | StrLike | _IndexSliceTuple | list[ScalarT] | IndexingInt | slice
),
value: (
Scalar
| NAType
| NaTType
| ArrayLike
| IndexOpsMixin
| DataFrame
| Sequence[Scalar]
| Sequence[Sequence[Scalar]]
| Mapping[Hashable, Scalar | NAType | NaTType]
| None
),
value: _SetItemValueNotDataFrame | DataFrame,
) -> None: ...

# With mypy 1.14.1 and python 3.12, the second overload needs a type-ignore statement
if sys.version_info >= (3, 12):
class _GetItemHack:
@overload
def __getitem__(self, key: Scalar | tuple[Hashable, ...]) -> Series: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload]
# With python 3.12+, the second overload needs a type-ignore statement
class _GetItemHack:
@overload
def __getitem__(self, key: Scalar | tuple[Hashable, ...]) -> Series: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload]
if sys.version_info >= (3, 12):
@overload
def __getitem__( # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload]
self, key: Iterable[Hashable] | slice
) -> Self: ...
@overload
def __getitem__(self, key: Hashable) -> Series: ...

else:
class _GetItemHack:
@overload
def __getitem__(self, key: Scalar | tuple[Hashable, ...]) -> Series: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload]
else:
@overload
def __getitem__( # pyright: ignore[reportOverlappingOverload]
self, key: Iterable[Hashable] | slice
) -> Self: ...
@overload
def __getitem__(self, key: Hashable) -> Series: ...

@overload
def __getitem__(self, key: Hashable) -> Series: ...

_AstypeArgExt: TypeAlias = (
AstypeArg
Expand Down Expand Up @@ -829,85 +778,22 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack):
# Keep in sync with `_iLocIndexerFrame.__setitem__`
@overload
def __setitem__(
self,
idx: (
int
| IndexType
| tuple[int, int]
| tuple[IndexType, int]
| tuple[IndexType, IndexType]
| tuple[int, IndexType]
),
value: (
Scalar
| IndexOpsMixin
| Sequence[Scalar]
| DataFrame
| np_ndarray
| NAType
| NaTType
| Mapping[Hashable, Scalar | NAType | NaTType]
| None
),
self, idx: _iLocSetItemKey, value: _SetItemValueNotDataFrame | DataFrame
) -> None: ...
# Keep in sync with `_LocIndexerFrame.__setitem__`
@overload
def __setitem__(
self,
idx: tuple[_IndexSliceTuple, Hashable],
value: (
Scalar
| NAType
| NaTType
| ArrayLike
| IndexOpsMixin
| Sequence[Scalar]
| Sequence[Sequence[Scalar]]
| Mapping[Hashable, Scalar | NAType | NaTType]
| None
),
self, idx: tuple[_IndexSliceTuple, Hashable], value: _SetItemValueNotDataFrame
) -> None: ...
@overload
def __setitem__(
self,
idx: (
MaskType
| Hashable
| _IndexSliceTuple
| Iterable[Scalar]
| IndexingInt
| slice
),
value: (
Scalar
| NAType
| NaTType
| ArrayLike
| IndexOpsMixin
| Sequence[Scalar]
| Sequence[Sequence[Scalar]]
| DataFrame
| Mapping[Hashable, Scalar | NAType | NaTType]
| None
),
self, idx: _LocSetItemKey, value: _SetItemValueNotDataFrame | DataFrame
) -> None: ...
# Extra cases not supported by `_LocIndexerFrame.__setitem__` /
# `_iLocIndexerFrame.__setitem__`.
@overload
def __setitem__(
self,
idx: IndexOpsMixin | DataFrame,
value: (
Scalar
| NAType
| NaTType
| ArrayLike
| IndexOpsMixin
| Sequence[Scalar]
| Sequence[Sequence[Scalar]]
| Mapping[Hashable, Scalar | NAType | NaTType]
| None
),
self, idx: IndexOpsMixin | DataFrame, value: _SetItemValueNotDataFrame
) -> None: ...
@overload
def query(
Expand Down
22 changes: 11 additions & 11 deletions tests/frame/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,17 +365,6 @@ def test_isetframe() -> None:
check(assert_type(frame.isetitem([0], [10, 12]), None), type(None))


def test_setitem_none() -> None:
df = pd.DataFrame(
{"A": [1, 2, 3], "B": ["abc", "def", "ghi"]}, index=["x", "y", "z"]
)
df.loc["x", "B"] = None
df.iloc[2, 0] = None
sb = pd.Series([1, 2, 3], dtype=int)
sb.loc["y"] = None
sb.iloc[0] = None


def test_getsetitem_multiindex() -> None:
# GH 466
rows = pd.Index(["project A", "project B", "project C"])
Expand Down Expand Up @@ -422,6 +411,14 @@ def test_frame_setitem_na() -> None:
df.loc[ind, :] = pd.NaT
df.iloc[[0, 2], :] = pd.NaT

df.loc["a", "x"] = None
df.iloc[2, 0] = None

df.loc[:, "x"] = [None, pd.NA, pd.NaT]
df.iloc[:, 0] = [None, pd.NA, pd.NaT]
df.loc[:, ["x"]] = [[None], [pd.NA], [pd.NaT]] # type: ignore[assignment,index]
df.iloc[:, [0]] = [[None], [pd.NA], [pd.NaT]] # type: ignore[assignment]


def test_loc_set() -> None:
df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
Expand Down Expand Up @@ -571,6 +568,9 @@ def test_df_loc_dict() -> None:
df.iloc[0] = {"X": 0}
check(assert_type(df, pd.DataFrame), pd.DataFrame)

df.loc[0] = {None: None, pd.NA: pd.NA, pd.NaT: pd.NaT}
df.iloc[0] = {None: None, pd.NA: pd.NA, pd.NaT: pd.NaT}


def test_iloc_npint() -> None:
# GH 69
Expand Down
4 changes: 4 additions & 0 deletions tests/series/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ def test_series_setitem_na() -> None:
s2.loc[ind] = pd.NaT
s2.iloc[[0, 2]] = pd.NaT

sb = pd.Series([1, 2, 3], dtype=int)
sb.loc["y"] = None
sb.iloc[0] = None


def test_slice_timestamp() -> None:
dti = pd.date_range("1/1/2025", "2/28/2025")
Expand Down