From a82f0fc196849f9985feb265deb7f409a63d6d53 Mon Sep 17 00:00:00 2001 From: junheng Date: Sat, 4 Mar 2023 13:01:13 -0500 Subject: [PATCH 1/7] added changes to array with pre-commit run --- pandas/core/arrays/_ranges.py | 6 ++++- pandas/core/arrays/arrow/dtype.py | 17 ++++++------- pandas/core/arrays/arrow/extension_types.py | 6 +++-- pandas/core/arrays/base.py | 26 +++++++++----------- pandas/core/arrays/boolean.py | 12 ++++----- pandas/core/arrays/categorical.py | 25 ++++++++++--------- pandas/core/arrays/datetimes.py | 15 ++++++------ pandas/core/arrays/numeric.py | 11 +++++---- pandas/core/arrays/numpy_.py | 18 ++++++++------ pandas/core/arrays/period.py | 10 +++----- pandas/core/arrays/sparse/array.py | 27 +++++++++++---------- pandas/core/arrays/sparse/dtype.py | 11 +++++---- pandas/core/arrays/sparse/scipy_sparse.py | 12 ++++----- pandas/core/arrays/string_.py | 12 ++++----- pandas/core/arrays/string_arrow.py | 21 ++++++++++++---- pandas/core/arrays/timedeltas.py | 15 ++++++------ pyproject.toml | 1 - 17 files changed, 131 insertions(+), 114 deletions(-) diff --git a/pandas/core/arrays/_ranges.py b/pandas/core/arrays/_ranges.py index c93fc94685358..f2582cd6f2e58 100644 --- a/pandas/core/arrays/_ranges.py +++ b/pandas/core/arrays/_ranges.py @@ -4,6 +4,8 @@ """ from __future__ import annotations +from typing import TYPE_CHECKING + import numpy as np from pandas._libs.lib import i8max @@ -14,7 +16,9 @@ Timestamp, iNaT, ) -from pandas._typing import npt + +if TYPE_CHECKING: + from pandas._typing import npt def generate_regular_range( diff --git a/pandas/core/arrays/arrow/dtype.py b/pandas/core/arrays/arrow/dtype.py index b6515c2875718..bb5c0aad99b33 100644 --- a/pandas/core/arrays/arrow/dtype.py +++ b/pandas/core/arrays/arrow/dtype.py @@ -8,6 +8,7 @@ ) from decimal import Decimal import re +from typing import TYPE_CHECKING import numpy as np @@ -15,11 +16,6 @@ Timedelta, Timestamp, ) -from pandas._typing import ( - TYPE_CHECKING, - DtypeObj, - type_t, -) from pandas.compat import pa_version_under7p0 from pandas.util._decorators import cache_readonly @@ -32,6 +28,11 @@ import pyarrow as pa if TYPE_CHECKING: + from pandas._typing import ( + DtypeObj, + type_t, + ) + from pandas.core.arrays.arrow import ArrowExtensionArray @@ -108,11 +109,7 @@ def type(self): return float elif pa.types.is_string(pa_type): return str - elif ( - pa.types.is_binary(pa_type) - or pa.types.is_fixed_size_binary(pa_type) - or pa.types.is_large_binary(pa_type) - ): + elif pa.types.is_binary(pa_type): return bytes elif pa.types.is_boolean(pa_type): return bool diff --git a/pandas/core/arrays/arrow/extension_types.py b/pandas/core/arrays/arrow/extension_types.py index 25f597af5e3cf..05b70afc2e24c 100644 --- a/pandas/core/arrays/arrow/extension_types.py +++ b/pandas/core/arrays/arrow/extension_types.py @@ -1,13 +1,15 @@ from __future__ import annotations import json +from typing import TYPE_CHECKING import pyarrow -from pandas._typing import IntervalClosedType - from pandas.core.arrays.interval import VALID_CLOSED +if TYPE_CHECKING: + from pandas._typing import IntervalClosedType + class ArrowPeriodType(pyarrow.ExtensionType): def __init__(self, freq) -> None: diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index 1a082a7579dc3..4adc09be2f09d 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -25,20 +25,6 @@ import numpy as np from pandas._libs import lib -from pandas._typing import ( - ArrayLike, - AstypeArg, - AxisInt, - Dtype, - FillnaOptions, - PositionalIndexer, - ScalarIndexer, - SequenceIndexer, - Shape, - SortKind, - TakeIndexer, - npt, -) from pandas.compat import set_function_name from pandas.compat.numpy import function as nv from pandas.errors import AbstractMethodError @@ -90,8 +76,20 @@ if TYPE_CHECKING: from pandas._typing import ( + ArrayLike, + AstypeArg, + AxisInt, + Dtype, + FillnaOptions, NumpySorter, NumpyValueArrayLike, + PositionalIndexer, + ScalarIndexer, + SequenceIndexer, + Shape, + SortKind, + TakeIndexer, + npt, ) _extension_array_shared_docs: dict[str, str] = {} diff --git a/pandas/core/arrays/boolean.py b/pandas/core/arrays/boolean.py index 2dba557eda169..54bd4220bc060 100644 --- a/pandas/core/arrays/boolean.py +++ b/pandas/core/arrays/boolean.py @@ -12,11 +12,6 @@ lib, missing as libmissing, ) -from pandas._typing import ( - Dtype, - DtypeObj, - type_t, -) from pandas.core.dtypes.common import ( is_list_like, @@ -35,7 +30,12 @@ if TYPE_CHECKING: import pyarrow - from pandas._typing import npt + from pandas._typing import ( + Dtype, + DtypeObj, + npt, + type_t, + ) @register_extension_dtype diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index dd48da9ab6c16..09861f0c3749e 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -25,18 +25,6 @@ lib, ) from pandas._libs.arrays import NDArrayBacked -from pandas._typing import ( - ArrayLike, - AstypeArg, - AxisInt, - Dtype, - NpDtype, - Ordered, - Shape, - SortKind, - npt, - type_t, -) from pandas.compat.numpy import function as nv from pandas.util._validators import validate_bool_kwarg @@ -109,6 +97,19 @@ from pandas.io.formats import console if TYPE_CHECKING: + from pandas._typing import ( + ArrayLike, + AstypeArg, + AxisInt, + Dtype, + NpDtype, + Ordered, + Shape, + SortKind, + npt, + type_t, + ) + from pandas import ( DataFrame, Index, diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 1624870705b8f..2a014e3b1b49c 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -42,13 +42,6 @@ tzconversion, ) from pandas._libs.tslibs.dtypes import abbrev_to_npy_unit -from pandas._typing import ( - DateTimeErrorChoices, - IntervalClosedType, - TimeAmbiguous, - TimeNonexistent, - npt, -) from pandas.errors import PerformanceWarning from pandas.util._exceptions import find_stack_level from pandas.util._validators import validate_inclusive @@ -87,6 +80,14 @@ ) if TYPE_CHECKING: + from pandas._typing import ( + DateTimeErrorChoices, + IntervalClosedType, + TimeAmbiguous, + TimeNonexistent, + npt, + ) + from pandas import DataFrame from pandas.core.arrays import PeriodArray diff --git a/pandas/core/arrays/numeric.py b/pandas/core/arrays/numeric.py index 2d9a3ae63259d..d5fd759adc202 100644 --- a/pandas/core/arrays/numeric.py +++ b/pandas/core/arrays/numeric.py @@ -15,11 +15,6 @@ lib, missing as libmissing, ) -from pandas._typing import ( - Dtype, - DtypeObj, - npt, -) from pandas.errors import AbstractMethodError from pandas.util._decorators import cache_readonly @@ -40,6 +35,12 @@ if TYPE_CHECKING: import pyarrow + from pandas._typing import ( + Dtype, + DtypeObj, + npt, + ) + T = TypeVar("T", bound="NumericArray") diff --git a/pandas/core/arrays/numpy_.py b/pandas/core/arrays/numpy_.py index 216dbede39a6a..35182cb3a58de 100644 --- a/pandas/core/arrays/numpy_.py +++ b/pandas/core/arrays/numpy_.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import TYPE_CHECKING + import numpy as np from pandas._libs import lib @@ -7,13 +9,6 @@ get_unit_from_dtype, is_supported_unit, ) -from pandas._typing import ( - AxisInt, - Dtype, - NpDtype, - Scalar, - npt, -) from pandas.compat.numpy import function as nv from pandas.core.dtypes.astype import astype_array @@ -35,6 +30,15 @@ from pandas.core.construction import ensure_wrapped_if_datetimelike from pandas.core.strings.object_array import ObjectStringArrayMixin +if TYPE_CHECKING: + from pandas._typing import ( + AxisInt, + Dtype, + NpDtype, + Scalar, + npt, + ) + class PandasArray( OpsMixin, diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index f9404fbf57382..54190b0fdcaf7 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -45,12 +45,6 @@ get_period_field_arr, period_asfreq_arr, ) -from pandas._typing import ( - AnyArrayLike, - Dtype, - NpDtype, - npt, -) from pandas.util._decorators import ( cache_readonly, doc, @@ -81,8 +75,12 @@ if TYPE_CHECKING: from pandas._typing import ( + AnyArrayLike, + Dtype, + NpDtype, NumpySorter, NumpyValueArrayLike, + npt, ) from pandas.core.arrays import ( diff --git a/pandas/core/arrays/sparse/array.py b/pandas/core/arrays/sparse/array.py index fcebd17ace2d3..31f404c30c262 100644 --- a/pandas/core/arrays/sparse/array.py +++ b/pandas/core/arrays/sparse/array.py @@ -28,19 +28,6 @@ SparseIndex, ) from pandas._libs.tslibs import NaT -from pandas._typing import ( - ArrayLike, - AstypeArg, - Axis, - AxisInt, - Dtype, - NpDtype, - PositionalIndexer, - Scalar, - ScalarIndexer, - SequenceIndexer, - npt, -) from pandas.compat.numpy import function as nv from pandas.errors import PerformanceWarning from pandas.util._exceptions import find_stack_level @@ -120,6 +107,20 @@ class ellipsis(Enum): SparseIndexKind = Literal["integer", "block"] + from pandas._typing import ( + ArrayLike, + AstypeArg, + Axis, + AxisInt, + Dtype, + NpDtype, + PositionalIndexer, + Scalar, + ScalarIndexer, + SequenceIndexer, + npt, + ) + from pandas import Series else: diff --git a/pandas/core/arrays/sparse/dtype.py b/pandas/core/arrays/sparse/dtype.py index c7a44d3606fa6..dadd161ceeb38 100644 --- a/pandas/core/arrays/sparse/dtype.py +++ b/pandas/core/arrays/sparse/dtype.py @@ -10,11 +10,6 @@ import numpy as np -from pandas._typing import ( - Dtype, - DtypeObj, - type_t, -) from pandas.errors import PerformanceWarning from pandas.util._exceptions import find_stack_level @@ -36,6 +31,12 @@ ) if TYPE_CHECKING: + from pandas._typing import ( + Dtype, + DtypeObj, + type_t, + ) + from pandas.core.arrays.sparse.array import SparseArray diff --git a/pandas/core/arrays/sparse/scipy_sparse.py b/pandas/core/arrays/sparse/scipy_sparse.py index 723449dfcd4a3..1d190ad1919b3 100644 --- a/pandas/core/arrays/sparse/scipy_sparse.py +++ b/pandas/core/arrays/sparse/scipy_sparse.py @@ -10,13 +10,7 @@ Iterable, ) -import numpy as np - from pandas._libs import lib -from pandas._typing import ( - IndexLabel, - npt, -) from pandas.core.dtypes.missing import notna @@ -25,8 +19,14 @@ from pandas.core.series import Series if TYPE_CHECKING: + import numpy as np import scipy.sparse + from pandas._typing import ( + IndexLabel, + npt, + ) + def _check_is_partition(parts: Iterable, whole: Iterable): whole = set(whole) diff --git a/pandas/core/arrays/string_.py b/pandas/core/arrays/string_.py index 30b18bac7b73b..89f44de3386c7 100644 --- a/pandas/core/arrays/string_.py +++ b/pandas/core/arrays/string_.py @@ -14,13 +14,6 @@ missing as libmissing, ) from pandas._libs.arrays import NDArrayBacked -from pandas._typing import ( - AxisInt, - Dtype, - Scalar, - npt, - type_t, -) from pandas.compat import pa_version_under7p0 from pandas.compat.numpy import function as nv from pandas.util._decorators import doc @@ -58,8 +51,13 @@ import pyarrow from pandas._typing import ( + AxisInt, + Dtype, NumpySorter, NumpyValueArrayLike, + Scalar, + npt, + type_t, ) from pandas import Series diff --git a/pandas/core/arrays/string_arrow.py b/pandas/core/arrays/string_arrow.py index 4d2b39ec61fca..afec3a9340510 100644 --- a/pandas/core/arrays/string_arrow.py +++ b/pandas/core/arrays/string_arrow.py @@ -2,6 +2,7 @@ import re from typing import ( + TYPE_CHECKING, Callable, Union, ) @@ -12,11 +13,12 @@ lib, missing as libmissing, ) -from pandas._typing import ( - Dtype, - Scalar, - npt, -) + +# from pandas._typing import ( +# Dtype, +# Scalar, +# npt, +# ) from pandas.compat import pa_version_under7p0 from pandas.core.dtypes.common import ( @@ -46,6 +48,15 @@ from pandas.core.arrays.arrow._arrow_utils import fallback_performancewarning + +if TYPE_CHECKING: + from pandas._typing import ( + Dtype, + Scalar, + npt, + ) + + ArrowStringScalarOrNAT = Union[str, libmissing.NAType] diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index d38145295a4db..9c63dc92ca28b 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -39,13 +39,6 @@ parse_timedelta_unit, truediv_object_array, ) -from pandas._typing import ( - AxisInt, - DateTimeErrorChoices, - DtypeObj, - NpDtype, - npt, -) from pandas.compat.numpy import function as nv from pandas.util._validators import validate_endpoints @@ -72,6 +65,14 @@ from pandas.core.ops.common import unpack_zerodim_and_defer if TYPE_CHECKING: + from pandas._typing import ( + AxisInt, + DateTimeErrorChoices, + DtypeObj, + NpDtype, + npt, + ) + from pandas import DataFrame diff --git a/pyproject.toml b/pyproject.toml index edcf6384c432b..3c4de03fb6fcb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -291,7 +291,6 @@ exclude = [ # relative imports allowed for asv_bench "asv_bench/*" = ["TID"] # TCH to be enabled gradually -"pandas/core/arrays/*" = ["TCH"] "pandas/core/io/*" = ["TCH"] "pandas/core/array_algos/*" = ["TCH"] "pandas/core/dtypes/*" = ["TCH"] From eeed2691ff879569a61279906724c554665fb9ce Mon Sep 17 00:00:00 2001 From: Clark-W Date: Tue, 7 Mar 2023 12:53:32 -0500 Subject: [PATCH 2/7] updated dtype.py --- pandas/core/arrays/arrow/dtype.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pandas/core/arrays/arrow/dtype.py b/pandas/core/arrays/arrow/dtype.py index bb5c0aad99b33..50bb287e54365 100644 --- a/pandas/core/arrays/arrow/dtype.py +++ b/pandas/core/arrays/arrow/dtype.py @@ -109,7 +109,11 @@ def type(self): return float elif pa.types.is_string(pa_type): return str - elif pa.types.is_binary(pa_type): + elif ( + pa.types.is_binary(pa_type) + or pa.types.is_fixed_size_binary(pa_type) + or pa.types.is_large_binary(pa_type) + ): return bytes elif pa.types.is_boolean(pa_type): return bool From 7d023eee815da402aa2c74357813f1c28c9cff07 Mon Sep 17 00:00:00 2001 From: Clark-W Date: Wed, 8 Mar 2023 12:20:24 -0500 Subject: [PATCH 3/7] Updated string_arrow.py to remove added comments thanks for the feedback, really appreciate it! --- pandas/core/arrays/string_arrow.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pandas/core/arrays/string_arrow.py b/pandas/core/arrays/string_arrow.py index afec3a9340510..c003d2948a49d 100644 --- a/pandas/core/arrays/string_arrow.py +++ b/pandas/core/arrays/string_arrow.py @@ -13,12 +13,11 @@ lib, missing as libmissing, ) - -# from pandas._typing import ( -# Dtype, -# Scalar, -# npt, -# ) +from pandas._typing import ( + Dtype, + Scalar, + npt, +) from pandas.compat import pa_version_under7p0 from pandas.core.dtypes.common import ( From de863d2ef19bff12f103e0191b1c35954cd330de Mon Sep 17 00:00:00 2001 From: Clark-W Date: Wed, 8 Mar 2023 12:42:13 -0500 Subject: [PATCH 4/7] removed the comments they were in typechecking --- pandas/core/arrays/string_arrow.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pandas/core/arrays/string_arrow.py b/pandas/core/arrays/string_arrow.py index c003d2948a49d..ea7670a54cc20 100644 --- a/pandas/core/arrays/string_arrow.py +++ b/pandas/core/arrays/string_arrow.py @@ -13,11 +13,7 @@ lib, missing as libmissing, ) -from pandas._typing import ( - Dtype, - Scalar, - npt, -) + from pandas.compat import pa_version_under7p0 from pandas.core.dtypes.common import ( From 1d552987f41bfe33bb0ad6b65b3041f06ff4cb6a Mon Sep 17 00:00:00 2001 From: Junheng Date: Fri, 10 Mar 2023 16:13:40 -0500 Subject: [PATCH 5/7] run pre-commit --- pandas/core/arrays/string_arrow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/core/arrays/string_arrow.py b/pandas/core/arrays/string_arrow.py index fe45da139fb7d..d5c1f98e63f14 100644 --- a/pandas/core/arrays/string_arrow.py +++ b/pandas/core/arrays/string_arrow.py @@ -14,7 +14,6 @@ lib, missing as libmissing, ) - from pandas.compat import pa_version_under7p0 from pandas.util._exceptions import find_stack_level From 6471a095bb77a0a558a6bf7dcd80f7948354f9ab Mon Sep 17 00:00:00 2001 From: clark-w Date: Wed, 3 May 2023 23:10:09 -0400 Subject: [PATCH 6/7] code for intervals --- pandas/_libs/interval.pyx | 255 ++++++++++++++++++ pandas/core/arrays/interval.py | 36 +++ .../tests/arithmetic/test_interval_43629.py | 96 +++++++ .../test_interval_43629_acceptance.py | 71 +++++ .../arithmetic/test_interval_43629_unit.py | 96 +++++++ 5 files changed, 554 insertions(+) create mode 100644 pandas/tests/arithmetic/test_interval_43629.py create mode 100644 pandas/tests/arithmetic/test_interval_43629_acceptance.py create mode 100644 pandas/tests/arithmetic/test_interval_43629_unit.py diff --git a/pandas/_libs/interval.pyx b/pandas/_libs/interval.pyx index fe405b98f218c..831813e3c6284 100644 --- a/pandas/_libs/interval.pyx +++ b/pandas/_libs/interval.pyx @@ -489,6 +489,61 @@ cdef class Interval(IntervalMixin): ) ): return Interval(y.left + self, y.right + self, closed=y.closed) + if ( + isinstance(self, Interval) + and isinstance(y, Interval) + ): + # Check if both intervals intersect at a point taking into account of closed and open intersection points + is_intersecting = ( + self.overlaps(y) + or ((self.left == y.right) and ((self.closed in ['left', 'both']) or (y.closed in ['right', 'both']))) + or ((y.left == self.right) and ((y.closed in ['left', 'both']) or (self.closed in ['right', 'both']))) + ) + # If the intervals intersect, return one new interval that contains all elements + if(is_intersecting): + # Obtain the new interval's left hand side by taking the min of both intervals left hand side + left_closed = None + left = None + if(y.left < self.left): + left = y.left + left_closed = y.closed in ['left', 'both'] + elif (y.left > self.left): + left = self.left + left_closed = self.closed in ['left', 'both'] + else: + left = self.left + left_closed = y.closed in ['left', 'both'] or self.closed in ['left', 'both'] + + #Obtain the new interval's right hand side by taking the max of both intervals right hand side + right_closed = None + right = None + if(y.right > self.right): + right = y.right + right_closed = y.closed in ['right', 'both'] + elif (y.right < self.right): + right = self.right + right_closed = self.closed in ['right', 'both'] + else: + right = self.right + right_closed = y.closed in ['right', 'both'] or self.closed in ['right', 'both'] + + closed = None + if left_closed and right_closed: + closed = 'both' + elif left_closed: + closed = 'left' + elif right_closed: + closed = 'right' + else: + closed = 'neither' + + return Interval(left, right, closed=closed) + + # Return a list of the intervals in sorted order if they don't intersect + first_interval = y if y.left < self.left else self + second_interval = y if first_interval == self else self + return [first_interval, second_interval] + return NotImplemented def __radd__(self, other): @@ -507,6 +562,144 @@ cdef class Interval(IntervalMixin): or is_timedelta64_object(y) ): return Interval(self.left - y, self.right - y, closed=self.closed) + + if isinstance(y, list) and isinstance(self, Interval): + # If the list is empty, return the interval + if not y: + return self + # If the list is not empty, return the difference between self and each interval in the y list + else: + new_interval = [] + for interval in y: + new_interval.append(self - interval) + return new_interval + if isinstance(self, list) and isinstance(y, Interval): + # If the list is empty, return the interval + if not self: + return y + # If the list is not empty, return the difference between each interval in the list and the y interval + else: + new_interval = [] + for interval in self: + new_interval.append(interval - y) + return new_interval + if isinstance(self, list) and isinstance(y, list): + # If the list is empty, return the interval + if not self: + return y + # If the list is not empty, return the difference between each interval in the list and each interval in the y list + else: + new_interval = [] + for interval in self: + for interval2 in y: + new_interval.append(interval - interval2) + return new_interval + # Performs set difference on the two intervals + if ( + isinstance(self, Interval) + and isinstance(y, Interval) + ): + if not self.overlaps(y): + return self + else: + # If the right side of the interval is the same, an empty interval is returned + if (y.left == self.left and y.right == self.right or y.left < self.left and y.right > self.right): + return Interval(0, 0, closed='neither') + # If the left side of the interval is the same, return the right side of the interval + if (y.left == self.left and y.right < self.right): + if y.closed in ['left', 'neither'] and self.closed in ['right', 'both']: + return Interval(y.right, self.right, closed='both') + if y.closed in ['right', 'both'] and self.closed in ['right', 'both']: + return Interval(y.right, self.right, closed='right') + if y.closed in ['left', 'neither'] and self.closed in ['left', 'neither']: + return Interval(y.right, self.right, closed='left') + if y.closed in ['right', 'both'] and self.closed in ['left', 'neither']: + return Interval(y.right, self.right, closed='neither') + # If the right side of the interval is the same, return the left side of the interval + if (y.left > self.left and y.right == self.right): + if y.closed in ['right', 'neither'] and self.closed in ['left', 'both']: + return Interval(self.left, y.left, closed='both') + if y.closed in ['left', 'both'] and self.closed in ['left', 'both']: + return Interval(self.left, y.left, closed='left') + if y.closed in ['right', 'neither'] and self.closed in ['right', 'neither']: + return Interval(self.left, y.left, closed='right') + if y.closed in ['left', 'both'] and self.closed in ['right', 'neither']: + return Interval(self.left, y.left, closed='neither') + + + interval_split = False + left_closed = None + left = None + if(y.left < self.left and y.right < self.right): + # lefy of y is left of self and right of y is in self + left = y.right + left_closed = y.closed not in ['right', 'both'] + elif (y.left > self.left and y.right > self.right): + # left of y is in self and right of y is right of self + left = self.left + left_closed = self.closed in ['left', 'both'] + else: + # left of y is in self and right of y is in self + # This will split the interval into two + interval_split = True + + right_closed = None + right = None + if(y.left > self.left and y.right > self.right): + # left of y is right of self and right of y is right of self + right = y.left + right_closed = y.closed not in ['left', 'both'] + elif (y.left < self.left and y.right < self.right): + # left of y is left of self and right of y is in self + right = self.right + right_closed = self.closed in ['right', 'both'] + else: + # left of y is in self and right of y is in self + # This will split the interval into two + interval_split = True + + # If the interval needs to be split, return two intervals with the correct closed values + def create_new_intervals(y_closed, self_closed): + closed_cases = { + ('left', 'left'): ('left', 'left'), + ('left', 'right'): ('neither', 'both'), + ('left', 'both'): ('left', 'both'), + ('left', 'neither'): ('neither', 'left'), + ('right', 'left'): ('both', 'neither'), + ('right', 'right'): ('right', 'right'), + ('right', 'both'): ('both', 'right'), + ('right', 'neither'): ('right', 'neither'), + ('both', 'left'): ('left', 'neither'), + ('both', 'right'): ('neither', 'right'), + ('both', 'both'): ('left', 'right'), + ('both', 'neither'): ('neither', 'neither'), + ('neither', 'left'): ('both', 'left'), + ('neither', 'right'): ('right', 'both'), + ('neither', 'both'): ('both', 'both'), + ('neither', 'neither'): ('right', 'left') + } + return closed_cases[(y_closed, self_closed)] + + if interval_split: + new_interval = [] + left_interval_closed, right_interval_closed = create_new_intervals(y.closed, self.closed) + new_interval.append(Interval(self.left, y.left, closed=left_interval_closed)) + new_interval.append(Interval(y.right, self.right, closed=right_interval_closed)) + return new_interval + + + # If the interval is not split, return a single interval + if left_closed: + if right_closed: + closed = 'both' + else: + closed = 'left' + else: + if right_closed: + closed = 'right' + else: + closed = 'neither' + return Interval(left, right, closed=closed) return NotImplemented def __mul__(self, y): @@ -516,8 +709,70 @@ cdef class Interval(IntervalMixin): # __radd__ semantics # TODO(cython3): remove this return Interval(y.left * self, y.right * self, closed=y.closed) + if ( + isinstance(self, Interval) + and isinstance(y, Interval) + ): + # If the intervals overlap, return the overlapping interval + if(self.overlaps(y)): + closed = None + left=0 + left_closed=None + right=0 + right_closed=None + # If the left side of the interval is the same, return the right side of the interval + if (self.left None: """ diff --git a/pandas/tests/arithmetic/test_interval_43629.py b/pandas/tests/arithmetic/test_interval_43629.py new file mode 100644 index 0000000000000..e48329f4c4165 --- /dev/null +++ b/pandas/tests/arithmetic/test_interval_43629.py @@ -0,0 +1,96 @@ +from pandas.arrays import IntervalArray +from pandas import Interval +import pandas._testing as tm + +# Unit tests for issue 43629 +class TestIntervalArithmetic: + def test_addition(self): + cases = [ + # [1, 5] + [2, 7] = [1, 7] + (Interval(1, 5, 'both'), Interval(2, 7, 'both'), Interval(1, 7, 'both')), + # (1, 5) + (2, 7) = (1, 7) + (Interval(1, 5, 'neither'), Interval(2, 7, 'neither'), Interval(1, 7, 'neither')),pytest pandas/tests/arithmetic/test_interval_43629.py -v + # (1, 5] + (2, 7] = (1, 7] + (Interval(1, 5, 'right'), Interval(2, 7, 'right'), Interval(1, 7, 'right')), + # (1, 5] + (5, 7] = (1, 7] + (Interval(1, 5, 'right'), Interval(5, 7, 'right'), Interval(1, 7, 'right')), + # (1, 5) + [5, 7] = (1, 7] + (Interval(1, 5, 'neither'), Interval(5, 7, 'both'), Interval(1, 7, 'right')), + # (1, 5) + (5, 7) = [(1, 5), (5, 7)] + (Interval(1, 5, 'neither'), Interval(5, 7, 'neither'), [Interval(1, 5, 'neither'), Interval(5, 7, 'neither')]), + # (1, 4) + (5, 7] = [(1, 4), (5, 7]] + (Interval(1, 4, 'neither'), Interval(5, 7, 'right'), [Interval(1, 4, 'neither'), Interval(5, 7, 'right')]), + # (1, 7) + (5, 6] = (1, 7) + (Interval(1, 7, 'neither'), Interval(5, 6, 'right'), Interval(1, 7, 'neither')), + #(1,7] + [1,7) = [1,7] + (Interval(1, 7, 'right'), Interval(1, 7, 'left'), Interval(1, 7, 'both')) + ] + for (interval1, interval2, expected) in cases: + actual1 = interval1 + interval2 + actual2 = interval2 + interval1 + assert actual1 == expected + assert actual2 == expected + + + def test_multiplication(self): + cases = [ + # (1, 5) * (2, 7) = (2, 5) + (Interval(1, 5, 'neither'), Interval(2, 7, 'neither'), Interval(2, 5, 'neither')), + # (1, 5) * (6, 7) = (0, 0) + (Interval(1, 5, 'neither'), Interval(6, 7, 'neither'), Interval(0, 0, 'neither')), + # (1, 5] * (2, 7) = (2, 5] + (Interval(1, 5, 'right'), Interval(2, 7, 'neither'), Interval(2, 5, 'right')), + # (1, 7) * [3, 5] = [3, 5] + (Interval(1, 7, 'neither'), Interval(3, 5, 'both'), Interval(3, 5, 'both')), + # (0, 10] * (1, 2] = (1, 2] + (Interval(0, 10, 'right'), Interval(1, 2, 'right'), Interval(1, 2, 'right')), + + ] + for (interval1, interval2, expected) in cases: + actual1 = interval1 * interval2 + actual2 = interval2 * interval1 + assert actual1 == expected + assert actual2 == expected + + def test_subtraction(self): + cases = [ + # (0, 3) - (2, 6) = (0, 2] + (Interval(0, 3, 'neither'), Interval(2, 6, 'neither'), Interval(0, 2, 'right')), + # (1, 10) - (5, 6) = [(1,5],[6,10)] + (Interval(1, 10, 'neither'), Interval(5, 6, 'neither'), [Interval(1, 5, 'right'), Interval(6, 10, 'left')]), + # (1, 10) - [5, 6) = [(1,5),[6,10)] + (Interval(1, 10, 'neither'), Interval(5, 6, 'left'), [Interval(1, 5, 'neither'), Interval(6, 10, 'left')]), + # (1, 2) - (2, 4) = (1,2) + (Interval(1, 2, 'neither'), Interval(2, 4, 'neither'), (Interval(1, 2, 'neither'))), + ] + for (interval1, interval2, expected) in cases: + actual1 = interval1 - interval2 + assert actual1 == expected + + # (1, 2) - (2, 4) - (3, 5) = (1,2) + assert Interval(1, 2, 'neither') - Interval(2, 4, 'neither') - Interval(3, 5, 'neither') == Interval(1, 2, 'neither') + # (1, 10) - (5, 6) - (1,2)= [[2,5], [6,10)] + assert Interval(1, 10, 'neither') - Interval(5, 6, 'neither') - Interval(1, 2, 'neither') == [Interval(2, 5, 'both'), Interval(6, 10, 'left')] + +class TestIntervalArrayArithmetic: + def test_addition(self): + cases = [ + # (1, 2] + (2, 3] + (3, 4] = [(1, 4]] + (IntervalArray([Interval(1, 2, 'right'), Interval(2, 3, 'right'), Interval(3, 4, 'right')]), IntervalArray([Interval(1,4,'right')])), + # (1, 2] + (2, 3] + (4, 5] = [(1, 3],(4,5]] + (IntervalArray([Interval(1, 2, 'right'), Interval(2, 3, 'right'), Interval(4, 5, 'right')]), IntervalArray([Interval(1,3,'right'),Interval(4,5,'right')])) + ] + for (interval_arr, expected) in cases: + actual = interval_arr.sum() + tm.assert_interval_array_equal(actual, expected) + + def test_multiplication(self): + cases = [ + # (1, 2] * (2, 3] * (3, 4] = [(0, 0)] + (IntervalArray([Interval(1, 2, 'right'), Interval(2, 3, 'right'), Interval(3, 4, 'right')]), IntervalArray([Interval(0,0,'neither')])), + # [1, 2] * [2, 3] * [2, 4] = [[2, 2]] + (IntervalArray([Interval(1, 2, 'both'), Interval(2, 3, 'both'), Interval(4, 5, 'both')]), IntervalArray([Interval(2,2,'both')])) + ] + for (interval_arr, expected) in cases: + actual = interval_arr.product() + tm.assert_interval_array_equal(actual, expected) diff --git a/pandas/tests/arithmetic/test_interval_43629_acceptance.py b/pandas/tests/arithmetic/test_interval_43629_acceptance.py new file mode 100644 index 0000000000000..342043c7d4289 --- /dev/null +++ b/pandas/tests/arithmetic/test_interval_43629_acceptance.py @@ -0,0 +1,71 @@ +import pandas as pd + +# Acceptance test for issue 43629 +print("Create and validate an interval") +interval = pd.Interval(1,2,"neither") +print("Interval left hand side:") +print(interval.left) +print("Expected Result:") +print(1) +print("Interval right hand side:") +print(interval.right) +print("Expected Result:") +print(2) +print("Interval closed sides:") +print(interval.closed) +print("Expected Result:") +print("neither") +print('++++++++++++++++++++++++++++++++++++++++++++') + +print("Add two intervals and validate their sum") +interval_a = pd.Interval(0, 10) +interval_b = pd.Interval(1, 2) +interval_c = interval_a + interval_b +print("Actual Result:") +print(interval_c) +print("Expected Result:") +print(pd.Interval(0, 10)) +print('++++++++++++++++++++++++++++++++++++++++++++') + +print("Subtract two intervals and validate their difference") +interval_d = interval_c - interval_a +print("Actual Result:") +print(interval_d) +print("Expected Result:") +print(pd.Interval(0, 0,'neither')) +print('++++++++++++++++++++++++++++++++++++++++++++') + +print("Multiply two intervals and validate their product") +interval_e = interval_c * interval_b +print("Actual Result:") +print(interval_e) +print("Expected Result:") +print(pd.Interval(1, 2)) +print('++++++++++++++++++++++++++++++++++++++++++++') + +# Create an interval array of the previous created intervals a and b +print("Create and validate an interval array") +interval_array = pd.arrays.IntervalArray([interval_a, interval_b]) +print("Actual Result:") +print(interval_array) +print("Expected Result:") +print( +''' +[(0, 10], (1, 2]] +Length: 2, dtype: interval[int64, right]''' +) + +print("Calculate and verify the sum of the interval array") +interval_arr_sum = interval_array.sum() +print("Actual Result:") +print(interval_arr_sum) +print("Expected Result:") +print(pd.arrays.IntervalArray([pd.Interval(0, 10)])) +print('++++++++++++++++++++++++++++++++++++++++++++') + +print("Calculate and verify the product of the interval array") +interval_arr_product = interval_array.product() +print("Actual Result:") +print(interval_arr_product) +print(pd.arrays.IntervalArray([pd.Interval(1, 2)])) +print('++++++++++++++++++++++++++++++++++++++++++++') \ No newline at end of file diff --git a/pandas/tests/arithmetic/test_interval_43629_unit.py b/pandas/tests/arithmetic/test_interval_43629_unit.py new file mode 100644 index 0000000000000..b826844d2cb44 --- /dev/null +++ b/pandas/tests/arithmetic/test_interval_43629_unit.py @@ -0,0 +1,96 @@ +from pandas.arrays import IntervalArray +from pandas import Interval +import pandas._testing as tm + +# Unit tests for issue 43629 +class TestIntervalArithmetic: + def test_addition(self): + cases = [ + # [1, 5] + [2, 7] = [1, 7] + (Interval(1, 5, 'both'), Interval(2, 7, 'both'), Interval(1, 7, 'both')), + # (1, 5) + (2, 7) = (1, 7) + (Interval(1, 5, 'neither'), Interval(2, 7, 'neither'), Interval(1, 7, 'neither')), + # (1, 5] + (2, 7] = (1, 7] + (Interval(1, 5, 'right'), Interval(2, 7, 'right'), Interval(1, 7, 'right')), + # (1, 5] + (5, 7] = (1, 7] + (Interval(1, 5, 'right'), Interval(5, 7, 'right'), Interval(1, 7, 'right')), + # (1, 5) + [5, 7] = (1, 7] + (Interval(1, 5, 'neither'), Interval(5, 7, 'both'), Interval(1, 7, 'right')), + # (1, 5) + (5, 7) = [(1, 5), (5, 7)] + (Interval(1, 5, 'neither'), Interval(5, 7, 'neither'), [Interval(1, 5, 'neither'), Interval(5, 7, 'neither')]), + # (1, 4) + (5, 7] = [(1, 4), (5, 7]] + (Interval(1, 4, 'neither'), Interval(5, 7, 'right'), [Interval(1, 4, 'neither'), Interval(5, 7, 'right')]), + # (1, 7) + (5, 6] = (1, 7) + (Interval(1, 7, 'neither'), Interval(5, 6, 'right'), Interval(1, 7, 'neither')), + #(1,7] + [1,7) = [1,7] + (Interval(1, 7, 'right'), Interval(1, 7, 'left'), Interval(1, 7, 'both')) + ] + for (interval1, interval2, expected) in cases: + actual1 = interval1 + interval2 + actual2 = interval2 + interval1 + assert actual1 == expected + assert actual2 == expected + + + def test_multiplication(self): + cases = [ + # (1, 5) * (2, 7) = (2, 5) + (Interval(1, 5, 'neither'), Interval(2, 7, 'neither'), Interval(2, 5, 'neither')), + # (1, 5) * (6, 7) = (0, 0) + (Interval(1, 5, 'neither'), Interval(6, 7, 'neither'), Interval(0, 0, 'neither')), + # (1, 5] * (2, 7) = (2, 5] + (Interval(1, 5, 'right'), Interval(2, 7, 'neither'), Interval(2, 5, 'right')), + # (1, 7) * [3, 5] = [3, 5] + (Interval(1, 7, 'neither'), Interval(3, 5, 'both'), Interval(3, 5, 'both')), + # (0, 10] * (1, 2] = (1, 2] + (Interval(0, 10, 'right'), Interval(1, 2, 'right'), Interval(1, 2, 'right')), + + ] + for (interval1, interval2, expected) in cases: + actual1 = interval1 * interval2 + actual2 = interval2 * interval1 + assert actual1 == expected + assert actual2 == expected + + def test_subtraction(self): + cases = [ + # (0, 3) - (2, 6) = (0, 2] + (Interval(0, 3, 'neither'), Interval(2, 6, 'neither'), Interval(0, 2, 'right')), + # (1, 10) - (5, 6) = [(1,5],[6,10)] + (Interval(1, 10, 'neither'), Interval(5, 6, 'neither'), [Interval(1, 5, 'right'), Interval(6, 10, 'left')]), + # (1, 10) - [5, 6) = [(1,5),[6,10)] + (Interval(1, 10, 'neither'), Interval(5, 6, 'left'), [Interval(1, 5, 'neither'), Interval(6, 10, 'left')]), + # (1, 2) - (2, 4) = (1,2) + (Interval(1, 2, 'neither'), Interval(2, 4, 'neither'), (Interval(1, 2, 'neither'))), + ] + for (interval1, interval2, expected) in cases: + actual1 = interval1 - interval2 + assert actual1 == expected + + # (1, 2) - (2, 4) - (3, 5) = (1,2) + assert Interval(1, 2, 'neither') - Interval(2, 4, 'neither') - Interval(3, 5, 'neither') == Interval(1, 2, 'neither') + # (1, 10) - (5, 6) - (1,2)= [[2,5], [6,10)] + assert Interval(1, 10, 'neither') - Interval(5, 6, 'neither') - Interval(1, 2, 'neither') == [Interval(2, 5, 'both'), Interval(6, 10, 'left')] + +class TestIntervalArrayArithmetic: + def test_addition(self): + cases = [ + # (1, 2] + (2, 3] + (3, 4] = [(1, 4]] + (IntervalArray([Interval(1, 2, 'right'), Interval(2, 3, 'right'), Interval(3, 4, 'right')]), IntervalArray([Interval(1,4,'right')])), + # (1, 2] + (2, 3] + (4, 5] = [(1, 3],(4,5]] + (IntervalArray([Interval(1, 2, 'right'), Interval(2, 3, 'right'), Interval(4, 5, 'right')]), IntervalArray([Interval(1,3,'right'),Interval(4,5,'right')])) + ] + for (interval_arr, expected) in cases: + actual = interval_arr.sum() + tm.assert_interval_array_equal(actual, expected) + + def test_multiplication(self): + cases = [ + # (1, 2] * (2, 3] * (3, 4] = [(0, 0)] + (IntervalArray([Interval(1, 2, 'right'), Interval(2, 3, 'right'), Interval(3, 4, 'right')]), IntervalArray([Interval(0,0,'neither')])), + # [1, 2] * [2, 3] * [2, 4] = [[2, 2]] + (IntervalArray([Interval(1, 2, 'both'), Interval(2, 3, 'both'), Interval(4, 5, 'both')]), IntervalArray([Interval(2,2,'both')])) + ] + for (interval_arr, expected) in cases: + actual = interval_arr.product() + tm.assert_interval_array_equal(actual, expected) From 0cac4508c3ca2399c017ff11126860d392af2658 Mon Sep 17 00:00:00 2001 From: clark-w Date: Fri, 5 May 2023 20:03:28 -0400 Subject: [PATCH 7/7] ran pre-commit and passed --- pandas/_libs/interval.pyx | 244 ++++++++++-------- pandas/core/arrays/interval.py | 25 +- .../tests/arithmetic/test_interval_43629.py | 96 ------- .../test_interval_43629_acceptance.py | 71 ----- .../arithmetic/test_interval_43629_unit.py | 96 ------- 5 files changed, 152 insertions(+), 380 deletions(-) delete mode 100644 pandas/tests/arithmetic/test_interval_43629.py delete mode 100644 pandas/tests/arithmetic/test_interval_43629_acceptance.py delete mode 100644 pandas/tests/arithmetic/test_interval_43629_unit.py diff --git a/pandas/_libs/interval.pyx b/pandas/_libs/interval.pyx index 831813e3c6284..1de04ab6263f2 100644 --- a/pandas/_libs/interval.pyx +++ b/pandas/_libs/interval.pyx @@ -493,52 +493,60 @@ cdef class Interval(IntervalMixin): isinstance(self, Interval) and isinstance(y, Interval) ): - # Check if both intervals intersect at a point taking into account of closed and open intersection points + # Check if both intervals intersect at a point taking into account of closed + # and open intersection points is_intersecting = ( self.overlaps(y) - or ((self.left == y.right) and ((self.closed in ['left', 'both']) or (y.closed in ['right', 'both']))) - or ((y.left == self.right) and ((y.closed in ['left', 'both']) or (self.closed in ['right', 'both']))) + or ((self.left == y.right) and ((self.closed in ["left", "both"]) + or (y.closed in ["right", "both"]))) + or ((y.left == self.right) + and ((y.closed in ["left", "both"]) + or (self.closed in ["right", "both"]))) ) - # If the intervals intersect, return one new interval that contains all elements + # If the intervals intersect, return one new interval that contains + # all elements if(is_intersecting): - # Obtain the new interval's left hand side by taking the min of both intervals left hand side + # Obtain the new interval's left hand side by taking the min of both + # intervals left hand side left_closed = None left = None if(y.left < self.left): left = y.left - left_closed = y.closed in ['left', 'both'] + left_closed = y.closed in ["left", "both"] elif (y.left > self.left): left = self.left - left_closed = self.closed in ['left', 'both'] + left_closed = self.closed in ["left", "both"] else: left = self.left - left_closed = y.closed in ['left', 'both'] or self.closed in ['left', 'both'] - - #Obtain the new interval's right hand side by taking the max of both intervals right hand side + left_closed = (y.closed in ["left", "both"] + or self.closed in ["left", "both"]) + # Obtain the new interval's right hand side by taking the max of + # both intervals right hand side right_closed = None right = None if(y.right > self.right): right = y.right - right_closed = y.closed in ['right', 'both'] + right_closed = y.closed in ["right", "both"] elif (y.right < self.right): right = self.right - right_closed = self.closed in ['right', 'both'] + right_closed = self.closed in ["right", "both"] else: right = self.right - right_closed = y.closed in ['right', 'both'] or self.closed in ['right', 'both'] - + right_closed = (y.closed in ["right", "both"] + or self.closed in ["right", "both"]) + closed = None if left_closed and right_closed: - closed = 'both' + closed = "both" elif left_closed: - closed = 'left' + closed = "left" elif right_closed: - closed = 'right' + closed = "right" else: - closed = 'neither' - + closed = "neither" + return Interval(left, right, closed=closed) - + # Return a list of the intervals in sorted order if they don't intersect first_interval = y if y.left < self.left else self second_interval = y if first_interval == self else self @@ -562,12 +570,13 @@ cdef class Interval(IntervalMixin): or is_timedelta64_object(y) ): return Interval(self.left - y, self.right - y, closed=self.closed) - + if isinstance(y, list) and isinstance(self, Interval): # If the list is empty, return the interval if not y: return self - # If the list is not empty, return the difference between self and each interval in the y list + # If the list is not empty, return the difference between self and + # each interval in the y list else: new_interval = [] for interval in y: @@ -577,7 +586,8 @@ cdef class Interval(IntervalMixin): # If the list is empty, return the interval if not self: return y - # If the list is not empty, return the difference between each interval in the list and the y interval + # If the list is not empty, return the difference between each interval + # in the list and the y interval else: new_interval = [] for interval in self: @@ -587,7 +597,8 @@ cdef class Interval(IntervalMixin): # If the list is empty, return the interval if not self: return y - # If the list is not empty, return the difference between each interval in the list and each interval in the y list + # If the list is not empty, return the difference between each interval + # in the list and each interval in the y list else: new_interval = [] for interval in self: @@ -602,103 +613,115 @@ cdef class Interval(IntervalMixin): if not self.overlaps(y): return self else: - # If the right side of the interval is the same, an empty interval is returned - if (y.left == self.left and y.right == self.right or y.left < self.left and y.right > self.right): - return Interval(0, 0, closed='neither') - # If the left side of the interval is the same, return the right side of the interval + # If the right side of the interval is the same, an empty interval + # is returned + if (y.left == self.left and y.right == self.right + or y.left < self.left and y.right > self.right): + return Interval(0, 0, closed="neither") + # If the left side of the interval is the same, return the right + # side of the interval if (y.left == self.left and y.right < self.right): - if y.closed in ['left', 'neither'] and self.closed in ['right', 'both']: - return Interval(y.right, self.right, closed='both') - if y.closed in ['right', 'both'] and self.closed in ['right', 'both']: - return Interval(y.right, self.right, closed='right') - if y.closed in ['left', 'neither'] and self.closed in ['left', 'neither']: - return Interval(y.right, self.right, closed='left') - if y.closed in ['right', 'both'] and self.closed in ['left', 'neither']: - return Interval(y.right, self.right, closed='neither') - # If the right side of the interval is the same, return the left side of the interval + if (y.closed in ["left", "neither"] + and self.closed in ["right", "both"]): + return Interval(y.right, self.right, closed="both") + if y.closed in ["right", "both"] + and self.closed in ["right", "both"]: + return Interval(y.right, self.right, closed="right") + if y.closed in ["left", "neither"] + and self.closed in ["left", "neither"]: + return Interval(y.right, self.right, closed="left") + if y.closed in ["right", "both"] + and self.closed in ["left", "neither"]: + return Interval(y.right, self.right, closed="neither") + # If the right side of the interval is the same, + # return the left side of the interval if (y.left > self.left and y.right == self.right): - if y.closed in ['right', 'neither'] and self.closed in ['left', 'both']: - return Interval(self.left, y.left, closed='both') - if y.closed in ['left', 'both'] and self.closed in ['left', 'both']: - return Interval(self.left, y.left, closed='left') - if y.closed in ['right', 'neither'] and self.closed in ['right', 'neither']: - return Interval(self.left, y.left, closed='right') - if y.closed in ['left', 'both'] and self.closed in ['right', 'neither']: - return Interval(self.left, y.left, closed='neither') - - + if y.closed in ["right", "neither"] + and self.closed in ["left", "both"]: + return Interval(self.left, y.left, closed="both") + if y.closed in ["left", "both"] and self.closed in ["left", "both"]: + return Interval(self.left, y.left, closed="left") + if y.closed in ["right", "neither"] + and self.closed in ["right", "neither"]: + return Interval(self.left, y.left, closed="right") + if y.closed in ["left", "both"] + and self.closed in ["right", "neither"]: + return Interval(self.left, y.left, closed="neither") interval_split = False left_closed = None left = None if(y.left < self.left and y.right < self.right): # lefy of y is left of self and right of y is in self left = y.right - left_closed = y.closed not in ['right', 'both'] + left_closed = y.closed not in ["right", "both"] elif (y.left > self.left and y.right > self.right): # left of y is in self and right of y is right of self left = self.left - left_closed = self.closed in ['left', 'both'] + left_closed = self.closed in ["left", "both"] else: # left of y is in self and right of y is in self # This will split the interval into two interval_split = True - + right_closed = None right = None if(y.left > self.left and y.right > self.right): # left of y is right of self and right of y is right of self right = y.left - right_closed = y.closed not in ['left', 'both'] + right_closed = y.closed not in ["left", "both"] elif (y.left < self.left and y.right < self.right): # left of y is left of self and right of y is in self right = self.right - right_closed = self.closed in ['right', 'both'] + right_closed = self.closed in ["right", "both"] else: # left of y is in self and right of y is in self # This will split the interval into two interval_split = True - - # If the interval needs to be split, return two intervals with the correct closed values + + # If the interval needs to be split, return two intervals with + # the correct closed values def create_new_intervals(y_closed, self_closed): closed_cases = { - ('left', 'left'): ('left', 'left'), - ('left', 'right'): ('neither', 'both'), - ('left', 'both'): ('left', 'both'), - ('left', 'neither'): ('neither', 'left'), - ('right', 'left'): ('both', 'neither'), - ('right', 'right'): ('right', 'right'), - ('right', 'both'): ('both', 'right'), - ('right', 'neither'): ('right', 'neither'), - ('both', 'left'): ('left', 'neither'), - ('both', 'right'): ('neither', 'right'), - ('both', 'both'): ('left', 'right'), - ('both', 'neither'): ('neither', 'neither'), - ('neither', 'left'): ('both', 'left'), - ('neither', 'right'): ('right', 'both'), - ('neither', 'both'): ('both', 'both'), - ('neither', 'neither'): ('right', 'left') + ("left", "left"): ("left", "left"), + ("left", "right"): ("neither", "both"), + ("left", "both"): ("left", "both"), + ("left", "neither"): ("neither", "left"), + ("right", "left"): ("both", "neither"), + ("right", "right"): ("right", "right"), + ("right", "both"): ("both", "right"), + ("right", "neither"): ("right", "neither"), + ("both", "left"): ("left", "neither"), + ("both", "right"): ("neither", "right"), + ("both", "both"): ("left", "right"), + ("both", "neither"): ("neither", "neither"), + ("neither", "left"): ("both", "left"), + ("neither", "right"): ("right", "both"), + ("neither", "both"): ("both", "both"), + ("neither", "neither"): ("right", "left") } return closed_cases[(y_closed, self_closed)] if interval_split: new_interval = [] - left_interval_closed, right_interval_closed = create_new_intervals(y.closed, self.closed) - new_interval.append(Interval(self.left, y.left, closed=left_interval_closed)) - new_interval.append(Interval(y.right, self.right, closed=right_interval_closed)) + left_interval_closed, right_interval_closed + = create_new_intervals(y.closed, self.closed) + new_interval.append + (Interval(self.left, y.left, closed=left_interval_closed)) + new_interval.append + (Interval(y.right, self.right, closed=right_interval_closed)) return new_interval - # If the interval is not split, return a single interval if left_closed: if right_closed: - closed = 'both' + closed = "both" else: - closed = 'left' + closed = "left" else: if right_closed: - closed = 'right' + closed = "right" else: - closed = 'neither' + closed = "neither" return Interval(left, right, closed=closed) return NotImplemented @@ -713,66 +736,73 @@ cdef class Interval(IntervalMixin): isinstance(self, Interval) and isinstance(y, Interval) ): - # If the intervals overlap, return the overlapping interval + # If the intervals overlap, return the overlapping interval if(self.overlaps(y)): closed = None left=0 left_closed=None right=0 right_closed=None - # If the left side of the interval is the same, return the right side of the interval + # If the left side of the interval is the same, + # return the right side of the interval if (self.left -[(0, 10], (1, 2]] -Length: 2, dtype: interval[int64, right]''' -) - -print("Calculate and verify the sum of the interval array") -interval_arr_sum = interval_array.sum() -print("Actual Result:") -print(interval_arr_sum) -print("Expected Result:") -print(pd.arrays.IntervalArray([pd.Interval(0, 10)])) -print('++++++++++++++++++++++++++++++++++++++++++++') - -print("Calculate and verify the product of the interval array") -interval_arr_product = interval_array.product() -print("Actual Result:") -print(interval_arr_product) -print(pd.arrays.IntervalArray([pd.Interval(1, 2)])) -print('++++++++++++++++++++++++++++++++++++++++++++') \ No newline at end of file diff --git a/pandas/tests/arithmetic/test_interval_43629_unit.py b/pandas/tests/arithmetic/test_interval_43629_unit.py deleted file mode 100644 index b826844d2cb44..0000000000000 --- a/pandas/tests/arithmetic/test_interval_43629_unit.py +++ /dev/null @@ -1,96 +0,0 @@ -from pandas.arrays import IntervalArray -from pandas import Interval -import pandas._testing as tm - -# Unit tests for issue 43629 -class TestIntervalArithmetic: - def test_addition(self): - cases = [ - # [1, 5] + [2, 7] = [1, 7] - (Interval(1, 5, 'both'), Interval(2, 7, 'both'), Interval(1, 7, 'both')), - # (1, 5) + (2, 7) = (1, 7) - (Interval(1, 5, 'neither'), Interval(2, 7, 'neither'), Interval(1, 7, 'neither')), - # (1, 5] + (2, 7] = (1, 7] - (Interval(1, 5, 'right'), Interval(2, 7, 'right'), Interval(1, 7, 'right')), - # (1, 5] + (5, 7] = (1, 7] - (Interval(1, 5, 'right'), Interval(5, 7, 'right'), Interval(1, 7, 'right')), - # (1, 5) + [5, 7] = (1, 7] - (Interval(1, 5, 'neither'), Interval(5, 7, 'both'), Interval(1, 7, 'right')), - # (1, 5) + (5, 7) = [(1, 5), (5, 7)] - (Interval(1, 5, 'neither'), Interval(5, 7, 'neither'), [Interval(1, 5, 'neither'), Interval(5, 7, 'neither')]), - # (1, 4) + (5, 7] = [(1, 4), (5, 7]] - (Interval(1, 4, 'neither'), Interval(5, 7, 'right'), [Interval(1, 4, 'neither'), Interval(5, 7, 'right')]), - # (1, 7) + (5, 6] = (1, 7) - (Interval(1, 7, 'neither'), Interval(5, 6, 'right'), Interval(1, 7, 'neither')), - #(1,7] + [1,7) = [1,7] - (Interval(1, 7, 'right'), Interval(1, 7, 'left'), Interval(1, 7, 'both')) - ] - for (interval1, interval2, expected) in cases: - actual1 = interval1 + interval2 - actual2 = interval2 + interval1 - assert actual1 == expected - assert actual2 == expected - - - def test_multiplication(self): - cases = [ - # (1, 5) * (2, 7) = (2, 5) - (Interval(1, 5, 'neither'), Interval(2, 7, 'neither'), Interval(2, 5, 'neither')), - # (1, 5) * (6, 7) = (0, 0) - (Interval(1, 5, 'neither'), Interval(6, 7, 'neither'), Interval(0, 0, 'neither')), - # (1, 5] * (2, 7) = (2, 5] - (Interval(1, 5, 'right'), Interval(2, 7, 'neither'), Interval(2, 5, 'right')), - # (1, 7) * [3, 5] = [3, 5] - (Interval(1, 7, 'neither'), Interval(3, 5, 'both'), Interval(3, 5, 'both')), - # (0, 10] * (1, 2] = (1, 2] - (Interval(0, 10, 'right'), Interval(1, 2, 'right'), Interval(1, 2, 'right')), - - ] - for (interval1, interval2, expected) in cases: - actual1 = interval1 * interval2 - actual2 = interval2 * interval1 - assert actual1 == expected - assert actual2 == expected - - def test_subtraction(self): - cases = [ - # (0, 3) - (2, 6) = (0, 2] - (Interval(0, 3, 'neither'), Interval(2, 6, 'neither'), Interval(0, 2, 'right')), - # (1, 10) - (5, 6) = [(1,5],[6,10)] - (Interval(1, 10, 'neither'), Interval(5, 6, 'neither'), [Interval(1, 5, 'right'), Interval(6, 10, 'left')]), - # (1, 10) - [5, 6) = [(1,5),[6,10)] - (Interval(1, 10, 'neither'), Interval(5, 6, 'left'), [Interval(1, 5, 'neither'), Interval(6, 10, 'left')]), - # (1, 2) - (2, 4) = (1,2) - (Interval(1, 2, 'neither'), Interval(2, 4, 'neither'), (Interval(1, 2, 'neither'))), - ] - for (interval1, interval2, expected) in cases: - actual1 = interval1 - interval2 - assert actual1 == expected - - # (1, 2) - (2, 4) - (3, 5) = (1,2) - assert Interval(1, 2, 'neither') - Interval(2, 4, 'neither') - Interval(3, 5, 'neither') == Interval(1, 2, 'neither') - # (1, 10) - (5, 6) - (1,2)= [[2,5], [6,10)] - assert Interval(1, 10, 'neither') - Interval(5, 6, 'neither') - Interval(1, 2, 'neither') == [Interval(2, 5, 'both'), Interval(6, 10, 'left')] - -class TestIntervalArrayArithmetic: - def test_addition(self): - cases = [ - # (1, 2] + (2, 3] + (3, 4] = [(1, 4]] - (IntervalArray([Interval(1, 2, 'right'), Interval(2, 3, 'right'), Interval(3, 4, 'right')]), IntervalArray([Interval(1,4,'right')])), - # (1, 2] + (2, 3] + (4, 5] = [(1, 3],(4,5]] - (IntervalArray([Interval(1, 2, 'right'), Interval(2, 3, 'right'), Interval(4, 5, 'right')]), IntervalArray([Interval(1,3,'right'),Interval(4,5,'right')])) - ] - for (interval_arr, expected) in cases: - actual = interval_arr.sum() - tm.assert_interval_array_equal(actual, expected) - - def test_multiplication(self): - cases = [ - # (1, 2] * (2, 3] * (3, 4] = [(0, 0)] - (IntervalArray([Interval(1, 2, 'right'), Interval(2, 3, 'right'), Interval(3, 4, 'right')]), IntervalArray([Interval(0,0,'neither')])), - # [1, 2] * [2, 3] * [2, 4] = [[2, 2]] - (IntervalArray([Interval(1, 2, 'both'), Interval(2, 3, 'both'), Interval(4, 5, 'both')]), IntervalArray([Interval(2,2,'both')])) - ] - for (interval_arr, expected) in cases: - actual = interval_arr.product() - tm.assert_interval_array_equal(actual, expected)