From 605004a31e8604fe8fd2cd3c91bc948abdf36a28 Mon Sep 17 00:00:00 2001 From: Riley Clement Date: Sun, 10 Oct 2021 00:00:04 +1100 Subject: [PATCH 01/11] Final logo --- docs/img/piso_social_transparent.svg | 309 +++++++++++++++++++++------ 1 file changed, 246 insertions(+), 63 deletions(-) diff --git a/docs/img/piso_social_transparent.svg b/docs/img/piso_social_transparent.svg index c858265..6fef4e8 100644 --- a/docs/img/piso_social_transparent.svg +++ b/docs/img/piso_social_transparent.svg @@ -29,9 +29,9 @@ showgrid="false" showguides="true" inkscape:guide-bbox="true" - inkscape:zoom="0.12787542" - inkscape:cx="1736.0647" - inkscape:cy="1360.6993" + inkscape:zoom="0.44147994" + inkscape:cx="2398.75" + inkscape:cy="1393.0418" inkscape:window-width="1920" inkscape:window-height="1002" inkscape:window-x="-8" @@ -240,9 +240,9 @@ y="0">PISO + id="tspan1899">PISO PISO + id="tspan1903">PISO P + id="tspan1907">P I + id="tspan1911">I S + id="tspan1915">S O + id="tspan1919">O P + id="tspan1923">P I + id="tspan1927">I S + id="tspan1931">S O + id="tspan1935">O P + id="tspan1939">P I + id="tspan1943">I S + id="tspan1947">S O + id="tspan1951">O P + id="tspan1955">P I + id="tspan1959">I S + id="tspan1963">S O + id="tspan1967">O P + id="tspan1971">P I + id="tspan1975">I S + id="tspan1979">S O + id="tspan1983">O P + id="tspan1987">P I + id="tspan1991">I S + id="tspan1995">S O + id="tspan1999">O P + id="tspan2003">P I + id="tspan2007">I S + id="tspan2011">S O + id="tspan2015">O + + + P + I + S + O + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Date: Sun, 10 Oct 2021 12:17:10 +1100 Subject: [PATCH 02/11] Minor doc updates --- README.md | 4 +--- docs/_static/custom.css | 4 +--- docs/index.rst | 3 ++- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 68bbfb5..b5649e2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@

piso logo

-

