From d149e67cca5239799d8710dcd479f82839a9ceb1 Mon Sep 17 00:00:00 2001 From: Venaturum <51399800+venaturum@users.noreply.github.com> Date: Fri, 15 Oct 2021 10:50:00 +1100 Subject: [PATCH 1/4] coverage method + tests + docs (GH12) (#14) * coverage method + tests + docs (GH12) * linting --- docs/reference/accessors.rst | 3 +- docs/reference/package.rst | 3 +- docs/release_notes/index.rst | 4 ++ piso/__init__.py | 1 + piso/accessor.py | 7 +++ piso/docstrings/accessor.py | 40 +++++++++++++++ piso/docstrings/intervalarray.py | 38 +++++++++++++++ piso/intervalarray.py | 27 +++++++++- tests/test_single_interval_array.py | 76 +++++++++++++++++++++++++++++ 9 files changed, 196 insertions(+), 3 deletions(-) diff --git a/docs/reference/accessors.rst b/docs/reference/accessors.rst index 5927321..d53bf93 100644 --- a/docs/reference/accessors.rst +++ b/docs/reference/accessors.rst @@ -15,4 +15,5 @@ Accessors ArrayAccessor.symmetric_difference ArrayAccessor.isdisjoint ArrayAccessor.issuperset - ArrayAccessor.issubset \ No newline at end of file + ArrayAccessor.issubset + ArrayAccessor.coverage \ No newline at end of file diff --git a/docs/reference/package.rst b/docs/reference/package.rst index b0f4af1..e5c4113 100644 --- a/docs/reference/package.rst +++ b/docs/reference/package.rst @@ -17,4 +17,5 @@ Top level functions symmetric_difference isdisjoint issuperset - issubset \ No newline at end of file + issubset + coverage \ No newline at end of file diff --git a/docs/release_notes/index.rst b/docs/release_notes/index.rst index ff5bc83..00e4945 100644 --- a/docs/release_notes/index.rst +++ b/docs/release_notes/index.rst @@ -4,6 +4,10 @@ Release notes ======================== +Added the following methods + +- :meth:`piso.coverage` +- :meth:`ArrayAccessor.coverage() ` ADD UNRELEASED CHANGES ABOVE THIS LINE diff --git a/piso/__init__.py b/piso/__init__.py index c589f89..b7263e1 100644 --- a/piso/__init__.py +++ b/piso/__init__.py @@ -1,4 +1,5 @@ from piso.intervalarray import ( + coverage, difference, intersection, isdisjoint, diff --git a/piso/accessor.py b/piso/accessor.py index 354c174..c6afdcc 100644 --- a/piso/accessor.py +++ b/piso/accessor.py @@ -141,6 +141,13 @@ def issubset(self, *interval_arrays, squeeze=False): squeeze=squeeze, ) + @Appender(docstrings.coverage_docstring, join="\n", indents=1) + def coverage(self, domain=None): + return intervalarray.coverage( + self._interval_array, + domain, + ) + def _register_accessors(): _register_accessor("piso", pd.IntervalIndex)(ArrayAccessor) diff --git a/piso/docstrings/accessor.py b/piso/docstrings/accessor.py index dc6e5d8..142b1eb 100644 --- a/piso/docstrings/accessor.py +++ b/piso/docstrings/accessor.py @@ -583,3 +583,43 @@ def join_params(list_of_param_strings): params=issubset_params, examples=issubset_examples, ) + + +coverage_docstring = """ +Calculates the fraction of a domain covered by a collection of intervals. + +The intervals are contained in the array object the accessor belongs to. +The (possibly overlapping) intervals may not, or partially, or wholly cover the domain. + +Parameters +---------- +domain : :class:`pandas.Interval`, or tuple equivalent, optional + Specifies the domain (lower and upper bounds) over which to assess the "coverage". + If *domain* is `None`, then the domain is considered to be the extremities of the intervals + contained in *interval_array* + +Returns +---------- +float + a number between 0 and 1, representing the fraction of the domain covered. + +Examples +----------- + +>>> import pandas as pd +>>> import piso +>>> piso.register_accessors() + +>>> arr1 = pd.arrays.IntervalArray.from_tuples( +... [(0, 4), (3, 5), (7, 8)], +... ) + +>>> arr1.piso.coverage() +0.75 + +>>> arr1.piso.coverage((0, 10)) +0.6 + +>>> arr1.piso.coverage(pd.Interval(-10, 10)) +0.3 +""" diff --git a/piso/docstrings/intervalarray.py b/piso/docstrings/intervalarray.py index 92e5f36..7eb1325 100644 --- a/piso/docstrings/intervalarray.py +++ b/piso/docstrings/intervalarray.py @@ -590,3 +590,41 @@ def join_params(list_of_param_strings): params=issubset_params, examples=issubset_examples, ) + + +coverage_docstring = """ +Calculates the fraction of a domain covered by a collection of intervals. + +Parameters +---------- +interval_array : :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` + Contains the (possibly overlapping) intervals which partially, or wholly cover the domain. +domain : :class:`pandas.Interval`, or tuple equivalent, optional + Specifies the domain (lower and upper bounds) over which to assess the "coverage". + If *domain* is none, then the domain is considered to be the extremities of the intervals + contained in *interval_array* + +Returns +---------- +float + a number between 0 and 1, representing the fraction of the domain covered. + +Examples +----------- + +>>> import pandas as pd +>>> import piso + +>>> arr1 = pd.arrays.IntervalArray.from_tuples( +... [(0, 4), (3, 5), (7, 8)], +... ) + +>>> piso.coverage(arr1) +0.75 + +>>> piso.coverage(arr1, (0, 10)) +0.6 + +>>> piso.coverage(arr1, pd.Interval(-10, 10)) +0.3 +""" diff --git a/piso/intervalarray.py b/piso/intervalarray.py index 3c635aa..86ec79e 100644 --- a/piso/intervalarray.py +++ b/piso/intervalarray.py @@ -55,7 +55,7 @@ def intersection( *interval_arrays, min_overlaps="all", squeeze=False, - return_type="infer" + return_type="infer", ): _validate_array_of_intervals_arrays(interval_array, *interval_arrays) klass = _get_return_type(interval_array, return_type) @@ -150,3 +150,28 @@ def _comp(ia): issuperset = _create_is_super_or_sub("superset", docstrings.issuperset_docstring) issubset = _create_is_super_or_sub("subset", docstrings.issubset_docstring) + + +def _get_domain_tuple(interval_array, domain): + if domain is None and len(interval_array) > 0: + domain = (interval_array.left.min(), interval_array.right.max()) + elif domain is None and len(interval_array) == 0: + domain = (0, 1) # dummy domain to ensure no failure + elif isinstance(domain, tuple): + if len(domain) != 2: + raise ValueError( + f"If domain parameter is tuple then it must have length 2. Supplied argument has length {len(domain)}." + ) + elif isinstance(domain, pd.Interval): + domain = (domain.left, domain.right) + else: + raise ValueError( + "The domain parameter must be either a 2-tuple, pandas.Interval, or None." + ) + return domain + + +@Appender(docstrings.coverage_docstring, join="\n", indents=1) +def coverage(interval_array, domain=None): + domain = _get_domain_tuple(interval_array, domain) + return _interval_x_to_stairs(interval_array).make_boolean().clip(*domain).mean() diff --git a/tests/test_single_interval_array.py b/tests/test_single_interval_array.py index bdb91dd..3a64f53 100644 --- a/tests/test_single_interval_array.py +++ b/tests/test_single_interval_array.py @@ -16,6 +16,7 @@ def get_accessor_method(self, function): piso_intervalarray.isdisjoint: self.piso.isdisjoint, piso_intervalarray.issuperset: self.piso.issuperset, piso_intervalarray.issubset: self.piso.issubset, + piso_intervalarray.coverage: self.piso.coverage, }[function] @@ -27,6 +28,7 @@ def get_package_method(function): piso_intervalarray.isdisjoint: piso_intervalarray.isdisjoint, piso_intervalarray.issuperset: piso.issuperset, piso_intervalarray.issubset: piso.issubset, + piso_intervalarray.coverage: piso.coverage, }[function] @@ -484,3 +486,77 @@ def test_isdisjoint(interval_index, tuples, expected, closed, date_type, how): interval_array = map_to_dates(interval_array, date_type) result = perform_op(interval_array, how=how, function=piso_intervalarray.isdisjoint) assert result == expected + + +@pytest.mark.parametrize( + "interval_index", + [True, False], +) +@pytest.mark.parametrize( + "domain, expected", + [(None, 10 / 12), ((0, 10), 0.8), (pd.Interval(0, 10), 0.8), ((15, 20), 0)], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +@pytest.mark.parametrize( + "how", + ["supplied", "accessor", "package"], +) +def test_coverage(interval_index, domain, expected, closed, how): + ia = make_ia1(interval_index, closed) + result = perform_op( + ia, + how=how, + function=piso_intervalarray.coverage, + domain=domain, + ) + assert result == expected + + +@pytest.mark.parametrize( + "interval_index", + [True, False], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +@pytest.mark.parametrize( + "how", + ["supplied", "accessor", "package"], +) +def test_coverage_edge_case(interval_index, closed, how): + ia = make_ia_from_tuples(interval_index, [], closed) + result = perform_op( + ia, + how=how, + function=piso_intervalarray.coverage, + domain=None, + ) + assert result == 0.0 + + +@pytest.mark.parametrize( + "interval_index", + [True, False], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +@pytest.mark.parametrize( + "how", + ["supplied", "accessor", "package"], +) +def test_coverage_exception(interval_index, closed, how): + domain = (1, 2, 3) + with pytest.raises(ValueError): + ia = make_ia1(interval_index, closed) + perform_op( + ia, + how=how, + function=piso_intervalarray.coverage, + domain=domain, + ) From b47ee7dd335842b69968896b909c2fc33a9acdef Mon Sep 17 00:00:00 2001 From: Venaturum Date: Sat, 16 Oct 2021 15:10:50 +1100 Subject: [PATCH 2/4] added complement method + docs + tests (#GH15) --- docs/reference/accessors.rst | 3 +- docs/reference/package.rst | 3 +- docs/release_notes/index.rst | 2 + piso/__init__.py | 1 + piso/accessor.py | 7 ++++ piso/docstrings/accessor.py | 55 +++++++++++++++++++++++++++ piso/docstrings/intervalarray.py | 59 ++++++++++++++++++++++++++++- piso/intervalarray.py | 12 ++++++ tests/test_single_interval_array.py | 45 ++++++++++++++++++++++ 9 files changed, 184 insertions(+), 3 deletions(-) diff --git a/docs/reference/accessors.rst b/docs/reference/accessors.rst index d53bf93..61fb829 100644 --- a/docs/reference/accessors.rst +++ b/docs/reference/accessors.rst @@ -16,4 +16,5 @@ Accessors ArrayAccessor.isdisjoint ArrayAccessor.issuperset ArrayAccessor.issubset - ArrayAccessor.coverage \ No newline at end of file + ArrayAccessor.coverage + ArrayAccessor.complement \ No newline at end of file diff --git a/docs/reference/package.rst b/docs/reference/package.rst index e5c4113..02633fc 100644 --- a/docs/reference/package.rst +++ b/docs/reference/package.rst @@ -18,4 +18,5 @@ Top level functions isdisjoint issuperset issubset - coverage \ No newline at end of file + coverage + complement \ No newline at end of file diff --git a/docs/release_notes/index.rst b/docs/release_notes/index.rst index 00e4945..6e8cc35 100644 --- a/docs/release_notes/index.rst +++ b/docs/release_notes/index.rst @@ -7,7 +7,9 @@ Release notes Added the following methods - :meth:`piso.coverage` +- :meth:`piso.complement` - :meth:`ArrayAccessor.coverage() ` +- :meth:`ArrayAccessor.complement() ` ADD UNRELEASED CHANGES ABOVE THIS LINE diff --git a/piso/__init__.py b/piso/__init__.py index b7263e1..d9bfae9 100644 --- a/piso/__init__.py +++ b/piso/__init__.py @@ -1,4 +1,5 @@ from piso.intervalarray import ( + complement, coverage, difference, intersection, diff --git a/piso/accessor.py b/piso/accessor.py index c6afdcc..0a73ce9 100644 --- a/piso/accessor.py +++ b/piso/accessor.py @@ -148,6 +148,13 @@ def coverage(self, domain=None): domain, ) + @Appender(docstrings.complement_docstring, join="\n", indents=1) + def complement(self, domain=None): + return intervalarray.complement( + self._interval_array, + domain, + ) + def _register_accessors(): _register_accessor("piso", pd.IntervalIndex)(ArrayAccessor) diff --git a/piso/docstrings/accessor.py b/piso/docstrings/accessor.py index 142b1eb..38453e7 100644 --- a/piso/docstrings/accessor.py +++ b/piso/docstrings/accessor.py @@ -623,3 +623,58 @@ def join_params(list_of_param_strings): >>> arr1.piso.coverage(pd.Interval(-10, 10)) 0.3 """ + +complement_docstring = """ +Calculates the complement of a collection of intervals (in an array) over some domain. + +Equivalent to the set difference of the domain and the intervals in the array that the accessor +belongs to. + +Parameters +---------- +domain : :py:class:`tuple`, :class:`pandas.Interval`, :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray`, optional + Specifies the domain over which to calculate the "complement". If *domain* is `None`, + then the domain is considered to be the extremities of the intervals contained in the interval array + that the accessor belongs to. If *domain* is a tuple then it should specify lower and upper bounds, and be equivalent to a + :class:`pandas.Interval`. If *domain* is a :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` + then the intervals it contains define a possibly disconnected domain. + +Returns +---------- +:class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` + The return type will be the same as the interval array object the accessor belongs to. + +Examples +----------- + +>>> import pandas as pd +>>> import piso + +>>> arr1 = pd.arrays.IntervalArray.from_tuples( +... [(0, 4), (3, 5), (7, 8)], +... ) + +>>> arr1.piso.complement() + +[(5, 7]] +Length: 1, closed: right, dtype: interval[int64] + +>>> arr1.piso.complement((-5, 10)) + +[(-5, 0], (5, 7], (8, 10]] +Length: 3, closed: right, dtype: interval[int64] + +>>> arr1.piso.complement(pd.Interval(-5, 6)) + +[(-5, 0], (5, 6]] +Length: 2, closed: right, dtype: interval[int64] + +>>> domain = pd.arrays.IntervalArray.from_tuples( +... [(-5,-2), (7,10)], +... ) + +>>> arr1.piso.complement(domain) + +[(-5, -2], (8, 10]] +Length: 2, closed: right, dtype: interval[int64] +""" diff --git a/piso/docstrings/intervalarray.py b/piso/docstrings/intervalarray.py index 7eb1325..557ed3a 100644 --- a/piso/docstrings/intervalarray.py +++ b/piso/docstrings/intervalarray.py @@ -601,7 +601,7 @@ def join_params(list_of_param_strings): Contains the (possibly overlapping) intervals which partially, or wholly cover the domain. domain : :class:`pandas.Interval`, or tuple equivalent, optional Specifies the domain (lower and upper bounds) over which to assess the "coverage". - If *domain* is none, then the domain is considered to be the extremities of the intervals + If *domain* is `None`, then the domain is considered to be the extremities of the intervals contained in *interval_array* Returns @@ -628,3 +628,60 @@ def join_params(list_of_param_strings): >>> piso.coverage(arr1, pd.Interval(-10, 10)) 0.3 """ + + +complement_docstring = """ +Calculates the complement of a collection of intervals (in an array) over some domain. + +Equivalent to the set difference of the domain and the intervals in the array. + +Parameters +---------- +interval_array : :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` + Contains the (possibly overlapping) intervals. +domain : :py:class:`tuple`, :class:`pandas.Interval`, :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray`, optional + Specifies the domain over which to calculate the "complement". If *domain* is `None`, + then the domain is considered to be the extremities of the intervals contained in *interval_array* + If *domain* is a tuple then it should specify lower and upper bounds, and be equivalent to a + :class:`pandas.Interval`. If *domain* is a :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` + then the intervals it contains define a possibly disconnected domain. + +Returns +---------- +:class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` + The return type will be the same as *interval_array*. + +Examples +----------- + +>>> import pandas as pd +>>> import piso + +>>> arr1 = pd.arrays.IntervalArray.from_tuples( +... [(0, 4), (3, 5), (7, 8)], +... ) + +>>> piso.complement(arr1) + +[(5, 7]] +Length: 1, closed: right, dtype: interval[int64] + +>>> piso.complement(arr1, (-5, 10)) + +[(-5, 0], (5, 7], (8, 10]] +Length: 3, closed: right, dtype: interval[int64] + +>>> piso.complement(arr1, pd.Interval(-5, 6)) + +[(-5, 0], (5, 6]] +Length: 2, closed: right, dtype: interval[int64] + +>>> domain = pd.arrays.IntervalArray.from_tuples( +... [(-5,-2), (7,10)], +... ) + +>>> piso.complement(arr1, domain) + +[(-5, -2], (8, 10]] +Length: 2, closed: right, dtype: interval[int64] +""" diff --git a/piso/intervalarray.py b/piso/intervalarray.py index 86ec79e..87f74cb 100644 --- a/piso/intervalarray.py +++ b/piso/intervalarray.py @@ -175,3 +175,15 @@ def _get_domain_tuple(interval_array, domain): def coverage(interval_array, domain=None): domain = _get_domain_tuple(interval_array, domain) return _interval_x_to_stairs(interval_array).make_boolean().clip(*domain).mean() + + +@Appender(docstrings.complement_docstring, join="\n", indents=1) +def complement(interval_array, domain=None): + stepfunction = _interval_x_to_stairs(interval_array).invert() + if isinstance(domain, (pd.IntervalIndex, pd.arrays.IntervalArray)): + domain = _interval_x_to_stairs(domain) + result = stepfunction.where(domain).fillna(0) + else: + domain = _get_domain_tuple(interval_array, domain) + result = stepfunction.clip(*domain).fillna(0) + return _boolean_stairs_to_interval_array(result, interval_array.__class__) diff --git a/tests/test_single_interval_array.py b/tests/test_single_interval_array.py index 3a64f53..aeb9f82 100644 --- a/tests/test_single_interval_array.py +++ b/tests/test_single_interval_array.py @@ -17,6 +17,7 @@ def get_accessor_method(self, function): piso_intervalarray.issuperset: self.piso.issuperset, piso_intervalarray.issubset: self.piso.issubset, piso_intervalarray.coverage: self.piso.coverage, + piso_intervalarray.complement: self.piso.complement, }[function] @@ -29,6 +30,7 @@ def get_package_method(function): piso_intervalarray.issuperset: piso.issuperset, piso_intervalarray.issubset: piso.issubset, piso_intervalarray.coverage: piso.coverage, + piso_intervalarray.complement: piso.complement, }[function] @@ -560,3 +562,46 @@ def test_coverage_exception(interval_index, closed, how): function=piso_intervalarray.coverage, domain=domain, ) + + +@pytest.mark.parametrize( + "interval_index", + [True, False], +) +@pytest.mark.parametrize( + "domain, expected_tuples", + [ + (None, [(6, 7), (9, 10)]), + ((-5, 15), [(-5, 0), (6, 7), (9, 10), (12, 15)]), + (pd.Interval(-5, 15), [(-5, 0), (6, 7), (9, 10), (12, 15)]), + ((6.5, 9.5), [(6.5, 7), (9, 9.5)]), + ((12, 15), [(12, 15)]), + ((6, 7), [(6, 7)]), + ((3, 4), []), + (pd.IntervalIndex.from_tuples([(-5, 5), (9, 11)]), [(-5, 0), (9, 10)]), + ], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +@pytest.mark.parametrize( + "how", + ["supplied", "accessor", "package"], +) +def test_complement(interval_index, domain, expected_tuples, closed, how): + if hasattr(domain, "set_closed"): + domain = domain.set_closed(closed) + ia = make_ia1(interval_index, closed) + expected = make_ia_from_tuples(False, expected_tuples, closed) + result = perform_op( + ia, + how=how, + function=piso_intervalarray.complement, + domain=domain, + ) + assert_interval_array_equal( + result, + expected, + interval_index, + ) From 37a76c0966ded2588bf3731318cb36b80da83604 Mon Sep 17 00:00:00 2001 From: Venaturum Date: Sat, 16 Oct 2021 15:33:36 +1100 Subject: [PATCH 3/4] allowing coverage method to accept interval array as domain specification (#16) --- piso/docstrings/accessor.py | 16 ++++++++++++---- piso/docstrings/intervalarray.py | 16 ++++++++++++---- piso/intervalarray.py | 10 ++++++++-- tests/test_single_interval_array.py | 11 ++++++++++- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/piso/docstrings/accessor.py b/piso/docstrings/accessor.py index 38453e7..aa70515 100644 --- a/piso/docstrings/accessor.py +++ b/piso/docstrings/accessor.py @@ -593,10 +593,12 @@ def join_params(list_of_param_strings): Parameters ---------- -domain : :class:`pandas.Interval`, or tuple equivalent, optional - Specifies the domain (lower and upper bounds) over which to assess the "coverage". - If *domain* is `None`, then the domain is considered to be the extremities of the intervals - contained in *interval_array* +domain : :py:class:`tuple`, :class:`pandas.Interval`, :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray`, optional + Specifies the domain over which to calculate the "coverage". If *domain* is `None`, + then the domain is considered to be the extremities of the intervals contained in the interval array the accessor belongs to. + If *domain* is a tuple then it should specify lower and upper bounds, and be equivalent to a + :class:`pandas.Interval`. If *domain* is a :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` + then the intervals it contains define a possibly disconnected domain. Returns ---------- @@ -622,6 +624,12 @@ def join_params(list_of_param_strings): >>> arr1.piso.coverage(pd.Interval(-10, 10)) 0.3 + +>>> domain = pd.arrays.IntervalArray.from_tuples( +... [(4,6), (7, 9)], +... ) +>>> arr1.piso.coverage(domain) +0.5 """ complement_docstring = """ diff --git a/piso/docstrings/intervalarray.py b/piso/docstrings/intervalarray.py index 557ed3a..89f26da 100644 --- a/piso/docstrings/intervalarray.py +++ b/piso/docstrings/intervalarray.py @@ -599,10 +599,12 @@ def join_params(list_of_param_strings): ---------- interval_array : :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` Contains the (possibly overlapping) intervals which partially, or wholly cover the domain. -domain : :class:`pandas.Interval`, or tuple equivalent, optional - Specifies the domain (lower and upper bounds) over which to assess the "coverage". - If *domain* is `None`, then the domain is considered to be the extremities of the intervals - contained in *interval_array* +domain : :py:class:`tuple`, :class:`pandas.Interval`, :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray`, optional + Specifies the domain over which to calculate the "coverage". If *domain* is `None`, + then the domain is considered to be the extremities of the intervals contained in *interval_array* + If *domain* is a tuple then it should specify lower and upper bounds, and be equivalent to a + :class:`pandas.Interval`. If *domain* is a :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` + then the intervals it contains define a possibly disconnected domain. Returns ---------- @@ -627,6 +629,12 @@ def join_params(list_of_param_strings): >>> piso.coverage(arr1, pd.Interval(-10, 10)) 0.3 + +>>> domain = pd.arrays.IntervalArray.from_tuples( +... [(4,6), (7, 9)], +... ) +>>> piso.coverage(arr1, domain) +0.5 """ diff --git a/piso/intervalarray.py b/piso/intervalarray.py index 87f74cb..6cb8a3f 100644 --- a/piso/intervalarray.py +++ b/piso/intervalarray.py @@ -173,8 +173,14 @@ def _get_domain_tuple(interval_array, domain): @Appender(docstrings.coverage_docstring, join="\n", indents=1) def coverage(interval_array, domain=None): - domain = _get_domain_tuple(interval_array, domain) - return _interval_x_to_stairs(interval_array).make_boolean().clip(*domain).mean() + stepfunction = _interval_x_to_stairs(interval_array).make_boolean() + if isinstance(domain, (pd.IntervalIndex, pd.arrays.IntervalArray)): + domain = _interval_x_to_stairs(domain) + result = stepfunction.where(domain).mean() + else: + domain = _get_domain_tuple(interval_array, domain) + result = stepfunction.clip(*domain).mean() + return result @Appender(docstrings.complement_docstring, join="\n", indents=1) diff --git a/tests/test_single_interval_array.py b/tests/test_single_interval_array.py index aeb9f82..e765b11 100644 --- a/tests/test_single_interval_array.py +++ b/tests/test_single_interval_array.py @@ -496,7 +496,14 @@ def test_isdisjoint(interval_index, tuples, expected, closed, date_type, how): ) @pytest.mark.parametrize( "domain, expected", - [(None, 10 / 12), ((0, 10), 0.8), (pd.Interval(0, 10), 0.8), ((15, 20), 0)], + [ + (None, 10 / 12), + ((0, 10), 0.8), + (pd.Interval(0, 10), 0.8), + ((15, 20), 0), + (pd.IntervalIndex.from_tuples([(0, 6), (10, 12)]), 1), + (pd.IntervalIndex.from_tuples([(6, 7), (9, 10)]), 0), + ], ) @pytest.mark.parametrize( "closed", @@ -507,6 +514,8 @@ def test_isdisjoint(interval_index, tuples, expected, closed, date_type, how): ["supplied", "accessor", "package"], ) def test_coverage(interval_index, domain, expected, closed, how): + if hasattr(domain, "set_closed"): + domain = domain.set_closed(closed) ia = make_ia1(interval_index, closed) result = perform_op( ia, From 512bb1639a10ca57ba02569d6d699d1998b0fc42 Mon Sep 17 00:00:00 2001 From: Riley Clement Date: Sat, 23 Oct 2021 09:46:40 +1100 Subject: [PATCH 4/4] v0.3.0 --- docs/reference/index.rst | 5 ++++- docs/release_notes/index.rst | 8 ++++++-- pyproject.toml | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/reference/index.rst b/docs/reference/index.rst index d33f943..ac45d1b 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -12,4 +12,7 @@ This page gives an overview of all public `piso` functionality. Classes and fun package accessors - interval \ No newline at end of file + interval + +.. automodule:: piso + :undoc-members: \ No newline at end of file diff --git a/docs/release_notes/index.rst b/docs/release_notes/index.rst index 6e8cc35..03d7053 100644 --- a/docs/release_notes/index.rst +++ b/docs/release_notes/index.rst @@ -4,6 +4,12 @@ Release notes ======================== + +ADD UNRELEASED CHANGES ABOVE THIS LINE + + +**v0.3.0 2021-10-23** + Added the following methods - :meth:`piso.coverage` @@ -11,8 +17,6 @@ Added the following methods - :meth:`ArrayAccessor.coverage() ` - :meth:`ArrayAccessor.complement() ` -ADD UNRELEASED CHANGES ABOVE THIS LINE - **v0.2.0 2021-10-15** diff --git a/pyproject.toml b/pyproject.toml index 7e8fbbd..a418b71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "poetry.masonry.api" [tool.poetry] name = "piso" -version = "0.2.0" +version = "0.3.0" description = "Pandas Interval Set Operations: methods for set operations for pandas' Interval, IntervalArray and IntervalIndex" readme = "README.md" authors = ["Riley Clement "]