Skip to content

TYP: libinterval #41059

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
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
89 changes: 89 additions & 0 deletions pandas/_libs/interval.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import numpy as np

from pandas import (
Timedelta,
Timestamp,
)

VALID_CLOSED: frozenset[str]


class IntervalMixin:
closed: str

@property
def closed_left(self) -> bool: ...

@property
def closed_right(self) -> bool: ...

@property
def open_left(self) -> bool: ...

@property
def open_right(self) -> bool: ...

@property
def mid(self): ...

@property
def length(self): ...

@property
def is_empty(self): ...

def _check_closed_matches(self, other, name: str = ...) -> None: ...


class Interval(IntervalMixin):
left: int | float | Timestamp | Timedelta
right: int | float | Timestamp | Timedelta

def __init__(self, left, right, closed: str = ...): ...

def __contains__(self, key) -> bool: ...
def __str__(self) -> str: ...
def __add__(self, y): ...
def __sub__(self, y): ...
def __mul__(self, y): ...
def __truediv__(self, y): ...
def __floordiv__(self, y): ...

def overlaps(self, other: Interval) -> bool: ...


def intervals_to_interval_bounds(
intervals: np.ndarray,
validate_closed: bool = ...,
) -> tuple[np.ndarray, np.ndarray, str]: ...


class IntervalTree(IntervalMixin):
def __init__(self, left, right, closed=..., leaf_size=...): ...

@property
def left_sorter(self) -> np.ndarray: ... # np.ndarray[np.intp]

@property
def right_sorter(self) -> np.ndarray: ... # np.ndarray[np.intp]

@property
def is_overlapping(self) -> bool: ...

@property
def is_monotonic_increasing(self) -> bool: ...

def get_indexer(
self,
target: np.ndarray, # scalar_t[:]
) -> np.ndarray: ... # np.ndarray[np.intp]

def get_indexer_non_unique(
self,
target: np.ndarray, # scalar_t[:]
) -> tuple[
np.ndarray, # np.ndarray[np.intp]
np.ndarray, # np.ndarray[np.intp]
]: ...

def clear_mapping(self) -> None: ...
14 changes: 13 additions & 1 deletion pandas/_libs/tslibs/nattype.pyi
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@

from datetime import datetime
from datetime import (
datetime,
timedelta,
)

import numpy as np

from pandas._libs.tslibs.period import Period

NaT: NaTType
iNaT: int
nat_strings: set[str]
Expand Down Expand Up @@ -133,3 +138,10 @@ class NaTType(datetime):
# inject Period properties
@property
def qyear(self) -> float: ...

def __eq__(self, other: NaTType | datetime | timedelta | Period) -> bool: ...
def __ne__(self, other: NaTType | datetime | timedelta | Period) -> bool: ...
def __lt__(self, other: NaTType | datetime | timedelta | Period) -> bool: ...
def __le__(self, other: NaTType | datetime | timedelta | Period) -> bool: ...
def __gt__(self, other: NaTType | datetime | timedelta | Period) -> bool: ...
def __ge__(self, other: NaTType | datetime | timedelta | Period) -> bool: ...
50 changes: 42 additions & 8 deletions pandas/core/arrays/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
)
import textwrap
from typing import (
TYPE_CHECKING,
Generic,
Sequence,
TypeVar,
cast,
Expand Down Expand Up @@ -85,7 +87,14 @@
unpack_zerodim_and_defer,
)

if TYPE_CHECKING:
from pandas.core.arrays import (
DatetimeArray,
TimedeltaArray,
)

IntervalArrayT = TypeVar("IntervalArrayT", bound="IntervalArray")
S = TypeVar("S", np.ndarray, "DatetimeArray", "TimedeltaArray")

_interval_shared_docs: dict[str, str] = {}

Expand Down Expand Up @@ -187,11 +196,15 @@
),
}
)
class IntervalArray(IntervalMixin, ExtensionArray):
class IntervalArray(IntervalMixin, ExtensionArray, Generic[S]):
ndim = 1
can_hold_na = True
_na_value = _fill_value = np.nan

_dtype: IntervalDtype
_left: S
_right: S

# ---------------------------------------------------------------------
# Constructors

Expand Down Expand Up @@ -587,7 +600,12 @@ def _validate(self):
"location both left and right sides"
)
raise ValueError(msg)
if not (self._left[left_mask] <= self._right[left_mask]).all():
# error: Item "bool" of "Union[Any, bool]" has no attribute "all"
if not ( # type: ignore[union-attr]
# error: Unsupported operand types for <= ("Timestamp" and "Timedelta")
self._left[left_mask] # type: ignore[operator]
<= self._right[left_mask]
).all():
msg = "left side of interval must be <= right side"
raise ValueError(msg)