@@ -58,14 +57,13 @@ python -m pip install piso To install the latest version through conda-forge:: ```sh -conda install -c conda-forge piso` +conda install -c conda-forge piso ``` ## Versioning [SemVer](http://semver.org/) is used by piso for versioning releases. For versions available, see the [tags on this repository](https://github.com/staircase-dev/piso/tags). - ## License This project is licensed under the [MIT License](https://github.com/staircase-dev/piso/blob/master/LICENSE) diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 00f2a6f..9037637 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -1,7 +1,5 @@ - :root { - /* Use softer blue from bootstrap's default info color */ - --pst-color-primary: 92,92,92; + --pst-color-primary: 92, 92, 92; --pst-color-active-navigation: 231, 4, 136; --pst-color-navbar-link: 231, 4, 136; --pst-color-navbar-link-hover: 240, 40, 180; diff --git a/docs/index.rst b/docs/index.rst index 061e96d..fd9f913 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,5 @@ .. image:: img/piso_social_white.svg - :width: 80% + :width: 100% :alt: piso logo :align: center @@ -19,6 +19,7 @@ Pandas Interval Set Operations: methods for set operations for pandas' Interval, IntervalArray and IntervalIndex .. image:: img/powered_by_staircase.svg + :target: https://www.staircase.dev :width: 200 :alt: powered_by_staircase :align: center From f7c28ce4d72fb38e10cffc6ad2e5ecf59f2f8f77 Mon Sep 17 00:00:00 2001 From: Riley Clement Date: Sun, 10 Oct 2021 15:54:32 +1100 Subject: [PATCH 03/11] Fixed typo in sphinx conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 933ffcf..a2569f9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -30,7 +30,7 @@ # -- Project information ----------------------------------------------------- -project = "staircase" +project = "piso" copyright = "2021, Riley Clement" author = "Riley Clement" version = piso.__version__ From 27ecb6872c171bd3e6ac2dbc09e6a96917e125f5 Mon Sep 17 00:00:00 2001 From: Riley Clement Date: Tue, 12 Oct 2021 17:58:14 +1100 Subject: [PATCH 04/11] Dependency update --- poetry.lock | 76 +++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1f95306..b1013b5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -196,7 +196,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.0.1" +version = "6.0.2" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -1221,7 +1221,7 @@ test = ["pytest"] [[package]] name = "staircase" -version = "2.0.3" +version = "2.0.4" description = "A data analysis package based on modelling and manipulation of mathematical step functions. Strongly aligned with pandas." category = "main" optional = false @@ -1407,7 +1407,7 @@ codecov = [] [metadata] lock-version = "1.1" python-versions = "^3.6.1" -content-hash = "0c02826b5a19e9649cfa35dc3509171da804c1c04f713da7ebaeb7846010e98f" +content-hash = "0a47702bf0b60eb123c87a8953108f3b390be1ada8017fe850f7af71b67e1836" [metadata.files] alabaster = [ @@ -1531,39 +1531,39 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:abe8207dfb8a61ded9cd830d26c1073c8218fc0ae17eb899cfe8ec0fafae6e22"}, - {file = "coverage-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83faa3692e8306b20293889714fdf573d10ef5efc5843bd7c7aea6971487bd6a"}, - {file = "coverage-6.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f82a17f2a77958f3eef40ad385fc82d4c6ba9a77a51a174efe03ce75daebbc16"}, - {file = "coverage-6.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b06f4f1729e2963281d9cd6e65e6976bf27b44d4c07ac5b47223ce45f822cec"}, - {file = "coverage-6.0.1-cp310-cp310-win32.whl", hash = "sha256:7600fac458f74c68b097379f76f3a6e3a630493fc7fc94b6508fedd9d498c194"}, - {file = "coverage-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:2c5f39d1556e75fc3c4fb071f9e7cfa618895a999a0de763a541d730775d0d5f"}, - {file = "coverage-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3edbb3ec580c73e5a264f5d04f30245bc98eff1a26765d46c5c65134f0a0e2f7"}, - {file = "coverage-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea452a2d83964d08232ade470091015e7ab9b8f53acbec10f2210fbab4ce7e43"}, - {file = "coverage-6.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1770d24f45f1f2daeae34cfa3b33fcb29702153544cd2ad40d58399dd4ff53b5"}, - {file = "coverage-6.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ad7182a82843f9f85487f44567c8c688f16c906bdb8d0e44ae462aed61cb8f1b"}, - {file = "coverage-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:9d242a2434801ef5125330deddb4cddba8990c9a49b3dec99dca17dd7eefba5a"}, - {file = "coverage-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e66c50f0ab445fec920a9f084914ea1776a809e3016c3738519048195f851bbb"}, - {file = "coverage-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5b1ceacb86e0a9558061dcc6baae865ed25933ea57effea644f21657cdce19bc"}, - {file = "coverage-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e15ab5afbee34abf716fece80ea33ea09a82e7450512f022723b1a82ec9a4e"}, - {file = "coverage-6.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6873f3f954d3e3ab8b1881f4e5307cc19f70c9f931c41048d9f7e6fd946eabe7"}, - {file = "coverage-6.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0898d6948b31df13391cd40568de8f35fa5901bc922c5ae05cf070587cb9c666"}, - {file = "coverage-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9c416ba03844608f45661a5b48dc59c6b5e89956efe388564dd138ca8caf540b"}, - {file = "coverage-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:66fe33e9e0df58675e08e83fe257f89e7f625e7633ea93d0872154e09cce2724"}, - {file = "coverage-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:353a50f123f0185cdb7a1e1e3e2cfb9d1fd7e293cfaf68eedaf5bd8e02e3ec32"}, - {file = "coverage-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b81a4e667c45b13658b84f9b8f1d32ef86d5405fabcbd181b76b9e51d295f397"}, - {file = "coverage-6.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:17426808e8e0824f864876312d41961223bf5e503bf8f1f846735279a60ea345"}, - {file = "coverage-6.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e11cca9eb5c9b3eaad899728ee2ce916138399ee8cbbccaadc1871fecb750827"}, - {file = "coverage-6.0.1-cp38-cp38-win32.whl", hash = "sha256:0a7e55cc9f7efa22d5cc9966276ec7a40a8803676f6ccbfdc06a486fba9aa9ee"}, - {file = "coverage-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:4eb9cd910ca8e243f930243a9940ea1a522e32435d15668445753d087c30ee12"}, - {file = "coverage-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:83682b73785d2e078e0b5f63410b8125b122e1a22422640c57edd4011c950f3e"}, - {file = "coverage-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b45f89a8ef65c29195f8f28dbe215f44ccb29d934f3e862d2a5c12e38698a793"}, - {file = "coverage-6.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73880a80fad0597eca43e213e5e1711bf6c0fcdb7eb6b01b3b17841ebe5a7f8d"}, - {file = "coverage-6.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f398d38e6ebc2637863db1d7be3d4f9c5174e7d24bb3b0716cdb1f204669cbcf"}, - {file = "coverage-6.0.1-cp39-cp39-win32.whl", hash = "sha256:1864bdf9b2ccb43e724051bc23a1c558daf101ad4488ede1945f2a8be1facdad"}, - {file = "coverage-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:c9c413c4397d4cdc7ca89286158d240ce524f9667b52c9a64dd7e13d16cf8815"}, - {file = "coverage-6.0.1-pp36-none-any.whl", hash = "sha256:65da6e3e8325291f012921bbf71fea0a97824e1c573981871096aac6e2cf0ec5"}, - {file = "coverage-6.0.1-pp37-none-any.whl", hash = "sha256:07efe1fbd72e67df026ad5109bcd216acbbd4a29d5208b3dab61779bae6b7b26"}, - {file = "coverage-6.0.1.tar.gz", hash = "sha256:3490ff6dbf3f7accf0750136ed60ae1f487bccc1f097740e3b21262bc9c89854"}, + {file = "coverage-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1549e1d08ce38259de2bc3e9a0d5f3642ff4a8f500ffc1b2df73fd621a6cdfc0"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcae10fccb27ca2a5f456bf64d84110a5a74144be3136a5e598f9d9fb48c0caa"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:53a294dc53cfb39c74758edaa6305193fb4258a30b1f6af24b360a6c8bd0ffa7"}, + {file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8251b37be1f2cd9c0e5ccd9ae0380909c24d2a5ed2162a41fcdbafaf59a85ebd"}, + {file = "coverage-6.0.2-cp310-cp310-win32.whl", hash = "sha256:db42baa892cba723326284490283a68d4de516bfb5aaba369b4e3b2787a778b7"}, + {file = "coverage-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:bbffde2a68398682623d9dd8c0ca3f46fda074709b26fcf08ae7a4c431a6ab2d"}, + {file = "coverage-6.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:60e51a3dd55540bec686d7fff61b05048ca31e804c1f32cbb44533e6372d9cc3"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a6a9409223a27d5ef3cca57dd7cd4dfcb64aadf2fad5c3b787830ac9223e01a"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4b34ae4f51bbfa5f96b758b55a163d502be3dcb24f505d0227858c2b3f94f5b9"}, + {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3bbda1b550e70fa6ac40533d3f23acd4f4e9cb4e6e77251ce77fdf41b3309fb2"}, + {file = "coverage-6.0.2-cp36-cp36m-win32.whl", hash = "sha256:4e28d2a195c533b58fc94a12826f4431726d8eb029ac21d874345f943530c122"}, + {file = "coverage-6.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a82d79586a0a4f5fd1cf153e647464ced402938fbccb3ffc358c7babd4da1dd9"}, + {file = "coverage-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3be1206dc09fb6298de3fce70593e27436862331a85daee36270b6d0e1c251c4"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cd3828bbe1a40070c11fe16a51df733fd2f0cb0d745fb83b7b5c1f05967df7"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d036dc1ed8e1388e995833c62325df3f996675779541f682677efc6af71e96cc"}, + {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04560539c19ec26995ecfb3d9307ff154fbb9a172cb57e3b3cfc4ced673103d1"}, + {file = "coverage-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:e4fb7ced4d9dec77d6cf533acfbf8e1415fe799430366affb18d69ee8a3c6330"}, + {file = "coverage-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:77b1da5767ed2f44611bc9bc019bc93c03fa495728ec389759b6e9e5039ac6b1"}, + {file = "coverage-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61b598cbdbaae22d9e34e3f675997194342f866bb1d781da5d0be54783dce1ff"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36e9040a43d2017f2787b28d365a4bb33fcd792c7ff46a047a04094dc0e2a30d"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9f1627e162e3864a596486774876415a7410021f4b67fd2d9efdf93ade681afc"}, + {file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7a0b42db2a47ecb488cde14e0f6c7679a2c5a9f44814393b162ff6397fcdfbb"}, + {file = "coverage-6.0.2-cp38-cp38-win32.whl", hash = "sha256:a1b73c7c4d2a42b9d37dd43199c5711d91424ff3c6c22681bc132db4a4afec6f"}, + {file = "coverage-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:1db67c497688fd4ba85b373b37cc52c50d437fd7267520ecd77bddbd89ea22c9"}, + {file = "coverage-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f2f184bf38e74f152eed7f87e345b51f3ab0b703842f447c22efe35e59942c24"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1cf1deb3d5544bd942356364a2fdc8959bad2b6cf6eb17f47d301ea34ae822"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad9b8c1206ae41d46ec7380b78ba735ebb77758a650643e841dd3894966c31d0"}, + {file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:381d773d896cc7f8ba4ff3b92dee4ed740fb88dfe33b6e42efc5e8ab6dfa1cfe"}, + {file = "coverage-6.0.2-cp39-cp39-win32.whl", hash = "sha256:424c44f65e8be58b54e2b0bd1515e434b940679624b1b72726147cfc6a9fc7ce"}, + {file = "coverage-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:abbff240f77347d17306d3201e14431519bf64495648ca5a49571f988f88dee9"}, + {file = "coverage-6.0.2-pp36-none-any.whl", hash = "sha256:7092eab374346121805fb637572483270324407bf150c30a3b161fc0c4ca5164"}, + {file = "coverage-6.0.2-pp37-none-any.whl", hash = "sha256:30922626ce6f7a5a30bdba984ad21021529d3d05a68b4f71ea3b16bda35b8895"}, + {file = "coverage-6.0.2.tar.gz", hash = "sha256:6807947a09510dc31fa86f43595bf3a14017cd60bf633cc746d52141bfa6b149"}, ] cycler = [ {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"}, @@ -2218,8 +2218,8 @@ sphinxcontrib-serializinghtml = [ {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] staircase = [ - {file = "staircase-2.0.3-py3-none-any.whl", hash = "sha256:d856f8d37ef789309ca575053457b9b52f77244ac1a5338af811c9ede982da50"}, - {file = "staircase-2.0.3.tar.gz", hash = "sha256:e0640f4813574b742a80a054cce25a00895571f8f43e4b7a52a58a53944b7872"}, + {file = "staircase-2.0.4-py3-none-any.whl", hash = "sha256:c45291e8c91c3faf485790b483563580098c55f2c2f586ac7b41f05cbd27d8ac"}, + {file = "staircase-2.0.4.tar.gz", hash = "sha256:25e019e3d3d930153bd2c5381029198e865db0b0732fdda6f85550930ffee14c"}, ] terminado = [ {file = "terminado-0.12.1-py3-none-any.whl", hash = "sha256:09fdde344324a1c9c6e610ee4ca165c4bb7f5bbf982fceeeb38998a988ef8452"}, diff --git a/pyproject.toml b/pyproject.toml index ae52f38..c69a84e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ classifiers=[ [tool.poetry.dependencies] python = "^3.6.1" -staircase = "^2.0.3" +staircase = "^2.0.4" pandas = "^1" From b5e4c7788606943cc5e7b46af2b936b91e375244 Mon Sep 17 00:00:00 2001 From: Riley Clement Date: Tue, 12 Oct 2021 19:40:57 +1100 Subject: [PATCH 05/11] added piso.isdisjoint and corresponding accessor method --- docs/reference/accessors.rst | 3 +- docs/reference/package.rst | 3 +- docs/release_notes/index.rst | 7 ++- piso/__init__.py | 8 ++- piso/accessor.py | 7 +++ piso/docstrings/accessor.py | 82 ++++++++++++++++++++++++--- piso/docstrings/intervalarray.py | 78 +++++++++++++++++++++++-- piso/intervalarray.py | 16 ++++++ piso/util.py | 1 + tests/test_multiple_interval_array.py | 65 +++++++++++++++++++++ tests/test_single_interval_array.py | 62 ++++++++++++++++++++ 11 files changed, 313 insertions(+), 19 deletions(-) diff --git a/docs/reference/accessors.rst b/docs/reference/accessors.rst index 9682181..ce664b6 100644 --- a/docs/reference/accessors.rst +++ b/docs/reference/accessors.rst @@ -12,4 +12,5 @@ Accessors ArrayAccessor.union ArrayAccessor.intersection ArrayAccessor.difference - ArrayAccessor.symmetric_difference \ No newline at end of file + ArrayAccessor.symmetric_difference + ArrayAccessor.isdisjoint \ No newline at end of file diff --git a/docs/reference/package.rst b/docs/reference/package.rst index bfc4708..fcd18c0 100644 --- a/docs/reference/package.rst +++ b/docs/reference/package.rst @@ -14,4 +14,5 @@ Top level functions union intersection difference - symmetric_difference \ No newline at end of file + symmetric_difference + isdisjoint \ No newline at end of file diff --git a/docs/release_notes/index.rst b/docs/release_notes/index.rst index 7873be7..56509a9 100644 --- a/docs/release_notes/index.rst +++ b/docs/release_notes/index.rst @@ -4,9 +4,13 @@ Release notes ======================== +- added :meth:`piso.isdisjoint` method, and corresponding accessor method + +ADD UNRELEASED CHANGED ABOVE THIS LINE + **v0.1.0 2021-10-10** -The following methods are included in the initial release of `piso` +The following methods (and corresponding accessor methods) are included in the initial release of `piso` - :meth:`piso.register_accessors` - :meth:`piso.union` @@ -17,3 +21,4 @@ The following methods are included in the initial release of `piso` - :meth:`piso.interval.intersection` - :meth:`piso.interval.difference` - :meth:`piso.interval.symmetric_difference` + diff --git a/piso/__init__.py b/piso/__init__.py index f9f67f2..f996cf9 100644 --- a/piso/__init__.py +++ b/piso/__init__.py @@ -1,4 +1,10 @@ -from piso.intervalarray import difference, intersection, symmetric_difference, union +from piso.intervalarray import ( + difference, + intersection, + isdisjoint, + symmetric_difference, + union, +) def register_accessors(): diff --git a/piso/accessor.py b/piso/accessor.py index cb03bf6..b22b3e4 100644 --- a/piso/accessor.py +++ b/piso/accessor.py @@ -118,6 +118,13 @@ def symmetric_difference( return_type=return_type, ) + @Appender(docstrings.isdisjoint_docstring, join="\n", indents=1) + def isdisjoint(self, *interval_arrays): + return intervalarray.isdisjoint( + self._interval_array, + *interval_arrays, + ) + def _register_accessors(): _register_accessor("piso", pd.IntervalIndex)(ArrayAccessor) diff --git a/piso/docstrings/accessor.py b/piso/docstrings/accessor.py index da9a779..a44dcfb 100644 --- a/piso/docstrings/accessor.py +++ b/piso/docstrings/accessor.py @@ -257,6 +257,38 @@ """ +isdisjoint_examples = """ +Examples +----------- + +>>> import pandas as pd +>>> import piso +>>> piso.register_accessors() + +>>> arr1 = pd.arrays.IntervalArray.from_tuples( +... [(0, 3), (2, 4)], +... ) +>>> arr2 = pd.arrays.IntervalArray.from_tuples( +... [(4, 7), (8, 11)], +... ) +>>> arr3 = pd.arrays.IntervalArray.from_tuples( +... [(2, 4), (7, 8)], +... ) + +>>> arr1.piso.isdisjoint() +False + +>>> arr2.piso.isdisjoint() +True + +>>> arr1.piso.isdisjoint(arr2) +True + +>>> arr1.piso.isdisjoint(arr3) +False +""" + + def join_params(list_of_param_strings): return "".join(list_of_param_strings).replace("\n\n", "\n") @@ -291,17 +323,15 @@ def join_params(list_of_param_strings): If supplied, must be done so as a keyword argument. """ - template_doc = """ -Performs a set {operation} operation. - What is considered a set is determined by the number of positional arguments used, that is, determined by the size of *interval_arrays*. -If *interval_arrays* is empty then the sets are considered to be the intervals contained in *interval_array*. +If *interval_arrays* is empty then the sets are considered to be the intervals contained in the array object the +accessor belongs to (an instance of :class:`pandas.IntervalIndex`, :class:`pandas.arrays.IntervalArray`). If *interval_arrays* is not empty then the sets are considered to be the elements in *interval_arrays*, in addition to the -interval array object the accessor belongs to (an instance of :class:`pandas.IntervalIndex`, :class:`pandas.arrays.IntervalArray`). +intervals in the array object the accessor belongs to. Each of these arrays is assumed to contain disjoint intervals (and satisfy the definition of a set). Any array containing overlaps between intervals will be mapped to one with disjoint intervals via a union operation. @@ -312,11 +342,18 @@ def join_params(list_of_param_strings): Returns ---------- -:class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` +{return_type} {examples} """ +operation_template_doc = ( + """ +Performs a set {operation} operation. +""" + + template_doc +) + doc_difference_template = """ Performs a set difference operation. @@ -346,6 +383,9 @@ def join_params(list_of_param_strings): {examples} """ +array_return_type = ( + ":class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray`" +) union_params = join_params( [ @@ -354,10 +394,11 @@ def join_params(list_of_param_strings): param_return_type, ] ) -union_docstring = template_doc.format( +union_docstring = operation_template_doc.format( operation="union", extra_desc="", params=union_params, + return_type=array_return_type, examples=union_examples, ) @@ -369,10 +410,11 @@ def join_params(list_of_param_strings): param_return_type, ] ) -intersection_docstring = template_doc.format( +intersection_docstring = operation_template_doc.format( operation="intersection", extra_desc="", params=intersection_params, + return_type=array_return_type, examples=intersection_examples, ) @@ -387,6 +429,7 @@ def join_params(list_of_param_strings): operation="difference", extra_desc="", params=difference_params, + return_type=array_return_type, examples=difference_examples, ) @@ -404,9 +447,30 @@ def join_params(list_of_param_strings): The parameter *min_overlaps* in :meth:`piso.intersection`, which defines the minimum number of intervals in an overlap required to constitute an intersection, follows through to symmetric difference under this definition. """ -symmetric_difference_docstring = template_doc.format( +symmetric_difference_docstring = operation_template_doc.format( operation="symmetric difference", extra_desc=symmetric_difference_extra_desc, params=symmetric_difference_params, + return_type=array_return_type, examples=symmetric_difference_examples, ) + + +isdisjoint_doc = ( + """ +Indicates whether one, or more, sets are disjoint or not. +""" + + template_doc +) + +isdisjoint_params = join_params( + [ + param_optional_args, + ] +) +isdisjoint_docstring = isdisjoint_doc.format( + extra_desc="", + params=isdisjoint_params, + return_type="boolean", + examples=isdisjoint_examples, +) diff --git a/piso/docstrings/intervalarray.py b/piso/docstrings/intervalarray.py index 09c6371..6ca7231 100644 --- a/piso/docstrings/intervalarray.py +++ b/piso/docstrings/intervalarray.py @@ -241,6 +241,37 @@ """ +isdisjoint_examples = """ +Examples +----------- + +>>> import pandas as pd +>>> import piso + +>>> arr1 = pd.arrays.IntervalArray.from_tuples( +... [(0, 3), (2, 4)], +... ) +>>> arr2 = pd.arrays.IntervalArray.from_tuples( +... [(4, 7), (8, 11)], +... ) +>>> arr3 = pd.arrays.IntervalArray.from_tuples( +... [(2, 4), (7, 8)], +... ) + +>>> piso.isdisjoint(arr1) +False + +>>> piso.isdisjoint(arr2) +True + +>>> piso.isdisjoint(arr1, arr2) +True + +>>> piso.isdisjoint(arr1, arr3) +False +""" + + def join_params(list_of_param_strings): return "".join(list_of_param_strings).replace("\n\n", "\n") @@ -282,8 +313,6 @@ def join_params(list_of_param_strings): template_doc = """ -Performs a set {operation} operation. - What is considered a set is determined by the number of positional arguments used, that is, determined by the size of *interval_arrays*. @@ -300,11 +329,20 @@ def join_params(list_of_param_strings): Returns ---------- -:class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` +{return_type} {examples} """ + +operation_template_doc = ( + """ +Performs a set {operation} operation. +""" + + template_doc +) + + doc_difference_template = """ Performs a set difference operation. @@ -332,6 +370,9 @@ def join_params(list_of_param_strings): {examples} """ +array_return_type = ( + ":class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray`" +) union_params = join_params( [ @@ -341,10 +382,11 @@ def join_params(list_of_param_strings): param_return_type, ] ) -union_docstring = template_doc.format( +union_docstring = operation_template_doc.format( operation="union", extra_desc="", params=union_params, + return_type=array_return_type, examples=union_examples, ) @@ -357,10 +399,11 @@ def join_params(list_of_param_strings): param_return_type, ] ) -intersection_docstring = template_doc.format( +intersection_docstring = operation_template_doc.format( operation="intersection", extra_desc="", params=intersection_params, + return_type=array_return_type, examples=intersection_examples, ) @@ -376,6 +419,7 @@ def join_params(list_of_param_strings): operation="difference", extra_desc="", params=difference_params, + return_type=array_return_type, examples=difference_examples, ) @@ -394,9 +438,31 @@ def join_params(list_of_param_strings): The parameter *min_overlaps* in :meth:`piso.intersection`, which defines the minimum number of intervals in an overlap required to constitute an intersection, follows through to symmetric difference under this definition. """ -symmetric_difference_docstring = template_doc.format( +symmetric_difference_docstring = operation_template_doc.format( operation="symmetric difference", extra_desc=symmetric_difference_extra_desc, params=symmetric_difference_params, + return_type=array_return_type, examples=symmetric_difference_examples, ) + + +isdisjoint_doc = ( + """ +Indicates whether one, or more, sets are disjoint or not. +""" + + template_doc +) + +isdisjoint_params = join_params( + [ + param_interval_array.format(operation="isdisjoint"), + param_optional_args, + ] +) +isdisjoint_docstring = isdisjoint_doc.format( + extra_desc="", + params=isdisjoint_params, + return_type="boolean", + examples=isdisjoint_examples, +) diff --git a/piso/intervalarray.py b/piso/intervalarray.py index 400ccaa..f98a2c3 100644 --- a/piso/intervalarray.py +++ b/piso/intervalarray.py @@ -1,3 +1,4 @@ +import numpy as np import pandas as pd import staircase as sc @@ -103,3 +104,18 @@ def symmetric_difference( if squeeze and len(result) == 1: result = result[0] return result + + +@Appender(docstrings.isdisjoint_docstring, join="\n", indents=1) +def isdisjoint(interval_array, *interval_arrays): + _validate_array_of_intervals_arrays(interval_array, *interval_arrays) + if interval_arrays: + stairs = _make_stairs(interval_array, *interval_arrays) + result = stairs.max() <= 1 + elif len(interval_array) == 0: + result = True + else: + arr = np.stack([interval_array.left.values, interval_array.right.values]) + arr = arr[arr[:, 0].argsort()] + result = np.all(arr[0, 1:] >= arr[1, :-1]) + return result diff --git a/piso/util.py b/piso/util.py index 5f05796..086f5da 100644 --- a/piso/util.py +++ b/piso/util.py @@ -1,3 +1,4 @@ +import pandas as pd import staircase as sc from piso._exceptions import ClosedValueError, DegenerateIntervalError diff --git a/tests/test_multiple_interval_array.py b/tests/test_multiple_interval_array.py index 74ed169..1d271a4 100644 --- a/tests/test_multiple_interval_array.py +++ b/tests/test_multiple_interval_array.py @@ -14,6 +14,7 @@ def get_accessor_method(self, function): piso_intervalarray.intersection: self.piso.intersection, piso_intervalarray.difference: self.piso.difference, piso_intervalarray.symmetric_difference: self.piso.symmetric_difference, + piso_intervalarray.isdisjoint: self.piso.isdisjoint, }[function] @@ -23,6 +24,7 @@ def get_package_method(function): piso_intervalarray.intersection: piso.intersection, piso_intervalarray.symmetric_difference: piso.symmetric_difference, piso_intervalarray.difference: piso.difference, + piso_intervalarray.isdisjoint: piso.isdisjoint, }[function] @@ -429,3 +431,66 @@ def test_difference_4(closed, interval_index, return_type, how): expected, interval_index, ) + + +def map_to_dates(interval_array, date_type): + def make_date(x): + ts = pd.Timestamp(f"2021-10-{x}") + if date_type == "numpy": + return ts.to_numpy() + if date_type == "datetime": + return ts.to_pydatetime() + if date_type == "timedelta": + return ts - pd.Timestamp("2021-10-1") + return ts + + return interval_array.from_arrays( + interval_array.left.map(make_date), + interval_array.right.map(make_date), + ) + + +def make_ia_from_tuples(interval_index, tuples, closed): + klass = pd.IntervalIndex if interval_index else pd.arrays.IntervalArray + return klass.from_tuples(tuples, closed=closed) + + +@pytest.mark.parametrize( + "interval_index", + [True, False], +) +@pytest.mark.parametrize( + "tuples, expected", + [ + ([], True), + ([(1, 3)], True), + ([(3, 11)], False), + ([(1, 2), (2, 3)], True), + ([(1, 2), (1, 3)], True), + ([(1, 3), (7, 9)], False), + ([(1, 5), (6, 7)], False), + ([(1, 2), (6, 7), (9, 10)], False), + ], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +@pytest.mark.parametrize( + "date_type", + ["timestamp", "numpy", "datetime", "timedelta", None], +) +@pytest.mark.parametrize( + "how", + ["supplied", "accessor", "package"], +) +def test_isdisjoint(interval_index, tuples, expected, closed, date_type, how): + # all intervals are compared to ia3 + ia3 = make_ia3(interval_index, closed) # intervals = (3,4), (8,11) + ia3 = map_to_dates(ia3, date_type) + interval_array = make_ia_from_tuples(interval_index, tuples, closed) + interval_array = map_to_dates(interval_array, date_type) + result = perform_op( + ia3, interval_array, how=how, function=piso_intervalarray.isdisjoint + ) + assert result == expected diff --git a/tests/test_single_interval_array.py b/tests/test_single_interval_array.py index 3d7875e..5b7d5d4 100644 --- a/tests/test_single_interval_array.py +++ b/tests/test_single_interval_array.py @@ -13,6 +13,7 @@ def get_accessor_method(self, function): piso_intervalarray.union: self.piso.union, piso_intervalarray.intersection: self.piso.intersection, piso_intervalarray.symmetric_difference: self.piso.symmetric_difference, + piso_intervalarray.isdisjoint: self.piso.isdisjoint, }[function] @@ -21,6 +22,7 @@ def get_package_method(function): piso_intervalarray.union: piso.union, piso_intervalarray.intersection: piso.intersection, piso_intervalarray.symmetric_difference: piso.symmetric_difference, + piso_intervalarray.isdisjoint: piso_intervalarray.isdisjoint, }[function] @@ -65,6 +67,11 @@ def make_ia3(interval_index, closed): return ia3 +def make_ia_from_tuples(interval_index, tuples, closed): + klass = pd.IntervalIndex if interval_index else pd.arrays.IntervalArray + return klass.from_tuples(tuples, closed=closed) + + def assert_interval_array_equal(interval_array, expected, interval_index): if interval_index: interval_array = interval_array.values @@ -418,3 +425,58 @@ def test_symmetric_difference_min_overlaps_all_2( expected, interval_index, ) + + +def map_to_dates(interval_array, date_type): + def make_date(x): + ts = pd.Timestamp(f"2021-10-{x}") + if date_type == "numpy": + return ts.to_numpy() + if date_type == "datetime": + return ts.to_pydatetime() + if date_type == "timedelta": + return ts - pd.Timestamp("2021-10-1") + return ts + + return interval_array.from_arrays( + interval_array.left.map(make_date), + interval_array.right.map(make_date), + ) + + +@pytest.mark.parametrize( + "interval_index", + [True, False], +) +@pytest.mark.parametrize( + "tuples, expected", + [ + ([], True), + ([(1, 2), (2, 3)], True), + ([(1, 2), (3, 4)], True), + ([(1, 3), (2, 4)], False), + ([(1, 4), (2, 3)], False), + ([(1, 2), (2, 3), (3, 4)], True), + ([(1, 2), (3, 4), (5, 6)], True), + ([(1, 3), (2, 4), (5, 6)], False), + ([(1, 4), (2, 3), (5, 6)], False), + ], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +@pytest.mark.parametrize( + "date_type", + ["timestamp", "numpy", "datetime", "timedelta", None], +) +@pytest.mark.parametrize( + "how", + ["supplied", "accessor", "package"], +) +def test_isdisjoint(interval_index, tuples, expected, closed, date_type, how): + + interval_array = make_ia_from_tuples(interval_index, tuples, closed) + interval_array = map_to_dates(interval_array, date_type) + result = perform_op(interval_array, how=how, function=piso_intervalarray.isdisjoint) + assert result == expected From f73b02a4a196691eca8b8aef5bf7bf212828f164 Mon Sep 17 00:00:00 2001 From: Riley Clement Date: Tue, 12 Oct 2021 19:52:22 +1100 Subject: [PATCH 06/11] removing unused import --- piso/util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/piso/util.py b/piso/util.py index 086f5da..5f05796 100644 --- a/piso/util.py +++ b/piso/util.py @@ -1,4 +1,3 @@ -import pandas as pd import staircase as sc from piso._exceptions import ClosedValueError, DegenerateIntervalError From 1c21c20704b5b18e06dedd84a84191da15f3424a Mon Sep 17 00:00:00 2001 From: Riley Clement Date: Wed, 13 Oct 2021 00:26:32 +1100 Subject: [PATCH 07/11] functionality implemented for intervalarray.issubset and intervalarray.issuperset --- piso/__init__.py | 2 + piso/accessor.py | 14 +++++ piso/intervalarray.py | 45 ++++++++++++++++ tests/test_multiple_interval_array.py | 71 ++++++++++++++++++++++++ tests/test_single_interval_array.py | 77 +++++++++++++++++++++++++++ 5 files changed, 209 insertions(+) diff --git a/piso/__init__.py b/piso/__init__.py index f996cf9..c589f89 100644 --- a/piso/__init__.py +++ b/piso/__init__.py @@ -2,6 +2,8 @@ difference, intersection, isdisjoint, + issubset, + issuperset, symmetric_difference, union, ) diff --git a/piso/accessor.py b/piso/accessor.py index b22b3e4..0e0b8b2 100644 --- a/piso/accessor.py +++ b/piso/accessor.py @@ -125,6 +125,20 @@ def isdisjoint(self, *interval_arrays): *interval_arrays, ) + def issuperset(self, *interval_arrays, squeeze=False): + return intervalarray.issuperset( + self._interval_array, + *interval_arrays, + squeeze=squeeze, + ) + + def issubset(self, *interval_arrays, squeeze=False): + return intervalarray.issubset( + self._interval_array, + *interval_arrays, + squeeze=squeeze, + ) + def _register_accessors(): _register_accessor("piso", pd.IntervalIndex)(ArrayAccessor) diff --git a/piso/intervalarray.py b/piso/intervalarray.py index f98a2c3..64dd83e 100644 --- a/piso/intervalarray.py +++ b/piso/intervalarray.py @@ -119,3 +119,48 @@ def isdisjoint(interval_array, *interval_arrays): arr = arr[arr[:, 0].argsort()] result = np.all(arr[0, 1:] >= arr[1, :-1]) return result + + +def _create_is_super_or_sub(which): + + comparator_func = {"superset": sc.Stairs.ge, "subset": sc.Stairs.le}[which] + left_bound_comparator = {"superset": np.less_equal, "subset": np.greater_equal}[ + which + ] + right_bound_comparator = {"superset": np.greater_equal, "subset": np.less_equal}[ + which + ] + + def func(interval_array, *interval_arrays, squeeze=False): + _validate_array_of_intervals_arrays(interval_array, *interval_arrays) + + if interval_arrays: + stepfunction = _interval_x_to_stairs(interval_array).make_boolean() + + def _comp(ia): + return bool( + comparator_func( + stepfunction, + _interval_x_to_stairs(ia).make_boolean(), + ) + ) + + result = np.array([_comp(ia) for ia in interval_arrays]) + else: + assert len(interval_array) >= 2 + result = np.logical_and( + left_bound_comparator(interval_array[0].left, interval_array[1:].left), + right_bound_comparator( + interval_array[0].right, interval_array[1:].right + ), + ) + + if squeeze and len(result) == 1: + result = result[0] + return result + + return func + + +issuperset = _create_is_super_or_sub("superset") +issubset = _create_is_super_or_sub("subset") diff --git a/tests/test_multiple_interval_array.py b/tests/test_multiple_interval_array.py index 1d271a4..93c7096 100644 --- a/tests/test_multiple_interval_array.py +++ b/tests/test_multiple_interval_array.py @@ -1,3 +1,6 @@ +import operator + +import numpy as np import pandas as pd import pytest @@ -15,6 +18,8 @@ def get_accessor_method(self, function): piso_intervalarray.difference: self.piso.difference, piso_intervalarray.symmetric_difference: self.piso.symmetric_difference, piso_intervalarray.isdisjoint: self.piso.isdisjoint, + piso_intervalarray.issuperset: self.piso.issuperset, + piso_intervalarray.issubset: self.piso.issubset, }[function] @@ -25,6 +30,8 @@ def get_package_method(function): piso_intervalarray.symmetric_difference: piso.symmetric_difference, piso_intervalarray.difference: piso.difference, piso_intervalarray.isdisjoint: piso.isdisjoint, + piso_intervalarray.issuperset: piso.issuperset, + piso_intervalarray.issubset: piso.issubset, }[function] @@ -494,3 +501,67 @@ def test_isdisjoint(interval_index, tuples, expected, closed, date_type, how): ia3, interval_array, how=how, function=piso_intervalarray.isdisjoint ) assert result == expected + + +@pytest.mark.parametrize( + "interval_index", + [True, False], +) +@pytest.mark.parametrize( + "ia_makers, expected", + [ + ([make_ia1, make_ia2], True), + ([make_ia1, make_ia3], False), + ([make_ia1, make_ia2, make_ia3], np.array([True, False])), + ], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +@pytest.mark.parametrize( + "how", + ["supplied", "accessor", "package"], +) +def test_issuperset(interval_index, ia_makers, expected, closed, how): + ias = [make_ia(interval_index, closed) for make_ia in ia_makers] + result = perform_op( + *ias, + how=how, + function=piso_intervalarray.issuperset, + squeeze=True, + ) + equal_op = np.array_equal if isinstance(expected, np.ndarray) else operator.eq + assert equal_op(result, expected) + + +@pytest.mark.parametrize( + "interval_index", + [True, False], +) +@pytest.mark.parametrize( + "ia_makers, expected", + [ + ([make_ia2, make_ia1], True), + ([make_ia3, make_ia1], False), + ([make_ia2, make_ia1, make_ia3], np.array([True, False])), + ], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +@pytest.mark.parametrize( + "how", + ["supplied", "accessor", "package"], +) +def test_issubset(interval_index, ia_makers, expected, closed, how): + ias = [make_ia(interval_index, closed) for make_ia in ia_makers] + result = perform_op( + *ias, + how=how, + function=piso_intervalarray.issubset, + squeeze=True, + ) + equal_op = np.array_equal if isinstance(expected, np.ndarray) else operator.eq + assert equal_op(result, expected) diff --git a/tests/test_single_interval_array.py b/tests/test_single_interval_array.py index 5b7d5d4..ef47246 100644 --- a/tests/test_single_interval_array.py +++ b/tests/test_single_interval_array.py @@ -1,3 +1,6 @@ +import operator + +import numpy as np import pandas as pd import pytest @@ -14,6 +17,8 @@ def get_accessor_method(self, function): piso_intervalarray.intersection: self.piso.intersection, piso_intervalarray.symmetric_difference: self.piso.symmetric_difference, piso_intervalarray.isdisjoint: self.piso.isdisjoint, + piso_intervalarray.issuperset: self.piso.issuperset, + piso_intervalarray.issubset: self.piso.issubset, }[function] @@ -23,6 +28,8 @@ def get_package_method(function): piso_intervalarray.intersection: piso.intersection, piso_intervalarray.symmetric_difference: piso.symmetric_difference, piso_intervalarray.isdisjoint: piso_intervalarray.isdisjoint, + piso_intervalarray.issuperset: piso.issuperset, + piso_intervalarray.issubset: piso.issubset, }[function] @@ -480,3 +487,73 @@ 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( + "tuples, squeeze, expected", + [ + ([(1, 2), (1, 2)], True, True), + ([(1, 3), (0, 2)], True, False), + ([(1, 3), (1, 2), (0, 1)], True, np.array([True, False])), + ([(1, 2), (1, 2)], False, np.array([True])), + ([(1, 3), (0, 2)], False, np.array([False])), + ([(1, 3), (1, 2), (0, 1)], False, np.array([True, False])), + ], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +@pytest.mark.parametrize( + "how", + ["supplied", "accessor", "package"], +) +def test_issuperset(interval_index, tuples, squeeze, expected, closed, how): + interval_array = make_ia_from_tuples(interval_index, tuples, closed) + result = perform_op( + interval_array, + how=how, + function=piso_intervalarray.issuperset, + squeeze=squeeze, + ) + equal_op = np.array_equal if isinstance(expected, np.ndarray) else operator.eq + assert equal_op(result, expected) + + +@pytest.mark.parametrize( + "interval_index", + [True, False], +) +@pytest.mark.parametrize( + "tuples, squeeze, expected", + [ + ([(1, 2), (1, 2)], True, True), + ([(1, 3), (0, 2)], True, False), + ([(1, 3), (1, 4), (0, 1)], True, np.array([True, False])), + ([(1, 2), (1, 2)], False, np.array([True])), + ([(1, 3), (0, 2)], False, np.array([False])), + ([(1, 3), (1, 4), (0, 1)], False, np.array([True, False])), + ], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +@pytest.mark.parametrize( + "how", + ["supplied", "accessor", "package"], +) +def test_issubset(interval_index, tuples, squeeze, expected, closed, how): + interval_array = make_ia_from_tuples(interval_index, tuples, closed) + result = perform_op( + interval_array, + how=how, + function=piso_intervalarray.issubset, + squeeze=squeeze, + ) + equal_op = np.array_equal if isinstance(expected, np.ndarray) else operator.eq + assert equal_op(result, expected) From 98f15e8bb24ad09f4cb26e4737ac3ae3764e3482 Mon Sep 17 00:00:00 2001 From: Riley Clement Date: Thu, 14 Oct 2021 09:58:19 +1100 Subject: [PATCH 08/11] Added issuperset and issubset --- docs/reference/accessors.rst | 4 +- docs/reference/interval.rst | 4 +- docs/reference/package.rst | 4 +- docs/release_notes/index.rst | 19 +++- piso/accessor.py | 2 + piso/docstrings/accessor.py | 131 +++++++++++++++++++++-- piso/docstrings/interval.py | 104 +++++++++++++++++- piso/docstrings/intervalarray.py | 148 +++++++++++++++++++++++--- piso/interval.py | 29 +++++ piso/intervalarray.py | 46 +++----- tests/test_interval.py | 49 +++++++++ tests/test_multiple_interval_array.py | 30 +++--- tests/test_single_interval_array.py | 70 ------------ 13 files changed, 497 insertions(+), 143 deletions(-) diff --git a/docs/reference/accessors.rst b/docs/reference/accessors.rst index ce664b6..5927321 100644 --- a/docs/reference/accessors.rst +++ b/docs/reference/accessors.rst @@ -13,4 +13,6 @@ Accessors ArrayAccessor.intersection ArrayAccessor.difference ArrayAccessor.symmetric_difference - ArrayAccessor.isdisjoint \ No newline at end of file + ArrayAccessor.isdisjoint + ArrayAccessor.issuperset + ArrayAccessor.issubset \ No newline at end of file diff --git a/docs/reference/interval.rst b/docs/reference/interval.rst index bd67928..a8ec10c 100644 --- a/docs/reference/interval.rst +++ b/docs/reference/interval.rst @@ -12,4 +12,6 @@ Interval union intersection difference - symmetric_difference \ No newline at end of file + symmetric_difference + issuperset + issubset \ No newline at end of file diff --git a/docs/reference/package.rst b/docs/reference/package.rst index fcd18c0..b0f4af1 100644 --- a/docs/reference/package.rst +++ b/docs/reference/package.rst @@ -15,4 +15,6 @@ Top level functions intersection difference symmetric_difference - isdisjoint \ No newline at end of file + isdisjoint + issuperset + issubset \ No newline at end of file diff --git a/docs/release_notes/index.rst b/docs/release_notes/index.rst index 56509a9..bc89392 100644 --- a/docs/release_notes/index.rst +++ b/docs/release_notes/index.rst @@ -4,19 +4,32 @@ Release notes ======================== -- added :meth:`piso.isdisjoint` method, and corresponding accessor method +Added the following methods -ADD UNRELEASED CHANGED ABOVE THIS LINE +- :meth:`piso.isdisjoint` +- :meth:`piso.issuperset` +- :meth:`piso.issubset` +- :meth:`ArrayAccessor.isdisjoint() ` +- :meth:`ArrayAccessor.issuperset() ` +- :meth:`ArrayAccessor.issubset() ` +- :meth:`piso.interval.issuperset` +- :meth:`piso.interval.issubset` + +ADD UNRELEASED CHANGES ABOVE THIS LINE **v0.1.0 2021-10-10** -The following methods (and corresponding accessor methods) are included in the initial release of `piso` +The following methods are included in the initial release of `piso` - :meth:`piso.register_accessors` - :meth:`piso.union` - :meth:`piso.intersection` - :meth:`piso.difference` - :meth:`piso.symmetric_difference` +- :meth:`ArrayAccessor.union() ` +- :meth:`ArrayAccessor.intersection() ` +- :meth:`ArrayAccessor.difference() ` +- :meth:`ArrayAccessor.symmetric_difference() ` - :meth:`piso.interval.union` - :meth:`piso.interval.intersection` - :meth:`piso.interval.difference` diff --git a/piso/accessor.py b/piso/accessor.py index 0e0b8b2..354c174 100644 --- a/piso/accessor.py +++ b/piso/accessor.py @@ -125,6 +125,7 @@ def isdisjoint(self, *interval_arrays): *interval_arrays, ) + @Appender(docstrings.issuperset_docstring, join="\n", indents=1) def issuperset(self, *interval_arrays, squeeze=False): return intervalarray.issuperset( self._interval_array, @@ -132,6 +133,7 @@ def issuperset(self, *interval_arrays, squeeze=False): squeeze=squeeze, ) + @Appender(docstrings.issubset_docstring, join="\n", indents=1) def issubset(self, *interval_arrays, squeeze=False): return intervalarray.issubset( self._interval_array, diff --git a/piso/docstrings/accessor.py b/piso/docstrings/accessor.py index a44dcfb..dc6e5d8 100644 --- a/piso/docstrings/accessor.py +++ b/piso/docstrings/accessor.py @@ -288,6 +288,69 @@ False """ +issuperset_examples = """ +Examples +----------- + +>>> import pandas as pd +>>> import piso +>>> piso.register_accessors() + +>>> arr1 = pd.arrays.IntervalArray.from_tuples( +... [(0, 4), (3, 6), (7, 8), (10, 12)], +... ) +>>> arr2 = pd.arrays.IntervalArray.from_tuples( +... [(2, 5), (7, 8)], +... ) +>>> arr3 = pd.arrays.IntervalArray.from_tuples( +... [(3, 4), (10, 11)], +... ) + +>>> arr1.piso.issuperset(arr2) +True + +>>> arr1.piso.issuperset(arr2, squeeze=False) +array([ True]) + +>>> arr1.piso.issuperset(arr2, arr3) +array([ True, True]) + +>>> arr2.piso.issuperset(arr3) +False +""" + + +issubset_examples = """ +Examples +----------- + +>>> import pandas as pd +>>> import piso +>>> piso.register_accessors() + +>>> arr1 = pd.arrays.IntervalArray.from_tuples( +... [(2, 5), (7, 8)], +... ) +>>> arr2 = pd.arrays.IntervalArray.from_tuples( +... [(0, 4), (3, 6), (7, 8), (10, 12)], +... ) +>>> arr3 = pd.arrays.IntervalArray.from_tuples( +... [(3, 4), (10, 11)], +... ) + +>>> arr1.piso.issubset(arr2) +True + +>>> arr1.piso.issubset(arr2, squeeze=False) +array([ True]) + +>>> arr1.piso.issubset(arr2, arr3) +array([ True, False]) + +>>> arr1.piso.issubset(arr3) +False +""" + def join_params(list_of_param_strings): return "".join(list_of_param_strings).replace("\n\n", "\n") @@ -298,7 +361,7 @@ def join_params(list_of_param_strings): May contain zero or more arguments. """ -param_optional_args_difference = """ +param_optional_args_min_one = """ *interval_arrays : argument list of :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` Must contain at least one argument. """ @@ -312,8 +375,8 @@ def join_params(list_of_param_strings): """ param_squeeze = """ -squeeze : boolean, default True - If True, will try to coerce the return value to a pandas.Interval. +squeeze : boolean, default {default} + If True, will try to coerce the return value to a single pandas.Interval. If supplied, must be done so as a keyword argument. """ @@ -368,9 +431,6 @@ def join_params(list_of_param_strings): and the union of the sets in *interval_arrays*. This is equivalent to iteratively applying a set difference operation with each array in *interval_arrays* as the second operand. -Each of these array operands is assumed to contain disjoint intervals (and satisfy the definition of a set). Any array containing -overlaps between intervals will be mapped to one with disjoint intervals via a union operation. - {extra_desc} Parameters ---------- @@ -383,6 +443,30 @@ def join_params(list_of_param_strings): {examples} """ +is_super_sub_set_template = """ +Indicates whether a set is a {operation} of one, or more, other sets. + +The array elements of *interval_arrays*, and the interval array object the accessor belongs to +(an instance of :class:`pandas.IntervalIndex`, :class:`pandas.arrays.IntervalArray`) are considered to be the sets over which +the operation is performed. Each of these arrays is assumed to contain disjoint intervals (and satisfy the definition of a set). +Any array containing overlaps between intervals will be mapped to one with disjoint intervals via a union operation. + +The list *interval_arrays* must contain at least one element. The {operation} comparison is iteratively applied between +the interval array the accessor belongs to, and each array in *interval_arrays*. When *interval_arrays* contains multiple +interval arrays, the return type will be a numpy array. If it contains one interval array then the result can be coerced to +a single boolean using the *squeeze* parameter. + +Parameters +---------- +{params} + +Returns +---------- +:class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` + +{examples} +""" + array_return_type = ( ":class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray`" ) @@ -390,7 +474,7 @@ def join_params(list_of_param_strings): union_params = join_params( [ param_optional_args, - param_squeeze, + param_squeeze.format(default="False"), param_return_type, ] ) @@ -406,7 +490,7 @@ def join_params(list_of_param_strings): [ param_optional_args, param_min_overlaps, - param_squeeze, + param_squeeze.format(default="False"), param_return_type, ] ) @@ -420,8 +504,8 @@ def join_params(list_of_param_strings): difference_params = join_params( [ - param_optional_args_difference, - param_squeeze, + param_optional_args_min_one, + param_squeeze.format(default="False"), param_return_type, ] ) @@ -438,7 +522,7 @@ def join_params(list_of_param_strings): [ param_optional_args, param_min_overlaps, - param_squeeze, + param_squeeze.format(default="False"), param_return_type, ] ) @@ -474,3 +558,28 @@ def join_params(list_of_param_strings): return_type="boolean", examples=isdisjoint_examples, ) + + +issuperset_params = join_params( + [ + param_optional_args_min_one, + param_squeeze.format(default="True"), + ] +) +issuperset_docstring = is_super_sub_set_template.format( + operation="superset", + params=issuperset_params, + examples=issuperset_examples, +) + +issubset_params = join_params( + [ + param_optional_args_min_one, + param_squeeze.format(default="True"), + ] +) +issubset_docstring = is_super_sub_set_template.format( + operation="subset", + params=issubset_params, + examples=issubset_examples, +) diff --git a/piso/docstrings/interval.py b/piso/docstrings/interval.py index 301f6ec..f326b96 100644 --- a/piso/docstrings/interval.py +++ b/piso/docstrings/interval.py @@ -150,8 +150,77 @@ Length: 2, closed: right, dtype: interval[float64] """ +issuperset_examples = """ +Examples +----------- + +>>> import pandas as pd +>>> import piso.interval + +>>> piso.interval.issuperset( +... pd.Interval(1, 4), +... pd.Interval(2, 4), +... ) +True + +>>> piso.interval.issuperset( +... pd.Interval(1, 4), +... pd.Interval(0, 3), +... ) +False + +>>> piso.interval.issuperset( +... pd.Interval(1, 4), +... pd.Interval(2, 4), +... pd.Interval(0, 3), +... ) +array([ True, False]) + +>>> piso.interval.issuperset( +... pd.Interval(0, 3), +... pd.Interval(0, 3), +... squeeze=False +... ) +array([ True]) +""" + + +issubset_examples = """ +Examples +----------- + +>>> import pandas as pd +>>> import piso.interval + +>>> piso.interval.issubset( +... pd.Interval(2, 4), +... pd.Interval(1, 4), +... ) +True + +>>> piso.interval.issubset( +... pd.Interval(2, 4), +... pd.Interval(0, 3), +... ) +False + +>>> piso.interval.issubset( +... pd.Interval(2, 4), +... pd.Interval(1, 4), +... pd.Interval(0, 3), +... ) +array([ True, False]) + +>>> piso.interval.issubset( +... pd.Interval(1, 4), +... pd.Interval(1, 4), +... squeeze=False +... ) +array([ True]) +""" + template_doc = """ -Performs the {operation} of two pandas.Intervals +Performs the {operation} of two :class:`pandas.Interval` Parameters ---------- @@ -160,7 +229,7 @@ interval2 : pandas.Interval the second operand squeeze : boolean, default True - If True, will try to coerce the return value to a pandas.Interval + If True, will try to coerce the return value to a :class:`pandas.Interval` Returns ---------- @@ -169,6 +238,7 @@ {examples} """ + union_docstring = template_doc.format(operation="union", examples=union_examples) intersection_docstring = template_doc.format( operation="intersection", examples=intersection_examples @@ -179,3 +249,33 @@ symmetric_difference_docstring = template_doc.format( operation="symmetric difference", examples=symmetric_difference_examples ) + + +is_sub_super_doc = """ +Indicates whether one :class:`pandas.Interval` is a {operation} of one, or more, others. + +Parameters +---------- +interval : :class:`pandas.Interval` + An interval, against which all other intervals belonging to *intervals* are compared. +*intervals : argument list of :class:`pandas.Interval` + Must contain at least one argument. +squeeze : boolean, default True + If True, will try to coerce the return value to a single boolean + +Returns +---------- +boolean, or :class:`numpy.ndarray` of booleans + +{examples} +""" + +issuperset_docstring = is_sub_super_doc.format( + operation="superset", + examples=issuperset_examples, +) + +issubset_docstring = is_sub_super_doc.format( + operation="subset", + examples=issubset_examples, +) diff --git a/piso/docstrings/intervalarray.py b/piso/docstrings/intervalarray.py index 6ca7231..92e5f36 100644 --- a/piso/docstrings/intervalarray.py +++ b/piso/docstrings/intervalarray.py @@ -272,6 +272,68 @@ """ +issuperset_examples = """ +Examples +----------- + +>>> import pandas as pd +>>> import piso + +>>> arr1 = pd.arrays.IntervalArray.from_tuples( +... [(0, 4), (3, 6), (7, 8), (10, 12)], +... ) +>>> arr2 = pd.arrays.IntervalArray.from_tuples( +... [(2, 5), (7, 8)], +... ) +>>> arr3 = pd.arrays.IntervalArray.from_tuples( +... [(3, 4), (10, 11)], +... ) + +>>> piso.issuperset(arr1, arr2) +True + +>>> piso.issuperset(arr1, arr2, squeeze=False) +array([ True]) + +>>> piso.issuperset(arr1, arr2, arr3) +array([ True, True]) + +>>> piso.issuperset(arr2, arr3) +False +""" + + +issubset_examples = """ +Examples +----------- + +>>> import pandas as pd +>>> import piso + +>>> arr1 = pd.arrays.IntervalArray.from_tuples( +... [(2, 5), (7, 8)], +... ) +>>> arr2 = pd.arrays.IntervalArray.from_tuples( +... [(0, 4), (3, 6), (7, 8), (10, 12)], +... ) +>>> arr3 = pd.arrays.IntervalArray.from_tuples( +... [(3, 4), (10, 11)], +... ) + +>>> piso.issubset(arr1, arr2) +True + +>>> piso.issubset(arr1, arr2, squeeze=False) +array([ True]) + +>>> piso.issubset(arr1, arr2, arr3) +array([ True, False]) + +>>> piso.issubset(arr1, arr3) +False +""" + + def join_params(list_of_param_strings): return "".join(list_of_param_strings).replace("\n\n", "\n") @@ -281,12 +343,22 @@ def join_params(list_of_param_strings): The first (and possibly only) operand to the {operation} operation. """ +param_interval_array_non_optional = """ +interval_array : :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` + The first operand to the {operation} operation. +""" + +param_interval_sub_super_set = """ +interval_array : :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` + The first operand to which all others are compared operation. +""" + param_optional_args = """ *interval_arrays : argument list of :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` May contain zero or more arguments. """ -param_optional_args_difference = """ +param_optional_args_min_one = """ *interval_arrays : argument list of :class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray` Must contain at least one argument. """ @@ -300,8 +372,8 @@ def join_params(list_of_param_strings): """ param_squeeze = """ -squeeze : boolean, default True - If True, will try to coerce the return value to a pandas.Interval. +squeeze : boolean, default {default} + If True, will try to coerce the return value to a single pandas.Interval. If supplied, must be done so as a keyword argument. """ @@ -355,9 +427,6 @@ def join_params(list_of_param_strings): multiple elements then the result is the set difference between *interval_array* and the union of the sets in *interval_arrays*. This is equivalent to iteratively applying a set difference operation with each array in *interval_arrays* as the second operand. -Each of these array operands is assumed to contain disjoint intervals (and satisfy the definition of a set). Any array containing -overlaps between intervals will be mapped to one with disjoint intervals via a union operation. - {extra_desc} Parameters ---------- @@ -370,6 +439,31 @@ def join_params(list_of_param_strings): {examples} """ +doc_is_sub_super_set_template = """ +Indicates whether a set is a {operation} of one, or more, other sets. + +The argument *interval_array* and the array elements of *interval_arrays* are all considered to be the sets for the purposes +of this set method. Each of these arrays is assumed to contain disjoint intervals (and satisfy the definition of a set). +Any array containing overlaps between intervals will be mapped to one with disjoint intervals via a union operation. + +The list *interval_arrays* must contain at least one element. The {operation} comparison is iteratively applied between +*interval_array* and each array in *interval_arrays*. When *interval_arrays* contains multiple interval arrays, the return +type will be a numpy array. If it contains one interval array then the result can be coerced to a single boolean using the +*squeeze* parameter. + +{extra_desc} +Parameters +---------- +{params} + +Returns +---------- +boolean, or :class:`numpy.ndarray` of boolean + +{examples} +""" + + array_return_type = ( ":class:`pandas.IntervalIndex` or :class:`pandas.arrays.IntervalArray`" ) @@ -378,7 +472,7 @@ def join_params(list_of_param_strings): [ param_interval_array.format(operation="union"), param_optional_args, - param_squeeze, + param_squeeze.format(default="False"), param_return_type, ] ) @@ -395,7 +489,7 @@ def join_params(list_of_param_strings): param_interval_array.format(operation="intersection"), param_optional_args, param_min_overlaps, - param_squeeze, + param_squeeze.format(default="False"), param_return_type, ] ) @@ -409,9 +503,9 @@ def join_params(list_of_param_strings): difference_params = join_params( [ - param_interval_array.format(operation="difference"), - param_optional_args_difference, - param_squeeze, + param_interval_array_non_optional.format(operation="difference"), + param_optional_args_min_one, + param_squeeze.format(default="False"), param_return_type, ] ) @@ -429,7 +523,7 @@ def join_params(list_of_param_strings): param_interval_array.format(operation="symmetric difference"), param_optional_args, param_min_overlaps, - param_squeeze, + param_squeeze.format(default="False"), param_return_type, ] ) @@ -466,3 +560,33 @@ def join_params(list_of_param_strings): return_type="boolean", examples=isdisjoint_examples, ) + + +issuperset_params = join_params( + [ + param_interval_sub_super_set, + param_optional_args_min_one, + param_squeeze.format(default="True"), + ] +) +issuperset_docstring = doc_is_sub_super_set_template.format( + operation="superset", + extra_desc="", + params=issuperset_params, + examples=issuperset_examples, +) + + +issubset_params = join_params( + [ + param_interval_sub_super_set, + param_optional_args_min_one, + param_squeeze.format(default="True"), + ] +) +issubset_docstring = doc_is_sub_super_set_template.format( + operation="subset", + extra_desc="", + params=issubset_params, + examples=issubset_examples, +) diff --git a/piso/interval.py b/piso/interval.py index 4ac94db..619be29 100644 --- a/piso/interval.py +++ b/piso/interval.py @@ -1,3 +1,4 @@ +import numpy as np import pandas as pd import piso.docstrings.interval as docstrings @@ -109,3 +110,31 @@ def symmetric_difference(interval1, interval2, squeeze=True): closed=interval1.closed, ) return result + + +def _make_is_sub_or_superset(which, docstring): + + left_bound_comparator = {"super": np.less_equal, "sub": np.greater_equal}[which] + right_bound_comparator = {"super": np.greater_equal, "sub": np.less_equal}[which] + + @Appender(docstring, join="\n", indents=1) + def func(interval, *intervals, squeeze=True): + assert intervals + lefts = np.array([i.left for i in intervals]) + rights = np.array([i.right for i in intervals]) + + result = np.logical_and( + left_bound_comparator(interval.left, lefts), + right_bound_comparator(interval.right, rights), + ) + + if len(result) == 1 and squeeze: + result = result[0] + + return result + + return func + + +issuperset = _make_is_sub_or_superset("super", docstrings.issuperset_docstring) +issubset = _make_is_sub_or_superset("sub", docstrings.issubset_docstring) diff --git a/piso/intervalarray.py b/piso/intervalarray.py index 64dd83e..3c635aa 100644 --- a/piso/intervalarray.py +++ b/piso/intervalarray.py @@ -121,40 +121,26 @@ def isdisjoint(interval_array, *interval_arrays): return result -def _create_is_super_or_sub(which): +def _create_is_super_or_sub(which, docstring): comparator_func = {"superset": sc.Stairs.ge, "subset": sc.Stairs.le}[which] - left_bound_comparator = {"superset": np.less_equal, "subset": np.greater_equal}[ - which - ] - right_bound_comparator = {"superset": np.greater_equal, "subset": np.less_equal}[ - which - ] - - def func(interval_array, *interval_arrays, squeeze=False): - _validate_array_of_intervals_arrays(interval_array, *interval_arrays) - - if interval_arrays: - stepfunction = _interval_x_to_stairs(interval_array).make_boolean() - def _comp(ia): - return bool( - comparator_func( - stepfunction, - _interval_x_to_stairs(ia).make_boolean(), - ) + @Appender(docstring, join="\n", indents=1) + def func(interval_array, *interval_arrays, squeeze=True): + _validate_array_of_intervals_arrays(interval_array, *interval_arrays) + assert interval_arrays + stepfunction = _interval_x_to_stairs(interval_array).make_boolean() + + def _comp(ia): + return bool( + comparator_func( + stepfunction, + _interval_x_to_stairs(ia).make_boolean(), ) - - result = np.array([_comp(ia) for ia in interval_arrays]) - else: - assert len(interval_array) >= 2 - result = np.logical_and( - left_bound_comparator(interval_array[0].left, interval_array[1:].left), - right_bound_comparator( - interval_array[0].right, interval_array[1:].right - ), ) + result = np.array([_comp(ia) for ia in interval_arrays]) + if squeeze and len(result) == 1: result = result[0] return result @@ -162,5 +148,5 @@ def _comp(ia): return func -issuperset = _create_is_super_or_sub("superset") -issubset = _create_is_super_or_sub("subset") +issuperset = _create_is_super_or_sub("superset", docstrings.issuperset_docstring) +issubset = _create_is_super_or_sub("subset", docstrings.issubset_docstring) diff --git a/tests/test_interval.py b/tests/test_interval.py index b0fda74..d546f0e 100644 --- a/tests/test_interval.py +++ b/tests/test_interval.py @@ -1,3 +1,6 @@ +import operator + +import numpy as np import pandas as pd import pytest @@ -624,3 +627,49 @@ def test_symmetric_difference_closed_value_error(closed_values): ) with pytest.raises(ClosedValueError): piso_interval.symmetric_difference(*intervals) + + + + +@pytest.mark.parametrize( + "tuples, squeeze, expected", + [ + ([(1, 2), (1, 2)], True, True), + ([(1, 3), (0, 2)], True, False), + ([(1, 3), (1, 2), (0, 1)], True, np.array([True, False])), + ([(1, 2), (1, 2)], False, np.array([True])), + ([(1, 3), (0, 2)], False, np.array([False])), + ([(1, 3), (1, 2), (0, 1)], False, np.array([True, False])), + ], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +def test_issuperset(tuples, squeeze, expected, closed): + intervals = [pd.Interval(*i, closed=closed) for i in tuples] + result = piso_interval.issuperset(*intervals, squeeze=squeeze) + equal_op = np.array_equal if isinstance(expected, np.ndarray) else operator.eq + assert equal_op(result, expected) + + +@pytest.mark.parametrize( + "tuples, squeeze, expected", + [ + ([(1, 2), (1, 2)], True, True), + ([(1, 3), (0, 2)], True, False), + ([(1, 3), (1, 4), (0, 1)], True, np.array([True, False])), + ([(1, 2), (1, 2)], False, np.array([True])), + ([(1, 3), (0, 2)], False, np.array([False])), + ([(1, 3), (1, 4), (0, 1)], False, np.array([True, False])), + ], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +def test_issubset(tuples, squeeze, expected, closed): + intervals = [pd.Interval(*i, closed=closed) for i in tuples] + result = piso_interval.issubset(*intervals, squeeze=squeeze) + equal_op = np.array_equal if isinstance(expected, np.ndarray) else operator.eq + assert equal_op(result, expected) diff --git a/tests/test_multiple_interval_array.py b/tests/test_multiple_interval_array.py index 93c7096..24dc187 100644 --- a/tests/test_multiple_interval_array.py +++ b/tests/test_multiple_interval_array.py @@ -508,11 +508,14 @@ def test_isdisjoint(interval_index, tuples, expected, closed, date_type, how): [True, False], ) @pytest.mark.parametrize( - "ia_makers, expected", + "ia_makers, squeeze, expected", [ - ([make_ia1, make_ia2], True), - ([make_ia1, make_ia3], False), - ([make_ia1, make_ia2, make_ia3], np.array([True, False])), + ([make_ia1, make_ia2], True, True), + ([make_ia1, make_ia3], True, False), + ([make_ia1, make_ia2, make_ia3], True, np.array([True, False])), + ([make_ia1, make_ia2], False, np.array([True])), + ([make_ia1, make_ia3], False, np.array([False])), + ([make_ia1, make_ia2, make_ia3], False, np.array([True, False])), ], ) @pytest.mark.parametrize( @@ -523,13 +526,13 @@ def test_isdisjoint(interval_index, tuples, expected, closed, date_type, how): "how", ["supplied", "accessor", "package"], ) -def test_issuperset(interval_index, ia_makers, expected, closed, how): +def test_issuperset(interval_index, ia_makers, squeeze, expected, closed, how): ias = [make_ia(interval_index, closed) for make_ia in ia_makers] result = perform_op( *ias, how=how, function=piso_intervalarray.issuperset, - squeeze=True, + squeeze=squeeze, ) equal_op = np.array_equal if isinstance(expected, np.ndarray) else operator.eq assert equal_op(result, expected) @@ -540,11 +543,14 @@ def test_issuperset(interval_index, ia_makers, expected, closed, how): [True, False], ) @pytest.mark.parametrize( - "ia_makers, expected", + "ia_makers, squeeze, expected", [ - ([make_ia2, make_ia1], True), - ([make_ia3, make_ia1], False), - ([make_ia2, make_ia1, make_ia3], np.array([True, False])), + ([make_ia2, make_ia1], True, True), + ([make_ia3, make_ia1], True, False), + ([make_ia2, make_ia1, make_ia3], True, np.array([True, False])), + ([make_ia2, make_ia1], False, np.array([True])), + ([make_ia3, make_ia1], False, np.array([False])), + ([make_ia2, make_ia1, make_ia3], False, np.array([True, False])), ], ) @pytest.mark.parametrize( @@ -555,13 +561,13 @@ def test_issuperset(interval_index, ia_makers, expected, closed, how): "how", ["supplied", "accessor", "package"], ) -def test_issubset(interval_index, ia_makers, expected, closed, how): +def test_issubset(interval_index, ia_makers, squeeze, expected, closed, how): ias = [make_ia(interval_index, closed) for make_ia in ia_makers] result = perform_op( *ias, how=how, function=piso_intervalarray.issubset, - squeeze=True, + squeeze=squeeze, ) equal_op = np.array_equal if isinstance(expected, np.ndarray) else operator.eq assert equal_op(result, expected) diff --git a/tests/test_single_interval_array.py b/tests/test_single_interval_array.py index ef47246..d1ad43c 100644 --- a/tests/test_single_interval_array.py +++ b/tests/test_single_interval_array.py @@ -487,73 +487,3 @@ 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( - "tuples, squeeze, expected", - [ - ([(1, 2), (1, 2)], True, True), - ([(1, 3), (0, 2)], True, False), - ([(1, 3), (1, 2), (0, 1)], True, np.array([True, False])), - ([(1, 2), (1, 2)], False, np.array([True])), - ([(1, 3), (0, 2)], False, np.array([False])), - ([(1, 3), (1, 2), (0, 1)], False, np.array([True, False])), - ], -) -@pytest.mark.parametrize( - "closed", - ["left", "right"], -) -@pytest.mark.parametrize( - "how", - ["supplied", "accessor", "package"], -) -def test_issuperset(interval_index, tuples, squeeze, expected, closed, how): - interval_array = make_ia_from_tuples(interval_index, tuples, closed) - result = perform_op( - interval_array, - how=how, - function=piso_intervalarray.issuperset, - squeeze=squeeze, - ) - equal_op = np.array_equal if isinstance(expected, np.ndarray) else operator.eq - assert equal_op(result, expected) - - -@pytest.mark.parametrize( - "interval_index", - [True, False], -) -@pytest.mark.parametrize( - "tuples, squeeze, expected", - [ - ([(1, 2), (1, 2)], True, True), - ([(1, 3), (0, 2)], True, False), - ([(1, 3), (1, 4), (0, 1)], True, np.array([True, False])), - ([(1, 2), (1, 2)], False, np.array([True])), - ([(1, 3), (0, 2)], False, np.array([False])), - ([(1, 3), (1, 4), (0, 1)], False, np.array([True, False])), - ], -) -@pytest.mark.parametrize( - "closed", - ["left", "right"], -) -@pytest.mark.parametrize( - "how", - ["supplied", "accessor", "package"], -) -def test_issubset(interval_index, tuples, squeeze, expected, closed, how): - interval_array = make_ia_from_tuples(interval_index, tuples, closed) - result = perform_op( - interval_array, - how=how, - function=piso_intervalarray.issubset, - squeeze=squeeze, - ) - equal_op = np.array_equal if isinstance(expected, np.ndarray) else operator.eq - assert equal_op(result, expected) From de163114a4b453f676176deab48cd7e24f9a7a4e Mon Sep 17 00:00:00 2001 From: Riley Clement Date: Thu, 14 Oct 2021 10:07:48 +1100 Subject: [PATCH 09/11] black reformat --- tests/test_interval.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_interval.py b/tests/test_interval.py index d546f0e..2927308 100644 --- a/tests/test_interval.py +++ b/tests/test_interval.py @@ -629,8 +629,6 @@ def test_symmetric_difference_closed_value_error(closed_values): piso_interval.symmetric_difference(*intervals) - - @pytest.mark.parametrize( "tuples, squeeze, expected", [ From 7992cf6cc31faebf6024d810f435caee303d8057 Mon Sep 17 00:00:00 2001 From: Riley Clement Date: Thu, 14 Oct 2021 10:17:45 +1100 Subject: [PATCH 10/11] linting --- tests/test_single_interval_array.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_single_interval_array.py b/tests/test_single_interval_array.py index d1ad43c..bdb91dd 100644 --- a/tests/test_single_interval_array.py +++ b/tests/test_single_interval_array.py @@ -1,6 +1,3 @@ -import operator - -import numpy as np import pandas as pd import pytest From 0b2edde0ab89a91f36d0e2fab65f7e373078ef7a Mon Sep 17 00:00:00 2001 From: Riley Clement Date: Fri, 15 Oct 2021 09:12:14 +1100 Subject: [PATCH 11/11] v0.2.0 --- docs/release_notes/index.rst | 7 ++++++- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/release_notes/index.rst b/docs/release_notes/index.rst index bc89392..ff5bc83 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.2.0 2021-10-15** + Added the following methods - :meth:`piso.isdisjoint` @@ -15,7 +21,6 @@ Added the following methods - :meth:`piso.interval.issuperset` - :meth:`piso.interval.issubset` -ADD UNRELEASED CHANGES ABOVE THIS LINE **v0.1.0 2021-10-10** diff --git a/pyproject.toml b/pyproject.toml index c69a84e..7e8fbbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "poetry.masonry.api" [tool.poetry] name = "piso" -version = "0.1.0" +version = "0.2.0" description = "Pandas Interval Set Operations: methods for set operations for pandas' Interval, IntervalArray and IntervalIndex" readme = "README.md" authors = ["Riley Clement "]