Expand Down Expand Up @@ -931,9 +949,9 @@ def shift(
from pandas import Index

fill_value = Index(self._left, copy=False)._na_value
empty = IntervalArray.from_breaks([fill_value] * (empty_len + 1))
empty = type(self).from_breaks([fill_value] * (empty_len + 1))
else:
empty = self._from_sequence([fill_value] * empty_len)
empty = type(self)._from_sequence([fill_value] * empty_len)

if periods > 0:
a = empty
Expand Down Expand Up @@ -1362,15 +1380,31 @@ def is_non_overlapping_monotonic(self) -> bool:
# at a point when both sides of intervals are included
if self.closed == "both":
return bool(
(self._right[:-1] < self._left[1:]).all()
or (self._left[:-1] > self._right[1:]).all()
# error: Item "bool" of "Union[Any, bool]" has no attribute "all"
# error: Unsupported operand types for > ("Timedelta" and "Timestamp")
( # type: ignore[union-attr]
self._right[:-1] < self._left[1:] # type: ignore[operator]
).all()
# error: Item "bool" of "Union[Any, bool]" has no attribute "all"
# error: Unsupported operand types for > ("Timedelta" and "Timestamp")
or ( # type: ignore[union-attr]
self._left[:-1] > self._right[1:] # type: ignore[operator]
).all()
)

# non-strict inequality when closed != 'both'; at least one side is
# not included in the intervals, so equality does not imply overlapping
return bool(
(self._right[:-1] <= self._left[1:]).all()
or (self._left[:-1] >= self._right[1:]).all()
# error: Item "bool" of "Union[Any, bool]" has no attribute "all"
# error: Unsupported operand types for <= ("Timestamp" and "Timedelta")
( # type: ignore[union-attr]
self._right[:-1] <= self._left[1:] # type: ignore[operator]
).all()
# error: Item "bool" of "Union[Any, bool]" has no attribute "all"
# error: Unsupported operand types for >= ("Timedelta" and "Timestamp")
or ( # type: ignore[union-attr]
self._left[:-1] >= self._right[1:] # type: ignore[operator]
).all()
)

# ---------------------------------------------------------------------
Expand Down
4 changes: 1 addition & 3 deletions pandas/core/arrays/string_.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,7 @@ def __init__(self, values, copy=False):
values = extract_array(values)

super().__init__(values, copy=copy)
# error: Incompatible types in assignment (expression has type "StringDtype",
# variable has type "PandasDtype")
self._dtype = StringDtype() # type: ignore[assignment]
self._dtype = StringDtype()
if not isinstance(values, type(self)):
self._validate()

Expand Down
3 changes: 2 additions & 1 deletion pandas/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1125,7 +1125,8 @@ def _memory_usage(self, deep: bool = False) -> int:
return v

@doc(
algorithms.factorize,
# error: Cannot determine type of 'factorize'
algorithms.factorize, # type: ignore[has-type]
values="",
order="",
size_hint="",
Expand Down
6 changes: 6 additions & 0 deletions pandas/core/computation/pytables.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,12 @@ def __repr__(self) -> str:

def evaluate(self):
""" create and return the numexpr condition and filter """
if self.terms is None:
raise ValueError(
f"cannot process expression [{self.expr}], [{self}] "
"is not a valid condition"
)

try:
self.condition = self.terms.prune(ConditionBinOp)
except AttributeError as err:
Expand Down
9 changes: 6 additions & 3 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,8 @@ class DatetimeIndex(DatetimeTimedeltaMixin):
# --------------------------------------------------------------------
# methods that dispatch to DatetimeArray and wrap result

@doc(DatetimeArray.strftime)
# error: Cannot determine type of 'strftime'
@doc(DatetimeArray.strftime) # type: ignore[has-type]
def strftime(self, date_format) -> Index:
arr = self._data.strftime(date_format)
return Index(arr, name=self.name)
Expand All @@ -278,12 +279,14 @@ def tz_convert(self, tz) -> DatetimeIndex:
arr = self._data.tz_convert(tz)
return type(self)._simple_new(arr, name=self.name)

@doc(DatetimeArray.tz_localize)
# error: Cannot determine type of 'tz_localize'
@doc(DatetimeArray.tz_localize) # type: ignore[has-type]
def tz_localize(self, tz, ambiguous="raise", nonexistent="raise") -> DatetimeIndex:
arr = self._data.tz_localize(tz, ambiguous, nonexistent)
return type(self)._simple_new(arr, name=self.name)

@doc(DatetimeArray.to_period)
# error: Cannot determine type of 'to_period'
@doc(DatetimeArray.to_period) # type: ignore[has-type]
def to_period(self, freq=None) -> PeriodIndex:
from pandas.core.indexes.api import PeriodIndex

Expand Down
9 changes: 7 additions & 2 deletions pandas/core/indexes/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,9 @@ def __getitem__(self, key):
return type(self)(result, name=self._name)
# Unpack to ndarray for MPL compat

result = result._ndarray
# error: Item "IntervalArray" of "Union[Any, IntervalArray,
# NDArrayBackedExtensionArray]" has no attribute "_ndarray"
result = result._ndarray # type: ignore[union-attr]

# Includes cases where we get a 2D ndarray back for MPL compat
deprecate_ndim_indexing(result)
Expand Down Expand Up @@ -400,8 +402,11 @@ class NDArrayBackedExtensionIndex(ExtensionIndex):

_data: NDArrayBackedExtensionArray

# Argument 1 of "_simple_new" is incompatible with supertype "ExtensionIndex";
# supertype defines the argument type as
# "Union[IntervalArray, NDArrayBackedExtensionArray]"
@classmethod
def _simple_new(
def _simple_new( # type: ignore[override]
cls,
values: NDArrayBackedExtensionArray,
name: Hashable = None,
Expand Down
10 changes: 7 additions & 3 deletions pandas/core/indexes/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ class IntervalIndex(ExtensionIndex):
is_non_overlapping_monotonic: bool
closed_left: bool
closed_right: bool
open_left: bool
open_right: bool

# we would like our indexing holder to defer to us
_defer_to_indexing = True
Expand Down Expand Up @@ -442,7 +444,8 @@ def inferred_type(self) -> str:
"""Return a string of the type inferred from the values"""
return "interval"

@Appender(Index.memory_usage.__doc__)
# error: Cannot determine type of 'memory_usage'
@Appender(Index.memory_usage.__doc__) # type: ignore[has-type]
def memory_usage(self, deep: bool = False) -> int:
# we don't use an explicit engine
# so return the bytes here
Expand Down Expand Up @@ -587,8 +590,9 @@ def _maybe_convert_i8(self, key):
# convert left/right and reconstruct
left = self._maybe_convert_i8(key.left)
right = self._maybe_convert_i8(key.right)
constructor = Interval if scalar else IntervalIndex.from_arrays
return constructor(left, right, closed=self.closed)
if scalar:
return Interval(left, right, closed=self.closed)
return IntervalIndex.from_arrays(left, right, closed=self.closed)

if scalar:
# Timestamp/Timedelta
Expand Down
3 changes: 2 additions & 1 deletion pandas/core/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1242,7 +1242,8 @@ def f(level):

return any(f(level) for level in self._inferred_type_levels)

@doc(Index.memory_usage)
# error: Cannot determine type of 'memory_usage'
@doc(Index.memory_usage) # type: ignore[has-type]
def memory_usage(self, deep: bool = False) -> int:
# we are overwriting our base class to avoid
# computing .values here which could materialize
Expand Down
12 changes: 8 additions & 4 deletions pandas/core/indexes/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,9 @@ class PeriodIndex(DatetimeIndexOpsMixin):
# methods that dispatch to array and wrap result in Index
# These are defined here instead of via inherit_names for mypy

# error: Cannot determine type of 'asfreq'
@doc(
PeriodArray.asfreq,
PeriodArray.asfreq, # type: ignore[has-type]
other="pandas.arrays.PeriodArray",
other_name="PeriodArray",
**_shared_doc_kwargs,
Expand All @@ -191,21 +192,24 @@ def to_timestamp(self, freq=None, how="start") -> DatetimeIndex:
# https://github.com/python/mypy/issues/1362
# error: Decorated property not supported
@property # type:ignore[misc]
@doc(PeriodArray.hour.fget)
# error: Cannot determine type of 'hour'
@doc(PeriodArray.hour.fget) # type: ignore[has-type]
def hour(self) -> Int64Index:
return Int64Index(self._data.hour, name=self.name)

# https://github.com/python/mypy/issues/1362
# error: Decorated property not supported
@property # type:ignore[misc]
@doc(PeriodArray.minute.fget)
# error: Cannot determine type of 'minute'
@doc(PeriodArray.minute.fget) # type: ignore[has-type]
def minute(self) -> Int64Index:
return Int64Index(self._data.minute, name=self.name)

# https://github.com/python/mypy/issues/1362
# error: Decorated property not supported
@property # type:ignore[misc]
@doc(PeriodArray.second.fget)
# error: Cannot determine type of 'second'
@doc(PeriodArray.second.fget) # type: ignore[has-type]
def second(self) -> Int64Index:
return Int64Index(self._data.second, name=self.name)

Expand Down
3 changes: 2 additions & 1 deletion pandas/core/indexes/range.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,8 @@ def tolist(self) -> list[int]:
def __iter__(self):
yield from self._range

@doc(Int64Index._shallow_copy)
# error: Cannot determine type of '_shallow_copy'
@doc(Int64Index._shallow_copy) # type: ignore[has-type]
def _shallow_copy(self, values, name: Hashable = no_default):
name = self.name if name is no_default else name

Expand Down
Loading