diff --git a/.codacy.yml b/.codacy.yml index 01aaf02..a11f11a 100644 --- a/.codacy.yml +++ b/.codacy.yml @@ -1,6 +1,8 @@ --- exclude_paths: - 'staircase/_version.py' + - 'staircase/_typing.py' + - 'staircase/__init__.py' - 'tests/**/*' - 'tests/*' - 'benchmarks/**/*' @@ -9,3 +11,4 @@ exclude_paths: - 'README.md' - 'docs/*' - 'docs/**/*' + - '.github/*' diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..4c44789 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,48 @@ +--- + +name: Bug Report +about: Create a bug report to help us improve staircase +title: "BUG:" +labels: "Bug" + +--- + +- [ ] I have checked that this issue has not already been reported. + +- [ ] I have confirmed this bug exists on the latest version of staircase. + +--- + +**Note**: Please read [this guide](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) detailing how to provide the necessary information for us to reproduce your bug. + +#### Code Sample, a copy-pastable example + +```python +# Your code here + +``` + +#### Problem description + +[this should explain **why** the current behaviour is a problem and why the expected output is a better solution] + +#### Expected Output + +#### Dependency Versions`` + +Please run the following code: + +```python +import staircase +import pandas +import numpy + +for pkg in (staircase, pandas, numpy): + print(pkg.__name__, pkg.__version__) +``` + +
+ +[paste the output here leaving a blank line after the details tag] + +
diff --git a/.github/ISSUE_TEMPLATE/documentation_improvement.md b/.github/ISSUE_TEMPLATE/documentation_improvement.md new file mode 100644 index 0000000..530fd34 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation_improvement.md @@ -0,0 +1,22 @@ +--- + +name: Documentation Improvement +about: Report wrong or missing documentation +title: "DOC:" +labels: "Documentation" + +--- + +#### Location of the documentation + +[this should provide the location of the documentation, e.g. "https://www.staircase.dev/en/latest/reference/api/staircase.Stairs.fillna.html"] + +**Note**: You can check the latest versions of the docs on `master` [here](https://www.staircase.dev/en/master/). + +#### Documentation problem + +[this should provide a description of what documentation you believe needs to be fixed/improved] + +#### Suggested fix for documentation + +[this should explain the suggested fix and **why** it's better than the existing documentation] diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..85bdbb5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,25 @@ +--- + +name: Feature Request +about: Suggest an enhancement for staircase +title: "ENH:" +labels: "Enhancement" + +--- + +#### Is your feature request related to a problem? + +[if so, what is the problem? What do you want to do?"] + +#### Describe the solution you'd like + +[a DETAILED description of the feature request] + +#### Additional context + +[add any other context, code examples, or references to existing implementations about the feature request here] + +```python +# Your code here, if applicable + +``` diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..8705fdb --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,4 @@ +- [ ] closes #xxxx +- [ ] tests added / passed +- [ ] ensure all linting tests pass +- [ ] changelog entry diff --git a/.github/workflows/assign.yml b/.github/workflows/assign.yml new file mode 100644 index 0000000..a6d3f1f --- /dev/null +++ b/.github/workflows/assign.yml @@ -0,0 +1,14 @@ +name: Assign +on: + issue_comment: + types: created + +jobs: + one: + runs-on: ubuntu-latest + steps: + - if: github.event.comment.body == 'take' + name: + run: | + echo "Assigning issue ${{ github.event.issue.number }} to ${{ github.event.comment.user.login }}" + curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -d '{"assignees": ["${{ github.event.comment.user.login }}"]}' https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees diff --git a/.github/workflows/publish-test-pypi.yml b/.github/workflows/publish-pypi.yml similarity index 73% rename from .github/workflows/publish-test-pypi.yml rename to .github/workflows/publish-pypi.yml index 00d18b0..8fec603 100644 --- a/.github/workflows/publish-test-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,4 +1,4 @@ -name: Upload to test-PYPI +name: Upload to PYPI on: release: @@ -19,8 +19,7 @@ jobs: poetry install - name: Build package run: poetry build - - name: Publish to Test-PyPI + - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@master with: - password: ${{ secrets.TEST_PYPI_API_TOKEN }} - repository_url: https://test.pypi.org/legacy/ \ No newline at end of file + password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file diff --git a/docs/release_notes/changelog.rst b/docs/release_notes/changelog.rst index 3bd2a99..3871e95 100644 --- a/docs/release_notes/changelog.rst +++ b/docs/release_notes/changelog.rst @@ -6,6 +6,15 @@ Changelog ========= +v2.0.1 2021-09-13 + +- bugfix for incorrect closed parameter not being produced by operations with right-closed step functions (#GH95) +- bugfix for slicing with non-fixed frequency period index (#GH108) +- bugfix for Stairs binary operations with np.nan reporting incorrect number of step changes (#GH109) +- throw `ClosedMismatchError` on binary operations with different `closed` values (#GH96) +Contributors: @amagee (Andrew Magee) + + v2.0.0 2021-08-25 - see :ref:`What's new in Version 2 ` @@ -123,4 +132,4 @@ v1.0.0 2020-09-01 - updated documentation to include :ref:`A note on interval endpoints` - parameter *start* in :meth:`staircase.Stairs.layer` made optional to make method symmetric with respect to time -- removed *staircase.Stairs.evaluate* method (superseded by :meth:`staircase.Stairs.sample`) \ No newline at end of file +- removed *staircase.Stairs.evaluate* method (superseded by :meth:`staircase.Stairs.sample`) diff --git a/poetry.lock b/poetry.lock index ad8d548..5a19e0c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6,14 +6,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "appnope" version = "0.1.2" @@ -24,19 +16,18 @@ python-versions = "*" [[package]] name = "argon2-cffi" -version = "20.1.0" +version = "21.1.0" description = "The secure Argon2 password hashing algorithm." category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.5" [package.dependencies] cffi = ">=1.0.0" -six = "*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest", "sphinx", "wheel", "pre-commit"] -docs = ["sphinx"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest", "sphinx", "furo", "wheel", "pre-commit"] +docs = ["sphinx", "furo"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"] [[package]] @@ -105,32 +96,36 @@ testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3 [[package]] name = "black" -version = "21.7b0" +version = "21.8b0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -appdirs = "*" click = ">=7.1.2" dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} mypy-extensions = ">=0.4.3" -pathspec = ">=0.8.1,<1" +pathspec = ">=0.9.0,<1" +platformdirs = ">=2" regex = ">=2020.1.8" tomli = ">=0.2.6,<2.0.0" typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} -typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} +typing-extensions = [ + {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, + {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, +] [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] python2 = ["typed-ast (>=1.4.2)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "bleach" -version = "4.0.0" +version = "4.1.0" description = "An easy safelist-based HTML-sanitizing tool." category = "dev" optional = false @@ -162,7 +157,7 @@ pycparser = "*" [[package]] name = "cfgv" -version = "3.3.0" +version = "3.3.1" description = "Validate configuration and produce human readable error messages." category = "dev" optional = false @@ -331,7 +326,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.6.4" +version = "4.8.1" description = "Read metadata from Python packages" category = "dev" optional = false @@ -428,16 +423,17 @@ python-versions = "*" [[package]] name = "isort" -version = "5.8.0" +version = "5.9.3" description = "A Python utility / library to sort Python imports." category = "dev" optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.6.1,<4.0" [package.extras] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] [[package]] name = "jedi" @@ -488,13 +484,14 @@ format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator [[package]] name = "jupyter-client" -version = "6.1.13" +version = "7.0.2" description = "Jupyter protocol implementation and client libraries" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6.1" [package.dependencies] +entrypoints = "*" jupyter-core = ">=4.6.0" nest-asyncio = ">=1.5" python-dateutil = ">=2.1" @@ -503,8 +500,8 @@ tornado = ">=4.1" traitlets = "*" [package.extras] -doc = ["sphinx (>=1.3.6)", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] -test = ["async-generator", "ipykernel", "ipython", "mock", "pytest-asyncio", "pytest-timeout", "pytest", "mypy", "pre-commit", "jedi (<0.18)"] +doc = ["myst-parser", "sphinx (>=1.3.6)", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] +test = ["codecov", "coverage", "ipykernel", "ipython", "mock", "mypy", "pre-commit", "pytest", "pytest-asyncio", "pytest-cov", "pytest-timeout", "jedi (<0.18)"] [[package]] name = "jupyter-core" @@ -599,14 +596,14 @@ python-versions = "*" [[package]] name = "nbclient" -version = "0.5.1" +version = "0.5.4" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.1" [package.dependencies] -async-generator = "*" +async-generator = {version = "*", markers = "python_version < \"3.7\""} jupyter-client = ">=6.1.5" nbformat = ">=5.0" nest-asyncio = "*" @@ -747,36 +744,20 @@ pyparsing = ">=2.0.2" [[package]] name = "pandas" -version = "0.25.3" +version = "1.1.5" description = "Powerful data structures for data analysis, time series, and statistics" category = "main" optional = false -python-versions = ">=3.5.3" +python-versions = ">=3.6.1" [package.dependencies] -numpy = ">=1.13.3" -python-dateutil = ">=2.6.1" +numpy = ">=1.15.4" +python-dateutil = ">=2.7.3" pytz = ">=2017.2" [package.extras] test = ["pytest (>=4.0.2)", "pytest-xdist", "hypothesis (>=3.58)"] -[[package]] -name = "pandas" -version = "1.3.2" -description = "Powerful data structures for data analysis, time series, and statistics" -category = "main" -optional = false -python-versions = ">=3.7.1" - -[package.dependencies] -numpy = ">=1.17.3" -python-dateutil = ">=2.7.3" -pytz = ">=2017.3" - -[package.extras] -test = ["hypothesis (>=3.58)", "pytest (>=6.0)", "pytest-xdist"] - [[package]] name = "pandocfilters" version = "1.4.3" @@ -826,7 +807,7 @@ python-versions = "*" [[package]] name = "pillow" -version = "8.3.1" +version = "8.3.2" description = "Python Imaging Library (Fork)" category = "main" optional = false @@ -834,7 +815,7 @@ python-versions = ">=3.6" [[package]] name = "platformdirs" -version = "2.2.0" +version = "2.3.0" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false @@ -846,21 +827,22 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "2.14.0" +version = "2.15.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -889,11 +871,11 @@ twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.3" +version = "3.0.19" description = "Library for building powerful interactive command lines in Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.1" [package.dependencies] wcwidth = "*" @@ -964,7 +946,7 @@ python-versions = ">=3.6" [[package]] name = "pytest" -version = "6.2.4" +version = "6.2.5" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -977,7 +959,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0.0a1" +pluggy = ">=0.12,<2.0" py = ">=1.8.2" toml = "*" @@ -1029,7 +1011,7 @@ python-versions = "*" [[package]] name = "pywinpty" -version = "1.1.3" +version = "1.1.4" description = "Pseudo terminal support for Windows from Python." category = "dev" optional = false @@ -1057,7 +1039,7 @@ py = {version = "*", markers = "implementation_name == \"pypy\""} [[package]] name = "regex" -version = "2021.8.21" +version = "2021.8.28" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -1255,7 +1237,7 @@ test = ["pytest"] [[package]] name = "terminado" -version = "0.11.1" +version = "0.12.1" description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." category = "dev" optional = false @@ -1304,6 +1286,29 @@ category = "dev" optional = false python-versions = ">= 3.5" +[[package]] +name = "tox" +version = "3.24.3" +description = "tox is a generic virtualenv management and test command line tool" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} +filelock = ">=3.0.0" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +packaging = ">=14" +pluggy = ">=0.12.0" +py = ">=1.4.17" +six = ">=1.14.0" +toml = ">=0.9.4" +virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" + +[package.extras] +docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] +testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)", "pathlib2 (>=2.3.3)"] + [[package]] name = "traitlets" version = "4.3.3" @@ -1330,7 +1335,7 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.10.0.0" +version = "3.10.0.2" description = "Backported and Experimental Type Hints for Python 3.5+" category = "dev" optional = false @@ -1403,45 +1408,30 @@ codecov = [] [metadata] lock-version = "1.1" -python-versions = "^3.6" -content-hash = "8d1f5cf9ac3dee3e542251b8382444a5b90cff5a46b0965838ad67dd8f3f7ffe" +python-versions = "^3.6.1" +content-hash = "b110e6c00c3c4654ed9d1a5de31fe38d1212bd61ff48c93835e7e4e1acd94ba7" [metadata.files] alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] appnope = [ {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, ] argon2-cffi = [ - {file = "argon2-cffi-20.1.0.tar.gz", hash = "sha256:d8029b2d3e4b4cea770e9e5a0104dd8fa185c1724a0f01528ae4826a6d25f97d"}, - {file = "argon2_cffi-20.1.0-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:6ea92c980586931a816d61e4faf6c192b4abce89aa767ff6581e6ddc985ed003"}, - {file = "argon2_cffi-20.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:05a8ac07c7026542377e38389638a8a1e9b78f1cd8439cd7493b39f08dd75fbf"}, - {file = "argon2_cffi-20.1.0-cp27-cp27m-win32.whl", hash = "sha256:0bf066bc049332489bb2d75f69216416329d9dc65deee127152caeb16e5ce7d5"}, - {file = "argon2_cffi-20.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:57358570592c46c420300ec94f2ff3b32cbccd10d38bdc12dc6979c4a8484fbc"}, - {file = "argon2_cffi-20.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7d455c802727710e9dfa69b74ccaab04568386ca17b0ad36350b622cd34606fe"}, - {file = "argon2_cffi-20.1.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:b160416adc0f012fb1f12588a5e6954889510f82f698e23ed4f4fa57f12a0647"}, - {file = "argon2_cffi-20.1.0-cp35-cp35m-win32.whl", hash = "sha256:9bee3212ba4f560af397b6d7146848c32a800652301843df06b9e8f68f0f7361"}, - {file = "argon2_cffi-20.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:392c3c2ef91d12da510cfb6f9bae52512a4552573a9e27600bdb800e05905d2b"}, - {file = "argon2_cffi-20.1.0-cp36-cp36m-win32.whl", hash = "sha256:ba7209b608945b889457f949cc04c8e762bed4fe3fec88ae9a6b7765ae82e496"}, - {file = "argon2_cffi-20.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:da7f0445b71db6d3a72462e04f36544b0de871289b0bc8a7cc87c0f5ec7079fa"}, - {file = "argon2_cffi-20.1.0-cp37-abi3-macosx_10_6_intel.whl", hash = "sha256:cc0e028b209a5483b6846053d5fd7165f460a1f14774d79e632e75e7ae64b82b"}, - {file = "argon2_cffi-20.1.0-cp37-cp37m-win32.whl", hash = "sha256:18dee20e25e4be86680b178b35ccfc5d495ebd5792cd00781548d50880fee5c5"}, - {file = "argon2_cffi-20.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6678bb047373f52bcff02db8afab0d2a77d83bde61cfecea7c5c62e2335cb203"}, - {file = "argon2_cffi-20.1.0-cp38-cp38-win32.whl", hash = "sha256:77e909cc756ef81d6abb60524d259d959bab384832f0c651ed7dcb6e5ccdbb78"}, - {file = "argon2_cffi-20.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:9dfd5197852530294ecb5795c97a823839258dfd5eb9420233c7cfedec2058f2"}, - {file = "argon2_cffi-20.1.0-cp39-cp39-win32.whl", hash = "sha256:e2db6e85c057c16d0bd3b4d2b04f270a7467c147381e8fd73cbbe5bc719832be"}, - {file = "argon2_cffi-20.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8a84934bd818e14a17943de8099d41160da4a336bcc699bb4c394bbb9b94bd32"}, - {file = "argon2_cffi-20.1.0-pp36-pypy36_pp73-macosx_10_7_x86_64.whl", hash = "sha256:b94042e5dcaa5d08cf104a54bfae614be502c6f44c9c89ad1535b2ebdaacbd4c"}, - {file = "argon2_cffi-20.1.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:8282b84ceb46b5b75c3a882b28856b8cd7e647ac71995e71b6705ec06fc232c3"}, - {file = "argon2_cffi-20.1.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3aa804c0e52f208973845e8b10c70d8957c9e5a666f702793256242e9167c4e0"}, - {file = "argon2_cffi-20.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:36320372133a003374ef4275fbfce78b7ab581440dfca9f9471be3dd9a522428"}, + {file = "argon2-cffi-21.1.0.tar.gz", hash = "sha256:f710b61103d1a1f692ca3ecbd1373e28aa5e545ac625ba067ff2feca1b2bb870"}, + {file = "argon2_cffi-21.1.0-cp35-abi3-macosx_10_14_x86_64.whl", hash = "sha256:217b4f0f853ccbbb5045242946ad2e162e396064575860141b71a85eb47e475a"}, + {file = "argon2_cffi-21.1.0-cp35-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa7e7d1fc22514a32b1761fdfa1882b6baa5c36bb3ef557bdd69e6fc9ba14a41"}, + {file = "argon2_cffi-21.1.0-cp35-abi3-win32.whl", hash = "sha256:e4d8f0ae1524b7b0372a3e574a2561cbdddb3fdb6c28b70a72868189bda19659"}, + {file = "argon2_cffi-21.1.0-cp35-abi3-win_amd64.whl", hash = "sha256:65213a9174320a1aee03fe826596e0620783966b49eb636955958b3074e87ff9"}, + {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-macosx_10_7_x86_64.whl", hash = "sha256:245f64a203012b144b7b8c8ea6d468cb02b37caa5afee5ba4a10c80599334f6a"}, + {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4ad152c418f7eb640eac41ac815534e6aa61d1624530b8e7779114ecfbf327f8"}, + {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:bc513db2283c385ea4da31a2cd039c33380701f376f4edd12fe56db118a3b21a"}, + {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c7a7c8cc98ac418002090e4add5bebfff1b915ea1cb459c578cd8206fef10378"}, + {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:165cadae5ac1e26644f5ade3bd9c18d89963be51d9ea8817bd671006d7909057"}, + {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:566ffb581bbd9db5562327aee71b2eda24a1c15b23a356740abe3c011bbe0dcb"}, ] async-generator = [ {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, @@ -1468,12 +1458,12 @@ backcall = [ {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"}, ] black = [ - {file = "black-21.7b0-py3-none-any.whl", hash = "sha256:1c7aa6ada8ee864db745b22790a32f94b2795c253a75d6d9b5e439ff10d23116"}, - {file = "black-21.7b0.tar.gz", hash = "sha256:c8373c6491de9362e39271630b65b964607bc5c79c83783547d76c839b3aa219"}, + {file = "black-21.8b0-py3-none-any.whl", hash = "sha256:2a0f9a8c2b2a60dbcf1ccb058842fb22bdbbcb2f32c6cc02d9578f90b92ce8b7"}, + {file = "black-21.8b0.tar.gz", hash = "sha256:570608d28aa3af1792b98c4a337dbac6367877b47b12b88ab42095cfc1a627c2"}, ] bleach = [ - {file = "bleach-4.0.0-py2.py3-none-any.whl", hash = "sha256:c1685a132e6a9a38bf93752e5faab33a9517a6c0bb2f37b785e47bf253bdb51d"}, - {file = "bleach-4.0.0.tar.gz", hash = "sha256:ffa9221c6ac29399cc50fcc33473366edd0cf8d5e2cbbbb63296dc327fb67cc8"}, + {file = "bleach-4.1.0-py2.py3-none-any.whl", hash = "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"}, + {file = "bleach-4.1.0.tar.gz", hash = "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da"}, ] certifi = [ {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, @@ -1527,8 +1517,8 @@ cffi = [ {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, ] cfgv = [ - {file = "cfgv-3.3.0-py2.py3-none-any.whl", hash = "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"}, - {file = "cfgv-3.3.0.tar.gz", hash = "sha256:9e600479b3b99e8af981ecdfc80a0296104ee610cab48a5ae4ffd0b668650eb1"}, + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] charset-normalizer = [ {file = "charset-normalizer-2.0.4.tar.gz", hash = "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"}, @@ -1648,8 +1638,8 @@ imagesize = [ {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.6.4-py3-none-any.whl", hash = "sha256:ed5157fef23a4bc4594615a0dd8eba94b2bb36bf2a343fa3d8bb2fa0a62a99d5"}, - {file = "importlib_metadata-4.6.4.tar.gz", hash = "sha256:7b30a78db2922d78a6f47fb30683156a14f3c6aa5cc23f77cc8967e9ab2d002f"}, + {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, + {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, ] importlib-resources = [ {file = "importlib_resources-5.2.2-py3-none-any.whl", hash = "sha256:2480d8e07d1890056cb53c96e3de44fead9c62f2ba949b0f2e4c4345f4afa977"}, @@ -1672,8 +1662,8 @@ ipython-genutils = [ {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, ] isort = [ - {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, - {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, + {file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"}, + {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"}, ] jedi = [ {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, @@ -1688,8 +1678,8 @@ jsonschema = [ {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, ] jupyter-client = [ - {file = "jupyter_client-6.1.13-py3-none-any.whl", hash = "sha256:1df17b0525b45cc03645fc9eeab023765882d3c18fb100f82499cf6a353b3941"}, - {file = "jupyter_client-6.1.13.tar.gz", hash = "sha256:d03558bc9b7955d8b4a6df604a8d9d257e00bcea7fb364fe41cdef81d998a966"}, + {file = "jupyter_client-7.0.2-py3-none-any.whl", hash = "sha256:37a30c13d3655b819add61c830594090af7fca40cd2d74f41cad9e2e12118501"}, + {file = "jupyter_client-7.0.2.tar.gz", hash = "sha256:0c6cabd07e003a2e9692394bf1ae794188ad17d2e250ed747232d7a473aa772c"}, ] jupyter-core = [ {file = "jupyter_core-4.7.1-py3-none-any.whl", hash = "sha256:8c6c0cac5c1b563622ad49321d5ec47017bd18b94facb381c6973a0486395f8e"}, @@ -1812,8 +1802,8 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] nbclient = [ - {file = "nbclient-0.5.1-py3-none-any.whl", hash = "sha256:4d6b116187c795c99b9dba13d46e764d596574b14c296d60670c8dfe454db364"}, - {file = "nbclient-0.5.1.tar.gz", hash = "sha256:01e2d726d16eaf2cde6db74a87e2451453547e8832d142f73f72fddcd4fe0250"}, + {file = "nbclient-0.5.4-py3-none-any.whl", hash = "sha256:95a300c6fbe73721736cf13972a46d8d666f78794b832866ed7197a504269e11"}, + {file = "nbclient-0.5.4.tar.gz", hash = "sha256:6c8ad36a28edad4562580847f9f1636fe5316a51a323ed85a24a4ad37d4aefce"}, ] nbconvert = [ {file = "nbconvert-6.0.7-py3-none-any.whl", hash = "sha256:39e9f977920b203baea0be67eea59f7b37a761caa542abe80f5897ce3cf6311d"}, @@ -1880,44 +1870,30 @@ packaging = [ {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, ] pandas = [ - {file = "pandas-0.25.3-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:df8864824b1fe488cf778c3650ee59c3a0d8f42e53707de167ba6b4f7d35f133"}, - {file = "pandas-0.25.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7458c48e3d15b8aaa7d575be60e1e4dd70348efcd9376656b72fecd55c59a4c3"}, - {file = "pandas-0.25.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:61741f5aeb252f39c3031d11405305b6d10ce663c53bc3112705d7ad66c013d0"}, - {file = "pandas-0.25.3-cp35-cp35m-win32.whl", hash = "sha256:adc3d3a3f9e59a38d923e90e20c4922fc62d1e5a03d083440468c6d8f3f1ae0a"}, - {file = "pandas-0.25.3-cp35-cp35m-win_amd64.whl", hash = "sha256:975c461accd14e89d71772e89108a050fa824c0b87a67d34cedf245f6681fc17"}, - {file = "pandas-0.25.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ee50c2142cdcf41995655d499a157d0a812fce55c97d9aad13bc1eef837ed36c"}, - {file = "pandas-0.25.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4545467a637e0e1393f7d05d61dace89689ad6d6f66f267f86fff737b702cce9"}, - {file = "pandas-0.25.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bbe3eb765a0b1e578833d243e2814b60c825b7fdbf4cdfe8e8aae8a08ed56ecf"}, - {file = "pandas-0.25.3-cp36-cp36m-win32.whl", hash = "sha256:8153705d6545fd9eb6dd2bc79301bff08825d2e2f716d5dced48daafc2d0b81f"}, - {file = "pandas-0.25.3-cp36-cp36m-win_amd64.whl", hash = "sha256:26382aab9c119735908d94d2c5c08020a4a0a82969b7e5eefb92f902b3b30ad7"}, - {file = "pandas-0.25.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:00dff3a8e337f5ed7ad295d98a31821d3d0fe7792da82d78d7fd79b89c03ea9d"}, - {file = "pandas-0.25.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e45055c30a608076e31a9fcd780a956ed3b1fa20db61561b8d88b79259f526f7"}, - {file = "pandas-0.25.3-cp37-cp37m-win32.whl", hash = "sha256:255920e63850dc512ce356233081098554d641ba99c3767dde9e9f35630f994b"}, - {file = "pandas-0.25.3-cp37-cp37m-win_amd64.whl", hash = "sha256:22361b1597c8c2ffd697aa9bf85423afa9e1fcfa6b1ea821054a244d5f24d75e"}, - {file = "pandas-0.25.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9962957a27bfb70ab64103d0a7b42fa59c642fb4ed4cb75d0227b7bb9228535d"}, - {file = "pandas-0.25.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:78bf638993219311377ce9836b3dc05f627a666d0dbc8cec37c0ff3c9ada673b"}, - {file = "pandas-0.25.3-cp38-cp38-win32.whl", hash = "sha256:6a3ac2c87e4e32a969921d1428525f09462770c349147aa8e9ab95f88c71ec71"}, - {file = "pandas-0.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:33970f4cacdd9a0ddb8f21e151bfb9f178afb7c36eb7c25b9094c02876f385c2"}, - {file = "pandas-0.25.3.tar.gz", hash = "sha256:52da74df8a9c9a103af0a72c9d5fdc8e0183a90884278db7f386b5692a2220a4"}, - {file = "pandas-1.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ba7ceb8abc6dbdb1e34612d1173d61e4941f1a1eb7e6f703b2633134ae6a6c89"}, - {file = "pandas-1.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcb71b1935249de80e3a808227189eee381d4d74a31760ced2df21eedc92a8e3"}, - {file = "pandas-1.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa54dc1d3e5d004a09ab0b1751473698011ddf03e14f1f59b84ad9a6ac630975"}, - {file = "pandas-1.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34ced9ce5d5b17b556486da7256961b55b471d64a8990b56e67a84ebeb259416"}, - {file = "pandas-1.3.2-cp37-cp37m-win32.whl", hash = "sha256:a56246de744baf646d1f3e050c4653d632bc9cd2e0605f41051fea59980e880a"}, - {file = "pandas-1.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:53b17e4debba26b7446b1e4795c19f94f0c715e288e08145e44bdd2865e819b3"}, - {file = "pandas-1.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f07a9745ca075ae73a5ce116f5e58f691c0dc9de0bff163527858459df5c176f"}, - {file = "pandas-1.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9e8e0ce5284ebebe110efd652c164ed6eab77f5de4c3533abc756302ee77765"}, - {file = "pandas-1.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a78d7066d1c921a77e3306aa0ebf6e55396c097d5dfcc4df8defe3dcecb735"}, - {file = "pandas-1.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:132def05e73d292c949b02e7ef873debb77acc44a8b119d215921046f0c3a91d"}, - {file = "pandas-1.3.2-cp38-cp38-win32.whl", hash = "sha256:69e1b2f5811f46827722fd641fdaeedb26002bd1e504eacc7a8ec36bdc25393e"}, - {file = "pandas-1.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:7996d311413379136baf0f3cf2a10e331697657c87ced3f17ac7c77f77fe34a3"}, - {file = "pandas-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1738154049062156429a5cf2fd79a69c9f3fa4f231346a7ec6fd156cd1a9a621"}, - {file = "pandas-1.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cce01f6d655b4add966fcd36c32c5d1fe84628e200626b3f5e2f40db2d16a0f"}, - {file = "pandas-1.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1099e2a0cd3a01ec62cca183fc1555833a2d43764950ef8cb5948c8abfc51014"}, - {file = "pandas-1.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cd5776be891331a3e6b425b5abeab9596abea18435c5982191356f9b24ae731"}, - {file = "pandas-1.3.2-cp39-cp39-win32.whl", hash = "sha256:66a95361b81b4ba04b699ecd2416b0591f40cd1e24c60a8bfe0d19009cfa575a"}, - {file = "pandas-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:89f40e5d21814192802421df809f948247d39ffe171e45fe2ab4abf7bd4279d8"}, - {file = "pandas-1.3.2.tar.gz", hash = "sha256:cbcb84d63867af3411fa063af3de64902665bb5b3d40b25b2059e40603594e87"}, + {file = "pandas-1.1.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bf23a3b54d128b50f4f9d4675b3c1857a688cc6731a32f931837d72effb2698d"}, + {file = "pandas-1.1.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5a780260afc88268a9d3ac3511d8f494fdcf637eece62fb9eb656a63d53eb7ca"}, + {file = "pandas-1.1.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b61080750d19a0122469ab59b087380721d6b72a4e7d962e4d7e63e0c4504814"}, + {file = "pandas-1.1.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:0de3ddb414d30798cbf56e642d82cac30a80223ad6fe484d66c0ce01a84d6f2f"}, + {file = "pandas-1.1.5-cp36-cp36m-win32.whl", hash = "sha256:70865f96bb38fec46f7ebd66d4b5cfd0aa6b842073f298d621385ae3898d28b5"}, + {file = "pandas-1.1.5-cp36-cp36m-win_amd64.whl", hash = "sha256:19a2148a1d02791352e9fa637899a78e371a3516ac6da5c4edc718f60cbae648"}, + {file = "pandas-1.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26fa92d3ac743a149a31b21d6f4337b0594b6302ea5575b37af9ca9611e8981a"}, + {file = "pandas-1.1.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c16d59c15d946111d2716856dd5479221c9e4f2f5c7bc2d617f39d870031e086"}, + {file = "pandas-1.1.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3be7a7a0ca71a2640e81d9276f526bca63505850add10206d0da2e8a0a325dae"}, + {file = "pandas-1.1.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:573fba5b05bf2c69271a32e52399c8de599e4a15ab7cec47d3b9c904125ab788"}, + {file = "pandas-1.1.5-cp37-cp37m-win32.whl", hash = "sha256:21b5a2b033380adbdd36b3116faaf9a4663e375325831dac1b519a44f9e439bb"}, + {file = "pandas-1.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:24c7f8d4aee71bfa6401faeba367dd654f696a77151a8a28bc2013f7ced4af98"}, + {file = "pandas-1.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2860a97cbb25444ffc0088b457da0a79dc79f9c601238a3e0644312fcc14bf11"}, + {file = "pandas-1.1.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5008374ebb990dad9ed48b0f5d0038124c73748f5384cc8c46904dace27082d9"}, + {file = "pandas-1.1.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2c2f7c670ea4e60318e4b7e474d56447cf0c7d83b3c2a5405a0dbb2600b9c48e"}, + {file = "pandas-1.1.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0a643bae4283a37732ddfcecab3f62dd082996021b980f580903f4e8e01b3c5b"}, + {file = "pandas-1.1.5-cp38-cp38-win32.whl", hash = "sha256:5447ea7af4005b0daf695a316a423b96374c9c73ffbd4533209c5ddc369e644b"}, + {file = "pandas-1.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:4c62e94d5d49db116bef1bd5c2486723a292d79409fc9abd51adf9e05329101d"}, + {file = "pandas-1.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:731568be71fba1e13cae212c362f3d2ca8932e83cb1b85e3f1b4dd77d019254a"}, + {file = "pandas-1.1.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c61c043aafb69329d0f961b19faa30b1dab709dd34c9388143fc55680059e55a"}, + {file = "pandas-1.1.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2b1c6cd28a0dfda75c7b5957363333f01d370936e4c6276b7b8e696dd500582a"}, + {file = "pandas-1.1.5-cp39-cp39-win32.whl", hash = "sha256:c94ff2780a1fd89f190390130d6d36173ca59fcfb3fe0ff596f9a56518191ccb"}, + {file = "pandas-1.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:edda9bacc3843dfbeebaf7a701763e68e741b08fccb889c003b0a52f0ee95782"}, + {file = "pandas-1.1.5.tar.gz", hash = "sha256:f10fc41ee3c75a474d3bdf68d396f10782d013d7f67db99c0efbfd0acb99701b"}, ] pandocfilters = [ {file = "pandocfilters-1.4.3.tar.gz", hash = "sha256:bc63fbb50534b4b1f8ebe1860889289e8af94a23bff7445259592df25a3906eb"}, @@ -1939,65 +1915,79 @@ pickleshare = [ {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] pillow = [ - {file = "Pillow-8.3.1-1-cp36-cp36m-win_amd64.whl", hash = "sha256:fd7eef578f5b2200d066db1b50c4aa66410786201669fb76d5238b007918fb24"}, - {file = "Pillow-8.3.1-1-cp37-cp37m-win_amd64.whl", hash = "sha256:75e09042a3b39e0ea61ce37e941221313d51a9c26b8e54e12b3ececccb71718a"}, - {file = "Pillow-8.3.1-1-cp38-cp38-win_amd64.whl", hash = "sha256:c0e0550a404c69aab1e04ae89cca3e2a042b56ab043f7f729d984bf73ed2a093"}, - {file = "Pillow-8.3.1-1-cp39-cp39-win_amd64.whl", hash = "sha256:479ab11cbd69612acefa8286481f65c5dece2002ffaa4f9db62682379ca3bb77"}, - {file = "Pillow-8.3.1-1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f156d6ecfc747ee111c167f8faf5f4953761b5e66e91a4e6767e548d0f80129c"}, - {file = "Pillow-8.3.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:196560dba4da7a72c5e7085fccc5938ab4075fd37fe8b5468869724109812edd"}, - {file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c9569049d04aaacd690573a0398dbd8e0bf0255684fee512b413c2142ab723"}, - {file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c088a000dfdd88c184cc7271bfac8c5b82d9efa8637cd2b68183771e3cf56f04"}, - {file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fc214a6b75d2e0ea7745488da7da3c381f41790812988c7a92345978414fad37"}, - {file = "Pillow-8.3.1-cp36-cp36m-win32.whl", hash = "sha256:a17ca41f45cf78c2216ebfab03add7cc350c305c38ff34ef4eef66b7d76c5229"}, - {file = "Pillow-8.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:67b3666b544b953a2777cb3f5a922e991be73ab32635666ee72e05876b8a92de"}, - {file = "Pillow-8.3.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:ff04c373477723430dce2e9d024c708a047d44cf17166bf16e604b379bf0ca14"}, - {file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9364c81b252d8348e9cc0cb63e856b8f7c1b340caba6ee7a7a65c968312f7dab"}, - {file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a2f381932dca2cf775811a008aa3027671ace723b7a38838045b1aee8669fdcf"}, - {file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d0da39795049a9afcaadec532e7b669b5ebbb2a9134576ebcc15dd5bdae33cc0"}, - {file = "Pillow-8.3.1-cp37-cp37m-win32.whl", hash = "sha256:2b6dfa068a8b6137da34a4936f5a816aba0ecc967af2feeb32c4393ddd671cba"}, - {file = "Pillow-8.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a4eef1ff2d62676deabf076f963eda4da34b51bc0517c70239fafed1d5b51500"}, - {file = "Pillow-8.3.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:660a87085925c61a0dcc80efb967512ac34dbb256ff7dd2b9b4ee8dbdab58cf4"}, - {file = "Pillow-8.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:15a2808e269a1cf2131930183dcc0419bc77bb73eb54285dde2706ac9939fa8e"}, - {file = "Pillow-8.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:969cc558cca859cadf24f890fc009e1bce7d7d0386ba7c0478641a60199adf79"}, - {file = "Pillow-8.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ee77c14a0299d0541d26f3d8500bb57e081233e3fa915fa35abd02c51fa7fae"}, - {file = "Pillow-8.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c11003197f908878164f0e6da15fce22373ac3fc320cda8c9d16e6bba105b844"}, - {file = "Pillow-8.3.1-cp38-cp38-win32.whl", hash = "sha256:3f08bd8d785204149b5b33e3b5f0ebbfe2190ea58d1a051c578e29e39bfd2367"}, - {file = "Pillow-8.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:70af7d222df0ff81a2da601fab42decb009dc721545ed78549cb96e3a1c5f0c8"}, - {file = "Pillow-8.3.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:37730f6e68bdc6a3f02d2079c34c532330d206429f3cee651aab6b66839a9f0e"}, - {file = "Pillow-8.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bc3c7ef940eeb200ca65bd83005eb3aae8083d47e8fcbf5f0943baa50726856"}, - {file = "Pillow-8.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c35d09db702f4185ba22bb33ef1751ad49c266534339a5cebeb5159d364f6f82"}, - {file = "Pillow-8.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b2efa07f69dc395d95bb9ef3299f4ca29bcb2157dc615bae0b42c3c20668ffc"}, - {file = "Pillow-8.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cc866706d56bd3a7dbf8bac8660c6f6462f2f2b8a49add2ba617bc0c54473d83"}, - {file = "Pillow-8.3.1-cp39-cp39-win32.whl", hash = "sha256:9a211b663cf2314edbdb4cf897beeb5c9ee3810d1d53f0e423f06d6ebbf9cd5d"}, - {file = "Pillow-8.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:c2a5ff58751670292b406b9f06e07ed1446a4b13ffced6b6cab75b857485cbc8"}, - {file = "Pillow-8.3.1-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c379425c2707078dfb6bfad2430728831d399dc95a7deeb92015eb4c92345eaf"}, - {file = "Pillow-8.3.1-pp36-pypy36_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:114f816e4f73f9ec06997b2fde81a92cbf0777c9e8f462005550eed6bae57e63"}, - {file = "Pillow-8.3.1-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8960a8a9f4598974e4c2aeb1bff9bdd5db03ee65fd1fce8adf3223721aa2a636"}, - {file = "Pillow-8.3.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:147bd9e71fb9dcf08357b4d530b5167941e222a6fd21f869c7911bac40b9994d"}, - {file = "Pillow-8.3.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1fd5066cd343b5db88c048d971994e56b296868766e461b82fa4e22498f34d77"}, - {file = "Pillow-8.3.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4ebde71785f8bceb39dcd1e7f06bcc5d5c3cf48b9f69ab52636309387b097c8"}, - {file = "Pillow-8.3.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c03e24be975e2afe70dfc5da6f187eea0b49a68bb2b69db0f30a61b7031cee4"}, - {file = "Pillow-8.3.1.tar.gz", hash = "sha256:2cac53839bfc5cece8fdbe7f084d5e3ee61e1303cccc86511d351adcb9e2c792"}, + {file = "Pillow-8.3.2-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:c691b26283c3a31594683217d746f1dad59a7ae1d4cfc24626d7a064a11197d4"}, + {file = "Pillow-8.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f514c2717012859ccb349c97862568fdc0479aad85b0270d6b5a6509dbc142e2"}, + {file = "Pillow-8.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be25cb93442c6d2f8702c599b51184bd3ccd83adebd08886b682173e09ef0c3f"}, + {file = "Pillow-8.3.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d675a876b295afa114ca8bf42d7f86b5fb1298e1b6bb9a24405a3f6c8338811c"}, + {file = "Pillow-8.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59697568a0455764a094585b2551fd76bfd6b959c9f92d4bdec9d0e14616303a"}, + {file = "Pillow-8.3.2-cp310-cp310-win32.whl", hash = "sha256:2d5e9dc0bf1b5d9048a94c48d0813b6c96fccfa4ccf276d9c36308840f40c228"}, + {file = "Pillow-8.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:11c27e74bab423eb3c9232d97553111cc0be81b74b47165f07ebfdd29d825875"}, + {file = "Pillow-8.3.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:11eb7f98165d56042545c9e6db3ce394ed8b45089a67124298f0473b29cb60b2"}, + {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f23b2d3079522fdf3c09de6517f625f7a964f916c956527bed805ac043799b8"}, + {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19ec4cfe4b961edc249b0e04b5618666c23a83bc35842dea2bfd5dfa0157f81b"}, + {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5a31c07cea5edbaeb4bdba6f2b87db7d3dc0f446f379d907e51cc70ea375629"}, + {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15ccb81a6ffc57ea0137f9f3ac2737ffa1d11f786244d719639df17476d399a7"}, + {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8f284dc1695caf71a74f24993b7c7473d77bc760be45f776a2c2f4e04c170550"}, + {file = "Pillow-8.3.2-cp36-cp36m-win32.whl", hash = "sha256:4abc247b31a98f29e5224f2d31ef15f86a71f79c7f4d2ac345a5d551d6393073"}, + {file = "Pillow-8.3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a048dad5ed6ad1fad338c02c609b862dfaa921fcd065d747194a6805f91f2196"}, + {file = "Pillow-8.3.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:06d1adaa284696785375fa80a6a8eb309be722cf4ef8949518beb34487a3df71"}, + {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd24054aaf21e70a51e2a2a5ed1183560d3a69e6f9594a4bfe360a46f94eba83"}, + {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a330bf7014ee034046db43ccbb05c766aa9e70b8d6c5260bfc38d73103b0ba"}, + {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13654b521fb98abdecec105ea3fb5ba863d1548c9b58831dd5105bb3873569f1"}, + {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1bd983c565f92779be456ece2479840ec39d386007cd4ae83382646293d681b"}, + {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4326ea1e2722f3dc00ed77c36d3b5354b8fb7399fb59230249ea6d59cbed90da"}, + {file = "Pillow-8.3.2-cp37-cp37m-win32.whl", hash = "sha256:085a90a99404b859a4b6c3daa42afde17cb3ad3115e44a75f0d7b4a32f06a6c9"}, + {file = "Pillow-8.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:18a07a683805d32826c09acfce44a90bf474e6a66ce482b1c7fcd3757d588df3"}, + {file = "Pillow-8.3.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4e59e99fd680e2b8b11bbd463f3c9450ab799305d5f2bafb74fefba6ac058616"}, + {file = "Pillow-8.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4d89a2e9219a526401015153c0e9dd48319ea6ab9fe3b066a20aa9aee23d9fd3"}, + {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56fd98c8294f57636084f4b076b75f86c57b2a63a8410c0cd172bc93695ee979"}, + {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b11c9d310a3522b0fd3c35667914271f570576a0e387701f370eb39d45f08a4"}, + {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0412516dcc9de9b0a1e0ae25a280015809de8270f134cc2c1e32c4eeb397cf30"}, + {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bcb04ff12e79b28be6c9988f275e7ab69f01cc2ba319fb3114f87817bb7c74b6"}, + {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0b9911ec70731711c3b6ebcde26caea620cbdd9dcb73c67b0730c8817f24711b"}, + {file = "Pillow-8.3.2-cp38-cp38-win32.whl", hash = "sha256:ce2e5e04bb86da6187f96d7bab3f93a7877830981b37f0287dd6479e27a10341"}, + {file = "Pillow-8.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:35d27687f027ad25a8d0ef45dd5208ef044c588003cdcedf05afb00dbc5c2deb"}, + {file = "Pillow-8.3.2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:04835e68ef12904bc3e1fd002b33eea0779320d4346082bd5b24bec12ad9c3e9"}, + {file = "Pillow-8.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10e00f7336780ca7d3653cf3ac26f068fa11b5a96894ea29a64d3dc4b810d630"}, + {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cde7a4d3687f21cffdf5bb171172070bb95e02af448c4c8b2f223d783214056"}, + {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c3ff00110835bdda2b1e2b07f4a2548a39744bb7de5946dc8e95517c4fb2ca6"}, + {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d409030bf3bd05fa66fb5fdedc39c521b397f61ad04309c90444e893d05f7d"}, + {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bff50ba9891be0a004ef48828e012babaaf7da204d81ab9be37480b9020a82b"}, + {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7dbfbc0020aa1d9bc1b0b8bcf255a7d73f4ad0336f8fd2533fcc54a4ccfb9441"}, + {file = "Pillow-8.3.2-cp39-cp39-win32.whl", hash = "sha256:963ebdc5365d748185fdb06daf2ac758116deecb2277ec5ae98139f93844bc09"}, + {file = "Pillow-8.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:cc9d0dec711c914ed500f1d0d3822868760954dce98dfb0b7382a854aee55d19"}, + {file = "Pillow-8.3.2-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2c661542c6f71dfd9dc82d9d29a8386287e82813b0375b3a02983feac69ef864"}, + {file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:548794f99ff52a73a156771a0402f5e1c35285bd981046a502d7e4793e8facaa"}, + {file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b68f565a4175e12e68ca900af8910e8fe48aaa48fd3ca853494f384e11c8bcd"}, + {file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:838eb85de6d9307c19c655c726f8d13b8b646f144ca6b3771fa62b711ebf7624"}, + {file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:feb5db446e96bfecfec078b943cc07744cc759893cef045aa8b8b6d6aaa8274e"}, + {file = "Pillow-8.3.2-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:fc0db32f7223b094964e71729c0361f93db43664dd1ec86d3df217853cedda87"}, + {file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd4fd83aa912d7b89b4b4a1580d30e2a4242f3936882a3f433586e5ab97ed0d5"}, + {file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d0c8ebbfd439c37624db98f3877d9ed12c137cadd99dde2d2eae0dab0bbfc355"}, + {file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cb3dd7f23b044b0737317f892d399f9e2f0b3a02b22b2c692851fb8120d82c6"}, + {file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a66566f8a22561fc1a88dc87606c69b84fa9ce724f99522cf922c801ec68f5c1"}, + {file = "Pillow-8.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ce651ca46d0202c302a535d3047c55a0131a720cf554a578fc1b8a2aff0e7d96"}, + {file = "Pillow-8.3.2.tar.gz", hash = "sha256:dde3f3ed8d00c72631bc19cbfff8ad3b6215062a5eed402381ad365f82f0c18c"}, ] platformdirs = [ - {file = "platformdirs-2.2.0-py3-none-any.whl", hash = "sha256:4666d822218db6a262bdfdc9c39d21f23b4cfdb08af331a81e92751daf6c866c"}, - {file = "platformdirs-2.2.0.tar.gz", hash = "sha256:632daad3ab546bd8e6af0537d09805cec458dce201bccfe23012df73332e181e"}, + {file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"}, + {file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"}, ] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] pre-commit = [ - {file = "pre_commit-2.14.0-py2.py3-none-any.whl", hash = "sha256:ec3045ae62e1aa2eecfb8e86fa3025c2e3698f77394ef8d2011ce0aedd85b2d4"}, - {file = "pre_commit-2.14.0.tar.gz", hash = "sha256:2386eeb4cf6633712c7cc9ede83684d53c8cafca6b59f79c738098b51c6d206c"}, + {file = "pre_commit-2.15.0-py2.py3-none-any.whl", hash = "sha256:a4ed01000afcb484d9eb8d504272e642c4c4099bbad3a6b27e519bd6a3e928a6"}, + {file = "pre_commit-2.15.0.tar.gz", hash = "sha256:3c25add78dbdfb6a28a651780d5c311ac40dd17f160eb3954a0c59da40a505a7"}, ] prometheus-client = [ {file = "prometheus_client-0.11.0-py2.py3-none-any.whl", hash = "sha256:b014bc76815eb1399da8ce5fc84b7717a3e63652b0c0f8804092c9363acab1b2"}, {file = "prometheus_client-0.11.0.tar.gz", hash = "sha256:3a8baade6cb80bcfe43297e33e7623f3118d660d41387593758e2fb1ea173a86"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.3-py3-none-any.whl", hash = "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"}, - {file = "prompt_toolkit-3.0.3.tar.gz", hash = "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e"}, + {file = "prompt_toolkit-3.0.19-py3-none-any.whl", hash = "sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88"}, + {file = "prompt_toolkit-3.0.19.tar.gz", hash = "sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f"}, ] ptyprocess = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, @@ -2051,8 +2041,8 @@ pyrsistent = [ {file = "pyrsistent-0.18.0.tar.gz", hash = "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b"}, ] pytest = [ - {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, - {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-cov = [ {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, @@ -2079,11 +2069,11 @@ pywin32 = [ {file = "pywin32-301-cp39-cp39-win_amd64.whl", hash = "sha256:87604a4087434cd814ad8973bd47d6524bd1fa9e971ce428e76b62a5e0860fdf"}, ] pywinpty = [ - {file = "pywinpty-1.1.3-cp36-none-win_amd64.whl", hash = "sha256:81dc6f16d917b756e06fc58943e9750d59dbefc0ffd2086871d3fa5f33824446"}, - {file = "pywinpty-1.1.3-cp37-none-win_amd64.whl", hash = "sha256:54557887e712ea3215ab0d9f089ed55a6cc8d826cd5d1e340d75300654c9663f"}, - {file = "pywinpty-1.1.3-cp38-none-win_amd64.whl", hash = "sha256:f5e25197397f1fef0362caf3eb89f25441827a1e48bf15827c27021592fd2160"}, - {file = "pywinpty-1.1.3-cp39-none-win_amd64.whl", hash = "sha256:b767276224f86b7560eb9173ba7956758cafcdfab97bb33837d42d2a0f1dbf67"}, - {file = "pywinpty-1.1.3.tar.gz", hash = "sha256:3a1d57b338390333812a5eed31c93c7d8ba82b131078063703e731946d90c9f2"}, + {file = "pywinpty-1.1.4-cp36-none-win_amd64.whl", hash = "sha256:fb975976ad92be44801de95fdf2b0366747767cb0528478553aff85dd63ebb09"}, + {file = "pywinpty-1.1.4-cp37-none-win_amd64.whl", hash = "sha256:5d25b30a2f87105778bc2f57cb1271f58aaa25568921ef042faf001b3b0a7307"}, + {file = "pywinpty-1.1.4-cp38-none-win_amd64.whl", hash = "sha256:c5c3550100689632f6663f39865ef8716835dab1838a9eb9b472644af92673f8"}, + {file = "pywinpty-1.1.4-cp39-none-win_amd64.whl", hash = "sha256:ad60a336d92ac38e2159320db6d5999c4c2726a141c3ed3f9694021feb6a234e"}, + {file = "pywinpty-1.1.4.tar.gz", hash = "sha256:cc700c9d5a9fcebf677ac93a4943ca9a24db6e2f11a5f0e7e8e226184c5036f7"}, ] pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, @@ -2149,47 +2139,47 @@ pyzmq = [ {file = "pyzmq-22.2.1.tar.gz", hash = "sha256:6d18c76676771fd891ca8e0e68da0bbfb88e30129835c0ade748016adb3b6242"}, ] regex = [ - {file = "regex-2021.8.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b0c211c55d4aac4309c3209833c803fada3fc21cdf7b74abedda42a0c9dc3ce"}, - {file = "regex-2021.8.21-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d5209c3ba25864b1a57461526ebde31483db295fc6195fdfc4f8355e10f7376"}, - {file = "regex-2021.8.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c835c30f3af5c63a80917b72115e1defb83de99c73bc727bddd979a3b449e183"}, - {file = "regex-2021.8.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:615fb5a524cffc91ab4490b69e10ae76c1ccbfa3383ea2fad72e54a85c7d47dd"}, - {file = "regex-2021.8.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9966337353e436e6ba652814b0a957a517feb492a98b8f9d3b6ba76d22301dcc"}, - {file = "regex-2021.8.21-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a49f85f0a099a5755d0a2cc6fc337e3cb945ad6390ec892332c691ab0a045882"}, - {file = "regex-2021.8.21-cp310-cp310-win32.whl", hash = "sha256:f93a9d8804f4cec9da6c26c8cfae2c777028b4fdd9f49de0302e26e00bb86504"}, - {file = "regex-2021.8.21-cp310-cp310-win_amd64.whl", hash = "sha256:a795829dc522227265d72b25d6ee6f6d41eb2105c15912c230097c8f5bfdbcdc"}, - {file = "regex-2021.8.21-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bca14dfcfd9aae06d7d8d7e105539bd77d39d06caaae57a1ce945670bae744e0"}, - {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41acdd6d64cd56f857e271009966c2ffcbd07ec9149ca91f71088574eaa4278a"}, - {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96f0c79a70642dfdf7e6a018ebcbea7ea5205e27d8e019cad442d2acfc9af267"}, - {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:45f97ade892ace20252e5ccecdd7515c7df5feeb42c3d2a8b8c55920c3551c30"}, - {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f9974826aeeda32a76648fc677e3125ade379869a84aa964b683984a2dea9f1"}, - {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea9753d64cba6f226947c318a923dadaf1e21cd8db02f71652405263daa1f033"}, - {file = "regex-2021.8.21-cp36-cp36m-win32.whl", hash = "sha256:ef9326c64349e2d718373415814e754183057ebc092261387a2c2f732d9172b2"}, - {file = "regex-2021.8.21-cp36-cp36m-win_amd64.whl", hash = "sha256:6dbd51c3db300ce9d3171f4106da18fe49e7045232630fe3d4c6e37cb2b39ab9"}, - {file = "regex-2021.8.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a89ca4105f8099de349d139d1090bad387fe2b208b717b288699ca26f179acbe"}, - {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6c2b1d78ceceb6741d703508cd0e9197b34f6bf6864dab30f940f8886e04ade"}, - {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a34ba9e39f8269fd66ab4f7a802794ffea6d6ac500568ec05b327a862c21ce23"}, - {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ecb6e7c45f9cd199c10ec35262b53b2247fb9a408803ed00ee5bb2b54aa626f5"}, - {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:330836ad89ff0be756b58758878409f591d4737b6a8cef26a162e2a4961c3321"}, - {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:71a904da8c9c02aee581f4452a5a988c3003207cb8033db426f29e5b2c0b7aea"}, - {file = "regex-2021.8.21-cp37-cp37m-win32.whl", hash = "sha256:b511c6009d50d5c0dd0bab85ed25bc8ad6b6f5611de3a63a59786207e82824bb"}, - {file = "regex-2021.8.21-cp37-cp37m-win_amd64.whl", hash = "sha256:93f9f720081d97acee38a411e861d4ce84cbc8ea5319bc1f8e38c972c47af49f"}, - {file = "regex-2021.8.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a195e26df1fbb40ebee75865f9b64ba692a5824ecb91c078cc665b01f7a9a36"}, - {file = "regex-2021.8.21-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06ba444bbf7ede3890a912bd4904bb65bf0da8f0d8808b90545481362c978642"}, - {file = "regex-2021.8.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8d551f1bd60b3e1c59ff55b9e8d74607a5308f66e2916948cafd13480b44a3"}, - {file = "regex-2021.8.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ebbceefbffae118ab954d3cd6bf718f5790db66152f95202ebc231d58ad4e2c2"}, - {file = "regex-2021.8.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccd721f1d4fc42b541b633d6e339018a08dd0290dc67269df79552843a06ca92"}, - {file = "regex-2021.8.21-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ae87ab669431f611c56e581679db33b9a467f87d7bf197ac384e71e4956b4456"}, - {file = "regex-2021.8.21-cp38-cp38-win32.whl", hash = "sha256:38600fd58c2996829480de7d034fb2d3a0307110e44dae80b6b4f9b3d2eea529"}, - {file = "regex-2021.8.21-cp38-cp38-win_amd64.whl", hash = "sha256:61e734c2bcb3742c3f454dfa930ea60ea08f56fd1a0eb52d8cb189a2f6be9586"}, - {file = "regex-2021.8.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b091dcfee169ad8de21b61eb2c3a75f9f0f859f851f64fdaf9320759a3244239"}, - {file = "regex-2021.8.21-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:640ccca4d0a6fcc6590f005ecd7b16c3d8f5d52174e4854f96b16f34c39d6cb7"}, - {file = "regex-2021.8.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac95101736239260189f426b1e361dc1b704513963357dc474beb0f39f5b7759"}, - {file = "regex-2021.8.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b79dc2b2e313565416c1e62807c7c25c67a6ff0a0f8d83a318df464555b65948"}, - {file = "regex-2021.8.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b623fc429a38a881ab2d9a56ef30e8ea20c72a891c193f5ebbddc016e083ee"}, - {file = "regex-2021.8.21-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8021dee64899f993f4b5cca323aae65aabc01a546ed44356a0965e29d7893c94"}, - {file = "regex-2021.8.21-cp39-cp39-win32.whl", hash = "sha256:d6ec4ae13760ceda023b2e5ef1f9bc0b21e4b0830458db143794a117fdbdc044"}, - {file = "regex-2021.8.21-cp39-cp39-win_amd64.whl", hash = "sha256:03840a07a402576b8e3a6261f17eb88abd653ad4e18ec46ef10c9a63f8c99ebd"}, - {file = "regex-2021.8.21.tar.gz", hash = "sha256:faf08b0341828f6a29b8f7dd94d5cf8cc7c39bfc3e67b78514c54b494b66915a"}, + {file = "regex-2021.8.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308"}, + {file = "regex-2021.8.28-cp310-cp310-win32.whl", hash = "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed"}, + {file = "regex-2021.8.28-cp310-cp310-win_amd64.whl", hash = "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8"}, + {file = "regex-2021.8.28-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f"}, + {file = "regex-2021.8.28-cp36-cp36m-win32.whl", hash = "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354"}, + {file = "regex-2021.8.28-cp36-cp36m-win_amd64.whl", hash = "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645"}, + {file = "regex-2021.8.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906"}, + {file = "regex-2021.8.28-cp37-cp37m-win32.whl", hash = "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a"}, + {file = "regex-2021.8.28-cp37-cp37m-win_amd64.whl", hash = "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc"}, + {file = "regex-2021.8.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e"}, + {file = "regex-2021.8.28-cp38-cp38-win32.whl", hash = "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d"}, + {file = "regex-2021.8.28-cp38-cp38-win_amd64.whl", hash = "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2"}, + {file = "regex-2021.8.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed"}, + {file = "regex-2021.8.28-cp39-cp39-win32.whl", hash = "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374"}, + {file = "regex-2021.8.28-cp39-cp39-win_amd64.whl", hash = "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73"}, + {file = "regex-2021.8.28.tar.gz", hash = "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1"}, ] requests = [ {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, @@ -2271,8 +2261,8 @@ sphinxcontrib-serializinghtml = [ {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] terminado = [ - {file = "terminado-0.11.1-py3-none-any.whl", hash = "sha256:9e0457334863be3e6060c487ad60e0995fa1df54f109c67b24ff49a4f2f34df5"}, - {file = "terminado-0.11.1.tar.gz", hash = "sha256:962b402edbb480718054dc37027bada293972ecadfb587b89f01e2b8660a2132"}, + {file = "terminado-0.12.1-py3-none-any.whl", hash = "sha256:09fdde344324a1c9c6e610ee4ca165c4bb7f5bbf982fceeeb38998a988ef8452"}, + {file = "terminado-0.12.1.tar.gz", hash = "sha256:b20fd93cc57c1678c799799d117874367cc07a3d2d55be95205b1a88fa08393f"}, ] testpath = [ {file = "testpath-0.5.0-py3-none-any.whl", hash = "sha256:8044f9a0bab6567fc644a3593164e872543bb44225b0e24846e2c89237937589"}, @@ -2329,6 +2319,10 @@ tornado = [ {file = "tornado-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4"}, {file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"}, ] +tox = [ + {file = "tox-3.24.3-py2.py3-none-any.whl", hash = "sha256:9fbf8e2ab758b2a5e7cb2c72945e4728089934853076f67ef18d7575c8ab6b88"}, + {file = "tox-3.24.3.tar.gz", hash = "sha256:c6c4e77705ada004283610fd6d9ba4f77bc85d235447f875df9f0ba1bc23b634"}, +] traitlets = [ {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"}, {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"}, @@ -2366,9 +2360,9 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, - {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, - {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] urllib3 = [ {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, diff --git a/pyproject.toml b/pyproject.toml index 3ce8bdd..f92d173 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "poetry.masonry.api" [tool.poetry] name = "staircase" -version = "2.0.0" +version = "2.0.1" description = "A data analysis package based on modelling and manipulation of mathematical step functions. Strongly aligned with pandas." readme = "README.md" authors = ["Riley Clement "] @@ -38,12 +38,9 @@ classifiers=[ ] [tool.poetry.dependencies] -python = "^3.6" -pandas = [ - {version = ">=0.24,<2", python = "<3.9"}, - {version = ">=1.1.3", python = ">=3.9"} -] -numpy = "^1.12" +python = "^3.6.1" +pandas = "^1" +numpy = "^1.14" matplotlib = ">=2" pytz = "*" @@ -62,6 +59,7 @@ isort = ">=5.8" black = {version = "*", python = "^3.6.2"} flake8 = ">=3.9" pre-commit = {version = ">=2.13", python = "^3.6.1"} +tox=">=3.15" [tool.poetry.extras] diff --git a/staircase/_typing.py b/staircase/_typing.py index e69de29..95ea31f 100644 --- a/staircase/_typing.py +++ b/staircase/_typing.py @@ -0,0 +1,17 @@ +from typing import ( + IO, + TYPE_CHECKING, + Any, + AnyStr, + Callable, + Collection, + Dict, + Hashable, + List, + Mapping, + Optional, + Sequence, + Tuple, +) +from typing import Type as type_t +from typing import TypeVar, Union diff --git a/staircase/core/arrays/aggregation.py b/staircase/core/arrays/aggregation.py index 6b279dd..174b078 100644 --- a/staircase/core/arrays/aggregation.py +++ b/staircase/core/arrays/aggregation.py @@ -37,6 +37,7 @@ def _aggregate(collection, func): index=index, name="value", ).to_frame(), + closed=collection[0].closed, )._remove_redundant_step_points() diff --git a/staircase/core/exceptions/__init__.py b/staircase/core/exceptions/__init__.py new file mode 100644 index 0000000..17184b7 --- /dev/null +++ b/staircase/core/exceptions/__init__.py @@ -0,0 +1,14 @@ +""" +General exceptions +""" + + +class ClosedMismatchError(ValueError): + def __init__(self, stairs1, stairs2): + """ + Create a `ClosedMismatchError` indicating that `stairs1` and + `stairs2` were supposed to have the same `clased` values but didn't. + """ + super().__init__( + f"`closed` values must be same but were {stairs1._closed} and {stairs2._closed}" + ) diff --git a/staircase/core/ops/arithmetic.py b/staircase/core/ops/arithmetic.py index 734dd54..8002b33 100644 --- a/staircase/core/ops/arithmetic.py +++ b/staircase/core/ops/arithmetic.py @@ -6,7 +6,7 @@ import staircase as sc from staircase.core.ops import docstrings -from staircase.core.ops.common import _combine_stairs_via_values +from staircase.core.ops.common import _combine_stairs_via_values, requires_closed_match from staircase.util import _sanitize_binary_operands from staircase.util._decorators import Appender @@ -17,6 +17,7 @@ def negate(self): return sc.Stairs._new( initial_value=-self.initial_value, data=data, + closed=self.closed, ) @@ -39,30 +40,40 @@ def _add_or_sub_deltas_no_mask(self, other, series_op, float_op): def _make_add_or_sub_func(docstring, series_op, float_op, series_rop): @Appender(docstring, join="\n", indents=1) + @requires_closed_match def func(self, other): self, other = _sanitize_binary_operands(self, other) if self._data is None and other._data is None: return sc.Stairs._new( initial_value=float_op(self.initial_value, other.initial_value), data=None, + closed=self.closed, ) elif other._data is None: # means self._data is not None - data = self._data.copy() - if self._valid_values: - data["value"] = series_op(data["value"], other.initial_value) + if np.isnan(other.initial_value): + data = None + else: + data = self._data.copy() + if self._valid_values: + data["value"] = series_op(data["value"], other.initial_value) return sc.Stairs._new( initial_value=float_op(self.initial_value, other.initial_value), data=data, + closed=self.closed, ) elif self._data is None: # means other._data is not None - data = other._data.copy() - if other._valid_values: - data["value"] = series_rop(data["value"], self.initial_value) - if other._valid_deltas: - data["delta"] = series_rop(data["delta"], 0) + if np.isnan(self.initial_value): + data = None + else: + data = other._data.copy() + if other._valid_values: + data["value"] = series_rop(data["value"], self.initial_value) + if other._valid_deltas: + data["delta"] = series_rop(data["delta"], 0) return sc.Stairs._new( initial_value=float_op(self.initial_value, other.initial_value), data=data, + closed=other.closed, ) # self._data or other._data exists elif self._has_na() or other._has_na(): @@ -94,11 +105,13 @@ def func(self, other): def _make_mul_div_func(docstring, series_op, float_op, series_rop, float_rop): @Appender(docstring, join="\n", indents=1) + @requires_closed_match def func(self, other): def op_with_scalar(self, other, series_op, float_op): + # other is scalar if other == 0 and series_op == pd.Series.divide: - return sc.Stairs._new(np.nan, None) - if self._data is None: + return sc.Stairs._new(np.nan, None, closed=self.closed) + if self._data is None or np.isnan(other): data = None else: data = pd.DataFrame({"value": series_op(self._get_values(), other)}) @@ -109,6 +122,7 @@ def op_with_scalar(self, other, series_op, float_op): return sc.Stairs._new( initial_value=initial_value, data=data, + closed=self.closed, ) self, other = _sanitize_binary_operands(self, other) diff --git a/staircase/core/ops/common.py b/staircase/core/ops/common.py index 4f19c4d..241e5ef 100644 --- a/staircase/core/ops/common.py +++ b/staircase/core/ops/common.py @@ -1,7 +1,10 @@ +import functools + import numpy as np import pandas as pd import staircase as sc +from staircase.core.exceptions import ClosedMismatchError def _not_arithmetic_op(series_op): @@ -101,6 +104,27 @@ def _combine_stairs_via_values(stairs1, stairs2, series_op, float_op): new_instance = sc.Stairs._new( initial_value=initial_value, data=pd.DataFrame({"value": values}), + closed=stairs1.closed, ) new_instance._remove_redundant_step_points() return new_instance + + +def _assert_closeds_equal(stairs1, stairs2): + if ( + isinstance(stairs1, sc.Stairs) + and isinstance(stairs2, sc.Stairs) + and stairs1.number_of_steps != 0 + and stairs2.number_of_steps != 0 + and stairs1._closed != stairs2._closed + ): + raise ClosedMismatchError(stairs1, stairs2) + + +def requires_closed_match(func): + @functools.wraps(func) + def wrapper(stairs1, stairs2, *args, **kwargs): + _assert_closeds_equal(stairs1, stairs2) + return func(stairs1, stairs2, *args, **kwargs) + + return wrapper diff --git a/staircase/core/ops/logical.py b/staircase/core/ops/logical.py index ba722e2..1271d40 100644 --- a/staircase/core/ops/logical.py +++ b/staircase/core/ops/logical.py @@ -5,7 +5,7 @@ import staircase as sc from staircase.core.ops import docstrings -from staircase.core.ops.common import _combine_stairs_via_values +from staircase.core.ops.common import _combine_stairs_via_values, requires_closed_match from staircase.util import _sanitize_binary_operands from staircase.util._decorators import Appender @@ -19,7 +19,7 @@ def func(self): initial_value = float_comp(self.initial_value, 0) * 1 if self._data is None: - return sc.Stairs(initial_value=initial_value) + return sc.Stairs(initial_value=initial_value, closed=self.closed) values = series_comp(self._get_values(), 0) * 1 values.loc[np.isnan(self._get_values().values)] = np.nan result = sc.Stairs._new( @@ -27,6 +27,7 @@ def func(self): data=pd.DataFrame( {"value": values}, ), + closed=self.closed, ) result._remove_redundant_step_points() return result @@ -45,23 +46,23 @@ def func(self): def _make_logical_func(docstring, array_op, float_op): def _op_with_scalar_and(self, other): if np.isnan(other): - return sc.Stairs._new(np.nan, None) + return sc.Stairs._new(np.nan, None, closed=self.closed) elif other == 0: - return sc.Stairs._new(0, None) + return sc.Stairs._new(0, None, closed=self.closed) else: return self.make_boolean() def _op_with_scalar_or(self, other): if np.isnan(other): - return sc.Stairs._new(np.nan, None) + return sc.Stairs._new(np.nan, None, closed=self.closed) elif other == 0: return self.make_boolean() else: - return sc.Stairs._new(1, None) + return sc.Stairs._new(1, None, closed=self.closed) def _op_with_scalar_xor(self, other): if np.isnan(other): - return sc.Stairs._new(np.nan, None) + return sc.Stairs._new(np.nan, None, closed=self.closed) elif other == 0: return self.make_boolean() else: @@ -74,6 +75,7 @@ def _op_with_scalar_xor(self, other): }[array_op] @Appender(docstring, join="\n", indents=1) + @requires_closed_match def func(self, other): self, other = _sanitize_binary_operands(self, other) if other._data is None: diff --git a/staircase/core/ops/masking.py b/staircase/core/ops/masking.py index 06d6eb7..ff25fe3 100644 --- a/staircase/core/ops/masking.py +++ b/staircase/core/ops/masking.py @@ -60,6 +60,7 @@ def clip(self, lower=-inf, upper=inf): result = sc.Stairs._new( initial_value=initial_value, data=data, + closed=self.closed, ) result._remove_redundant_step_points() return result @@ -79,6 +80,7 @@ def _maskify(self, inverse=False): return sc.Stairs._new( initial_value=np.nan if op(self.initial_value, 0) else 0, data=data, + closed=self.closed, ) @@ -141,7 +143,9 @@ def func(self): comp_func(self._get_values()) * 1, index=self._data.index ) - new_instance = sc.Stairs._new(initial_value=initial_value, data=data) + new_instance = sc.Stairs._new( + initial_value=initial_value, data=data, closed=self.closed + ) new_instance._remove_redundant_step_points() return new_instance @@ -178,6 +182,8 @@ def fillna(self, value): initial_value = values.iloc[0] data = pd.DataFrame({"value": values}) - new_instance = sc.Stairs._new(initial_value=initial_value, data=data) + new_instance = sc.Stairs._new( + initial_value=initial_value, data=data, closed=self.closed + ) new_instance._remove_redundant_step_points() return new_instance diff --git a/staircase/core/ops/relational.py b/staircase/core/ops/relational.py index c89074a..7e37928 100644 --- a/staircase/core/ops/relational.py +++ b/staircase/core/ops/relational.py @@ -5,7 +5,7 @@ import staircase as sc from staircase.core.ops import docstrings -from staircase.core.ops.common import _combine_stairs_via_values +from staircase.core.ops.common import _combine_stairs_via_values, requires_closed_match from staircase.util import _sanitize_binary_operands from staircase.util._decorators import Appender @@ -14,6 +14,7 @@ def _make_relational_func( docstring, numpy_relational, series_relational, float_relational ): @Appender(docstring, join="\n", indents=1) + @requires_closed_match def func(self, other): self, other = _sanitize_binary_operands( self, other @@ -25,10 +26,15 @@ def func(self, other): float_relational(self.initial_value, other.initial_value) * 1 ) - if self._data is None and other._data is None: + if ( + (self._data is None and other._data is None) + or (np.isnan(other.initial_value) and other._data is None) + or (np.isnan(self.initial_value) and self._data is None) + ): return sc.Stairs._new( initial_value=initial_value, data=None, + closed=other.closed if np.isnan(self.initial_value) else self.closed, ) elif self._data is None or other._data is None: if other._data is None: # self._data exists @@ -48,6 +54,7 @@ def func(self, other): {"value": new_values * 1}, index=new_index, ), + closed=self.closed, ) new_instance._remove_redundant_step_points() return new_instance diff --git a/staircase/core/slicing.py b/staircase/core/slicing.py index cbe1017..04fa3c5 100644 --- a/staircase/core/slicing.py +++ b/staircase/core/slicing.py @@ -171,8 +171,9 @@ def slice(self, cuts, closed="left"): if isinstance(cuts, pd.IntervalIndex): ii = cuts elif isinstance(cuts, pd.PeriodIndex): - end_times = cuts.end_time - end_times = end_times.ceil(end_times.freq) + end_times = cuts.end_time + pd.Timedelta( + "1ns" + ) # PeriodIndex leaves a 1ns gap between intervals ii = pd.IntervalIndex.from_arrays(cuts.start_time, end_times, closed=closed) elif is_list_like(cuts): ii = pd.IntervalIndex.from_breaks(cuts, closed=closed) diff --git a/staircase/core/stairs.py b/staircase/core/stairs.py index be2e5b2..ff81780 100644 --- a/staircase/core/stairs.py +++ b/staircase/core/stairs.py @@ -483,6 +483,7 @@ def copy(self): new_instance = Stairs._new( initial_value=self.initial_value, data=self._data.copy() if self._data is not None else None, + closed=self.closed, ) return new_instance @@ -562,6 +563,7 @@ def shift(self, delta): return Stairs._new( initial_value=self.initial_value, data=self._data.set_index(self._data.index + delta), + closed=self.closed, ) @Appender(docstrings.examples.diff_example, join="\n", indents=2) diff --git a/staircase/util/__init__.py b/staircase/util/__init__.py index a0db6df..d32fcfa 100644 --- a/staircase/util/__init__.py +++ b/staircase/util/__init__.py @@ -15,12 +15,12 @@ def _is_datetime_like(x): def _sanitize_binary_operands(self, other, copy_other=False): if not isinstance(self, sc.Stairs): - self = sc.Stairs(initial_value=self) + self = sc.Stairs(initial_value=self, closed=other.closed) else: self = self.copy() if not isinstance(other, sc.Stairs): - other = sc.Stairs(initial_value=other) + other = sc.Stairs(initial_value=other, closed=self.closed) else: if copy_other: other = other.copy() diff --git a/tests/test_dates/test_dates_arithmetic.py b/tests/test_dates/test_dates_arithmetic.py new file mode 100644 index 0000000..e0c6323 --- /dev/null +++ b/tests/test_dates/test_dates_arithmetic.py @@ -0,0 +1,341 @@ +from datetime import datetime + +import pandas as pd +import pytest +import pytz + +from staircase import Stairs +from staircase.constants import inf + + +def pytest_generate_tests(metafunc): + if "date_func" in metafunc.fixturenames: + metafunc.parametrize( + "date_func", + ["pandas", "pydatetime", "numpy", "pandas_tz", "pydatetime_tz"], + indirect=True, + ) + + +@pytest.fixture +def date_func(request): + # returns a func which takes a pandas timestamp + if request.param == "pandas": + return lambda x: x + elif request.param == "pydatetime": + return pd.Timestamp.to_pydatetime + elif request.param == "numpy": + return pd.Timestamp.to_datetime64 + elif request.param == "pandas_tz": + return lambda ts: pd.Timestamp.tz_localize( + ts, pytz.timezone("Australia/Sydney") + ) + elif request.param == "pydatetime_tz": + return lambda ts: ( + pd.Timestamp.tz_localize( + ts, pytz.timezone("Australia/Sydney") + ).to_pydatetime() + ) + else: + assert False, "should not happen" + + +def timestamp(*args, date_func, **kwargs): + ts = pd.Timestamp(*args, **kwargs) + return date_func(ts) + + +def assert_expected_type(stairs, date_func): + if stairs._data is None: + return + example_type = timestamp(2020, 1, 1, date_func=date_func) + example_type = pd.Timestamp( + example_type + ) # pandas natively converts datetimes to timestamps + assert all( + [type(example_type) == type(x) for x in stairs._data.index] + ), "Unexpected type in step points" + if isinstance(example_type, (pd.Timestamp, datetime)): + assert all( + [example_type.tzinfo == x.tzinfo for x in stairs._data.index] + ), "Unexpected timezone in step points" + + +def s1(date_func): + int_seq1 = Stairs(initial_value=0) + int_seq1.layer( + timestamp(2020, 1, 1, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + 2, + ) + int_seq1.layer( + timestamp(2020, 1, 3, date_func=date_func), + timestamp(2020, 1, 5, date_func=date_func), + 2.5, + ) + int_seq1.layer( + timestamp(2020, 1, 6, date_func=date_func), + timestamp(2020, 1, 7, date_func=date_func), + -2.5, + ) + int_seq1.layer( + timestamp(2020, 1, 7, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + -2.5, + ) + return int_seq1 + + +def s2(date_func): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer( + timestamp(2020, 1, 1, date_func=date_func), + timestamp(2020, 1, 7, date_func=date_func), + -2.5, + ) + int_seq2.layer( + timestamp(2020, 1, 8, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + 5, + ) + int_seq2.layer( + timestamp(2020, 1, 2, date_func=date_func), + timestamp(2020, 1, 5, date_func=date_func), + 4.5, + ) + int_seq2.layer( + timestamp(2020, 1, 2, 12, date_func=date_func), + timestamp(2020, 1, 4, date_func=date_func), + -2.5, + ) + return int_seq2 + + +def s3(date_func): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer( + timestamp(2020, 1, 10, date_func=date_func), + timestamp(2020, 1, 30, date_func=date_func), + 1, + ) + int_seq.layer( + timestamp(2020, 1, 12, date_func=date_func), + timestamp(2020, 1, 13, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 15, date_func=date_func), + timestamp(2020, 1, 18, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 20, 12, date_func=date_func), + timestamp(2020, 1, 21, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 23, date_func=date_func), + timestamp(2020, 1, 23, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 27, date_func=date_func), + timestamp(2020, 1, 29, 12, date_func=date_func), + -1, + ) + return int_seq + + +def s4(date_func): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer( + timestamp(2020, 1, 9, date_func=date_func), + timestamp(2020, 1, 29, date_func=date_func), + 1, + ) + int_seq.layer( + timestamp(2020, 1, 10, 12, date_func=date_func), + timestamp(2020, 1, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 12, 12, date_func=date_func), + timestamp(2020, 1, 13, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 20, date_func=date_func), + timestamp(2020, 1, 23, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 26, date_func=date_func), + timestamp(2020, 1, 26, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 27, date_func=date_func), + timestamp(2020, 1, 28, 12, date_func=date_func), + -1, + ) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +def test_add_dates(date_func): + expected_step_changes = pd.Series( + { + timestamp("2020-01-01 00:00:00", date_func=date_func): -0.5, + timestamp("2020-01-02 00:00:00", date_func=date_func): 4.5, + timestamp("2020-01-02 12:00:00", date_func=date_func): -2.5, + timestamp("2020-01-03 00:00:00", date_func=date_func): 2.5, + timestamp("2020-01-04 00:00:00", date_func=date_func): 2.5, + timestamp("2020-01-05 00:00:00", date_func=date_func): -7.0, + timestamp("2020-01-06 00:00:00", date_func=date_func): -2.5, + timestamp("2020-01-07 00:00:00", date_func=date_func): 2.5, + timestamp("2020-01-08 00:00:00", date_func=date_func): 5, + timestamp("2020-01-10 00:00:00", date_func=date_func): -4.5, + } + ) + result = s1(date_func) + s2(date_func) + pd.testing.assert_series_equal( + result.step_changes, + expected_step_changes, + check_names=False, + check_index_type=False, + ) + assert_expected_type(result, date_func) + + +def test_subtract_dates(date_func): + expected_step_changes = pd.Series( + { + timestamp("2020-01-01 00:00:00", date_func=date_func): 4.5, + timestamp("2020-01-02 00:00:00", date_func=date_func): -4.5, + timestamp("2020-01-02 12:00:00", date_func=date_func): 2.5, + timestamp("2020-01-03 00:00:00", date_func=date_func): 2.5, + timestamp("2020-01-04 00:00:00", date_func=date_func): -2.5, + timestamp("2020-01-05 00:00:00", date_func=date_func): 2.0, + timestamp("2020-01-06 00:00:00", date_func=date_func): -2.5, + timestamp("2020-01-07 00:00:00", date_func=date_func): -2.5, + timestamp("2020-01-08 00:00:00", date_func=date_func): -5, + timestamp("2020-01-10 00:00:00", date_func=date_func): 5.5, + } + ) + result = s1(date_func) - s2(date_func) + pd.testing.assert_series_equal( + result.step_changes, + expected_step_changes, + check_names=False, + check_index_type=False, + ) + assert_expected_type(result, date_func) + + +def test_multiply_dates(date_func): + expected_step_changes = pd.Series( + { + timestamp("2020-01-01 00:00:00", date_func=date_func): -5.0, + timestamp("2020-01-02 00:00:00", date_func=date_func): 9.0, + timestamp("2020-01-02 12:00:00", date_func=date_func): -5.0, + timestamp("2020-01-03 00:00:00", date_func=date_func): -1.25, + timestamp("2020-01-04 00:00:00", date_func=date_func): 11.25, + timestamp("2020-01-05 00:00:00", date_func=date_func): -14.0, + timestamp("2020-01-06 00:00:00", date_func=date_func): 6.25, + timestamp("2020-01-07 00:00:00", date_func=date_func): -1.25, + timestamp("2020-01-08 00:00:00", date_func=date_func): -2.5, + timestamp("2020-01-10 00:00:00", date_func=date_func): 2.5, + } + ) + result = s1(date_func) * s2(date_func) + pd.testing.assert_series_equal( + result.step_changes, + expected_step_changes, + check_names=False, + check_index_type=False, + ) + assert_expected_type(result, date_func) + + +def test_multiply_dates_scalar(date_func): + expected_step_changes = pd.Series( + { + timestamp("2020-01-01 00:00:00", date_func=date_func): 6.0, + timestamp("2020-01-03 00:00:00", date_func=date_func): 7.5, + timestamp("2020-01-05 00:00:00", date_func=date_func): -7.5, + timestamp("2020-01-06 00:00:00", date_func=date_func): -7.5, + timestamp("2020-01-10 00:00:00", date_func=date_func): 1.5, + } + ) + result = s1(date_func) * 3 + pd.testing.assert_series_equal( + result.step_changes, + expected_step_changes, + check_names=False, + check_index_type=False, + ) + assert_expected_type(result, date_func) + + +def test_divide_dates(date_func): + expected_step_changes = pd.Series( + { + timestamp("2020-01-01 00:00:00", date_func=date_func): -1.3333333333333333, + timestamp("2020-01-02 00:00:00", date_func=date_func): 2.0, + timestamp("2020-01-02 12:00:00", date_func=date_func): 3.3333333333333335, + timestamp("2020-01-03 00:00:00", date_func=date_func): 5.0, + timestamp("2020-01-04 00:00:00", date_func=date_func): -7.5, + timestamp("2020-01-05 00:00:00", date_func=date_func): -2.833333333333333, + timestamp("2020-01-06 00:00:00", date_func=date_func): 1.6666666666666665, + timestamp("2020-01-07 00:00:00", date_func=date_func): -0.8333333333333333, + timestamp("2020-01-08 00:00:00", date_func=date_func): 0.4166666666666667, + timestamp("2020-01-10 00:00:00", date_func=date_func): 0.08333333333333333, + } + ) + result = s1(date_func) / (s2(date_func) + 1) + pd.testing.assert_series_equal( + result.step_changes, + expected_step_changes, + check_names=False, + check_index_type=False, + ) + assert_expected_type(result, date_func) + + +def test_divide_dates_scalar(date_func): + expected_step_changes = pd.Series( + { + timestamp("2020-01-01 00:00:00", date_func=date_func): 4.0, + timestamp("2020-01-03 00:00:00", date_func=date_func): 5.0, + timestamp("2020-01-05 00:00:00", date_func=date_func): -5.0, + timestamp("2020-01-06 00:00:00", date_func=date_func): -5.0, + timestamp("2020-01-10 00:00:00", date_func=date_func): 1.0, + } + ) + result = s1(date_func) / 0.5 + pd.testing.assert_series_equal( + result.step_changes, + expected_step_changes, + check_names=False, + check_index_type=False, + ) + assert_expected_type(result, date_func) diff --git a/tests/test_stairs_arrays_dates.py b/tests/test_dates/test_dates_arrays.py similarity index 100% rename from tests/test_stairs_arrays_dates.py rename to tests/test_dates/test_dates_arrays.py diff --git a/tests/test_dates/test_dates_distribution.py b/tests/test_dates/test_dates_distribution.py new file mode 100644 index 0000000..3cb8184 --- /dev/null +++ b/tests/test_dates/test_dates_distribution.py @@ -0,0 +1,451 @@ +import itertools +from datetime import datetime + +import pandas as pd +import pytest +import pytz + +from staircase import Stairs +from staircase.constants import inf + + +def pytest_generate_tests(metafunc): + if "date_func" in metafunc.fixturenames: + metafunc.parametrize( + "date_func", + ["pandas", "pydatetime", "numpy", "pandas_tz", "pydatetime_tz"], + indirect=True, + ) + + +@pytest.fixture +def date_func(request): + # returns a func which takes a pandas timestamp + if request.param == "pandas": + return lambda x: x + elif request.param == "pydatetime": + return pd.Timestamp.to_pydatetime + elif request.param == "numpy": + return pd.Timestamp.to_datetime64 + elif request.param == "pandas_tz": + return lambda ts: pd.Timestamp.tz_localize( + ts, pytz.timezone("Australia/Sydney") + ) + elif request.param == "pydatetime_tz": + return lambda ts: ( + pd.Timestamp.tz_localize( + ts, pytz.timezone("Australia/Sydney") + ).to_pydatetime() + ) + else: + assert False, "should not happen" + + +def timestamp(*args, date_func, **kwargs): + ts = pd.Timestamp(*args, **kwargs) + return date_func(ts) + + +def assert_expected_type(stairs, date_func): + if stairs._data is None: + return + example_type = timestamp(2020, 1, 1, date_func=date_func) + example_type = pd.Timestamp( + example_type + ) # pandas natively converts datetimes to timestamps + assert all( + [type(example_type) == type(x) for x in stairs._data.index] + ), "Unexpected type in step points" + if isinstance(example_type, (pd.Timestamp, datetime)): + assert all( + [example_type.tzinfo == x.tzinfo for x in stairs._data.index] + ), "Unexpected timezone in step points" + + +def s1(date_func): + int_seq1 = Stairs(initial_value=0) + int_seq1.layer( + timestamp(2020, 1, 1, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + 2, + ) + int_seq1.layer( + timestamp(2020, 1, 3, date_func=date_func), + timestamp(2020, 1, 5, date_func=date_func), + 2.5, + ) + int_seq1.layer( + timestamp(2020, 1, 6, date_func=date_func), + timestamp(2020, 1, 7, date_func=date_func), + -2.5, + ) + int_seq1.layer( + timestamp(2020, 1, 7, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + -2.5, + ) + return int_seq1 + + +def s2(date_func): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer( + timestamp(2020, 1, 1, date_func=date_func), + timestamp(2020, 1, 7, date_func=date_func), + -2.5, + ) + int_seq2.layer( + timestamp(2020, 1, 8, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + 5, + ) + int_seq2.layer( + timestamp(2020, 1, 2, date_func=date_func), + timestamp(2020, 1, 5, date_func=date_func), + 4.5, + ) + int_seq2.layer( + timestamp(2020, 1, 2, 12, date_func=date_func), + timestamp(2020, 1, 4, date_func=date_func), + -2.5, + ) + return int_seq2 + + +def s3(date_func): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer( + timestamp(2020, 1, 10, date_func=date_func), + timestamp(2020, 1, 30, date_func=date_func), + 1, + ) + int_seq.layer( + timestamp(2020, 1, 12, date_func=date_func), + timestamp(2020, 1, 13, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 15, date_func=date_func), + timestamp(2020, 1, 18, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 20, 12, date_func=date_func), + timestamp(2020, 1, 21, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 23, date_func=date_func), + timestamp(2020, 1, 23, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 27, date_func=date_func), + timestamp(2020, 1, 29, 12, date_func=date_func), + -1, + ) + return int_seq + + +def s4(date_func): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer( + timestamp(2020, 1, 9, date_func=date_func), + timestamp(2020, 1, 29, date_func=date_func), + 1, + ) + int_seq.layer( + timestamp(2020, 1, 10, 12, date_func=date_func), + timestamp(2020, 1, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 12, 12, date_func=date_func), + timestamp(2020, 1, 13, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 20, date_func=date_func), + timestamp(2020, 1, 23, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 26, date_func=date_func), + timestamp(2020, 1, 26, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 27, date_func=date_func), + timestamp(2020, 1, 28, 12, date_func=date_func), + -1, + ) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +def test_percentile_dates_1(date_func): + assert s1(date_func).percentile(20) == -0.5, "Expected 20th percentile to be -0.5" + assert s1(date_func).percentile(40) == -0.5, "Expected 40th percentile to be -0.5" + assert s1(date_func).percentile(60) == 2, "Expected 60th percentile to be 2" + assert s1(date_func).percentile(80) == 4.5, "Expected 80th percentile to be 4.5" + + +def test_percentile_dates_2(date_func): + assert ( + s1(date_func) + .clip(None, timestamp(2020, 1, 6, date_func=date_func)) + .percentile(20) + == 2 + ), "Expected 20th percentile to be 2" + assert ( + s1(date_func) + .clip(None, timestamp(2020, 1, 6, date_func=date_func)) + .percentile(40) + == 2 + ), "Expected 40th percentile to be 2" + assert ( + s1(date_func) + .clip(None, timestamp(2020, 1, 6, date_func=date_func)) + .percentile(60) + == 3.25 + ), "Expected 60th percentile to be 3.25" + assert ( + s1(date_func) + .clip(None, timestamp(2020, 1, 6, date_func=date_func)) + .percentile(80) + == 4.5 + ), "Expected 80th percentile to be 4.5" + + +def test_percentile_dates_3(date_func): + assert ( + s1(date_func) + .clip(timestamp(2020, 1, 4, date_func=date_func), None) + .percentile(20) + == -0.5 + ), "Expected 20th percentile to be -0.5" + assert ( + s1(date_func) + .clip(timestamp(2020, 1, 4, date_func=date_func), None) + .percentile(40) + == -0.5 + ), "Expected 40th percentile to be -0.5" + assert ( + s1(date_func) + .clip(timestamp(2020, 1, 4, date_func=date_func), None) + .percentile(60) + == -0.5 + ), "Expected 60th percentile to be -0.5" + assert ( + s1(date_func) + .clip(timestamp(2020, 1, 4, date_func=date_func), None) + .percentile(80) + == 2 + ), "Expected 80th percentile to be 2" + + +def test_percentile_dates_4(date_func): + assert ( + s1(date_func) + .clip( + timestamp(2020, 1, 4, date_func=date_func), + timestamp(2020, 1, 8, date_func=date_func), + ) + .percentile(20) + == -0.5 + ), "Expected 20th percentile to be -0.5" + assert ( + s1(date_func) + .clip( + timestamp(2020, 1, 4, date_func=date_func), + timestamp(2020, 1, 8, date_func=date_func), + ) + .percentile(40) + == -0.5 + ), "Expected 40th percentile to be -0.5" + assert ( + s1(date_func) + .clip( + timestamp(2020, 1, 4, date_func=date_func), + timestamp(2020, 1, 8, date_func=date_func), + ) + .percentile(60) + == 2 + ), "Expected 60th percentile to be 2" + assert ( + s1(date_func) + .clip( + timestamp(2020, 1, 4, date_func=date_func), + timestamp(2020, 1, 8, date_func=date_func), + ) + .percentile(80) + == 4.5 + ), "Expected 80th percentile to be 4.5" + + +def test_get_percentiles_dates_1(date_func): + expected_step_values = pd.Series( + [-0.5, 2.0, 4.5, 4.5], index=[0, 44.444444, 77.77777778, 100] + ) + pd.testing.assert_series_equal( + s1(date_func).percentile.step_values, + expected_step_values, + check_names=False, + check_index_type=False, + ) + + +def test_get_percentiles_dates_2(date_func): + expected_step_values = pd.Series([2, 4.5, 4.5], index=[0, 60, 100]) + pd.testing.assert_series_equal( + s1(date_func) + .clip(None, timestamp(2020, 1, 6, date_func=date_func)) + .percentile.step_values, + expected_step_values, + check_names=False, + check_index_type=False, + ) + + +def test_get_percentiles_dates_3(date_func): + expected_step_values = pd.Series( + [-0.5, 2.0, 4.5, 4.5], index=[0, 66.6666666667, 83.333333333, 100] + ) + pd.testing.assert_series_equal( + s1(date_func) + .clip(timestamp(2020, 1, 4, date_func=date_func), None) + .percentile.step_values, + expected_step_values, + check_names=False, + check_index_type=False, + ) + + +def test_get_percentiles_dates_4(date_func): + expected_step_values = pd.Series([-0.5, 2.0, 4.5, 4.5], index=[0, 50, 75, 100]) + pd.testing.assert_series_equal( + s1(date_func) + .clip( + timestamp(2020, 1, 4, date_func=date_func), + timestamp(2020, 1, 8, date_func=date_func), + ) + .percentile.step_values, + expected_step_values, + check_names=False, + check_index_type=False, + ) + + +@pytest.mark.parametrize( + "stairs_func, bounds, cuts", + itertools.product( + [s1, s2, s3, s4], + [ + ((2020, 1, 3), (2020, 1, 4)), + ((2020, 1, 1), (2020, 1, 4)), + ((2020, 1, 2), (2020, 2, 4)), + ], + ["unit", (-2, 0, 0.5, 4, 4.5, 7)], + ), +) +def test_hist_default_bins_left_closed(date_func, stairs_func, bounds, cuts): + stairs_instance = stairs_func(date_func) + bounds = [timestamp(*args, date_func=date_func) for args in bounds] + + def make_expected_result(interval_index, lower, upper): + return pd.Series( + [ + ((stairs_instance >= i.left) * (stairs_instance < i.right)).agg( + "mean", (lower, upper) + ) + for i in interval_index + ], + index=interval_index, + ) + + hist = stairs_instance.clip(*bounds).hist(bins=cuts, stat="probability") + expected = make_expected_result(hist.index, *bounds) + pd.testing.assert_series_equal( + hist, + expected, + check_names=False, + check_index_type=False, + ) + + +@pytest.mark.parametrize( + "stairs_func, bounds, cuts", + itertools.product( + [s1, s2, s3, s4], + [ + ((2020, 1, 3), (2020, 1, 4)), + ((2020, 1, 1), (2020, 1, 4)), + ((2020, 1, 2), (2020, 2, 4)), + ], + ["unit", (-2, 0, 0.5, 4, 4.5, 7)], + ), +) +def test_hist_default_bins_right_closed(date_func, stairs_func, bounds, cuts): + stairs_instance = stairs_func(date_func) + bounds = [timestamp(*args, date_func=date_func) for args in bounds] + + def make_expected_result(interval_index, lower, upper): + return pd.Series( + [ + ((stairs_instance > i.left) * (stairs_instance <= i.right)).agg( + "mean", (lower, upper) + ) + for i in interval_index + ], + index=interval_index, + ) + + hist = stairs_instance.clip(*bounds).hist( + bins=cuts, closed="right", stat="probability" + ) + expected = make_expected_result(hist.index, *bounds) + pd.testing.assert_series_equal( + hist, + expected, + check_names=False, + check_index_type=False, + ) + + +@pytest.mark.parametrize( + "stairs_func, bounds, closed", + itertools.product( + [s1, s2, s3, s4], + [ + ((2020, 1, 3), (2020, 1, 4)), + ((2020, 1, 1), (2020, 1, 4)), + ((2020, 1, 2), (2020, 2, 4)), + ], + ["left", "right"], + ), +) +def test_hist_default_bins(date_func, stairs_func, bounds, closed): + # really testing the default binning process here + stairs_instance = stairs_func(date_func) + bounds = [timestamp(*args, date_func=date_func) for args in bounds] + hist = stairs_instance.clip(*bounds).hist(closed=closed, stat="probability") + assert abs(hist.sum() - 1) < 0.000001 diff --git a/tests/test_dates/test_dates_logical.py b/tests/test_dates/test_dates_logical.py new file mode 100644 index 0000000..a4374ac --- /dev/null +++ b/tests/test_dates/test_dates_logical.py @@ -0,0 +1,209 @@ +from datetime import datetime + +import pandas as pd +import pytest +import pytz + +from staircase import Stairs + + +def pytest_generate_tests(metafunc): + if "date_func" in metafunc.fixturenames: + metafunc.parametrize( + "date_func", + ["pandas", "pydatetime", "numpy", "pandas_tz", "pydatetime_tz"], + indirect=True, + ) + + +@pytest.fixture +def date_func(request): + # returns a func which takes a pandas timestamp + if request.param == "pandas": + return lambda x: x + elif request.param == "pydatetime": + return pd.Timestamp.to_pydatetime + elif request.param == "numpy": + return pd.Timestamp.to_datetime64 + elif request.param == "pandas_tz": + return lambda ts: pd.Timestamp.tz_localize( + ts, pytz.timezone("Australia/Sydney") + ) + elif request.param == "pydatetime_tz": + return lambda ts: ( + pd.Timestamp.tz_localize( + ts, pytz.timezone("Australia/Sydney") + ).to_pydatetime() + ) + else: + assert False, "should not happen" + + +def timestamp(*args, date_func, **kwargs): + ts = pd.Timestamp(*args, **kwargs) + return date_func(ts) + + +def assert_expected_type(stairs, date_func): + if stairs._data is None: + return + example_type = timestamp(2020, 1, 1, date_func=date_func) + example_type = pd.Timestamp( + example_type + ) # pandas natively converts datetimes to timestamps + assert all( + [type(example_type) == type(x) for x in stairs._data.index] + ), "Unexpected type in step points" + if isinstance(example_type, (pd.Timestamp, datetime)): + assert all( + [example_type.tzinfo == x.tzinfo for x in stairs._data.index] + ), "Unexpected timezone in step points" + + +def _compare_iterables(it1, it2): + it1 = [i for i in it1 if i is not None] + it2 = [i for i in it2 if i is not None] + for e1, e2 in zip(it1, it2): + if e1 != e2: + return False + return True + + +def s1(date_func): + int_seq1 = Stairs(initial_value=0) + int_seq1.layer( + timestamp(2020, 1, 1, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + 2, + ) + int_seq1.layer( + timestamp(2020, 1, 3, date_func=date_func), + timestamp(2020, 1, 5, date_func=date_func), + 2.5, + ) + int_seq1.layer( + timestamp(2020, 1, 6, date_func=date_func), + timestamp(2020, 1, 7, date_func=date_func), + -2.5, + ) + int_seq1.layer( + timestamp(2020, 1, 7, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + -2.5, + ) + return int_seq1 + + +def s2(date_func): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer( + timestamp(2020, 1, 1, date_func=date_func), + timestamp(2020, 1, 7, date_func=date_func), + -2.5, + ) + int_seq2.layer( + timestamp(2020, 1, 8, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + 5, + ) + int_seq2.layer( + timestamp(2020, 1, 2, date_func=date_func), + timestamp(2020, 1, 5, date_func=date_func), + 4.5, + ) + int_seq2.layer( + timestamp(2020, 1, 2, 12, date_func=date_func), + timestamp(2020, 1, 4, date_func=date_func), + -2.5, + ) + return int_seq2 + + +def s3(date_func): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer( + timestamp(2020, 1, 10, date_func=date_func), + timestamp(2020, 1, 30, date_func=date_func), + 1, + ) + int_seq.layer( + timestamp(2020, 1, 12, date_func=date_func), + timestamp(2020, 1, 13, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 15, date_func=date_func), + timestamp(2020, 1, 18, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 20, 12, date_func=date_func), + timestamp(2020, 1, 21, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 23, date_func=date_func), + timestamp(2020, 1, 23, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 27, date_func=date_func), + timestamp(2020, 1, 29, 12, date_func=date_func), + -1, + ) + return int_seq + + +def s4(date_func): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer( + timestamp(2020, 1, 9, date_func=date_func), + timestamp(2020, 1, 29, date_func=date_func), + 1, + ) + int_seq.layer( + timestamp(2020, 1, 10, 12, date_func=date_func), + timestamp(2020, 1, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 12, 12, date_func=date_func), + timestamp(2020, 1, 13, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 20, date_func=date_func), + timestamp(2020, 1, 23, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 26, date_func=date_func), + timestamp(2020, 1, 26, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 27, date_func=date_func), + timestamp(2020, 1, 28, 12, date_func=date_func), + -1, + ) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() diff --git a/tests/test_dates/test_dates_misc.py b/tests/test_dates/test_dates_misc.py new file mode 100644 index 0000000..d341bde --- /dev/null +++ b/tests/test_dates/test_dates_misc.py @@ -0,0 +1,312 @@ +from datetime import datetime + +import pandas as pd +import pytest +import pytz + +import staircase.test_data as test_data +from staircase import Stairs +from staircase.constants import inf + + +def pytest_generate_tests(metafunc): + if "date_func" in metafunc.fixturenames: + metafunc.parametrize( + "date_func", + ["pandas", "pydatetime", "numpy", "pandas_tz", "pydatetime_tz"], + indirect=True, + ) + + +@pytest.fixture +def date_func(request): + # returns a func which takes a pandas timestamp + if request.param == "pandas": + return lambda x: x + elif request.param == "pydatetime": + return pd.Timestamp.to_pydatetime + elif request.param == "numpy": + return pd.Timestamp.to_datetime64 + elif request.param == "pandas_tz": + return lambda ts: pd.Timestamp.tz_localize( + ts, pytz.timezone("Australia/Sydney") + ) + elif request.param == "pydatetime_tz": + return lambda ts: ( + pd.Timestamp.tz_localize( + ts, pytz.timezone("Australia/Sydney") + ).to_pydatetime() + ) + else: + assert False, "should not happen" + + +def timestamp(*args, date_func, **kwargs): + ts = pd.Timestamp(*args, **kwargs) + return date_func(ts) + + +def assert_expected_type(stairs, date_func): + if stairs._data is None: + return + example_type = timestamp(2020, 1, 1, date_func=date_func) + example_type = pd.Timestamp( + example_type + ) # pandas natively converts datetimes to timestamps + assert all( + [type(example_type) == type(x) for x in stairs._data.index] + ), "Unexpected type in step points" + if isinstance(example_type, (pd.Timestamp, datetime)): + assert all( + [example_type.tzinfo == x.tzinfo for x in stairs._data.index] + ), "Unexpected timezone in step points" + + +def _compare_iterables(it1, it2): + it1 = [i for i in it1 if i is not None] + it2 = [i for i in it2 if i is not None] + for e1, e2 in zip(it1, it2): + if e1 != e2: + return False + return True + + +def s1(date_func): + int_seq1 = Stairs(initial_value=0) + int_seq1.layer( + timestamp(2020, 1, 1, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + 2, + ) + int_seq1.layer( + timestamp(2020, 1, 3, date_func=date_func), + timestamp(2020, 1, 5, date_func=date_func), + 2.5, + ) + int_seq1.layer( + timestamp(2020, 1, 6, date_func=date_func), + timestamp(2020, 1, 7, date_func=date_func), + -2.5, + ) + int_seq1.layer( + timestamp(2020, 1, 7, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + -2.5, + ) + return int_seq1 + + +def s2(date_func): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer( + timestamp(2020, 1, 1, date_func=date_func), + timestamp(2020, 1, 7, date_func=date_func), + -2.5, + ) + int_seq2.layer( + timestamp(2020, 1, 8, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + 5, + ) + int_seq2.layer( + timestamp(2020, 1, 2, date_func=date_func), + timestamp(2020, 1, 5, date_func=date_func), + 4.5, + ) + int_seq2.layer( + timestamp(2020, 1, 2, 12, date_func=date_func), + timestamp(2020, 1, 4, date_func=date_func), + -2.5, + ) + return int_seq2 + + +def s3(date_func): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer( + timestamp(2020, 1, 10, date_func=date_func), + timestamp(2020, 1, 30, date_func=date_func), + 1, + ) + int_seq.layer( + timestamp(2020, 1, 12, date_func=date_func), + timestamp(2020, 1, 13, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 15, date_func=date_func), + timestamp(2020, 1, 18, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 20, 12, date_func=date_func), + timestamp(2020, 1, 21, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 23, date_func=date_func), + timestamp(2020, 1, 23, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 27, date_func=date_func), + timestamp(2020, 1, 29, 12, date_func=date_func), + -1, + ) + return int_seq + + +def s4(date_func): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer( + timestamp(2020, 1, 9, date_func=date_func), + timestamp(2020, 1, 29, date_func=date_func), + 1, + ) + int_seq.layer( + timestamp(2020, 1, 10, 12, date_func=date_func), + timestamp(2020, 1, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 12, 12, date_func=date_func), + timestamp(2020, 1, 13, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 20, date_func=date_func), + timestamp(2020, 1, 23, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 26, date_func=date_func), + timestamp(2020, 1, 26, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 27, date_func=date_func), + timestamp(2020, 1, 28, 12, date_func=date_func), + -1, + ) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +def test_plot(date_func): + s1(date_func).plot() + + +def test_step_changes_dates(date_func): + expected_step_changes = pd.Series( + [2, 2.5, -2.5, -2.5, 0.5], + index=[ + timestamp("2020-1-1", date_func=date_func), + timestamp("2020-1-3", date_func=date_func), + timestamp("2020-1-5", date_func=date_func), + timestamp("2020-1-6", date_func=date_func), + timestamp("2020-1-10", date_func=date_func), + ], + ) + pd.testing.assert_series_equal( + s1(date_func).step_changes, + expected_step_changes, + check_names=False, + check_index_type=False, + ) + + +def test_dataframe_dates(date_func): + ans = pd.DataFrame( + { + "start": [ + -inf, + timestamp("2020-01-01", date_func=date_func), + timestamp("2020-01-03", date_func=date_func), + timestamp("2020-01-05", date_func=date_func), + timestamp("2020-01-06", date_func=date_func), + timestamp("2020-01-10", date_func=date_func), + ], + "end": [ + timestamp("2020-01-01", date_func=date_func), + timestamp("2020-01-03", date_func=date_func), + timestamp("2020-01-05", date_func=date_func), + timestamp("2020-01-06", date_func=date_func), + timestamp("2020-01-10", date_func=date_func), + inf, + ], + "value": [0, 2, 4.5, 2, -0.5, 0], + } + ) + pd.testing.assert_frame_equal(s1(date_func).to_frame(), ans) + + +def test_shift(date_func): + ans = Stairs(initial_value=0) + ans.layer( + timestamp(2020, 1, 2, date_func=date_func), + timestamp(2020, 1, 11, date_func=date_func), + 2, + ) + ans.layer( + timestamp(2020, 1, 4, date_func=date_func), + timestamp(2020, 1, 6, date_func=date_func), + 2.5, + ) + ans.layer( + timestamp(2020, 1, 7, date_func=date_func), + timestamp(2020, 1, 8, date_func=date_func), + -2.5, + ) + ans.layer( + timestamp(2020, 1, 8, date_func=date_func), + timestamp(2020, 1, 11, date_func=date_func), + -2.5, + ) + result = s1(date_func).shift(pd.Timedelta(24, unit="H")) + assert bool(result == ans) + assert_expected_type(result, date_func) + + +def test_make_test_data(): + assert isinstance(test_data.make_test_data(dates=True), pd.DataFrame) + + +@pytest.mark.parametrize( + "kwargs", + [ + { + "lower": (2020, 1, 1), + }, + { + "lower": (2020, 1, 1), + "upper": (2020, 1, 8), + }, + { + "upper": (2020, 1, 8), + }, + ], +) +def test_clip_expected_type(date_func, kwargs): + kwargs = kwargs.copy() + kwargs = {key: timestamp(*val, date_func=date_func) for key, val in kwargs.items()} + result = s1(date_func).clip(**kwargs) + assert_expected_type(result, date_func) diff --git a/tests/test_dates/test_dates_relational.py b/tests/test_dates/test_dates_relational.py new file mode 100644 index 0000000..1f34852 --- /dev/null +++ b/tests/test_dates/test_dates_relational.py @@ -0,0 +1,209 @@ +from datetime import datetime + +import pandas as pd +import pytest +import pytz + +from staircase import Stairs +from staircase.constants import inf + + +def pytest_generate_tests(metafunc): + if "date_func" in metafunc.fixturenames: + metafunc.parametrize( + "date_func", + ["pandas", "pydatetime", "numpy", "pandas_tz", "pydatetime_tz"], + indirect=True, + ) + + +@pytest.fixture +def date_func(request): + # returns a func which takes a pandas timestamp + if request.param == "pandas": + return lambda x: x + elif request.param == "pydatetime": + return pd.Timestamp.to_pydatetime + elif request.param == "numpy": + return pd.Timestamp.to_datetime64 + elif request.param == "pandas_tz": + return lambda ts: pd.Timestamp.tz_localize( + ts, pytz.timezone("Australia/Sydney") + ) + elif request.param == "pydatetime_tz": + return lambda ts: ( + pd.Timestamp.tz_localize( + ts, pytz.timezone("Australia/Sydney") + ).to_pydatetime() + ) + else: + assert False, "should not happen" + + +def timestamp(*args, date_func, **kwargs): + ts = pd.Timestamp(*args, **kwargs) + return date_func(ts) + + +def assert_expected_type(stairs, date_func): + if stairs._data is None: + return + example_type = timestamp(2020, 1, 1, date_func=date_func) + example_type = pd.Timestamp( + example_type + ) # pandas natively converts datetimes to timestamps + assert all( + [type(example_type) == type(x) for x in stairs._data.index] + ), "Unexpected type in step points" + if isinstance(example_type, (pd.Timestamp, datetime)): + assert all( + [example_type.tzinfo == x.tzinfo for x in stairs._data.index] + ), "Unexpected timezone in step points" + + +def s1(date_func): + int_seq1 = Stairs(initial_value=0) + int_seq1.layer( + timestamp(2020, 1, 1, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + 2, + ) + int_seq1.layer( + timestamp(2020, 1, 3, date_func=date_func), + timestamp(2020, 1, 5, date_func=date_func), + 2.5, + ) + int_seq1.layer( + timestamp(2020, 1, 6, date_func=date_func), + timestamp(2020, 1, 7, date_func=date_func), + -2.5, + ) + int_seq1.layer( + timestamp(2020, 1, 7, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + -2.5, + ) + return int_seq1 + + +def s2(date_func): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer( + timestamp(2020, 1, 1, date_func=date_func), + timestamp(2020, 1, 7, date_func=date_func), + -2.5, + ) + int_seq2.layer( + timestamp(2020, 1, 8, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + 5, + ) + int_seq2.layer( + timestamp(2020, 1, 2, date_func=date_func), + timestamp(2020, 1, 5, date_func=date_func), + 4.5, + ) + int_seq2.layer( + timestamp(2020, 1, 2, 12, date_func=date_func), + timestamp(2020, 1, 4, date_func=date_func), + -2.5, + ) + return int_seq2 + + +def s3(date_func): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer( + timestamp(2020, 1, 10, date_func=date_func), + timestamp(2020, 1, 30, date_func=date_func), + 1, + ) + int_seq.layer( + timestamp(2020, 1, 12, date_func=date_func), + timestamp(2020, 1, 13, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 15, date_func=date_func), + timestamp(2020, 1, 18, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 20, 12, date_func=date_func), + timestamp(2020, 1, 21, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 23, date_func=date_func), + timestamp(2020, 1, 23, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 27, date_func=date_func), + timestamp(2020, 1, 29, 12, date_func=date_func), + -1, + ) + return int_seq + + +def s4(date_func): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer( + timestamp(2020, 1, 9, date_func=date_func), + timestamp(2020, 1, 29, date_func=date_func), + 1, + ) + int_seq.layer( + timestamp(2020, 1, 10, 12, date_func=date_func), + timestamp(2020, 1, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 12, 12, date_func=date_func), + timestamp(2020, 1, 13, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 20, date_func=date_func), + timestamp(2020, 1, 23, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 26, date_func=date_func), + timestamp(2020, 1, 26, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 27, date_func=date_func), + timestamp(2020, 1, 28, 12, date_func=date_func), + -1, + ) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +def test_eq(): + assert Stairs(initial_value=3) == 3 + + +def test_ne(date_func): + assert s1(date_func) != 3 diff --git a/tests/test_dates/test_dates_sample.py b/tests/test_dates/test_dates_sample.py new file mode 100644 index 0000000..6bc88fb --- /dev/null +++ b/tests/test_dates/test_dates_sample.py @@ -0,0 +1,266 @@ +from datetime import datetime + +import pandas as pd +import pytest +import pytz + +from staircase import Stairs +from staircase.constants import inf + + +def pytest_generate_tests(metafunc): + if "date_func" in metafunc.fixturenames: + metafunc.parametrize( + "date_func", + ["pandas", "pydatetime", "numpy", "pandas_tz", "pydatetime_tz"], + indirect=True, + ) + + +@pytest.fixture +def date_func(request): + # returns a func which takes a pandas timestamp + if request.param == "pandas": + return lambda x: x + elif request.param == "pydatetime": + return pd.Timestamp.to_pydatetime + elif request.param == "numpy": + return pd.Timestamp.to_datetime64 + elif request.param == "pandas_tz": + return lambda ts: pd.Timestamp.tz_localize( + ts, pytz.timezone("Australia/Sydney") + ) + elif request.param == "pydatetime_tz": + return lambda ts: ( + pd.Timestamp.tz_localize( + ts, pytz.timezone("Australia/Sydney") + ).to_pydatetime() + ) + else: + assert False, "should not happen" + + +def timestamp(*args, date_func, **kwargs): + ts = pd.Timestamp(*args, **kwargs) + return date_func(ts) + + +def assert_expected_type(stairs, date_func): + if stairs._data is None: + return + example_type = timestamp(2020, 1, 1, date_func=date_func) + example_type = pd.Timestamp( + example_type + ) # pandas natively converts datetimes to timestamps + assert all( + [type(example_type) == type(x) for x in stairs._data.index] + ), "Unexpected type in step points" + if isinstance(example_type, (pd.Timestamp, datetime)): + assert all( + [example_type.tzinfo == x.tzinfo for x in stairs._data.index] + ), "Unexpected timezone in step points" + + +def _compare_iterables(it1, it2): + it1 = [i for i in it1 if i is not None] + it2 = [i for i in it2 if i is not None] + for e1, e2 in zip(it1, it2): + if e1 != e2: + return False + return True + + +def s1(date_func): + int_seq1 = Stairs(initial_value=0) + int_seq1.layer( + timestamp(2020, 1, 1, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + 2, + ) + int_seq1.layer( + timestamp(2020, 1, 3, date_func=date_func), + timestamp(2020, 1, 5, date_func=date_func), + 2.5, + ) + int_seq1.layer( + timestamp(2020, 1, 6, date_func=date_func), + timestamp(2020, 1, 7, date_func=date_func), + -2.5, + ) + int_seq1.layer( + timestamp(2020, 1, 7, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + -2.5, + ) + return int_seq1 + + +def s2(date_func): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer( + timestamp(2020, 1, 1, date_func=date_func), + timestamp(2020, 1, 7, date_func=date_func), + -2.5, + ) + int_seq2.layer( + timestamp(2020, 1, 8, date_func=date_func), + timestamp(2020, 1, 10, date_func=date_func), + 5, + ) + int_seq2.layer( + timestamp(2020, 1, 2, date_func=date_func), + timestamp(2020, 1, 5, date_func=date_func), + 4.5, + ) + int_seq2.layer( + timestamp(2020, 1, 2, 12, date_func=date_func), + timestamp(2020, 1, 4, date_func=date_func), + -2.5, + ) + return int_seq2 + + +def s3(date_func): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer( + timestamp(2020, 1, 10, date_func=date_func), + timestamp(2020, 1, 30, date_func=date_func), + 1, + ) + int_seq.layer( + timestamp(2020, 1, 12, date_func=date_func), + timestamp(2020, 1, 13, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 15, date_func=date_func), + timestamp(2020, 1, 18, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 20, 12, date_func=date_func), + timestamp(2020, 1, 21, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 23, date_func=date_func), + timestamp(2020, 1, 23, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 27, date_func=date_func), + timestamp(2020, 1, 29, 12, date_func=date_func), + -1, + ) + return int_seq + + +def s4(date_func): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer( + timestamp(2020, 1, 9, date_func=date_func), + timestamp(2020, 1, 29, date_func=date_func), + 1, + ) + int_seq.layer( + timestamp(2020, 1, 10, 12, date_func=date_func), + timestamp(2020, 1, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 12, 12, date_func=date_func), + timestamp(2020, 1, 13, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 20, date_func=date_func), + timestamp(2020, 1, 23, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 26, date_func=date_func), + timestamp(2020, 1, 26, 12, date_func=date_func), + -1, + ) + int_seq.layer( + timestamp(2020, 1, 27, date_func=date_func), + timestamp(2020, 1, 28, 12, date_func=date_func), + -1, + ) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +def test_sample_dates_1(date_func): + assert s1(date_func).sample(timestamp(2020, 1, 6, date_func=date_func)) == -0.5 + + +def test_limit_dates_1(date_func): + assert ( + s1(date_func).limit(timestamp(2020, 1, 6, date_func=date_func), side="right") + == -0.5 + ) + + +def test_limit_dates_2(date_func): + assert ( + s1(date_func).limit(timestamp(2020, 1, 6, date_func=date_func), side="left") + == 2 + ) + + +def test_sample_dates_4(date_func): + assert _compare_iterables( + s1(date_func).sample( + [ + timestamp(2020, 1, 4, date_func=date_func), + timestamp(2020, 1, 6, date_func=date_func), + ] + ), + [4.5, -0.5], + ) + + +def test_limit_dates_3(date_func): + assert _compare_iterables( + s1(date_func).limit( + [ + timestamp(2020, 1, 4, date_func=date_func), + timestamp(2020, 1, 6, date_func=date_func), + ], + side="right", + ), + [4.5, -0.5], + ) + + +def test_limit_dates_4(date_func): + assert _compare_iterables( + s1(date_func).limit( + [ + timestamp(2020, 1, 4, date_func=date_func), + timestamp(2020, 1, 6, date_func=date_func), + ], + side="left", + ), + [4.5, 2], + ) diff --git a/tests/test_dates/test_dates_slicing.py b/tests/test_dates/test_dates_slicing.py new file mode 100644 index 0000000..6f3d2e7 --- /dev/null +++ b/tests/test_dates/test_dates_slicing.py @@ -0,0 +1,18 @@ +import pandas as pd +import pytest + +import staircase as sc + + +# Not testing "W", it's a pain for period index +@pytest.mark.parametrize("freq_str", ["N", "U", "L", "S", "T", "H", "D", "M"]) +def test_slice_month_period_range(freq_str): + # GH108 + date_freq = "MS" if freq_str == "M" else freq_str + pr = pd.period_range("2020", periods=100, freq=freq_str) + dr = pd.date_range("2020", periods=100, freq=date_freq) + slicer1 = sc.Stairs().slice(pr) + slicer2 = sc.Stairs().slice(dr) + slicer1._create_slices() + slicer2._create_slices() + assert all([s1.identical(s2) for s1, s2, in zip(slicer1._slices, slicer2._slices)]) diff --git a/tests/test_stairs_dates.py b/tests/test_dates/test_dates_stats.py similarity index 57% rename from tests/test_stairs_dates.py rename to tests/test_dates/test_dates_stats.py index 7677fee..7bc811a 100644 --- a/tests/test_stairs_dates.py +++ b/tests/test_dates/test_dates_stats.py @@ -1,4 +1,3 @@ -import itertools from datetime import datetime import numpy as np @@ -6,9 +5,7 @@ import pytest import pytz -import staircase.test_data as test_data from staircase import Stairs -from staircase.constants import inf def pytest_generate_tests(metafunc): @@ -64,15 +61,6 @@ def assert_expected_type(stairs, date_func): ), "Unexpected timezone in step points" -def _compare_iterables(it1, it2): - it1 = [i for i in it1 if i is not None] - it2 = [i for i in it2 if i is not None] - for e1, e2 in zip(it1, it2): - if e1 != e2: - return False - return True - - def s1(date_func): int_seq1 = Stairs(initial_value=0) int_seq1.layer( @@ -410,608 +398,6 @@ def test_integral_dates_4(date_func): ), "Expected integral to be 132 hours" -# def test_integral_and_mean_dates_1(date_func): -# integral, mean = s1(date_func)._get_integral_and_mean() -# assert abs(mean - 13 / 9) <= 0.00001, "Expected mean to be 13/9" -# assert integral() / pd.Timedelta("1 H") == 312, "Expected integral to be 312 hours" - - -# def test_integral_and_mean_dates_2(date_func): -# integral, mean = s1(date_func)._get_integral_and_mean( -# (None, timestamp(2020, 1, 6, date_func=date_func)) -# ) -# assert mean == 3, "Expected mean to be 3" -# assert integral() / pd.Timedelta("1 D") == 15, "Expected integral to be 15" - - -# def test_integral_and_mean_3(date_func): -# integral, mean = s1(date_func)._get_integral_and_mean( -# (timestamp(2020, 1, 4, date_func=date_func), None) -# ) -# assert mean == 0.75, "Expected mean to be 0.75" -# assert integral() / pd.Timedelta("1 H") == 108, "Expected integral to be 108 hours" - - -# def test_integral_and_mean_dates_4(date_func): -# integral, mean = s1(date_func)._get_integral_and_mean( -# ( -# timestamp(2020, 1, 4, date_func=date_func), -# timestamp(2020, 1, 8, date_func=date_func), -# ) -# ) -# assert mean == 1.375, "Expected mean to be 1.375" -# assert integral() / pd.Timedelta("1 H") == 132, "Expected integral to be 132 hours" - - -def test_percentile_dates_1(date_func): - assert s1(date_func).percentile(20) == -0.5, "Expected 20th percentile to be -0.5" - assert s1(date_func).percentile(40) == -0.5, "Expected 40th percentile to be -0.5" - assert s1(date_func).percentile(60) == 2, "Expected 60th percentile to be 2" - assert s1(date_func).percentile(80) == 4.5, "Expected 80th percentile to be 4.5" - - -def test_percentile_dates_2(date_func): - assert ( - s1(date_func) - .clip(None, timestamp(2020, 1, 6, date_func=date_func)) - .percentile(20) - == 2 - ), "Expected 20th percentile to be 2" - assert ( - s1(date_func) - .clip(None, timestamp(2020, 1, 6, date_func=date_func)) - .percentile(40) - == 2 - ), "Expected 40th percentile to be 2" - assert ( - s1(date_func) - .clip(None, timestamp(2020, 1, 6, date_func=date_func)) - .percentile(60) - == 3.25 - ), "Expected 60th percentile to be 3.25" - assert ( - s1(date_func) - .clip(None, timestamp(2020, 1, 6, date_func=date_func)) - .percentile(80) - == 4.5 - ), "Expected 80th percentile to be 4.5" - - -def test_percentile_dates_3(date_func): - assert ( - s1(date_func) - .clip(timestamp(2020, 1, 4, date_func=date_func), None) - .percentile(20) - == -0.5 - ), "Expected 20th percentile to be -0.5" - assert ( - s1(date_func) - .clip(timestamp(2020, 1, 4, date_func=date_func), None) - .percentile(40) - == -0.5 - ), "Expected 40th percentile to be -0.5" - assert ( - s1(date_func) - .clip(timestamp(2020, 1, 4, date_func=date_func), None) - .percentile(60) - == -0.5 - ), "Expected 60th percentile to be -0.5" - assert ( - s1(date_func) - .clip(timestamp(2020, 1, 4, date_func=date_func), None) - .percentile(80) - == 2 - ), "Expected 80th percentile to be 2" - - -def test_percentile_dates_4(date_func): - assert ( - s1(date_func) - .clip( - timestamp(2020, 1, 4, date_func=date_func), - timestamp(2020, 1, 8, date_func=date_func), - ) - .percentile(20) - == -0.5 - ), "Expected 20th percentile to be -0.5" - assert ( - s1(date_func) - .clip( - timestamp(2020, 1, 4, date_func=date_func), - timestamp(2020, 1, 8, date_func=date_func), - ) - .percentile(40) - == -0.5 - ), "Expected 40th percentile to be -0.5" - assert ( - s1(date_func) - .clip( - timestamp(2020, 1, 4, date_func=date_func), - timestamp(2020, 1, 8, date_func=date_func), - ) - .percentile(60) - == 2 - ), "Expected 60th percentile to be 2" - assert ( - s1(date_func) - .clip( - timestamp(2020, 1, 4, date_func=date_func), - timestamp(2020, 1, 8, date_func=date_func), - ) - .percentile(80) - == 4.5 - ), "Expected 80th percentile to be 4.5" - - -def test_get_percentiles_dates_1(date_func): - expected_step_values = pd.Series( - [-0.5, 2.0, 4.5, 4.5], index=[0, 44.444444, 77.77777778, 100] - ) - pd.testing.assert_series_equal( - s1(date_func).percentile.step_values, - expected_step_values, - check_names=False, - check_index_type=False, - ) - - -def test_get_percentiles_dates_2(date_func): - expected_step_values = pd.Series([2, 4.5, 4.5], index=[0, 60, 100]) - pd.testing.assert_series_equal( - s1(date_func) - .clip(None, timestamp(2020, 1, 6, date_func=date_func)) - .percentile.step_values, - expected_step_values, - check_names=False, - check_index_type=False, - ) - - -def test_get_percentiles_dates_3(date_func): - expected_step_values = pd.Series( - [-0.5, 2.0, 4.5, 4.5], index=[0, 66.6666666667, 83.333333333, 100] - ) - pd.testing.assert_series_equal( - s1(date_func) - .clip(timestamp(2020, 1, 4, date_func=date_func), None) - .percentile.step_values, - expected_step_values, - check_names=False, - check_index_type=False, - ) - - -def test_get_percentiles_dates_4(date_func): - expected_step_values = pd.Series([-0.5, 2.0, 4.5, 4.5], index=[0, 50, 75, 100]) - pd.testing.assert_series_equal( - s1(date_func) - .clip( - timestamp(2020, 1, 4, date_func=date_func), - timestamp(2020, 1, 8, date_func=date_func), - ) - .percentile.step_values, - expected_step_values, - check_names=False, - check_index_type=False, - ) - - -def test_plot(date_func): - s1(date_func).plot() - - -# def test_resample_dates_1(date_func): -# assert s1(date_func).resample(timestamp(2020, 1, 4)).step_changes == { -# timestamp(2020, 1, 4).tz_localize(s1(date_func)._keys()[0].tz): 4.5 -# } - - -# def test_resample_dates_2(date_func): -# assert s1(date_func).resample(timestamp(2020, 1, 6), how="right").step_changes == { -# timestamp(2020, 1, 6).tz_localize(s1(date_func)._keys()[0].tz): -0.5 -# } - - -# def test_resample_dates_3(date_func): -# assert s1(date_func).resample(timestamp(2020, 1, 6), how="left").step_changes == { -# timestamp(2020, 1, 6).tz_localize(s1(date_func)._keys()[0].tz): 2 -# } - - -# def test_resample_dates_4(date_func): -# assert s1(date_func).resample( -# [timestamp(2020, 1, 4), timestamp(2020, 1, 6)] -# ).step_changes == { -# timestamp(2020, 1, 4).tz_localize(s1(date_func)._keys()[0].tz): 4.5, -# timestamp(2020, 1, 6).tz_localize(s1(date_func)._keys()[0].tz): -5.0, -# } - - -# def test_resample_dates_5(date_func): -# assert s1(date_func).resample( -# [timestamp(2020, 1, 4), timestamp(2020, 1, 6)], how="right" -# ).step_changes == { -# timestamp(2020, 1, 4).tz_localize(s1(date_func)._keys()[0].tz): 4.5, -# timestamp(2020, 1, 6).tz_localize(s1(date_func)._keys()[0].tz): -5.0, -# } - - -# def test_resample_dates_6(date_func): -# assert s1(date_func).resample( -# [timestamp(2020, 1, 4), timestamp(2020, 1, 6)], how="left" -# ).step_changes == { -# timestamp(2020, 1, 4).tz_localize(s1(date_func)._keys()[0].tz): 4.5, -# timestamp(2020, 1, 6).tz_localize(s1(date_func)._keys()[0].tz): -2.5, -# } - - -def test_sample_dates_1(date_func): - assert s1(date_func).sample(timestamp(2020, 1, 6, date_func=date_func)) == -0.5 - - -def test_limit_dates_1(date_func): - assert ( - s1(date_func).limit(timestamp(2020, 1, 6, date_func=date_func), side="right") - == -0.5 - ) - - -def test_limit_dates_2(date_func): - assert ( - s1(date_func).limit(timestamp(2020, 1, 6, date_func=date_func), side="left") - == 2 - ) - - -def test_sample_dates_4(date_func): - assert _compare_iterables( - s1(date_func).sample( - [ - timestamp(2020, 1, 4, date_func=date_func), - timestamp(2020, 1, 6, date_func=date_func), - ] - ), - [4.5, -0.5], - ) - - -def test_limit_dates_3(date_func): - assert _compare_iterables( - s1(date_func).limit( - [ - timestamp(2020, 1, 4, date_func=date_func), - timestamp(2020, 1, 6, date_func=date_func), - ], - side="right", - ), - [4.5, -0.5], - ) - - -def test_limit_dates_4(date_func): - assert _compare_iterables( - s1(date_func).limit( - [ - timestamp(2020, 1, 4, date_func=date_func), - timestamp(2020, 1, 6, date_func=date_func), - ], - side="left", - ), - [4.5, 2], - ) - - -def test_step_changes_dates(date_func): - expected_step_changes = pd.Series( - [2, 2.5, -2.5, -2.5, 0.5], - index=[ - timestamp("2020-1-1", date_func=date_func), - timestamp("2020-1-3", date_func=date_func), - timestamp("2020-1-5", date_func=date_func), - timestamp("2020-1-6", date_func=date_func), - timestamp("2020-1-10", date_func=date_func), - ], - ) - pd.testing.assert_series_equal( - s1(date_func).step_changes, - expected_step_changes, - check_names=False, - check_index_type=False, - ) - - -def test_dataframe_dates(date_func): - ans = pd.DataFrame( - { - "start": [ - -inf, - timestamp("2020-01-01", date_func=date_func), - timestamp("2020-01-03", date_func=date_func), - timestamp("2020-01-05", date_func=date_func), - timestamp("2020-01-06", date_func=date_func), - timestamp("2020-01-10", date_func=date_func), - ], - "end": [ - timestamp("2020-01-01", date_func=date_func), - timestamp("2020-01-03", date_func=date_func), - timestamp("2020-01-05", date_func=date_func), - timestamp("2020-01-06", date_func=date_func), - timestamp("2020-01-10", date_func=date_func), - inf, - ], - "value": [0, 2, 4.5, 2, -0.5, 0], - } - ) - pd.testing.assert_frame_equal(s1(date_func).to_frame(), ans) - - -def test_add_dates(date_func): - expected_step_changes = pd.Series( - { - timestamp("2020-01-01 00:00:00", date_func=date_func): -0.5, - timestamp("2020-01-02 00:00:00", date_func=date_func): 4.5, - timestamp("2020-01-02 12:00:00", date_func=date_func): -2.5, - timestamp("2020-01-03 00:00:00", date_func=date_func): 2.5, - timestamp("2020-01-04 00:00:00", date_func=date_func): 2.5, - timestamp("2020-01-05 00:00:00", date_func=date_func): -7.0, - timestamp("2020-01-06 00:00:00", date_func=date_func): -2.5, - timestamp("2020-01-07 00:00:00", date_func=date_func): 2.5, - timestamp("2020-01-08 00:00:00", date_func=date_func): 5, - timestamp("2020-01-10 00:00:00", date_func=date_func): -4.5, - } - ) - result = s1(date_func) + s2(date_func) - pd.testing.assert_series_equal( - result.step_changes, - expected_step_changes, - check_names=False, - check_index_type=False, - ) - assert_expected_type(result, date_func) - - -def test_subtract_dates(date_func): - expected_step_changes = pd.Series( - { - timestamp("2020-01-01 00:00:00", date_func=date_func): 4.5, - timestamp("2020-01-02 00:00:00", date_func=date_func): -4.5, - timestamp("2020-01-02 12:00:00", date_func=date_func): 2.5, - timestamp("2020-01-03 00:00:00", date_func=date_func): 2.5, - timestamp("2020-01-04 00:00:00", date_func=date_func): -2.5, - timestamp("2020-01-05 00:00:00", date_func=date_func): 2.0, - timestamp("2020-01-06 00:00:00", date_func=date_func): -2.5, - timestamp("2020-01-07 00:00:00", date_func=date_func): -2.5, - timestamp("2020-01-08 00:00:00", date_func=date_func): -5, - timestamp("2020-01-10 00:00:00", date_func=date_func): 5.5, - } - ) - result = s1(date_func) - s2(date_func) - pd.testing.assert_series_equal( - result.step_changes, - expected_step_changes, - check_names=False, - check_index_type=False, - ) - assert_expected_type(result, date_func) - - -def test_multiply_dates(date_func): - expected_step_changes = pd.Series( - { - timestamp("2020-01-01 00:00:00", date_func=date_func): -5.0, - timestamp("2020-01-02 00:00:00", date_func=date_func): 9.0, - timestamp("2020-01-02 12:00:00", date_func=date_func): -5.0, - timestamp("2020-01-03 00:00:00", date_func=date_func): -1.25, - timestamp("2020-01-04 00:00:00", date_func=date_func): 11.25, - timestamp("2020-01-05 00:00:00", date_func=date_func): -14.0, - timestamp("2020-01-06 00:00:00", date_func=date_func): 6.25, - timestamp("2020-01-07 00:00:00", date_func=date_func): -1.25, - timestamp("2020-01-08 00:00:00", date_func=date_func): -2.5, - timestamp("2020-01-10 00:00:00", date_func=date_func): 2.5, - } - ) - result = s1(date_func) * s2(date_func) - pd.testing.assert_series_equal( - result.step_changes, - expected_step_changes, - check_names=False, - check_index_type=False, - ) - assert_expected_type(result, date_func) - - -def test_multiply_dates_scalar(date_func): - expected_step_changes = pd.Series( - { - timestamp("2020-01-01 00:00:00", date_func=date_func): 6.0, - timestamp("2020-01-03 00:00:00", date_func=date_func): 7.5, - timestamp("2020-01-05 00:00:00", date_func=date_func): -7.5, - timestamp("2020-01-06 00:00:00", date_func=date_func): -7.5, - timestamp("2020-01-10 00:00:00", date_func=date_func): 1.5, - } - ) - result = s1(date_func) * 3 - pd.testing.assert_series_equal( - result.step_changes, - expected_step_changes, - check_names=False, - check_index_type=False, - ) - assert_expected_type(result, date_func) - - -def test_divide_dates(date_func): - expected_step_changes = pd.Series( - { - timestamp("2020-01-01 00:00:00", date_func=date_func): -1.3333333333333333, - timestamp("2020-01-02 00:00:00", date_func=date_func): 2.0, - timestamp("2020-01-02 12:00:00", date_func=date_func): 3.3333333333333335, - timestamp("2020-01-03 00:00:00", date_func=date_func): 5.0, - timestamp("2020-01-04 00:00:00", date_func=date_func): -7.5, - timestamp("2020-01-05 00:00:00", date_func=date_func): -2.833333333333333, - timestamp("2020-01-06 00:00:00", date_func=date_func): 1.6666666666666665, - timestamp("2020-01-07 00:00:00", date_func=date_func): -0.8333333333333333, - timestamp("2020-01-08 00:00:00", date_func=date_func): 0.4166666666666667, - timestamp("2020-01-10 00:00:00", date_func=date_func): 0.08333333333333333, - } - ) - result = s1(date_func) / (s2(date_func) + 1) - pd.testing.assert_series_equal( - result.step_changes, - expected_step_changes, - check_names=False, - check_index_type=False, - ) - assert_expected_type(result, date_func) - - -def test_divide_dates_scalar(date_func): - expected_step_changes = pd.Series( - { - timestamp("2020-01-01 00:00:00", date_func=date_func): 4.0, - timestamp("2020-01-03 00:00:00", date_func=date_func): 5.0, - timestamp("2020-01-05 00:00:00", date_func=date_func): -5.0, - timestamp("2020-01-06 00:00:00", date_func=date_func): -5.0, - timestamp("2020-01-10 00:00:00", date_func=date_func): 1.0, - } - ) - result = s1(date_func) / 0.5 - pd.testing.assert_series_equal( - result.step_changes, - expected_step_changes, - check_names=False, - check_index_type=False, - ) - assert_expected_type(result, date_func) - - -def test_to_frame(date_func): - s1(date_func).to_frame() - - -@pytest.mark.parametrize( - "stairs_func, bounds, cuts", - itertools.product( - [s1, s2, s3, s4], - [ - ((2020, 1, 3), (2020, 1, 4)), - ((2020, 1, 1), (2020, 1, 4)), - ((2020, 1, 2), (2020, 2, 4)), - ], - ["unit", (-2, 0, 0.5, 4, 4.5, 7)], - ), -) -def test_hist_default_bins_left_closed(date_func, stairs_func, bounds, cuts): - stairs_instance = stairs_func(date_func) - bounds = [timestamp(*args, date_func=date_func) for args in bounds] - - def make_expected_result(interval_index, lower, upper): - return pd.Series( - [ - ((stairs_instance >= i.left) * (stairs_instance < i.right)).agg( - "mean", (lower, upper) - ) - for i in interval_index - ], - index=interval_index, - ) - - hist = stairs_instance.clip(*bounds).hist(bins=cuts, stat="probability") - expected = make_expected_result(hist.index, *bounds) - pd.testing.assert_series_equal( - hist, - expected, - check_names=False, - check_index_type=False, - ) - - -@pytest.mark.parametrize( - "stairs_func, bounds, cuts", - itertools.product( - [s1, s2, s3, s4], - [ - ((2020, 1, 3), (2020, 1, 4)), - ((2020, 1, 1), (2020, 1, 4)), - ((2020, 1, 2), (2020, 2, 4)), - ], - ["unit", (-2, 0, 0.5, 4, 4.5, 7)], - ), -) -def test_hist_default_bins_right_closed(date_func, stairs_func, bounds, cuts): - stairs_instance = stairs_func(date_func) - bounds = [timestamp(*args, date_func=date_func) for args in bounds] - - def make_expected_result(interval_index, lower, upper): - return pd.Series( - [ - ((stairs_instance > i.left) * (stairs_instance <= i.right)).agg( - "mean", (lower, upper) - ) - for i in interval_index - ], - index=interval_index, - ) - - hist = stairs_instance.clip(*bounds).hist( - bins=cuts, closed="right", stat="probability" - ) - expected = make_expected_result(hist.index, *bounds) - pd.testing.assert_series_equal( - hist, - expected, - check_names=False, - check_index_type=False, - ) - - -@pytest.mark.parametrize( - "stairs_func, bounds, closed", - itertools.product( - [s1, s2, s3, s4], - [ - ((2020, 1, 3), (2020, 1, 4)), - ((2020, 1, 1), (2020, 1, 4)), - ((2020, 1, 2), (2020, 2, 4)), - ], - ["left", "right"], - ), -) -def test_hist_default_bins(date_func, stairs_func, bounds, closed): - # really testing the default binning process here - stairs_instance = stairs_func(date_func) - bounds = [timestamp(*args, date_func=date_func) for args in bounds] - hist = stairs_instance.clip(*bounds).hist(closed=closed, stat="probability") - assert abs(hist.sum() - 1) < 0.000001 - - -def test_shift(date_func): - ans = Stairs(initial_value=0) - ans.layer( - timestamp(2020, 1, 2, date_func=date_func), - timestamp(2020, 1, 11, date_func=date_func), - 2, - ) - ans.layer( - timestamp(2020, 1, 4, date_func=date_func), - timestamp(2020, 1, 6, date_func=date_func), - 2.5, - ) - ans.layer( - timestamp(2020, 1, 7, date_func=date_func), - timestamp(2020, 1, 8, date_func=date_func), - -2.5, - ) - ans.layer( - timestamp(2020, 1, 8, date_func=date_func), - timestamp(2020, 1, 11, date_func=date_func), - -2.5, - ) - result = s1(date_func).shift(pd.Timedelta(24, unit="H")) - assert bool(result == ans) - assert_expected_type(result, date_func) - - # low, high = timestamp(2020,1,1, date_func=date_func), timestamp(2020,1,10, date_func=date_func) # total_secs = int((high-low).total_seconds()) # pts = [low + pd.Timedelta(x, unit='sec') for x in np.linspace(0, total_secs, total_secs)] @@ -1537,101 +923,6 @@ def test_crosscov(date_func, kwargs, expected): ) -# @pytest.mark.parametrize( -# "kwargs, expected_index, expected_vals", -# [ -# ( -# {"window": (-pd.Timedelta(1, "d"), pd.Timedelta(1, "d"))}, -# [ -# (2019, 12, 31), -# (2020, 1, 2), -# (2020, 1, 4), -# (2020, 1, 5), -# (2020, 1, 6), -# (2020, 1, 7), -# (2020, 1, 9), -# (2020, 1, 11), -# ], -# [0.0, 2.0, 4.5, 3.25, 0.75, -0.5, -0.5, 0.0], -# ), -# ( -# {"window": (-pd.Timedelta(2, "d"), pd.Timedelta(0, "d"))}, -# [ -# (2020, 1, 1), -# (2020, 1, 3), -# (2020, 1, 5), -# (2020, 1, 6), -# (2020, 1, 7), -# (2020, 1, 8), -# (2020, 1, 10), -# (2020, 1, 12), -# ], -# [0.0, 2.0, 4.5, 3.25, 0.75, -0.5, -0.5, 0.0], -# ), -# ( -# { -# "window": (-pd.Timedelta(1, "d"), pd.Timedelta(1, "d")), -# "lower": (2020, 1, 3), -# "upper": (2020, 1, 8), -# }, -# [ -# (2020, 1, 4), -# (2020, 1, 5), -# (2020, 1, 6), -# (2020, 1, 7), -# ], -# [4.5, 3.25, 0.75, -0.5], -# ), -# ], -# ) -# def test_s1_rolling_mean(date_func, kwargs, expected_index, expected_vals): -# expected_index = [timestamp(*args, date_func=date_func) for args in expected_index] -# new_kwargs = {**kwargs} -# if "lower" in kwargs: -# new_kwargs["lower"] = timestamp(*kwargs["lower"], date_func=date_func) -# if "upper" in kwargs: -# new_kwargs["upper"] = timestamp(*kwargs["upper"], date_func=date_func) -# new_kwargs - -# rm = s1(date_func).rolling_mean(**new_kwargs) -# assert list(rm.values) == expected_vals -# assert list(rm.index) == expected_index - - -def test_eq(): - assert Stairs(initial_value=3) == 3 - - -def test_ne(date_func): - assert s1(date_func) != 3 - - -def test_make_test_data(): - assert isinstance(test_data.make_test_data(dates=True), pd.DataFrame) - - -@pytest.mark.parametrize( - "kwargs", - [ - { - "lower": (2020, 1, 1), - }, - { - "lower": (2020, 1, 1), - "upper": (2020, 1, 8), - }, - { - "upper": (2020, 1, 8), - }, - ], -) -def test_clip_expected_type(date_func, kwargs): - kwargs = kwargs.copy() - kwargs = {key: timestamp(*val, date_func=date_func) for key, val in kwargs.items()} - result = s1(date_func).clip(**kwargs) - assert_expected_type(result, date_func) - - def test_integral_overflow(): with pytest.raises(OverflowError): s = ( diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py new file mode 100644 index 0000000..9886035 --- /dev/null +++ b/tests/test_exceptions.py @@ -0,0 +1,67 @@ +import operator + +import pytest + +from staircase import Stairs +from staircase.core.ops.common import ClosedMismatchError + + +def s1(closed="left"): + int_seq1 = Stairs(initial_value=0, closed=closed) + int_seq1.layer(1, 10, 2) + int_seq1.layer(-4, 5, -1.75) + int_seq1.layer(3, 5, 2.5) + int_seq1.layer(6, 7, -2.5) + int_seq1.layer(7, 10, -2.5) + return int_seq1 + + +@pytest.mark.parametrize( + ("left", "right", "ok"), + [ + (Stairs(start=0, closed="left"), 1, True), + (Stairs(start=0, closed="right"), 1, True), + (1, Stairs(start=0, closed="left"), True), + (1, Stairs(start=0, closed="right"), True), + (Stairs(closed="left"), 1, True), + (Stairs(closed="right"), 1, True), + (1, Stairs(closed="left"), True), + (1, Stairs(closed="right"), True), + (s1(closed="left"), s1(closed="right"), False), + (s1(closed="right"), s1(closed="left"), False), + ], +) +@pytest.mark.parametrize( + "op", + [ + operator.add, + operator.sub, + operator.mul, + operator.truediv, + operator.eq, + operator.ne, + operator.lt, + operator.gt, + operator.le, + operator.ge, + operator.and_, + operator.or_, + operator.xor, + ], +) +def test_binary_operation_closed_matching(left, right, op, ok): + """ + A binary operation should throw a a `ClosedMismatchError` if: + + 1. Both arguments are `Stairs` objects, and + 2. The arguments have different `closed` values, and + 3. Both arguments have a nonzero number of steps. + + For all our defined operations, test that the exception is thrown under the + above conditions but not if the arguments are ok. + """ + if ok: + op(left, right) + else: + with pytest.raises(ClosedMismatchError): + op(left, right) diff --git a/tests/test_floats/test_floats_arithmetic.py b/tests/test_floats/test_floats_arithmetic.py new file mode 100644 index 0000000..cd71dd2 --- /dev/null +++ b/tests/test_floats/test_floats_arithmetic.py @@ -0,0 +1,324 @@ +import operator + +import numpy as np +import pandas as pd +import pytest + +from staircase import Stairs + + +def s1(closed="left"): + int_seq1 = Stairs(initial_value=0, closed=closed) + int_seq1.layer(1, 10, 2) + int_seq1.layer(-4, 5, -1.75) + int_seq1.layer(3, 5, 2.5) + int_seq1.layer(6, 7, -2.5) + int_seq1.layer(7, 10, -2.5) + return int_seq1 + + +def s2(): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer(1, 7, -2.5) + int_seq2.layer(8, 10, 5) + int_seq2.layer(2, 5, 4.5) + int_seq2.layer(2.5, 4, -2.5) + int_seq2.layer(-2, 1, -1.75) + return int_seq2 + + +def s3(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-10, 10, 1) + int_seq.layer(-8, -7, -1) + int_seq.layer(-5, -2, -1) + int_seq.layer(0.5, 1, -1) + int_seq.layer(3, 3.5, -1) + int_seq.layer(7, 9.5, -1) + return int_seq + + +def s4(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-11, 9, 1) + int_seq.layer(-9.5, -8, -1) + int_seq.layer(-7.5, -7, -1) + int_seq.layer(0, 3, -1) + int_seq.layer(6, 6.5, -1) + int_seq.layer(7, 8.5, -1) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +def test_add_1(s1_fix, s2_fix): + assert pd.Series.equals( + (s1_fix + s2_fix).step_changes, + pd.Series( + { + -4: -1.75, + -2: -1.75, + 1: 1.25, + 2: 4.5, + 2.5: -2.5, + 3: 2.5, + 4: 2.5, + 5: -5.25, + 6: -2.5, + 7: 2.5, + 8: 5, + 10: -4.5, + } + ), + ) + + +def test_add_2(s1_fix): + s = s1_fix + 3 + assert s.initial_value == 3 + assert pd.Series.equals( + s.step_changes, + s1_fix.step_changes, + ) + + +def test_add_3(s1_fix): + s = 3 + s1_fix + assert s.initial_value == 3 + assert pd.Series.equals( + s.step_changes, + s1_fix.step_changes, + ) + + +def test_sub_1(s1_fix, s2_fix): + assert pd.Series.equals( + (s1_fix - s2_fix).step_values, + pd.Series( + { + -4.0: -1.75, + -2.0: 0.0, + 1.0: 2.75, + 2.0: -1.75, + 2.5: 0.75, + 3.0: 3.25, + 4.0: 0.75, + 5.0: 4.5, + 6.0: 2.0, + 7.0: -0.5, + 8.0: -5.5, + 10.0: 0.0, + } + ), + ) + + +def test_sub_2(s1_fix): + s = s1_fix - 3 + assert s.initial_value == -3 + assert pd.Series.equals( + s.step_changes, + s1_fix.step_changes, + ) + + +def test_sub_3(s1_fix): + s = 3 - s1_fix + assert s.initial_value == 3 + assert pd.Series.equals( + s.step_changes, + -(s1_fix.step_changes), + ) + + +def test_divide(s1_fix, s2_fix): + assert pd.Series.equals( + (s1_fix / (s2_fix + 1)).step_changes, + pd.Series( + { + -4: -1.75, + -2: 4.083333333333334, + 1: -2.5, + 2: 0.25, + 2.5: 0.4166666666666667, + 3: 5.0, + 4: -4.583333333333333, + 5: -2.25, + 6: 1.6666666666666665, + 7: -0.8333333333333333, + 8: 0.4166666666666667, + 10: 0.08333333333333333, + } + ), + ) + + +def test_divide_scalar(s1_fix): + assert pd.Series.equals( + (s1_fix / 0.5).step_changes, + pd.Series( + { + -4: -3.5, + 1: 4.0, + 3: 5.0, + 5: -1.5, + 6: -5.0, + 10: 1.0, + } + ), + ) + + +def test_scalar_divide(): + s = Stairs().layer([1, 2, 5], [3, 4, 7], [1, -1, 2]) + assert pd.Series.equals( + (2 / s).step_values, + pd.Series( + { + 1: 2.0, + 2: np.nan, + 3: -2.0, + 4: np.nan, + 5: 1.0, + 7: np.nan, + } + ), + ) + + +def test_multiply(s1_fix, s2_fix): + assert pd.Series.equals( + (s1_fix * s2_fix).step_changes, + pd.Series( + { + -2: 3.0625, + 1: -3.6875, + 2: 1.125, + 2.5: -0.625, + 3: -1.25, + 4: 6.875, + 5: -10.5, + 6: 6.25, + 7: -1.25, + 8: -2.5, + 10: 2.5, + } + ), + ) + + +def test_multiply_scalar(s1_fix): + assert pd.Series.equals( + (s1_fix * 3).step_changes, + pd.Series( + { + -4: -5.25, + 1: 6.0, + 3: 7.5, + 5: -2.25, + 6: -7.5, + 10: 1.5, + } + ), + ) + + +def test_multiply_scalar_2(s1_fix): + assert pd.Series.equals( + (3 * s1_fix).step_changes, + pd.Series( + { + -4: -5.25, + 1: 6.0, + 3: 7.5, + 5: -2.25, + 6: -7.5, + 10: 1.5, + } + ), + ) + + +def test_negate(s1_fix): + pd.testing.assert_series_equal( + (-s1_fix).step_values, + pd.Series({-4: 1.75, 1: -0.25, 3: -2.75, 5: -2.0, 6: 0.5, 10: 0.0}), + check_names=False, + check_index_type=False, + ) + + +@pytest.mark.parametrize( + "op", + [ + operator.add, + operator.sub, + operator.mul, + operator.truediv, + ], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +@pytest.mark.parametrize( + "operands", + [("stairs", "stairs"), ("stairs", "scalar"), ("scalar", "stairs")], +) +def test_closed_binary_ops(op, closed, operands): + operand_dict = {"stairs": Stairs(closed=closed), "scalar": 1} + operand0 = operand_dict[operands[0]] + operand1 = operand_dict[operands[1]] + result = op(operand0, operand1) + assert ( + result.closed == closed + ), f"result has closed value of {result.closed}, expected {closed}" + + +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +def test_closed_negate(closed): + s = -(Stairs(initial_value=1, closed=closed)) + assert ( + s.closed == closed + ), f"result has closed value of {s.closed}, expected {closed}" + + +@pytest.mark.parametrize( + "op", + [ + operator.add, + operator.sub, + operator.mul, + operator.truediv, + ], +) +@pytest.mark.parametrize( + "nan_pos", + ["first", "second"], +) +def test_binary_ops_with_nan(s1_fix, op, nan_pos): + # GH109 + operands = (np.nan, s1_fix) if nan_pos == "first" else (s1_fix, np.nan) + result = op(*operands) + assert result._data is None, "wrong internal representation in resulting Stairs" diff --git a/tests/test_stairs_arrays.py b/tests/test_floats/test_floats_arrays.py similarity index 100% rename from tests/test_stairs_arrays.py rename to tests/test_floats/test_floats_arrays.py diff --git a/tests/test_floats/test_floats_construction.py b/tests/test_floats/test_floats_construction.py new file mode 100644 index 0000000..5149f41 --- /dev/null +++ b/tests/test_floats/test_floats_construction.py @@ -0,0 +1,336 @@ +import itertools + +import numpy as np +import pandas as pd +import pytest + +from staircase import Stairs + + +def _expand_interval_definition(start, end=None, value=1): + return start, end, value + + +def _compare_iterables(it1, it2): + it1 = [i for i in it1 if i is not None] + it2 = [i for i in it2 if i is not None] + if len(it2) != len(it1): + return False + for e1, e2 in zip(it1, it2): + if e1 != e2: + return False + return True + + +def s1(closed="left"): + int_seq1 = Stairs(initial_value=0, closed=closed) + int_seq1.layer(1, 10, 2) + int_seq1.layer(-4, 5, -1.75) + int_seq1.layer(3, 5, 2.5) + int_seq1.layer(6, 7, -2.5) + int_seq1.layer(7, 10, -2.5) + return int_seq1 + + +def s2(): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer(1, 7, -2.5) + int_seq2.layer(8, 10, 5) + int_seq2.layer(2, 5, 4.5) + int_seq2.layer(2.5, 4, -2.5) + int_seq2.layer(-2, 1, -1.75) + return int_seq2 + + +def s3(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-10, 10, 1) + int_seq.layer(-8, -7, -1) + int_seq.layer(-5, -2, -1) + int_seq.layer(0.5, 1, -1) + int_seq.layer(3, 3.5, -1) + int_seq.layer(7, 9.5, -1) + return int_seq + + +def s4(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-11, 9, 1) + int_seq.layer(-9.5, -8, -1) + int_seq.layer(-7.5, -7, -1) + int_seq.layer(0, 3, -1) + int_seq.layer(6, 6.5, -1) + int_seq.layer(7, 8.5, -1) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +def test_init(): + assert Stairs(initial_value=0).identical(Stairs()) + assert Stairs().identical(Stairs(initial_value=0)) + + +@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) +def test_init2(init_value): + int_seq = Stairs(initial_value=init_value) + assert ( + int_seq.number_of_steps == 0 + ), "Initialised Stairs should have exactly one interval" + + +@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) +def test_init3(init_value): + int_seq = Stairs(initial_value=init_value) + assert ( + len(int_seq.step_points) == 0 + ), "Initialised Stairs should not have any finite interval endpoints" + + +@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) +def test_init4(init_value): + int_seq = Stairs(initial_value=init_value) + assert ( + int_seq(-1) == init_value + ), "Initialised Stairs should have initial value everywhere" + assert ( + int_seq(0) == init_value + ), "Initialised Stairs should have initial value everywhere" + assert ( + int_seq(1) == init_value + ), "Initialised Stairs should have initial value everywhere" + + +@pytest.mark.parametrize( + "init_value, added_interval", + itertools.product( + [0, 1.25, -1.25], + [(-2, 1), (3, 5, 2), (1, 5, -1), (-5, -3, 3), (3,), (2, None, 2)], + ), +) +def test_one_finite_interval(init_value, added_interval): + e = 0.0001 + int_seq = Stairs(initial_value=init_value) + int_seq.layer(*added_interval) + start, end, value = _expand_interval_definition(*added_interval) + assert int_seq.number_of_steps == 2 - ( + end is None + ), "One finite interval added to initial infinite interval should result in 3 intervals" + assert _compare_iterables( + int_seq.step_points, (start, end) + ), "Finite endpoints are not what is expected" + assert ( + int_seq(float("-inf")) == init_value + ), "Adding finite interval should not change initial value" + assert int_seq(float("inf")) == init_value + value * ( + end is None + ), "Adding finite interval should not change final value" + assert int_seq(start - e) == init_value + assert int_seq(start) == init_value + value + assert int_seq(start + e) == init_value + value + if end is not None: + assert int_seq(end - e) == init_value + value + assert int_seq(end) == init_value + + +@pytest.mark.parametrize( + "init_value, endpoints, value", + itertools.product( + [0, 1.25, -1.25, 2, -2], + [(-2, 1, 3), (-2, -1, 3), (-3, -2, -1), (1, 2, 3)], + [-1, 2, 3], + ), +) +def test_two_adjacent_finite_interval_same_value(init_value, endpoints, value): + e = 0.0001 + int_seq = Stairs(initial_value=init_value) + point1, point2, point3 = endpoints + int_seq.layer(point1, point2, value) + int_seq.layer(point2, point3, value) + assert int_seq.number_of_steps == 2, "Expected result to be 3 intervals" + assert _compare_iterables( + int_seq.step_points, (point1, point3) + ), "Finite endpoints are not what is expected" + assert ( + int_seq(float("-inf")) == init_value + ), "Adding finite interval should not change initial value" + assert ( + int_seq(float("inf")) == init_value + ), "Adding finite interval should not change final value" + assert int_seq(point1 - e) == init_value + assert int_seq(point1) == init_value + value + assert int_seq(point2) == init_value + value + assert int_seq(point3 - e) == init_value + value + assert int_seq(point3) == init_value + + +@pytest.mark.parametrize( + "init_value, endpoints, value, delta", + itertools.product( + [0, 1.25, -1.25, 2, -2], + [(-2, 1, 3), (-2, -1, 3), (-3, -2, -1), (1, 2, 3)], + [-1, 2, 4], + [3, -3, 1.5, -1.5], + ), +) +def test_two_adjacent_finite_interval_different_value( + init_value, endpoints, value, delta +): + e = 0.0001 + int_seq = Stairs(initial_value=init_value) + point1, point2, point3 = endpoints + int_seq.layer(point1, point2, value) + int_seq.layer(point2, point3, value + delta) + assert int_seq.number_of_steps == 3, "Expected result to be 4 intervals" + assert _compare_iterables( + int_seq.step_points, (point1, point2, point3) + ), "Finite endpoints are not what is expected" + assert ( + int_seq(float("-inf")) == init_value + ), "Adding finite interval should not change initial value" + assert ( + int_seq(float("inf")) == init_value + ), "Adding finite interval should not change final value" + assert int_seq(point1 - e) == init_value + assert int_seq(point1) == init_value + value + assert int_seq(point2) == init_value + value + delta + assert int_seq(point3 - e) == init_value + value + delta + assert int_seq(point3) == init_value + + +@pytest.mark.parametrize( + "init_value, endpoints, value, delta", + itertools.product( + [0, 1.25, -1.25, 2, -2], + [(-2, 1, 2, 3), (-3, -2, -1, 3), (-4, -3, -2, -1), (0, 1, 2, 3)], + [-1, 2, 4], + [3, -3, 1.5, -1.5], + ), +) +def test_two_overlapping_finite_interval(init_value, endpoints, value, delta): + e = 0.0001 + int_seq = Stairs(initial_value=init_value) + point1, point2, point3, point4 = endpoints + int_seq.layer(point1, point3, value) + int_seq.layer(point2, point4, value + delta) + assert int_seq.number_of_steps == 4, "Expected result to be 5 intervals" + assert _compare_iterables( + int_seq.step_points, (point1, point2, point3, point4) + ), "Finite endpoints are not what is expected" + assert ( + int_seq(float("-inf")) == init_value + ), "Adding finite interval should not change initial value" + assert ( + int_seq(float("inf")) == init_value + ), "Adding finite interval should not change final value" + assert int_seq(point1 - e) == init_value + assert int_seq(point1) == init_value + value + assert int_seq(point2) == init_value + 2 * value + delta + assert int_seq(point3 - e) == init_value + 2 * value + delta + assert int_seq(point3) == init_value + value + delta + assert int_seq(point4 - e) == init_value + value + delta + assert int_seq(point4) == init_value + + +@pytest.mark.parametrize( + "init_value, endpoints, value, delta", + itertools.product( + [0, 1.25, -1.25, 2, -2], + [(-2, 1, 2, 3), (-3, -2, -1, 3), (-4, -3, -2, -1), (0, 1, 2, 3)], + [-1, 2, 4], + [3, -3, 1.5, -1.5], + ), +) +def test_two_finite_interval_one_subinterval(init_value, endpoints, value, delta): + e = 0.0001 + int_seq = Stairs(initial_value=init_value) + point1, point2, point3, point4 = endpoints + int_seq.layer(point1, point4, value) + int_seq.layer(point2, point3, value + delta) + assert int_seq.number_of_steps == 4, "Expected result to be 5 intervals" + assert _compare_iterables( + int_seq.step_points, (point1, point2, point3, point4) + ), "Finite endpoints are not what is expected" + assert ( + int_seq.initial_value == init_value + ), "Adding finite interval should not change initial value" + assert ( + int_seq(float("inf")) == init_value + ), "Adding finite interval should not change final value" + assert int_seq(point1 - e) == init_value + assert int_seq(point1) == init_value + value + assert int_seq(point2) == init_value + 2 * value + delta + assert int_seq(point3 - e) == init_value + 2 * value + delta + assert int_seq(point3) == init_value + value + assert int_seq(point4 - e) == init_value + value + assert int_seq(point4) == init_value + + +@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) +def test_layer1(init_value): + intervals_to_add = [(-2, 1), (3, 5), (1, 5), (-5, -3), (None, 0), (0, None)] + int_seq = Stairs(initial_value=init_value) + int_seq2 = Stairs(initial_value=init_value) + for start, end in intervals_to_add: + int_seq.layer(start, end) + starts, ends = list(zip(*intervals_to_add)) + starts = [{None: np.nan}.get(x, x) for x in starts] + ends = [{None: np.nan}.get(x, x) for x in ends] + int_seq2.layer(starts, ends) + assert int_seq.identical(int_seq2) + assert int_seq2.identical(int_seq) + + +@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) +def test_layer2(init_value): + intervals_to_add = [(-2, 1, 1), (3, 5, 2), (1, 5, -1), (-5, -3, 3)] + int_seq = Stairs(initial_value=init_value) + int_seq2 = Stairs(initial_value=init_value) + for interval in intervals_to_add: + int_seq.layer(*interval) + starts, ends, values = list(zip(*intervals_to_add)) + int_seq2.layer(starts, ends, values) + assert int_seq.identical(int_seq2) + assert int_seq2.identical(int_seq) + + +def test_layering_index(s1_fix): + result = Stairs( + start=pd.Index([1, -4, 3, 6, 7]), + end=pd.Index([10, 5, 5, 7, 10]), + value=pd.Index([2, -1.75, 2.5, -2.5, -2.5]), + ) + assert result.identical(s1_fix) + + +def test_layering_frame(s1_fix): + df = pd.DataFrame( + { + "start": [1, -4, 3, 6, 7], + "end": [10, 5, 5, 7, 10], + "value": [2, -1.75, 2.5, -2.5, -2.5], + } + ) + assert Stairs(df, "start", "end", "value").identical(s1_fix) + + +def test_layering_trivial_1(s1_fix): + assert s1_fix.copy().layer(1, 1).identical(s1_fix) diff --git a/tests/test_floats/test_floats_distribution.py b/tests/test_floats/test_floats_distribution.py new file mode 100644 index 0000000..4ece058 --- /dev/null +++ b/tests/test_floats/test_floats_distribution.py @@ -0,0 +1,188 @@ +import itertools + +import numpy as np +import pandas as pd +import pytest + +from staircase import Stairs + + +def s1(closed="left"): + int_seq1 = Stairs(initial_value=0, closed=closed) + int_seq1.layer(1, 10, 2) + int_seq1.layer(-4, 5, -1.75) + int_seq1.layer(3, 5, 2.5) + int_seq1.layer(6, 7, -2.5) + int_seq1.layer(7, 10, -2.5) + return int_seq1 + + +def s2(): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer(1, 7, -2.5) + int_seq2.layer(8, 10, 5) + int_seq2.layer(2, 5, 4.5) + int_seq2.layer(2.5, 4, -2.5) + int_seq2.layer(-2, 1, -1.75) + return int_seq2 + + +def s3(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-10, 10, 1) + int_seq.layer(-8, -7, -1) + int_seq.layer(-5, -2, -1) + int_seq.layer(0.5, 1, -1) + int_seq.layer(3, 3.5, -1) + int_seq.layer(7, 9.5, -1) + return int_seq + + +def s4(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-11, 9, 1) + int_seq.layer(-9.5, -8, -1) + int_seq.layer(-7.5, -7, -1) + int_seq.layer(0, 3, -1) + int_seq.layer(6, 6.5, -1) + int_seq.layer(7, 8.5, -1) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +@pytest.mark.parametrize( + "stairs_instance, bounds, cuts", + itertools.product( + [s1(), s2(), s3(), s4()], + [(3, 4), (0, 10), (-10, 30), (-5, -1)], + ["unit", (0, 2.5, 4, 4.5, 7)], + ), +) +def test_hist_left_closed(stairs_instance, bounds, cuts): + def make_expected_result(interval_index, lower, upper): + return pd.Series( + [ + ((stairs_instance >= i.left) * (stairs_instance < i.right)).agg( + "mean", (lower, upper) + ) + for i in interval_index + ], + index=interval_index, + dtype="float64", + ) + + hist = stairs_instance.clip(*bounds).hist(bins=cuts, stat="probability") + expected = make_expected_result(hist.index, *bounds) + assert (hist.apply(round, 5) == expected.apply(round, 5)).all(), f"{bounds}, {cuts}" + + +@pytest.mark.parametrize( + "stairs_instance, bounds, cuts", + itertools.product( + [s1(), s2(), s3(), s4()], + [(3, 4), (0, 10), (-10, 30), (-5, -1)], + ["unit", (0, 2.5, 4, 4.5, 7)], + ), +) +def test_hist_right_closed(stairs_instance, bounds, cuts): + def make_expected_result(interval_index, lower, upper): + return pd.Series( + [ + ((stairs_instance > i.left) * (stairs_instance <= i.right)).agg( + "mean", (lower, upper) + ) + for i in interval_index + ], + index=interval_index, + dtype="float64", + ) + + hist = stairs_instance.clip(*bounds).hist( + bins=cuts, closed="right", stat="probability" + ) + expected = make_expected_result(hist.index, *bounds) + assert (hist.apply(round, 5) == expected.apply(round, 5)).all(), f"{bounds}, {cuts}" + + +@pytest.mark.parametrize( + "stairs_instance, bounds, closed", + itertools.product( + [s1(), s2(), s3(), s4()], + [(3, 4), (0, 10), (-10, 30), (-5, -1)], + ["left", "right"], + ), +) +def test_hist_default_bins(stairs_instance, bounds, closed): + # really testing the default binning process here + hist = stairs_instance.clip(*bounds).hist(closed=closed, stat="probability") + assert abs(hist.sum() - 1) < 0.000001 + + +def test_value_sums(s1_fix): + pd.testing.assert_series_equal( + s1_fix.value_sums(), + pd.Series({-1.75: 5, -0.5: 4, 0.25: 2, 2.0: 1, 2.75: 2}), + check_names=False, + check_index_type=False, + ) + + +def test_hist_frequency(s1_fix): + index = pd.IntervalIndex.from_breaks([-2, 0, 2, 3], closed="left") + pd.testing.assert_series_equal( + s1_fix.hist(bins=[-2, 0, 2, 3], stat="frequency"), + pd.Series([4.5, 1, 3], index=index), + check_names=False, + check_index_type=False, + ) + + +def test_hist_density(s1_fix): + index = pd.IntervalIndex.from_breaks([-2, 0, 2, 3], closed="left") + pd.testing.assert_series_equal( + s1_fix.hist(bins=[-2, 0, 2, 3], stat="density"), + pd.Series([0.36, 0.08, 0.12], index=index), + check_names=False, + check_index_type=False, + ) + + +def test_hist_probability(s1_fix): + index = pd.IntervalIndex.from_breaks([-2, 0, 2, 3], closed="left") + pd.testing.assert_series_equal( + s1_fix.hist(bins=[-2, 0, 2, 3], stat="probability"), + pd.Series([0.642857, 0.142857, 0.214286], index=index), + check_names=False, + check_index_type=False, + ) + + +def test_quantiles(s1_fix): + assert (s1_fix.quantiles(4) == np.array([-1.75, -0.5, 0.25])).all() + + +def test_fractile(s1_fix): + assert list(map(s1().fractile, (0.25, 0.5, 0.75))) == [ + -1.75, + -0.5, + 0.25, + ] diff --git a/tests/test_floats/test_floats_logical.py b/tests/test_floats/test_floats_logical.py new file mode 100644 index 0000000..f222bfa --- /dev/null +++ b/tests/test_floats/test_floats_logical.py @@ -0,0 +1,253 @@ +import itertools +import operator + +import numpy as np +import pandas as pd +import pytest + +import staircase.test_data as test_data +from staircase import Stairs + + +def _expand_interval_definition(start, end=None, value=1): + return start, end, value + + +def _compare_iterables(it1, it2): + it1 = [i for i in it1 if i is not None] + it2 = [i for i in it2 if i is not None] + if len(it2) != len(it1): + return False + for e1, e2 in zip(it1, it2): + if e1 != e2: + return False + return True + + +def s1(closed="left"): + int_seq1 = Stairs(initial_value=0, closed=closed) + int_seq1.layer(1, 10, 2) + int_seq1.layer(-4, 5, -1.75) + int_seq1.layer(3, 5, 2.5) + int_seq1.layer(6, 7, -2.5) + int_seq1.layer(7, 10, -2.5) + return int_seq1 + + +def s2(): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer(1, 7, -2.5) + int_seq2.layer(8, 10, 5) + int_seq2.layer(2, 5, 4.5) + int_seq2.layer(2.5, 4, -2.5) + int_seq2.layer(-2, 1, -1.75) + return int_seq2 + + +def s3(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-10, 10, 1) + int_seq.layer(-8, -7, -1) + int_seq.layer(-5, -2, -1) + int_seq.layer(0.5, 1, -1) + int_seq.layer(3, 3.5, -1) + int_seq.layer(7, 9.5, -1) + return int_seq + + +def s4(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-11, 9, 1) + int_seq.layer(-9.5, -8, -1) + int_seq.layer(-7.5, -7, -1) + int_seq.layer(0, 3, -1) + int_seq.layer(6, 6.5, -1) + int_seq.layer(7, 8.5, -1) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +def test_logical_and_scalar_1(s3_fix): + assert (s3_fix & 1).identical(s3_fix) + + +def test_logical_rand_scalar_1(s3_fix): + assert (1 & s3_fix).identical(s3_fix) + + +def test_logical_and_scalar_2(s3_fix): + assert (s3_fix & 0).identical(0) + + +def test_logical_rand_scalar_2(s3_fix): + assert (0 & s3_fix).identical(0) + + +def test_logical_and_scalar_3(s3_fix): + assert (s3_fix & np.nan).identical(np.nan) + + +def test_logical_rand_scalar_3(s3_fix): + assert (np.nan & s3_fix).identical(np.nan) + + +def test_logical_or_scalar_1(s3_fix): + assert (s3_fix | 1).identical(1) + + +def test_logical_ror_scalar_1(s3_fix): + assert (1 | s3_fix).identical(1) + + +def test_logical_or_scalar_2(s3_fix): + assert (s3_fix | 0).identical(s3_fix) + + +def test_logical_ror_scalar_2(s3_fix): + assert (0 | s3_fix).identical(s3_fix) + + +def test_logical_or_scalar_3(s3_fix): + assert (s3_fix | np.nan).identical(np.nan) + + +def test_logical_ror_scalar_3(s3_fix): + assert (np.nan | s3_fix).identical(np.nan) + + +def test_logical_xor_scalar_1(s3_fix): + assert (s3_fix ^ 1).identical(~s3_fix) + + +def test_logical_rxor_scalar_1(s3_fix): + assert (1 ^ s3_fix).identical(~s3_fix) + + +def test_logical_xor_scalar_2(s3_fix): + assert (s3_fix ^ 0).identical(s3_fix) + + +def test_logical_rxor_scalar_2(s3_fix): + assert (0 ^ s3_fix).identical(s3_fix) + + +def test_logical_xor_scalar_3(s3_fix): + assert (s3_fix ^ np.nan).identical(np.nan) + + +def test_logical_rxor_scalar_3(s3_fix): + assert (np.nan ^ s3_fix).identical(np.nan) + + +def test_logical_xor_stairs_1(s3_fix): + assert (s3_fix ^ s3_fix).identical(0) + + +def test_logical_xor_stairs_2(s3_fix): + assert (s3_fix ^ ~s3_fix).identical(1) + + +def test_make_boolean(s2_fix): + int_seq = s2_fix + calc = int_seq.make_boolean() + expected = Stairs() + expected.layer(-2, 7, 1) + expected.layer(8, 10, 1) + assert calc.identical(expected), "Boolean calculation not what it should be" + assert expected.identical(calc), "Boolean calculation not what it should be" + + +def test_invert(s2_fix): + int_seq = s2_fix + calc = ~int_seq + expected = Stairs(initial_value=1) + expected.layer(-2, 7, -1) + expected.layer(8, 10, -1) + assert calc.identical(expected), "Invert calculation not what it should be" + assert expected.identical(calc), "Invert calculation not what it should be" + + +def test_and(s3_fix, s4_fix): + calc = s3_fix & s4_fix + expected = Stairs(initial_value=0) + expected.layer(-10, -9.5) + expected.layer(-7, -5) + expected.layer(-2, 0) + expected.layer(3.5, 6) + expected.layer(6.5, 7) + assert calc.identical(expected), "AND calculation not what it should be" + assert expected.identical(calc), "AND calculation not what it should be" + + +def test_or(s3_fix, s4_fix): + calc = s3_fix | s4_fix + expected = Stairs(initial_value=0) + expected.layer(-11, -7.5) + expected.layer(-7, 0.5) + expected.layer(1, 7) + expected.layer(8.5, 9) + expected.layer(9.5, 10) + assert calc.identical(expected), "OR calculation not what it should be" + assert expected.identical(calc), "OR calculation not what it should be" + + +@pytest.mark.parametrize( + "op", + [ + operator.and_, + operator.or_, + operator.xor, + ], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +@pytest.mark.parametrize( + "operands", + [("stairs", "stairs"), ("stairs", "scalar"), ("scalar", "stairs")], +) +def test_closed_binary_ops(op, closed, operands): + operand_dict = {"stairs": Stairs(closed=closed), "scalar": 1} + operand0 = operand_dict[operands[0]] + operand1 = operand_dict[operands[1]] + result = op(operand0, operand1) + assert result.closed == closed + + +@pytest.mark.parametrize( + "op", + [ + operator.and_, + operator.or_, + operator.xor, + ], +) +@pytest.mark.parametrize( + "nan_pos", + ["first", "second"], +) +def test_binary_ops_with_nan(s1_fix, op, nan_pos): + # GH109 + operands = (np.nan, s1_fix) if nan_pos == "first" else (s1_fix, np.nan) + result = op(*operands) + assert result._data is None, "wrong internal representation in resulting Stairs" diff --git a/tests/test_floats/test_floats_misc.py b/tests/test_floats/test_floats_misc.py new file mode 100644 index 0000000..43cbc0d --- /dev/null +++ b/tests/test_floats/test_floats_misc.py @@ -0,0 +1,255 @@ +import numpy as np +import pandas as pd +import pytest + +import staircase.test_data as test_data +from staircase import Stairs + + +def s1(closed="left"): + int_seq1 = Stairs(initial_value=0, closed=closed) + int_seq1.layer(1, 10, 2) + int_seq1.layer(-4, 5, -1.75) + int_seq1.layer(3, 5, 2.5) + int_seq1.layer(6, 7, -2.5) + int_seq1.layer(7, 10, -2.5) + return int_seq1 + + +def s2(): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer(1, 7, -2.5) + int_seq2.layer(8, 10, 5) + int_seq2.layer(2, 5, 4.5) + int_seq2.layer(2.5, 4, -2.5) + int_seq2.layer(-2, 1, -1.75) + return int_seq2 + + +def s3(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-10, 10, 1) + int_seq.layer(-8, -7, -1) + int_seq.layer(-5, -2, -1) + int_seq.layer(0.5, 1, -1) + int_seq.layer(3, 3.5, -1) + int_seq.layer(7, 9.5, -1) + return int_seq + + +def s4(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-11, 9, 1) + int_seq.layer(-9.5, -8, -1) + int_seq.layer(-7.5, -7, -1) + int_seq.layer(0, 3, -1) + int_seq.layer(6, 6.5, -1) + int_seq.layer(7, 8.5, -1) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) +def test_copy_and_equality(init_value): + int_seq = Stairs(initial_value=init_value) + int_seq_copy = int_seq.copy() + assert int_seq.identical(int_seq_copy) + assert int_seq_copy.identical(int_seq) + + +@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) +def test_deepcopy(init_value): + int_seq = Stairs(initial_value=init_value) + int_seq_copy = int_seq.copy() + int_seq_copy.layer(1, 2) + assert not int_seq.identical(int_seq_copy) + assert not int_seq_copy.identical(int_seq) + + +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +def test_copy_correct_closed_value(closed): + result = Stairs(closed=closed).copy() + assert result.closed == closed + + +def test_to_dataframe(s1_fix): + s1_fix.to_frame() + + +@pytest.mark.parametrize( + "kwargs, expected_index, expected_vals", + [ + ( + {"window": (-1, 1)}, + [-5, -3, 0, 2, 4, 5, 6, 7, 9, 11], + [0.0, -1.75, -1.75, 0.25, 2.75, 2.375, 0.75, -0.5, -0.5, 0.0], + ), + ( + {"window": (-2, 0)}, + [-4, -2, 1, 3, 5, 6, 7, 8, 10, 12], + [0.0, -1.75, -1.75, 0.25, 2.75, 2.375, 0.75, -0.5, -0.5, 0.0], + ), + ( + {"window": (-1, 1), "where": (0, 8)}, + [1, 2, 4, 5, 6, 7], + [-0.75, 0.25, 2.75, 2.375, 0.75, -0.5], + ), + ], +) +def test_s1_rolling_mean(s1_fix, kwargs, expected_index, expected_vals): + rm = s1_fix.rolling_mean(**kwargs) + assert list(rm.values) == expected_vals + assert list(rm.index) == expected_index + + +@pytest.mark.parametrize( + "kwargs", + [ + {}, + {"arrows": True, "style": "hlines"}, + {"arrows": False, "style": "hlines"}, + {"arrows": True, "style": "step"}, + {"arrows": False, "style": "step"}, + ], +) +def test_plot(s1_fix, kwargs): + s1_fix.plot(**kwargs) + + +def test_plot_trivial_1(): + Stairs().plot() + + +def test_plot_trivial_2(): + Stairs(initial_value=np.nan).plot() + + +def test_plot_ecdf(s1_fix): + s1_fix.plot.ecdf() + + +def test_plot_bad_backend(s1_fix): + with pytest.raises(ValueError): + s1_fix.plot(backend="") + + +def test_plot_ecdf_bad_backend(s1_fix): + with pytest.raises(ValueError): + s1_fix.plot.ecdf(backend="") + + +def test_diff(s1_fix): + assert pd.Series.equals( + s1_fix.diff(1).step_changes, + pd.Series( + { + -4: -1.75, + -3: 1.75, + 1: 2, + 2: -2, + 3: 2.5, + 4: -2.5, + 5: -0.75, + 6: -1.75, + 7: 2.5, + 10: 0.5, + 11: -0.5, + } + ), + ) + + +def test_str(s1_fix): + assert str(s1_fix) is not None + assert str(s1_fix) != "" + + +def test_repr(s1_fix): + assert repr(s1_fix) is not None + assert repr(s1_fix) != "" + + +def test_make_test_data(): + assert type(test_data.make_test_data()) == pd.DataFrame + + +def test_pipe(s1_fix): + def is_stairs(s): + return isinstance(s, Stairs) + + assert s1().pipe(is_stairs) + + +def test_step_changes(s1_fix): + pd.testing.assert_series_equal( + s1_fix.step_changes, + pd.Series({-4: -1.75, 1: 2.0, 3: 2.5, 5: -0.75, 6: -2.5, 10: 0.5}), + check_names=False, + check_index_type=False, + ) + + +def test_step_values(s1_fix): + pd.testing.assert_series_equal( + s1_fix.step_values, + pd.Series({-4: -1.75, 1: 0.25, 3: 2.75, 5: 2.0, 6: -0.5, 10: 0.0}), + check_names=False, + check_index_type=False, + ) + + +def test_step_points(s1_fix): + assert list(s1_fix.step_points) == [-4, 1, 3, 5, 6, 10] + + +def test_step_changes_stepless(): + pd.testing.assert_series_equal( + Stairs().step_changes, + pd.Series([], dtype="float64"), + check_names=False, + check_index_type=False, + ) + + +def test_step_values_stepless(): + pd.testing.assert_series_equal( + Stairs().step_values, + pd.Series([], dtype="float64"), + check_names=False, + check_index_type=False, + ) + + +def test_step_points_stepless(): + assert list(Stairs().step_points) == [] + + +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +def test_shift_correct_closed_value(closed): + result = Stairs(start=1, end=2, closed=closed).shift(1) + assert result.closed == closed diff --git a/tests/test_floats/test_floats_relational.py b/tests/test_floats/test_floats_relational.py new file mode 100644 index 0000000..382d95d --- /dev/null +++ b/tests/test_floats/test_floats_relational.py @@ -0,0 +1,190 @@ +import operator + +import numpy as np +import pytest + +from staircase import Stairs + + +def s1(closed="left"): + int_seq1 = Stairs(initial_value=0, closed=closed) + int_seq1.layer(1, 10, 2) + int_seq1.layer(-4, 5, -1.75) + int_seq1.layer(3, 5, 2.5) + int_seq1.layer(6, 7, -2.5) + int_seq1.layer(7, 10, -2.5) + return int_seq1 + + +def s2(): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer(1, 7, -2.5) + int_seq2.layer(8, 10, 5) + int_seq2.layer(2, 5, 4.5) + int_seq2.layer(2.5, 4, -2.5) + int_seq2.layer(-2, 1, -1.75) + return int_seq2 + + +def s3(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-10, 10, 1) + int_seq.layer(-8, -7, -1) + int_seq.layer(-5, -2, -1) + int_seq.layer(0.5, 1, -1) + int_seq.layer(3, 3.5, -1) + int_seq.layer(7, 9.5, -1) + return int_seq + + +def s4(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-11, 9, 1) + int_seq.layer(-9.5, -8, -1) + int_seq.layer(-7.5, -7, -1) + int_seq.layer(0, 3, -1) + int_seq.layer(6, 6.5, -1) + int_seq.layer(7, 8.5, -1) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +def test_lt(s1_fix, s2_fix): + calc = s1_fix < s2_fix + expected = Stairs(initial_value=0) + expected.layer(-4, -2) + expected.layer(2, 2.5) + expected.layer(7, 10) + assert calc.identical(expected), "LT calculation not what it should be" + assert expected.identical(calc), "LT calculation not what it should be" + + +def test_gt(s1_fix, s2_fix): + calc = s1_fix > s2_fix + expected = Stairs(initial_value=0) + expected.layer(1, 2) + expected.layer(2.5, 7) + assert calc.identical(expected), "GT calculation not what it should be" + assert expected.identical(calc), "GT calculation not what it should be" + + +def test_le(s1_fix, s2_fix): + calc = s1_fix <= s2_fix + expected = Stairs(initial_value=1) + expected.layer(1, 2, -1) + expected.layer(2.5, 7, -1) + assert calc.identical(expected), "LE calculation not what it should be" + assert expected.identical(calc), "LE calculation not what it should be" + + +def test_ge(s1_fix, s2_fix): + calc = s1_fix >= s2_fix + expected = Stairs(initial_value=1) + expected.layer(-4, -2, -1) + expected.layer(2, 2.5, -1) + expected.layer(7, 10, -1) + assert calc.identical(expected), "GE calculation not what it should be" + assert expected.identical(calc), "GE calculation not what it should be" + + +def test_eq_1(s1_fix, s2_fix): + calc = s1_fix == s2_fix + expected = Stairs(initial_value=1) + expected.layer(-4, -2, -1) + expected.layer(1, 10, -1) + assert calc.identical(expected), "EQ calculation not what it should be" + assert expected.identical(calc), "EQ calculation not what it should be" + + +def test_eq_2(s1_fix, s2_fix): + calc = s1_fix == s2_fix + expected = Stairs(initial_value=1) + expected.layer(-4, -2, -1) + expected.layer(1, 10, -1) + assert calc.identical(expected), "EQ calculation not what it should be" + assert expected.identical(calc), "EQ calculation not what it should be" + + +def test_ne(s1_fix, s2_fix): + calc = s1_fix != s2_fix + expected = Stairs(initial_value=0) + expected.layer(-4, -2, 1) + expected.layer(1, 10, 1) + assert calc.identical(expected), "NOT EQUAL calculation not what it should be" + assert expected.identical(calc), "NOT EQUAL calculation not what it should be" + + +def test_eq_3(): + assert Stairs(initial_value=3) == 3 + + +def test_ne_3(s1_fix): + assert s1_fix != 3 + + +@pytest.mark.parametrize( + "op", + [ + operator.eq, + operator.ne, + operator.lt, + operator.gt, + operator.le, + operator.ge, + ], +) +@pytest.mark.parametrize( + "closed", + ["left", "right"], +) +@pytest.mark.parametrize( + "operands", + [("stairs", "stairs"), ("stairs", "scalar"), ("scalar", "stairs")], +) +def test_closed_binary_ops(op, closed, operands): + operand_dict = {"stairs": Stairs(closed=closed), "scalar": 1} + operand0 = operand_dict[operands[0]] + operand1 = operand_dict[operands[1]] + result = op(operand0, operand1) + assert result.closed == closed + + +@pytest.mark.parametrize( + "op", + [ + operator.eq, + operator.ne, + operator.lt, + operator.gt, + operator.le, + operator.ge, + ], +) +@pytest.mark.parametrize( + "nan_pos", + ["first", "second"], +) +def test_binary_ops_with_nan(s1_fix, op, nan_pos): + # GH109 + operands = (np.nan, s1_fix) if nan_pos == "first" else (s1_fix, np.nan) + result = op(*operands) + assert result._data is None, "wrong internal representation in resulting Stairs" diff --git a/tests/test_floats/test_floats_sample.py b/tests/test_floats/test_floats_sample.py new file mode 100644 index 0000000..153d056 --- /dev/null +++ b/tests/test_floats/test_floats_sample.py @@ -0,0 +1,90 @@ +import numpy as np +import pytest + +from staircase import Stairs + + +def s1(closed="left"): + int_seq1 = Stairs(initial_value=0, closed=closed) + int_seq1.layer(1, 10, 2) + int_seq1.layer(-4, 5, -1.75) + int_seq1.layer(3, 5, 2.5) + int_seq1.layer(6, 7, -2.5) + int_seq1.layer(7, 10, -2.5) + return int_seq1 + + +def s2(): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer(1, 7, -2.5) + int_seq2.layer(8, 10, 5) + int_seq2.layer(2, 5, 4.5) + int_seq2.layer(2.5, 4, -2.5) + int_seq2.layer(-2, 1, -1.75) + return int_seq2 + + +def s3(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-10, 10, 1) + int_seq.layer(-8, -7, -1) + int_seq.layer(-5, -2, -1) + int_seq.layer(0.5, 1, -1) + int_seq.layer(3, 3.5, -1) + int_seq.layer(7, 9.5, -1) + return int_seq + + +def s4(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-11, 9, 1) + int_seq.layer(-9.5, -8, -1) + int_seq.layer(-7.5, -7, -1) + int_seq.layer(0, 3, -1) + int_seq.layer(6, 6.5, -1) + int_seq.layer(7, 8.5, -1) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +@pytest.mark.parametrize( + "x, kwargs, expected_val", + [ + ( + [-4, -2, 1, 3], + {"side": "right"}, + np.array([-1.75, -1.75, 0.25, 2.75]), + ), + ( + [-4, -2, 1, 3], + {"side": "right"}, + np.array([-1.75, -1.75, 0.25, 2.75]), + ), + ( + [-4, -2, 1, 3], + {"side": "left"}, + np.array([0.0, -1.75, -1.75, 0.25]), + ), + ], +) +def test_s1_limit(s1_fix, x, kwargs, expected_val): + assert np.array_equal(s1_fix.limit(x, **kwargs), expected_val) diff --git a/tests/test_floats/test_floats_slicing.py b/tests/test_floats/test_floats_slicing.py new file mode 100644 index 0000000..d1b8782 --- /dev/null +++ b/tests/test_floats/test_floats_slicing.py @@ -0,0 +1,310 @@ +import numpy as np +import pandas as pd +import pytest + +from staircase import Stairs + + +def s1(closed="left"): + int_seq1 = Stairs(initial_value=0, closed=closed) + int_seq1.layer(1, 10, 2) + int_seq1.layer(-4, 5, -1.75) + int_seq1.layer(3, 5, 2.5) + int_seq1.layer(6, 7, -2.5) + int_seq1.layer(7, 10, -2.5) + return int_seq1 + + +def s2(): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer(1, 7, -2.5) + int_seq2.layer(8, 10, 5) + int_seq2.layer(2, 5, 4.5) + int_seq2.layer(2.5, 4, -2.5) + int_seq2.layer(-2, 1, -1.75) + return int_seq2 + + +def s3(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-10, 10, 1) + int_seq.layer(-8, -7, -1) + int_seq.layer(-5, -2, -1) + int_seq.layer(0.5, 1, -1) + int_seq.layer(3, 3.5, -1) + int_seq.layer(7, 9.5, -1) + return int_seq + + +def s4(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-11, 9, 1) + int_seq.layer(-9.5, -8, -1) + int_seq.layer(-7.5, -7, -1) + int_seq.layer(0, 3, -1) + int_seq.layer(6, 6.5, -1) + int_seq.layer(7, 8.5, -1) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +@pytest.mark.parametrize( + "x, kwargs, expected_val", + [ + ( + [-4, -2, 1, 3], + {"aggfunc": "mean", "window": (-0.5, 0.5)}, + np.array([-0.875, -1.75, -0.75, 1.5]), + ), + ( + [-4, -2, 1, 3], + {"aggfunc": "mean", "window": (-1, 0)}, + np.array([0.0, -1.75, -1.75, 0.25]), + ), + ( + [-4, -2, 1, 3], + {"aggfunc": "mean", "window": (0, 1)}, + np.array([-1.75, -1.75, 0.25, 2.75]), + ), + ], +) +def test_s1_agg_mean(s1_fix, x, kwargs, expected_val): + window = kwargs["window"] + x = np.array(x) + ii = pd.IntervalIndex.from_arrays(x + window[0], x + window[1]) + assert np.array_equal(s1_fix.slice(ii).mean().values, expected_val) + + +@pytest.mark.parametrize( + "closed, x, kwargs, expected_val", + [ + ( + "left", + [0, 2, 7], + {"aggfunc": "max", "window": (-1, 1)}, + np.array([-1.75, 0.25, -0.5]), + ), + ( + "right", + [0, 2, 7], + {"aggfunc": "max", "window": (-1, 1), "closed": "left"}, + np.array([-1.75, 0.25, 2.0]), + ), + ( + "left", + [0, 2, 7], + {"aggfunc": "max", "window": (-1, 1), "closed": "right"}, + np.array([0.25, 2.75, -0.5]), + ), + ( + "right", + [0, 2, 7], + {"aggfunc": "max", "window": (-1, 1), "closed": "right"}, + np.array([-1.75, 0.25, -0.5]), + ), + ], +) +def test_s1_agg_max(closed, x, kwargs, expected_val): + window = kwargs["window"] + x = np.array(x) + ii = pd.IntervalIndex.from_arrays( + x + window[0], x + window[1], closed=kwargs.get("closed", "left") + ) + assert np.array_equal(s1(closed=closed).slice(ii).max().values, expected_val) + + +def test_slicing_mean(s1_fix): + pd.testing.assert_series_equal( + s1_fix.slice(range(-4, 11, 2)).mean(), + pd.Series( + { + pd.Interval(-4, -2, closed="left"): -1.75, + pd.Interval(-2, 0, closed="left"): -1.75, + pd.Interval(0, 2, closed="left"): -0.75, + pd.Interval(2, 4, closed="left"): 1.5, + pd.Interval(4, 6, closed="left"): 2.375, + pd.Interval(6, 8, closed="left"): -0.5, + pd.Interval(8, 10, closed="left"): -0.5, + } + ), + check_names=False, + check_index_type=False, + ) + + +def test_slicing_max(s1_fix): + pd.testing.assert_series_equal( + s1_fix.slice(range(-4, 11, 2)).max(), + pd.Series( + { + pd.Interval(-4, -2, closed="left"): -1.75, + pd.Interval(-2, 0, closed="left"): -1.75, + pd.Interval(0, 2, closed="left"): 0.25, + pd.Interval(2, 4, closed="left"): 2.75, + pd.Interval(4, 6, closed="left"): 2.75, + pd.Interval(6, 8, closed="left"): -0.5, + pd.Interval(8, 10, closed="left"): -0.5, + } + ), + check_names=False, + check_index_type=False, + ) + + +def test_slicing_min(s1_fix): + pd.testing.assert_series_equal( + s1_fix.slice(range(-4, 11, 2)).min(), + pd.Series( + { + pd.Interval(-4, -2, closed="left"): -1.75, + pd.Interval(-2, 0, closed="left"): -1.75, + pd.Interval(0, 2, closed="left"): -1.75, + pd.Interval(2, 4, closed="left"): 0.25, + pd.Interval(4, 6, closed="left"): 2.0, + pd.Interval(6, 8, closed="left"): -0.5, + pd.Interval(8, 10, closed="left"): -0.5, + } + ), + check_names=False, + check_index_type=False, + ) + + +def test_slicing_mode(s1_fix): + pd.testing.assert_series_equal( + s1_fix.slice(range(-4, 11, 2)).mode(), + pd.Series( + { + pd.Interval(-4, -2, closed="left"): -1.75, + pd.Interval(-2, 0, closed="left"): -1.75, + pd.Interval(0, 2, closed="left"): -1.75, + pd.Interval(2, 4, closed="left"): 0.25, + pd.Interval(4, 6, closed="left"): 2.0, + pd.Interval(6, 8, closed="left"): -0.5, + pd.Interval(8, 10, closed="left"): -0.5, + } + ), + check_names=False, + check_index_type=False, + ) + + +def test_slicing_median(s1_fix): + pd.testing.assert_series_equal( + s1_fix.slice(range(-4, 11, 2)).median(), + pd.Series( + { + pd.Interval(-4, -2, closed="left"): -1.75, + pd.Interval(-2, 0, closed="left"): -1.75, + pd.Interval(0, 2, closed="left"): -0.75, + pd.Interval(2, 4, closed="left"): 1.5, + pd.Interval(4, 6, closed="left"): 2.375, + pd.Interval(6, 8, closed="left"): -0.5, + pd.Interval(8, 10, closed="left"): -0.5, + } + ), + check_names=False, + check_index_type=False, + ) + + +def test_slicing_agg_min(s1_fix): + pd.testing.assert_series_equal( + s1_fix.slice(range(-4, 11, 2)).agg("min")["min"], + pd.Series( + { + pd.Interval(-4, -2, closed="left"): -1.75, + pd.Interval(-2, 0, closed="left"): -1.75, + pd.Interval(0, 2, closed="left"): -1.75, + pd.Interval(2, 4, closed="left"): 0.25, + pd.Interval(4, 6, closed="left"): 2.0, + pd.Interval(6, 8, closed="left"): -0.5, + pd.Interval(8, 10, closed="left"): -0.5, + } + ), + check_names=False, + check_index_type=False, + ) + + +def test_slicing_apply_min(s1_fix): + pd.testing.assert_series_equal( + s1_fix.slice(range(-4, 11, 2)).apply(Stairs.min), + pd.Series( + { + pd.Interval(-4, -2, closed="left"): -1.75, + pd.Interval(-2, 0, closed="left"): -1.75, + pd.Interval(0, 2, closed="left"): -1.75, + pd.Interval(2, 4, closed="left"): 0.25, + pd.Interval(4, 6, closed="left"): 2.0, + pd.Interval(6, 8, closed="left"): -0.5, + pd.Interval(8, 10, closed="left"): -0.5, + } + ), + check_names=False, + check_index_type=False, + ) + + +def test_slicing_agg_min_max(s1_fix): + result = s1_fix.slice(range(-4, 11, 2)).agg(["min", "max"]) + pd.testing.assert_series_equal( + result["min"], + pd.Series( + { + pd.Interval(-4, -2, closed="left"): -1.75, + pd.Interval(-2, 0, closed="left"): -1.75, + pd.Interval(0, 2, closed="left"): -1.75, + pd.Interval(2, 4, closed="left"): 0.25, + pd.Interval(4, 6, closed="left"): 2.0, + pd.Interval(6, 8, closed="left"): -0.5, + pd.Interval(8, 10, closed="left"): -0.5, + } + ), + check_names=False, + check_index_type=False, + ) + pd.testing.assert_series_equal( + result["max"], + pd.Series( + { + pd.Interval(-4, -2, closed="left"): -1.75, + pd.Interval(-2, 0, closed="left"): -1.75, + pd.Interval(0, 2, closed="left"): 0.25, + pd.Interval(2, 4, closed="left"): 2.75, + pd.Interval(4, 6, closed="left"): 2.75, + pd.Interval(6, 8, closed="left"): -0.5, + pd.Interval(8, 10, closed="left"): -0.5, + } + ), + check_names=False, + check_index_type=False, + ) + + +def test_slicing_resample_mean(s1_fix): + pd.testing.assert_series_equal( + s1_fix.slice(range(0, 7, 2)).resample("mean").step_values, + pd.Series({-4: -1.75, 0: -0.75, 2: 1.5, 4: 2.375, 6: -0.5, 10: 0.0}), + check_names=False, + check_index_type=False, + ) diff --git a/tests/test_floats/test_floats_stats.py b/tests/test_floats/test_floats_stats.py new file mode 100644 index 0000000..540f375 --- /dev/null +++ b/tests/test_floats/test_floats_stats.py @@ -0,0 +1,384 @@ +import numpy as np +import pytest + +from staircase import Stairs + + +def s1(closed="left"): + int_seq1 = Stairs(initial_value=0, closed=closed) + int_seq1.layer(1, 10, 2) + int_seq1.layer(-4, 5, -1.75) + int_seq1.layer(3, 5, 2.5) + int_seq1.layer(6, 7, -2.5) + int_seq1.layer(7, 10, -2.5) + return int_seq1 + + +def s2(): + int_seq2 = Stairs(initial_value=0) + int_seq2.layer(1, 7, -2.5) + int_seq2.layer(8, 10, 5) + int_seq2.layer(2, 5, 4.5) + int_seq2.layer(2.5, 4, -2.5) + int_seq2.layer(-2, 1, -1.75) + return int_seq2 + + +def s3(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-10, 10, 1) + int_seq.layer(-8, -7, -1) + int_seq.layer(-5, -2, -1) + int_seq.layer(0.5, 1, -1) + int_seq.layer(3, 3.5, -1) + int_seq.layer(7, 9.5, -1) + return int_seq + + +def s4(): # boolean + int_seq = Stairs(initial_value=0) + int_seq.layer(-11, 9, 1) + int_seq.layer(-9.5, -8, -1) + int_seq.layer(-7.5, -7, -1) + int_seq.layer(0, 3, -1) + int_seq.layer(6, 6.5, -1) + int_seq.layer(7, 8.5, -1) + return int_seq + + +@pytest.fixture +def s1_fix(): + return s1() + + +@pytest.fixture +def s2_fix(): + return s2() + + +@pytest.fixture +def s3_fix(): + return s3() + + +@pytest.fixture +def s4_fix(): + return s4() + + +@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) +def test_base_integral_0_2(init_value): + int_seq = Stairs(initial_value=init_value) + assert int_seq.agg("integral", (0, 2)) == 2 * init_value + + +@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) +def test_base_integral_neg1_1(init_value): + int_seq = Stairs(initial_value=init_value) + assert int_seq.agg("integral", (-1, 1)) == 2 * init_value + + +@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) +def test_base_integral_neg2_0(init_value): + int_seq = Stairs(initial_value=init_value) + assert int_seq.agg("integral", (-2, 0)) == 2 * init_value + + +@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) +def test_base_integral_point5_1(init_value): + int_seq = Stairs(initial_value=init_value) + assert int_seq.agg("integral", (0.5, 1)) == 0.5 * init_value + + +def test_integral1(s1_fix, s2_fix): + assert s1_fix.integral() == -2.75 + assert s2_fix.integral() == -0.5 + + +def test_integral2(s1_fix, s2_fix): + assert s1_fix.agg("integral", (-1, 5.5)) == 3.5 + assert s2_fix.agg("integral", (-1, 5.5)) == -5 + + +def test_mean1(s1_fix, s2_fix): + assert abs(s1_fix.mean() - -0.19642857) < 0.000001 + assert abs(s2_fix.mean() - -0.04166666) < 0.000001 + + +def test_mean2(s1_fix, s2_fix): + assert abs(s1_fix.agg("mean", (2, 8)) - 1.125) < 0.000001 + assert abs(s2_fix.agg("mean", (2, 8)) - -0.45833333) < 0.000001 + + +def test_integral_0(): + assert Stairs(initial_value=0).layer(None, 0).integral() is np.nan + + +def test_mean_nan(): + assert Stairs(initial_value=0).layer(None, 0).mean() is np.nan + + +# np.var(st1(np.linspace(-4,10, 10000000))) = 2.501594244387741 +# np.var(st1(np.linspace(-5,10, 10000000))) = 2.3372686165530117 +# np.var(st1(np.linspace(1,12, 10000000))) = 1.5433884747933315 + + +@pytest.mark.parametrize( + "bounds, expected", + [ + ((), 2.501594244387741), + (((-5, 10),), 2.3372686165530117), + (((1, 12),), 1.5433884747933315), + ], +) +def test_s1_var(bounds, expected): + assert np.isclose(s1().agg("var", *bounds), expected, atol=0.0001) + + +# np.var(st2(np.linspace(-2, 10, 10000000))) = 7.024303861110942 +# np.var(st2(np.linspace(-3, 7.5, 10000000))) = 2.2678568437499633 +# np.var(st2(np.linspace(0, 14, 10000000))) = 5.538902194132663 + + +@pytest.mark.parametrize( + "bounds, expected", + [ + ((), 7.024303861110942), + (((-3, 7.5),), 2.2678568437499633), + (((0, 14),), 5.538902194132663), + ], +) +def test_s2_var(bounds, expected): + assert np.isclose(s2().agg("var", *bounds), expected, atol=0.0001) + + +# np.std(st1(np.linspace(-4,10, 10000000))) = 1.5816428940780978 +# np.std(st1(np.linspace(-5,10, 10000000))) = 1.528797568034358 +# np.std(st1(np.linspace(1,12, 10000000))) = 1.242331869829206 + + +@pytest.mark.parametrize( + "bounds, expected", + [ + ((), 1.5816428940780978), + (((-5, 10),), 1.528797568034358), + (((1, 12),), 1.242331869829206), + ], +) +def test_s1_std(bounds, expected): + assert np.isclose(s1().agg("std", *bounds), expected, atol=0.0001) + + +# np.std(st2(np.linspace(-2, 10, 10000000))) = 2.650340329299417 +# np.std(st2(np.linspace(-3, 7.5, 10000000))) = 1.5059405179986238 +# np.std(st2(np.linspace(0, 14, 10000000))) = 2.3534872411238315 + + +@pytest.mark.parametrize( + "bounds, expected", + [ + ((), 2.650340329299417), + (((-3, 7.5),), 1.5059405179986238), + (((0, 14),), 2.3534872411238315), + ], +) +def test_s2_std(bounds, expected): + assert np.isclose(s2().agg("std", *bounds), expected, atol=0.0001) + + +# # np.cov(st1(pts[:-100000]), st1(pts[100000:]))[0,1] = 1.9386094481108465 +# # np.cov(st1(np.linspace(-4, 8, 12*100000 + 1)), st1(np.linspace(-2, 10, 12*100000 + 1)))[0,1] = 1.1184896017794723 +# # np.cov(st1(np.linspace(-4, 8, 12*100000 + 1)), st1.shift(-2)(np.linspace(-4, 8, 12*100000 + 1)))[0,1] = 1.1184896017794723 + + +@pytest.mark.parametrize( + "kwargs, expected", + [ + ({"where": (-4, 10), "lag": 1}, 1.9386094481108465), + ({"where": (-4, 10), "lag": 2}, 1.1184896017794723), + ({"where": (-4, 8), "lag": 2, "clip": "post"}, 1.1184896017794723), + ], +) +def test_s1_autocov(kwargs, expected): + assert np.isclose(s1().cov(s1(), **kwargs), expected, atol=0.00001) + + +# # np.cov(st2(np.linspace(-2, 9, 11*100000 + 1)), st2(np.linspace(-1, 10, 11*100000 + 1)))[0,1 = 3.1022721590913256 +# # np.cov(st2(np.linspace(0, 6, 12*100000 + 1)), st2(np.linspace(2, 8, 12*100000 + 1)))[0,1] = -0.7291746267294938 +# # np.cov(st2(np.linspace(0, 6, 12*100000 + 1)), st2.shift(-2)(np.linspace(0, 6, 12*100000 + 1)))[0,1] = -0.7291746267294938 + + +@pytest.mark.parametrize( + "kwargs, expected", + [ + ({"where": (-2, 10), "lag": 1}, 3.1022721590913256), + ({"where": (0, 8), "lag": 2}, -0.7291746267294938), + ({"where": (0, 6), "lag": 2, "clip": "post"}, -0.7291746267294938), + ], +) +def test_s2_autocov(kwargs, expected): + assert np.isclose(s2().cov(s2(), **kwargs), expected, atol=0.00001) + + +# # np.cov(st1(np.linspace(-2, 9, 11*100000 + 1)), st2(np.linspace(-1, 10, 11*100000 + 1)))[0,1 = -0.08677679611199672 +# # np.cov(st1(np.linspace(0, 6, 12*100000 + 1)), st2(np.linspace(2, 8, 12*100000 + 1)))[0,1] = -1.970493123547197 +# # np.cov(st1(np.linspace(0, 6, 12*100000 + 1)), st2.shift(-2)(np.linspace(0, 6, 12*100000 + 1)))[0,1] = -1.970493123547197 + + +@pytest.mark.parametrize( + "kwargs, expected", + [ + ({"where": (-2, 10), "lag": 1}, -0.08677679611199672), + ({"where": (0, 8), "lag": 2}, -1.970493123547197), + ({"where": (0, 6), "lag": 2, "clip": "post"}, -1.970493123547197), + ], +) +def test_crosscov(kwargs, expected): + assert np.isclose(s1().cov(s2(), **kwargs), expected, atol=0.00001) + + +# # np.corrcoef(st1(pts[:-100000]), st1(pts[100000:]))[0,1] = 0.6927353407369307 +# # np.corrcoef(st1(np.linspace(-4, 8, 12*100000 + 1)), st1(np.linspace(-2, 10, 12*100000 + 1)))[0,1] = -0.2147502741669856 +# # np.corrcoef(st1(np.linspace(-4, 8, 12*100000 + 1)), st1.shift(-2)(np.linspace(-4, 8, 12*100000 + 1)))[0,1] = -0.2147502741669856 + + +@pytest.mark.parametrize( + "kwargs, expected", + [ + ({"where": (-2, 10), "lag": 1}, 0.6927353407369307), + ({"where": (0, 8), "lag": 2}, -0.2147502741669856), + ({"where": (0, 6), "lag": 2, "clip": "post"}, -0.2147502741669856), + ], +) +def test_s1_autocorr(kwargs, expected): + assert np.isclose(s1().corr(s1(), **kwargs), expected, atol=0.00001) + + +# # np.corrcoef(st2(pts[:-100000]), st2(pts[100000:]))[0,1] = 0.5038199912440895 +# # np.corrcoef(st2(np.linspace(-4, 8, 12*100000 + 1)), st2(np.linspace(-2, 10, 12*100000 + 1)))[0,1] = -0.2419504099129966 +# # np.corrcoef(st2(np.linspace(-4, 8, 12*100000 + 1)), st2.shift(-2)(np.linspace(-4, 8, 12*100000 + 1)))[0,1] = -0.2419504099129966 + + +@pytest.mark.parametrize( + "kwargs, expected", + [ + ({"where": (-2, 10), "lag": 1}, 0.5038199912440895), + ({"where": (0, 8), "lag": 2}, -0.2419504099129966), + ({"where": (0, 6), "lag": 2, "clip": "post"}, -0.2419504099129966), + ], +) +def test_s2_autocorr(kwargs, expected): + assert np.isclose(s2().corr(s2(), **kwargs), expected, atol=0.00001) + + +# # np.corrcoef(st1(pts[:-100000]), st2(pts[100000:]))[0,1] = -0.01966642657198049 +# # np.corrcoef(st1(np.linspace(-4, 8, 12*100000 + 1)), st2(np.linspace(-2, 10, 12*100000 + 1)))[0,1] = -0.7086484036832666 +# # np.corrcoef(st1(np.linspace(-4, 8, 12*100000 + 1)), st2.shift(-2)(np.linspace(-4, 8, 12*100000 + 1)))[0,1] = -0.7086484036832666 + + +@pytest.mark.parametrize( + "kwargs, expected", + [ + ({"where": (-2, 10), "lag": 1}, -0.01966642657198049), + ({"where": (0, 8), "lag": 2}, -0.7086484036832666), + ({"where": (0, 6), "lag": 2, "clip": "post"}, -0.7086484036832666), + ], +) +def test_crosscorr(kwargs, expected): + assert np.isclose(s1().corr(s2(), **kwargs), expected, atol=0.00001) + + +@pytest.mark.parametrize( + "closed, kwargs, expected_val", + [ + ( + "left", + {}, + -1.75, + ), + ( + "left", + {"where": (1, 6)}, + 0.25, + ), + ( + "right", + {"where": (1, 6), "closed": "left"}, + -1.75, + ), + ( + "left", + {"where": (1, 6), "closed": "right"}, + -0.5, + ), + ], +) +def test_s1_min(closed, kwargs, expected_val): + from staircase.core import stats + + assert stats.min(s1(closed=closed), **kwargs) == expected_val + + +@pytest.mark.parametrize( + "closed, kwargs, expected_val", + [ + ( + "left", + {}, + 2.75, + ), + ( + "left", + {"where": (-4, 1)}, + -1.75, + ), + ( + "right", + {"where": (-4, 1), "closed": "left"}, + 0.0, + ), + ( + "left", + {"where": (-4, 1), "closed": "right"}, + 0.25, + ), + ], +) +def test_s1_max(closed, kwargs, expected_val): + from staircase.core import stats + + assert stats.max(s1(closed=closed), **kwargs) == expected_val + + +@pytest.mark.parametrize( + "closed, kwargs, expected_val", + [ + ( + "left", + {}, + np.array([-1.75, -0.5, 0.0, 0.25, 2.0, 2.75]), + ), + ( + "left", + {"where": (-4, 10)}, + np.array([-1.75, -0.5, 0.25, 2.0, 2.75]), + ), + ( + "left", + {"where": (1, 6)}, + np.array([0.25, 2.0, 2.75]), + ), + ( + "right", + {"where": (1, 6), "closed": "left"}, + np.array([-1.75, 0.25, 2.0, 2.75]), + ), + ( + "left", + {"where": (1, 6), "closed": "right"}, + np.array([-0.5, 0.25, 2.0, 2.75]), + ), + ], +) +def test_s1_values_in_range(closed, kwargs, expected_val): + assert np.array_equal(s1(closed=closed).values_in_range(**kwargs), expected_val) diff --git a/tests/test_stairs.py b/tests/test_stairs.py deleted file mode 100644 index 6367d94..0000000 --- a/tests/test_stairs.py +++ /dev/null @@ -1,1596 +0,0 @@ -import itertools - -import numpy as np -import pandas as pd -import pytest - -import staircase.test_data as test_data -from staircase import Stairs - - -def _expand_interval_definition(start, end=None, value=1): - return start, end, value - - -def _compare_iterables(it1, it2): - it1 = [i for i in it1 if i is not None] - it2 = [i for i in it2 if i is not None] - if len(it2) != len(it1): - return False - for e1, e2 in zip(it1, it2): - if e1 != e2: - return False - return True - - -def s1(closed="left"): - int_seq1 = Stairs(initial_value=0, closed=closed) - int_seq1.layer(1, 10, 2) - int_seq1.layer(-4, 5, -1.75) - int_seq1.layer(3, 5, 2.5) - int_seq1.layer(6, 7, -2.5) - int_seq1.layer(7, 10, -2.5) - return int_seq1 - - -def s2(): - int_seq2 = Stairs(initial_value=0) - int_seq2.layer(1, 7, -2.5) - int_seq2.layer(8, 10, 5) - int_seq2.layer(2, 5, 4.5) - int_seq2.layer(2.5, 4, -2.5) - int_seq2.layer(-2, 1, -1.75) - return int_seq2 - - -def s3(): # boolean - int_seq = Stairs(initial_value=0) - int_seq.layer(-10, 10, 1) - int_seq.layer(-8, -7, -1) - int_seq.layer(-5, -2, -1) - int_seq.layer(0.5, 1, -1) - int_seq.layer(3, 3.5, -1) - int_seq.layer(7, 9.5, -1) - return int_seq - - -def s4(): # boolean - int_seq = Stairs(initial_value=0) - int_seq.layer(-11, 9, 1) - int_seq.layer(-9.5, -8, -1) - int_seq.layer(-7.5, -7, -1) - int_seq.layer(0, 3, -1) - int_seq.layer(6, 6.5, -1) - int_seq.layer(7, 8.5, -1) - return int_seq - - -@pytest.fixture -def s1_fix(): - return s1() - - -@pytest.fixture -def s2_fix(): - return s2() - - -@pytest.fixture -def s3_fix(): - return s3() - - -@pytest.fixture -def s4_fix(): - return s4() - - -def test_init(): - assert Stairs(initial_value=0).identical(Stairs()) - assert Stairs().identical(Stairs(initial_value=0)) - - -@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) -def test_init2(init_value): - int_seq = Stairs(initial_value=init_value) - assert ( - int_seq.number_of_steps == 0 - ), "Initialised Stairs should have exactly one interval" - - -@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) -def test_init3(init_value): - int_seq = Stairs(initial_value=init_value) - assert ( - len(int_seq.step_points) == 0 - ), "Initialised Stairs should not have any finite interval endpoints" - - -@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) -def test_init4(init_value): - int_seq = Stairs(initial_value=init_value) - assert ( - int_seq(-1) == init_value - ), "Initialised Stairs should have initial value everywhere" - assert ( - int_seq(0) == init_value - ), "Initialised Stairs should have initial value everywhere" - assert ( - int_seq(1) == init_value - ), "Initialised Stairs should have initial value everywhere" - - -@pytest.mark.parametrize( - "init_value, added_interval", - itertools.product( - [0, 1.25, -1.25], - [(-2, 1), (3, 5, 2), (1, 5, -1), (-5, -3, 3), (3,), (2, None, 2)], - ), -) -def test_one_finite_interval(init_value, added_interval): - e = 0.0001 - int_seq = Stairs(initial_value=init_value) - int_seq.layer(*added_interval) - start, end, value = _expand_interval_definition(*added_interval) - assert int_seq.number_of_steps == 2 - ( - end is None - ), "One finite interval added to initial infinite interval should result in 3 intervals" - assert _compare_iterables( - int_seq.step_points, (start, end) - ), "Finite endpoints are not what is expected" - assert ( - int_seq(float("-inf")) == init_value - ), "Adding finite interval should not change initial value" - assert int_seq(float("inf")) == init_value + value * ( - end is None - ), "Adding finite interval should not change final value" - assert int_seq(start - e) == init_value - assert int_seq(start) == init_value + value - assert int_seq(start + e) == init_value + value - if end is not None: - assert int_seq(end - e) == init_value + value - assert int_seq(end) == init_value - - -@pytest.mark.parametrize( - "init_value, endpoints, value", - itertools.product( - [0, 1.25, -1.25, 2, -2], - [(-2, 1, 3), (-2, -1, 3), (-3, -2, -1), (1, 2, 3)], - [-1, 2, 3], - ), -) -def test_two_adjacent_finite_interval_same_value(init_value, endpoints, value): - e = 0.0001 - int_seq = Stairs(initial_value=init_value) - point1, point2, point3 = endpoints - int_seq.layer(point1, point2, value) - int_seq.layer(point2, point3, value) - assert int_seq.number_of_steps == 2, "Expected result to be 3 intervals" - assert _compare_iterables( - int_seq.step_points, (point1, point3) - ), "Finite endpoints are not what is expected" - assert ( - int_seq(float("-inf")) == init_value - ), "Adding finite interval should not change initial value" - assert ( - int_seq(float("inf")) == init_value - ), "Adding finite interval should not change final value" - assert int_seq(point1 - e) == init_value - assert int_seq(point1) == init_value + value - assert int_seq(point2) == init_value + value - assert int_seq(point3 - e) == init_value + value - assert int_seq(point3) == init_value - - -@pytest.mark.parametrize( - "init_value, endpoints, value, delta", - itertools.product( - [0, 1.25, -1.25, 2, -2], - [(-2, 1, 3), (-2, -1, 3), (-3, -2, -1), (1, 2, 3)], - [-1, 2, 4], - [3, -3, 1.5, -1.5], - ), -) -def test_two_adjacent_finite_interval_different_value( - init_value, endpoints, value, delta -): - e = 0.0001 - int_seq = Stairs(initial_value=init_value) - point1, point2, point3 = endpoints - int_seq.layer(point1, point2, value) - int_seq.layer(point2, point3, value + delta) - assert int_seq.number_of_steps == 3, "Expected result to be 4 intervals" - assert _compare_iterables( - int_seq.step_points, (point1, point2, point3) - ), "Finite endpoints are not what is expected" - assert ( - int_seq(float("-inf")) == init_value - ), "Adding finite interval should not change initial value" - assert ( - int_seq(float("inf")) == init_value - ), "Adding finite interval should not change final value" - assert int_seq(point1 - e) == init_value - assert int_seq(point1) == init_value + value - assert int_seq(point2) == init_value + value + delta - assert int_seq(point3 - e) == init_value + value + delta - assert int_seq(point3) == init_value - - -@pytest.mark.parametrize( - "init_value, endpoints, value, delta", - itertools.product( - [0, 1.25, -1.25, 2, -2], - [(-2, 1, 2, 3), (-3, -2, -1, 3), (-4, -3, -2, -1), (0, 1, 2, 3)], - [-1, 2, 4], - [3, -3, 1.5, -1.5], - ), -) -def test_two_overlapping_finite_interval(init_value, endpoints, value, delta): - e = 0.0001 - int_seq = Stairs(initial_value=init_value) - point1, point2, point3, point4 = endpoints - int_seq.layer(point1, point3, value) - int_seq.layer(point2, point4, value + delta) - assert int_seq.number_of_steps == 4, "Expected result to be 5 intervals" - assert _compare_iterables( - int_seq.step_points, (point1, point2, point3, point4) - ), "Finite endpoints are not what is expected" - assert ( - int_seq(float("-inf")) == init_value - ), "Adding finite interval should not change initial value" - assert ( - int_seq(float("inf")) == init_value - ), "Adding finite interval should not change final value" - assert int_seq(point1 - e) == init_value - assert int_seq(point1) == init_value + value - assert int_seq(point2) == init_value + 2 * value + delta - assert int_seq(point3 - e) == init_value + 2 * value + delta - assert int_seq(point3) == init_value + value + delta - assert int_seq(point4 - e) == init_value + value + delta - assert int_seq(point4) == init_value - - -@pytest.mark.parametrize( - "init_value, endpoints, value, delta", - itertools.product( - [0, 1.25, -1.25, 2, -2], - [(-2, 1, 2, 3), (-3, -2, -1, 3), (-4, -3, -2, -1), (0, 1, 2, 3)], - [-1, 2, 4], - [3, -3, 1.5, -1.5], - ), -) -def test_two_finite_interval_one_subinterval(init_value, endpoints, value, delta): - e = 0.0001 - int_seq = Stairs(initial_value=init_value) - point1, point2, point3, point4 = endpoints - int_seq.layer(point1, point4, value) - int_seq.layer(point2, point3, value + delta) - assert int_seq.number_of_steps == 4, "Expected result to be 5 intervals" - assert _compare_iterables( - int_seq.step_points, (point1, point2, point3, point4) - ), "Finite endpoints are not what is expected" - assert ( - int_seq.initial_value == init_value - ), "Adding finite interval should not change initial value" - assert ( - int_seq(float("inf")) == init_value - ), "Adding finite interval should not change final value" - assert int_seq(point1 - e) == init_value - assert int_seq(point1) == init_value + value - assert int_seq(point2) == init_value + 2 * value + delta - assert int_seq(point3 - e) == init_value + 2 * value + delta - assert int_seq(point3) == init_value + value - assert int_seq(point4 - e) == init_value + value - assert int_seq(point4) == init_value - - -@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) -def test_copy_and_equality(init_value): - int_seq = Stairs(initial_value=init_value) - int_seq_copy = int_seq.copy() - assert int_seq.identical(int_seq_copy) - assert int_seq_copy.identical(int_seq) - - -@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) -def test_deepcopy(init_value): - int_seq = Stairs(initial_value=init_value) - int_seq_copy = int_seq.copy() - int_seq_copy.layer(1, 2) - assert not int_seq.identical(int_seq_copy) - assert not int_seq_copy.identical(int_seq) - - -@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) -def test_layer1(init_value): - intervals_to_add = [(-2, 1), (3, 5), (1, 5), (-5, -3), (None, 0), (0, None)] - int_seq = Stairs(initial_value=init_value) - int_seq2 = Stairs(initial_value=init_value) - for start, end in intervals_to_add: - int_seq.layer(start, end) - starts, ends = list(zip(*intervals_to_add)) - starts = [{None: np.nan}.get(x, x) for x in starts] - ends = [{None: np.nan}.get(x, x) for x in ends] - int_seq2.layer(starts, ends) - assert int_seq.identical(int_seq2) - assert int_seq2.identical(int_seq) - - -@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) -def test_layer2(init_value): - intervals_to_add = [(-2, 1, 1), (3, 5, 2), (1, 5, -1), (-5, -3, 3)] - int_seq = Stairs(initial_value=init_value) - int_seq2 = Stairs(initial_value=init_value) - for interval in intervals_to_add: - int_seq.layer(*interval) - starts, ends, values = list(zip(*intervals_to_add)) - int_seq2.layer(starts, ends, values) - assert int_seq.identical(int_seq2) - assert int_seq2.identical(int_seq) - - -def test_make_boolean(s2_fix): - int_seq = s2_fix - calc = int_seq.make_boolean() - expected = Stairs() - expected.layer(-2, 7, 1) - expected.layer(8, 10, 1) - assert calc.identical(expected), "Boolean calculation not what it should be" - assert expected.identical(calc), "Boolean calculation not what it should be" - - -def test_invert(s2_fix): - int_seq = s2_fix - calc = ~int_seq - expected = Stairs(initial_value=1) - expected.layer(-2, 7, -1) - expected.layer(8, 10, -1) - assert calc.identical(expected), "Invert calculation not what it should be" - assert expected.identical(calc), "Invert calculation not what it should be" - - -def test_and(s3_fix, s4_fix): - calc = s3_fix & s4_fix - expected = Stairs(initial_value=0) - expected.layer(-10, -9.5) - expected.layer(-7, -5) - expected.layer(-2, 0) - expected.layer(3.5, 6) - expected.layer(6.5, 7) - assert calc.identical(expected), "AND calculation not what it should be" - assert expected.identical(calc), "AND calculation not what it should be" - - -def test_or(s3_fix, s4_fix): - calc = s3_fix | s4_fix - expected = Stairs(initial_value=0) - expected.layer(-11, -7.5) - expected.layer(-7, 0.5) - expected.layer(1, 7) - expected.layer(8.5, 9) - expected.layer(9.5, 10) - assert calc.identical(expected), "OR calculation not what it should be" - assert expected.identical(calc), "OR calculation not what it should be" - - -def test_lt(s1_fix, s2_fix): - calc = s1_fix < s2_fix - expected = Stairs(initial_value=0) - expected.layer(-4, -2) - expected.layer(2, 2.5) - expected.layer(7, 10) - assert calc.identical(expected), "LT calculation not what it should be" - assert expected.identical(calc), "LT calculation not what it should be" - - -def test_gt(s1_fix, s2_fix): - calc = s1_fix > s2_fix - expected = Stairs(initial_value=0) - expected.layer(1, 2) - expected.layer(2.5, 7) - assert calc.identical(expected), "GT calculation not what it should be" - assert expected.identical(calc), "GT calculation not what it should be" - - -def test_le(s1_fix, s2_fix): - calc = s1_fix <= s2_fix - expected = Stairs(initial_value=1) - expected.layer(1, 2, -1) - expected.layer(2.5, 7, -1) - assert calc.identical(expected), "LE calculation not what it should be" - assert expected.identical(calc), "LE calculation not what it should be" - - -def test_ge(s1_fix, s2_fix): - calc = s1_fix >= s2_fix - expected = Stairs(initial_value=1) - expected.layer(-4, -2, -1) - expected.layer(2, 2.5, -1) - expected.layer(7, 10, -1) - assert calc.identical(expected), "GE calculation not what it should be" - assert expected.identical(calc), "GE calculation not what it should be" - - -def test_eq_1(s1_fix, s2_fix): - calc = s1_fix == s2_fix - expected = Stairs(initial_value=1) - expected.layer(-4, -2, -1) - expected.layer(1, 10, -1) - assert calc.identical(expected), "EQ calculation not what it should be" - assert expected.identical(calc), "EQ calculation not what it should be" - - -def test_eq_2(s1_fix, s2_fix): - calc = s1_fix == s2_fix - expected = Stairs(initial_value=1) - expected.layer(-4, -2, -1) - expected.layer(1, 10, -1) - assert calc.identical(expected), "EQ calculation not what it should be" - assert expected.identical(calc), "EQ calculation not what it should be" - - -def test_ne(s1_fix, s2_fix): - calc = s1_fix != s2_fix - expected = Stairs(initial_value=0) - expected.layer(-4, -2, 1) - expected.layer(1, 10, 1) - assert calc.identical(expected), "NOT EQUAL calculation not what it should be" - assert expected.identical(calc), "NOT EQUAL calculation not what it should be" - - -@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) -def test_base_integral_0_2(init_value): - int_seq = Stairs(initial_value=init_value) - assert int_seq.agg("integral", (0, 2)) == 2 * init_value - - -@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) -def test_base_integral_neg1_1(init_value): - int_seq = Stairs(initial_value=init_value) - assert int_seq.agg("integral", (-1, 1)) == 2 * init_value - - -@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) -def test_base_integral_neg2_0(init_value): - int_seq = Stairs(initial_value=init_value) - assert int_seq.agg("integral", (-2, 0)) == 2 * init_value - - -@pytest.mark.parametrize("init_value", [0, 1.25, -1.25, 2, -2]) -def test_base_integral_point5_1(init_value): - int_seq = Stairs(initial_value=init_value) - assert int_seq.agg("integral", (0.5, 1)) == 0.5 * init_value - - -def test_integral1(s1_fix, s2_fix): - assert s1_fix.integral() == -2.75 - assert s2_fix.integral() == -0.5 - - -def test_integral2(s1_fix, s2_fix): - assert s1_fix.agg("integral", (-1, 5.5)) == 3.5 - assert s2_fix.agg("integral", (-1, 5.5)) == -5 - - -def test_mean1(s1_fix, s2_fix): - assert abs(s1_fix.mean() - -0.19642857) < 0.000001 - assert abs(s2_fix.mean() - -0.04166666) < 0.000001 - - -def test_mean2(s1_fix, s2_fix): - assert abs(s1_fix.agg("mean", (2, 8)) - 1.125) < 0.000001 - assert abs(s2_fix.agg("mean", (2, 8)) - -0.45833333) < 0.000001 - - -def test_integral_0(): - assert Stairs(initial_value=0).layer(None, 0).integral() is np.nan - - -def test_mean_nan(): - assert Stairs(initial_value=0).layer(None, 0).mean() is np.nan - - -def test_to_dataframe(s1_fix): - s1_fix.to_frame() - - -@pytest.mark.parametrize( - "stairs_instance, bounds, cuts", - itertools.product( - [s1(), s2(), s3(), s4()], - [(3, 4), (0, 10), (-10, 30), (-5, -1)], - ["unit", (0, 2.5, 4, 4.5, 7)], - ), -) -def test_hist_left_closed(stairs_instance, bounds, cuts): - def make_expected_result(interval_index, lower, upper): - return pd.Series( - [ - ((stairs_instance >= i.left) * (stairs_instance < i.right)).agg( - "mean", (lower, upper) - ) - for i in interval_index - ], - index=interval_index, - dtype="float64", - ) - - hist = stairs_instance.clip(*bounds).hist(bins=cuts, stat="probability") - expected = make_expected_result(hist.index, *bounds) - assert (hist.apply(round, 5) == expected.apply(round, 5)).all(), f"{bounds}, {cuts}" - - -@pytest.mark.parametrize( - "stairs_instance, bounds, cuts", - itertools.product( - [s1(), s2(), s3(), s4()], - [(3, 4), (0, 10), (-10, 30), (-5, -1)], - ["unit", (0, 2.5, 4, 4.5, 7)], - ), -) -def test_hist_right_closed(stairs_instance, bounds, cuts): - def make_expected_result(interval_index, lower, upper): - return pd.Series( - [ - ((stairs_instance > i.left) * (stairs_instance <= i.right)).agg( - "mean", (lower, upper) - ) - for i in interval_index - ], - index=interval_index, - dtype="float64", - ) - - hist = stairs_instance.clip(*bounds).hist( - bins=cuts, closed="right", stat="probability" - ) - expected = make_expected_result(hist.index, *bounds) - assert (hist.apply(round, 5) == expected.apply(round, 5)).all(), f"{bounds}, {cuts}" - - -@pytest.mark.parametrize( - "stairs_instance, bounds, closed", - itertools.product( - [s1(), s2(), s3(), s4()], - [(3, 4), (0, 10), (-10, 30), (-5, -1)], - ["left", "right"], - ), -) -def test_hist_default_bins(stairs_instance, bounds, closed): - # really testing the default binning process here - hist = stairs_instance.clip(*bounds).hist(closed=closed, stat="probability") - assert abs(hist.sum() - 1) < 0.000001 - - -# np.var(st1(np.linspace(-4,10, 10000000))) = 2.501594244387741 -# np.var(st1(np.linspace(-5,10, 10000000))) = 2.3372686165530117 -# np.var(st1(np.linspace(1,12, 10000000))) = 1.5433884747933315 - - -@pytest.mark.parametrize( - "bounds, expected", - [ - ((), 2.501594244387741), - (((-5, 10),), 2.3372686165530117), - (((1, 12),), 1.5433884747933315), - ], -) -def test_s1_var(bounds, expected): - assert np.isclose(s1().agg("var", *bounds), expected, atol=0.0001) - - -# np.var(st2(np.linspace(-2, 10, 10000000))) = 7.024303861110942 -# np.var(st2(np.linspace(-3, 7.5, 10000000))) = 2.2678568437499633 -# np.var(st2(np.linspace(0, 14, 10000000))) = 5.538902194132663 - - -@pytest.mark.parametrize( - "bounds, expected", - [ - ((), 7.024303861110942), - (((-3, 7.5),), 2.2678568437499633), - (((0, 14),), 5.538902194132663), - ], -) -def test_s2_var(bounds, expected): - assert np.isclose(s2().agg("var", *bounds), expected, atol=0.0001) - - -# np.std(st1(np.linspace(-4,10, 10000000))) = 1.5816428940780978 -# np.std(st1(np.linspace(-5,10, 10000000))) = 1.528797568034358 -# np.std(st1(np.linspace(1,12, 10000000))) = 1.242331869829206 - - -@pytest.mark.parametrize( - "bounds, expected", - [ - ((), 1.5816428940780978), - (((-5, 10),), 1.528797568034358), - (((1, 12),), 1.242331869829206), - ], -) -def test_s1_std(bounds, expected): - assert np.isclose(s1().agg("std", *bounds), expected, atol=0.0001) - - -# np.std(st2(np.linspace(-2, 10, 10000000))) = 2.650340329299417 -# np.std(st2(np.linspace(-3, 7.5, 10000000))) = 1.5059405179986238 -# np.std(st2(np.linspace(0, 14, 10000000))) = 2.3534872411238315 - - -@pytest.mark.parametrize( - "bounds, expected", - [ - ((), 2.650340329299417), - (((-3, 7.5),), 1.5059405179986238), - (((0, 14),), 2.3534872411238315), - ], -) -def test_s2_std(bounds, expected): - assert np.isclose(s2().agg("std", *bounds), expected, atol=0.0001) - - -# # np.cov(st1(pts[:-100000]), st1(pts[100000:]))[0,1] = 1.9386094481108465 -# # np.cov(st1(np.linspace(-4, 8, 12*100000 + 1)), st1(np.linspace(-2, 10, 12*100000 + 1)))[0,1] = 1.1184896017794723 -# # np.cov(st1(np.linspace(-4, 8, 12*100000 + 1)), st1.shift(-2)(np.linspace(-4, 8, 12*100000 + 1)))[0,1] = 1.1184896017794723 - - -@pytest.mark.parametrize( - "kwargs, expected", - [ - ({"where": (-4, 10), "lag": 1}, 1.9386094481108465), - ({"where": (-4, 10), "lag": 2}, 1.1184896017794723), - ({"where": (-4, 8), "lag": 2, "clip": "post"}, 1.1184896017794723), - ], -) -def test_s1_autocov(kwargs, expected): - assert np.isclose(s1().cov(s1(), **kwargs), expected, atol=0.00001) - - -# # np.cov(st2(np.linspace(-2, 9, 11*100000 + 1)), st2(np.linspace(-1, 10, 11*100000 + 1)))[0,1 = 3.1022721590913256 -# # np.cov(st2(np.linspace(0, 6, 12*100000 + 1)), st2(np.linspace(2, 8, 12*100000 + 1)))[0,1] = -0.7291746267294938 -# # np.cov(st2(np.linspace(0, 6, 12*100000 + 1)), st2.shift(-2)(np.linspace(0, 6, 12*100000 + 1)))[0,1] = -0.7291746267294938 - - -@pytest.mark.parametrize( - "kwargs, expected", - [ - ({"where": (-2, 10), "lag": 1}, 3.1022721590913256), - ({"where": (0, 8), "lag": 2}, -0.7291746267294938), - ({"where": (0, 6), "lag": 2, "clip": "post"}, -0.7291746267294938), - ], -) -def test_s2_autocov(kwargs, expected): - assert np.isclose(s2().cov(s2(), **kwargs), expected, atol=0.00001) - - -# # np.cov(st1(np.linspace(-2, 9, 11*100000 + 1)), st2(np.linspace(-1, 10, 11*100000 + 1)))[0,1 = -0.08677679611199672 -# # np.cov(st1(np.linspace(0, 6, 12*100000 + 1)), st2(np.linspace(2, 8, 12*100000 + 1)))[0,1] = -1.970493123547197 -# # np.cov(st1(np.linspace(0, 6, 12*100000 + 1)), st2.shift(-2)(np.linspace(0, 6, 12*100000 + 1)))[0,1] = -1.970493123547197 - - -@pytest.mark.parametrize( - "kwargs, expected", - [ - ({"where": (-2, 10), "lag": 1}, -0.08677679611199672), - ({"where": (0, 8), "lag": 2}, -1.970493123547197), - ({"where": (0, 6), "lag": 2, "clip": "post"}, -1.970493123547197), - ], -) -def test_crosscov(kwargs, expected): - assert np.isclose(s1().cov(s2(), **kwargs), expected, atol=0.00001) - - -# # np.corrcoef(st1(pts[:-100000]), st1(pts[100000:]))[0,1] = 0.6927353407369307 -# # np.corrcoef(st1(np.linspace(-4, 8, 12*100000 + 1)), st1(np.linspace(-2, 10, 12*100000 + 1)))[0,1] = -0.2147502741669856 -# # np.corrcoef(st1(np.linspace(-4, 8, 12*100000 + 1)), st1.shift(-2)(np.linspace(-4, 8, 12*100000 + 1)))[0,1] = -0.2147502741669856 - - -@pytest.mark.parametrize( - "kwargs, expected", - [ - ({"where": (-2, 10), "lag": 1}, 0.6927353407369307), - ({"where": (0, 8), "lag": 2}, -0.2147502741669856), - ({"where": (0, 6), "lag": 2, "clip": "post"}, -0.2147502741669856), - ], -) -def test_s1_autocorr(kwargs, expected): - assert np.isclose(s1().corr(s1(), **kwargs), expected, atol=0.00001) - - -# # np.corrcoef(st2(pts[:-100000]), st2(pts[100000:]))[0,1] = 0.5038199912440895 -# # np.corrcoef(st2(np.linspace(-4, 8, 12*100000 + 1)), st2(np.linspace(-2, 10, 12*100000 + 1)))[0,1] = -0.2419504099129966 -# # np.corrcoef(st2(np.linspace(-4, 8, 12*100000 + 1)), st2.shift(-2)(np.linspace(-4, 8, 12*100000 + 1)))[0,1] = -0.2419504099129966 - - -@pytest.mark.parametrize( - "kwargs, expected", - [ - ({"where": (-2, 10), "lag": 1}, 0.5038199912440895), - ({"where": (0, 8), "lag": 2}, -0.2419504099129966), - ({"where": (0, 6), "lag": 2, "clip": "post"}, -0.2419504099129966), - ], -) -def test_s2_autocorr(kwargs, expected): - assert np.isclose(s2().corr(s2(), **kwargs), expected, atol=0.00001) - - -# # np.corrcoef(st1(pts[:-100000]), st2(pts[100000:]))[0,1] = -0.01966642657198049 -# # np.corrcoef(st1(np.linspace(-4, 8, 12*100000 + 1)), st2(np.linspace(-2, 10, 12*100000 + 1)))[0,1] = -0.7086484036832666 -# # np.corrcoef(st1(np.linspace(-4, 8, 12*100000 + 1)), st2.shift(-2)(np.linspace(-4, 8, 12*100000 + 1)))[0,1] = -0.7086484036832666 - - -@pytest.mark.parametrize( - "kwargs, expected", - [ - ({"where": (-2, 10), "lag": 1}, -0.01966642657198049), - ({"where": (0, 8), "lag": 2}, -0.7086484036832666), - ({"where": (0, 6), "lag": 2, "clip": "post"}, -0.7086484036832666), - ], -) -def test_crosscorr(kwargs, expected): - assert np.isclose(s1().corr(s2(), **kwargs), expected, atol=0.00001) - - -@pytest.mark.parametrize( - "kwargs, expected_index, expected_vals", - [ - ( - {"window": (-1, 1)}, - [-5, -3, 0, 2, 4, 5, 6, 7, 9, 11], - [0.0, -1.75, -1.75, 0.25, 2.75, 2.375, 0.75, -0.5, -0.5, 0.0], - ), - ( - {"window": (-2, 0)}, - [-4, -2, 1, 3, 5, 6, 7, 8, 10, 12], - [0.0, -1.75, -1.75, 0.25, 2.75, 2.375, 0.75, -0.5, -0.5, 0.0], - ), - ( - {"window": (-1, 1), "where": (0, 8)}, - [1, 2, 4, 5, 6, 7], - [-0.75, 0.25, 2.75, 2.375, 0.75, -0.5], - ), - ], -) -def test_s1_rolling_mean(s1_fix, kwargs, expected_index, expected_vals): - rm = s1_fix.rolling_mean(**kwargs) - assert list(rm.values) == expected_vals - assert list(rm.index) == expected_index - - -@pytest.mark.parametrize( - "closed, kwargs, expected_val", - [ - ( - "left", - {}, - -1.75, - ), - ( - "left", - {"where": (1, 6)}, - 0.25, - ), - ( - "right", - {"where": (1, 6), "closed": "left"}, - -1.75, - ), - ( - "left", - {"where": (1, 6), "closed": "right"}, - -0.5, - ), - ], -) -def test_s1_min(closed, kwargs, expected_val): - from staircase.core import stats - - assert stats.min(s1(closed=closed), **kwargs) == expected_val - - -@pytest.mark.parametrize( - "closed, kwargs, expected_val", - [ - ( - "left", - {}, - 2.75, - ), - ( - "left", - {"where": (-4, 1)}, - -1.75, - ), - ( - "right", - {"where": (-4, 1), "closed": "left"}, - 0.0, - ), - ( - "left", - {"where": (-4, 1), "closed": "right"}, - 0.25, - ), - ], -) -def test_s1_max(closed, kwargs, expected_val): - from staircase.core import stats - - assert stats.max(s1(closed=closed), **kwargs) == expected_val - - -@pytest.mark.parametrize( - "closed, kwargs, expected_val", - [ - ( - "left", - {}, - np.array([-1.75, -0.5, 0.0, 0.25, 2.0, 2.75]), - ), - ( - "left", - {"where": (-4, 10)}, - np.array([-1.75, -0.5, 0.25, 2.0, 2.75]), - ), - ( - "left", - {"where": (1, 6)}, - np.array([0.25, 2.0, 2.75]), - ), - ( - "right", - {"where": (1, 6), "closed": "left"}, - np.array([-1.75, 0.25, 2.0, 2.75]), - ), - ( - "left", - {"where": (1, 6), "closed": "right"}, - np.array([-0.5, 0.25, 2.0, 2.75]), - ), - ], -) -def test_s1_values_in_range(closed, kwargs, expected_val): - assert np.array_equal(s1(closed=closed).values_in_range(**kwargs), expected_val) - - -@pytest.mark.parametrize( - "x, kwargs, expected_val", - [ - ( - [-4, -2, 1, 3], - {"side": "right"}, - np.array([-1.75, -1.75, 0.25, 2.75]), - ), - ( - [-4, -2, 1, 3], - {"side": "right"}, - np.array([-1.75, -1.75, 0.25, 2.75]), - ), - ( - [-4, -2, 1, 3], - {"side": "left"}, - np.array([0.0, -1.75, -1.75, 0.25]), - ), - ], -) -def test_s1_sample(s1_fix, x, kwargs, expected_val): - assert np.array_equal(s1_fix.limit(x, **kwargs), expected_val) - - -@pytest.mark.parametrize( - "x, kwargs, expected_val", - [ - ( - [-4, -2, 1, 3], - {"aggfunc": "mean", "window": (-0.5, 0.5)}, - np.array([-0.875, -1.75, -0.75, 1.5]), - ), - ( - [-4, -2, 1, 3], - {"aggfunc": "mean", "window": (-1, 0)}, - np.array([0.0, -1.75, -1.75, 0.25]), - ), - ( - [-4, -2, 1, 3], - {"aggfunc": "mean", "window": (0, 1)}, - np.array([-1.75, -1.75, 0.25, 2.75]), - ), - ], -) -def test_s1_agg_mean(s1_fix, x, kwargs, expected_val): - window = kwargs["window"] - x = np.array(x) - ii = pd.IntervalIndex.from_arrays(x + window[0], x + window[1]) - assert np.array_equal(s1_fix.slice(ii).mean().values, expected_val) - - -@pytest.mark.parametrize( - "closed, x, kwargs, expected_val", - [ - ( - "left", - [0, 2, 7], - {"aggfunc": "max", "window": (-1, 1)}, - np.array([-1.75, 0.25, -0.5]), - ), - ( - "right", - [0, 2, 7], - {"aggfunc": "max", "window": (-1, 1), "closed": "left"}, - np.array([-1.75, 0.25, 2.0]), - ), - ( - "left", - [0, 2, 7], - {"aggfunc": "max", "window": (-1, 1), "closed": "right"}, - np.array([0.25, 2.75, -0.5]), - ), - ( - "right", - [0, 2, 7], - {"aggfunc": "max", "window": (-1, 1), "closed": "right"}, - np.array([-1.75, 0.25, -0.5]), - ), - ], -) -def test_s1_agg_max(closed, x, kwargs, expected_val): - window = kwargs["window"] - x = np.array(x) - ii = pd.IntervalIndex.from_arrays( - x + window[0], x + window[1], closed=kwargs.get("closed", "left") - ) - assert np.array_equal(s1(closed=closed).slice(ii).max().values, expected_val) - - -@pytest.mark.parametrize( - "kwargs", - [ - {}, - {"arrows": True, "style": "hlines"}, - {"arrows": False, "style": "hlines"}, - {"arrows": True, "style": "step"}, - {"arrows": False, "style": "step"}, - ], -) -def test_plot(s1_fix, kwargs): - s1_fix.plot(**kwargs) - - -def test_plot_trivial_1(): - Stairs().plot() - - -def test_plot_trivial_2(): - Stairs(initial_value=np.nan).plot() - - -def test_plot_ecdf(s1_fix): - s1_fix.plot.ecdf() - - -def test_plot_bad_backend(s1_fix): - with pytest.raises(ValueError): - s1_fix.plot(backend="") - - -def test_plot_ecdf_bad_backend(s1_fix): - with pytest.raises(ValueError): - s1_fix.plot.ecdf(backend="") - - -def test_add_1(s1_fix, s2_fix): - assert pd.Series.equals( - (s1_fix + s2_fix).step_changes, - pd.Series( - { - -4: -1.75, - -2: -1.75, - 1: 1.25, - 2: 4.5, - 2.5: -2.5, - 3: 2.5, - 4: 2.5, - 5: -5.25, - 6: -2.5, - 7: 2.5, - 8: 5, - 10: -4.5, - } - ), - ) - - -def test_add_2(s1_fix): - s = s1_fix + 3 - assert s.initial_value == 3 - assert pd.Series.equals( - s.step_changes, - s1_fix.step_changes, - ) - - -def test_add_3(s1_fix): - s = 3 + s1_fix - assert s.initial_value == 3 - assert pd.Series.equals( - s.step_changes, - s1_fix.step_changes, - ) - - -def test_sub_1(s1_fix, s2_fix): - assert pd.Series.equals( - (s1_fix - s2_fix).step_values, - pd.Series( - { - -4.0: -1.75, - -2.0: 0.0, - 1.0: 2.75, - 2.0: -1.75, - 2.5: 0.75, - 3.0: 3.25, - 4.0: 0.75, - 5.0: 4.5, - 6.0: 2.0, - 7.0: -0.5, - 8.0: -5.5, - 10.0: 0.0, - } - ), - ) - - -def test_sub_2(s1_fix): - s = s1_fix - 3 - assert s.initial_value == -3 - assert pd.Series.equals( - s.step_changes, - s1_fix.step_changes, - ) - - -def test_sub_3(s1_fix): - s = 3 - s1_fix - assert s.initial_value == 3 - assert pd.Series.equals( - s.step_changes, - -(s1_fix.step_changes), - ) - - -def test_divide(s1_fix, s2_fix): - assert pd.Series.equals( - (s1_fix / (s2_fix + 1)).step_changes, - pd.Series( - { - -4: -1.75, - -2: 4.083333333333334, - 1: -2.5, - 2: 0.25, - 2.5: 0.4166666666666667, - 3: 5.0, - 4: -4.583333333333333, - 5: -2.25, - 6: 1.6666666666666665, - 7: -0.8333333333333333, - 8: 0.4166666666666667, - 10: 0.08333333333333333, - } - ), - ) - - -def test_divide_scalar(s1_fix): - assert pd.Series.equals( - (s1_fix / 0.5).step_changes, - pd.Series( - { - -4: -3.5, - 1: 4.0, - 3: 5.0, - 5: -1.5, - 6: -5.0, - 10: 1.0, - } - ), - ) - - -def test_scalar_divide(): - s = Stairs().layer([1, 2, 5], [3, 4, 7], [1, -1, 2]) - assert pd.Series.equals( - (2 / s).step_values, - pd.Series( - { - 1: 2.0, - 2: np.nan, - 3: -2.0, - 4: np.nan, - 5: 1.0, - 7: np.nan, - } - ), - ) - - -def test_multiply(s1_fix, s2_fix): - assert pd.Series.equals( - (s1_fix * s2_fix).step_changes, - pd.Series( - { - -2: 3.0625, - 1: -3.6875, - 2: 1.125, - 2.5: -0.625, - 3: -1.25, - 4: 6.875, - 5: -10.5, - 6: 6.25, - 7: -1.25, - 8: -2.5, - 10: 2.5, - } - ), - ) - - -def test_multiply_scalar(s1_fix): - assert pd.Series.equals( - (s1_fix * 3).step_changes, - pd.Series( - { - -4: -5.25, - 1: 6.0, - 3: 7.5, - 5: -2.25, - 6: -7.5, - 10: 1.5, - } - ), - ) - - -def test_multiply_scalar_2(s1_fix): - assert pd.Series.equals( - (3 * s1_fix).step_changes, - pd.Series( - { - -4: -5.25, - 1: 6.0, - 3: 7.5, - 5: -2.25, - 6: -7.5, - 10: 1.5, - } - ), - ) - - -def test_eq_3(): - assert Stairs(initial_value=3) == 3 - - -def test_ne_3(s1_fix): - assert s1_fix != 3 - - -def test_diff(s1_fix): - assert pd.Series.equals( - s1_fix.diff(1).step_changes, - pd.Series( - { - -4: -1.75, - -3: 1.75, - 1: 2, - 2: -2, - 3: 2.5, - 4: -2.5, - 5: -0.75, - 6: -1.75, - 7: 2.5, - 10: 0.5, - 11: -0.5, - } - ), - ) - - -def test_str(s1_fix): - assert str(s1_fix) is not None - assert str(s1_fix) != "" - - -def test_repr(s1_fix): - assert repr(s1_fix) is not None - assert repr(s1_fix) != "" - - -def test_make_test_data(): - assert type(test_data.make_test_data()) == pd.DataFrame - - -def test_logical_and_scalar_1(s3_fix): - assert (s3_fix & 1).identical(s3_fix) - - -def test_logical_rand_scalar_1(s3_fix): - assert (1 & s3_fix).identical(s3_fix) - - -def test_logical_and_scalar_2(s3_fix): - assert (s3_fix & 0).identical(0) - - -def test_logical_rand_scalar_2(s3_fix): - assert (0 & s3_fix).identical(0) - - -def test_logical_and_scalar_3(s3_fix): - assert (s3_fix & np.nan).identical(np.nan) - - -def test_logical_rand_scalar_3(s3_fix): - assert (np.nan & s3_fix).identical(np.nan) - - -def test_logical_or_scalar_1(s3_fix): - assert (s3_fix | 1).identical(1) - - -def test_logical_ror_scalar_1(s3_fix): - assert (1 | s3_fix).identical(1) - - -def test_logical_or_scalar_2(s3_fix): - assert (s3_fix | 0).identical(s3_fix) - - -def test_logical_ror_scalar_2(s3_fix): - assert (0 | s3_fix).identical(s3_fix) - - -def test_logical_or_scalar_3(s3_fix): - assert (s3_fix | np.nan).identical(np.nan) - - -def test_logical_ror_scalar_3(s3_fix): - assert (np.nan | s3_fix).identical(np.nan) - - -def test_logical_xor_scalar_1(s3_fix): - assert (s3_fix ^ 1).identical(~s3_fix) - - -def test_logical_rxor_scalar_1(s3_fix): - assert (1 ^ s3_fix).identical(~s3_fix) - - -def test_logical_xor_scalar_2(s3_fix): - assert (s3_fix ^ 0).identical(s3_fix) - - -def test_logical_rxor_scalar_2(s3_fix): - assert (0 ^ s3_fix).identical(s3_fix) - - -def test_logical_xor_scalar_3(s3_fix): - assert (s3_fix ^ np.nan).identical(np.nan) - - -def test_logical_rxor_scalar_3(s3_fix): - assert (np.nan ^ s3_fix).identical(np.nan) - - -def test_slicing_mean(s1_fix): - pd.testing.assert_series_equal( - s1_fix.slice(range(-4, 11, 2)).mean(), - pd.Series( - { - pd.Interval(-4, -2, closed="left"): -1.75, - pd.Interval(-2, 0, closed="left"): -1.75, - pd.Interval(0, 2, closed="left"): -0.75, - pd.Interval(2, 4, closed="left"): 1.5, - pd.Interval(4, 6, closed="left"): 2.375, - pd.Interval(6, 8, closed="left"): -0.5, - pd.Interval(8, 10, closed="left"): -0.5, - } - ), - check_names=False, - check_index_type=False, - ) - - -def test_slicing_max(s1_fix): - pd.testing.assert_series_equal( - s1_fix.slice(range(-4, 11, 2)).max(), - pd.Series( - { - pd.Interval(-4, -2, closed="left"): -1.75, - pd.Interval(-2, 0, closed="left"): -1.75, - pd.Interval(0, 2, closed="left"): 0.25, - pd.Interval(2, 4, closed="left"): 2.75, - pd.Interval(4, 6, closed="left"): 2.75, - pd.Interval(6, 8, closed="left"): -0.5, - pd.Interval(8, 10, closed="left"): -0.5, - } - ), - check_names=False, - check_index_type=False, - ) - - -def test_slicing_min(s1_fix): - pd.testing.assert_series_equal( - s1_fix.slice(range(-4, 11, 2)).min(), - pd.Series( - { - pd.Interval(-4, -2, closed="left"): -1.75, - pd.Interval(-2, 0, closed="left"): -1.75, - pd.Interval(0, 2, closed="left"): -1.75, - pd.Interval(2, 4, closed="left"): 0.25, - pd.Interval(4, 6, closed="left"): 2.0, - pd.Interval(6, 8, closed="left"): -0.5, - pd.Interval(8, 10, closed="left"): -0.5, - } - ), - check_names=False, - check_index_type=False, - ) - - -def test_slicing_mode(s1_fix): - pd.testing.assert_series_equal( - s1_fix.slice(range(-4, 11, 2)).mode(), - pd.Series( - { - pd.Interval(-4, -2, closed="left"): -1.75, - pd.Interval(-2, 0, closed="left"): -1.75, - pd.Interval(0, 2, closed="left"): -1.75, - pd.Interval(2, 4, closed="left"): 0.25, - pd.Interval(4, 6, closed="left"): 2.0, - pd.Interval(6, 8, closed="left"): -0.5, - pd.Interval(8, 10, closed="left"): -0.5, - } - ), - check_names=False, - check_index_type=False, - ) - - -def test_slicing_median(s1_fix): - pd.testing.assert_series_equal( - s1_fix.slice(range(-4, 11, 2)).median(), - pd.Series( - { - pd.Interval(-4, -2, closed="left"): -1.75, - pd.Interval(-2, 0, closed="left"): -1.75, - pd.Interval(0, 2, closed="left"): -0.75, - pd.Interval(2, 4, closed="left"): 1.5, - pd.Interval(4, 6, closed="left"): 2.375, - pd.Interval(6, 8, closed="left"): -0.5, - pd.Interval(8, 10, closed="left"): -0.5, - } - ), - check_names=False, - check_index_type=False, - ) - - -def test_slicing_agg_min(s1_fix): - pd.testing.assert_series_equal( - s1_fix.slice(range(-4, 11, 2)).agg("min")["min"], - pd.Series( - { - pd.Interval(-4, -2, closed="left"): -1.75, - pd.Interval(-2, 0, closed="left"): -1.75, - pd.Interval(0, 2, closed="left"): -1.75, - pd.Interval(2, 4, closed="left"): 0.25, - pd.Interval(4, 6, closed="left"): 2.0, - pd.Interval(6, 8, closed="left"): -0.5, - pd.Interval(8, 10, closed="left"): -0.5, - } - ), - check_names=False, - check_index_type=False, - ) - - -def test_slicing_apply_min(s1_fix): - pd.testing.assert_series_equal( - s1_fix.slice(range(-4, 11, 2)).apply(Stairs.min), - pd.Series( - { - pd.Interval(-4, -2, closed="left"): -1.75, - pd.Interval(-2, 0, closed="left"): -1.75, - pd.Interval(0, 2, closed="left"): -1.75, - pd.Interval(2, 4, closed="left"): 0.25, - pd.Interval(4, 6, closed="left"): 2.0, - pd.Interval(6, 8, closed="left"): -0.5, - pd.Interval(8, 10, closed="left"): -0.5, - } - ), - check_names=False, - check_index_type=False, - ) - - -def test_slicing_agg_min_max(s1_fix): - result = s1_fix.slice(range(-4, 11, 2)).agg(["min", "max"]) - pd.testing.assert_series_equal( - result["min"], - pd.Series( - { - pd.Interval(-4, -2, closed="left"): -1.75, - pd.Interval(-2, 0, closed="left"): -1.75, - pd.Interval(0, 2, closed="left"): -1.75, - pd.Interval(2, 4, closed="left"): 0.25, - pd.Interval(4, 6, closed="left"): 2.0, - pd.Interval(6, 8, closed="left"): -0.5, - pd.Interval(8, 10, closed="left"): -0.5, - } - ), - check_names=False, - check_index_type=False, - ) - pd.testing.assert_series_equal( - result["max"], - pd.Series( - { - pd.Interval(-4, -2, closed="left"): -1.75, - pd.Interval(-2, 0, closed="left"): -1.75, - pd.Interval(0, 2, closed="left"): 0.25, - pd.Interval(2, 4, closed="left"): 2.75, - pd.Interval(4, 6, closed="left"): 2.75, - pd.Interval(6, 8, closed="left"): -0.5, - pd.Interval(8, 10, closed="left"): -0.5, - } - ), - check_names=False, - check_index_type=False, - ) - - -def test_slicing_resample_mean(s1_fix): - pd.testing.assert_series_equal( - s1_fix.slice(range(0, 7, 2)).resample("mean").step_values, - pd.Series({-4: -1.75, 0: -0.75, 2: 1.5, 4: 2.375, 6: -0.5, 10: 0.0}), - check_names=False, - check_index_type=False, - ) - - -def test_layering_index(s1_fix): - result = Stairs( - start=pd.Index([1, -4, 3, 6, 7]), - end=pd.Index([10, 5, 5, 7, 10]), - value=pd.Index([2, -1.75, 2.5, -2.5, -2.5]), - ) - assert result.identical(s1_fix) - - -def test_layering_frame(s1_fix): - df = pd.DataFrame( - { - "start": [1, -4, 3, 6, 7], - "end": [10, 5, 5, 7, 10], - "value": [2, -1.75, 2.5, -2.5, -2.5], - } - ) - assert Stairs(df, "start", "end", "value").identical(s1_fix) - - -def test_layering_trivial_1(s1_fix): - assert s1_fix.copy().layer(1, 1).identical(s1_fix) - - -def test_pipe(s1_fix): - def is_stairs(s): - return isinstance(s, Stairs) - - assert s1().pipe(is_stairs) - - -def test_value_sums(s1_fix): - pd.testing.assert_series_equal( - s1_fix.value_sums(), - pd.Series({-1.75: 5, -0.5: 4, 0.25: 2, 2.0: 1, 2.75: 2}), - check_names=False, - check_index_type=False, - ) - - -def test_step_changes(s1_fix): - pd.testing.assert_series_equal( - s1_fix.step_changes, - pd.Series({-4: -1.75, 1: 2.0, 3: 2.5, 5: -0.75, 6: -2.5, 10: 0.5}), - check_names=False, - check_index_type=False, - ) - - -def test_step_values(s1_fix): - pd.testing.assert_series_equal( - s1_fix.step_values, - pd.Series({-4: -1.75, 1: 0.25, 3: 2.75, 5: 2.0, 6: -0.5, 10: 0.0}), - check_names=False, - check_index_type=False, - ) - - -def test_step_points(s1_fix): - assert list(s1_fix.step_points) == [-4, 1, 3, 5, 6, 10] - - -def test_step_changes_stepless(): - pd.testing.assert_series_equal( - Stairs().step_changes, - pd.Series([], dtype="float64"), - check_names=False, - check_index_type=False, - ) - - -def test_step_values_stepless(): - pd.testing.assert_series_equal( - Stairs().step_values, - pd.Series([], dtype="float64"), - check_names=False, - check_index_type=False, - ) - - -def test_step_points_stepless(): - assert list(Stairs().step_points) == [] - - -def test_negate(s1_fix): - pd.testing.assert_series_equal( - (-s1_fix).step_values, - pd.Series({-4: 1.75, 1: -0.25, 3: -2.75, 5: -2.0, 6: 0.5, 10: 0.0}), - check_names=False, - check_index_type=False, - ) - - -def test_hist_frequency(s1_fix): - index = pd.IntervalIndex.from_breaks([-2, 0, 2, 3], closed="left") - pd.testing.assert_series_equal( - s1_fix.hist(bins=[-2, 0, 2, 3], stat="frequency"), - pd.Series([4.5, 1, 3], index=index), - check_names=False, - check_index_type=False, - ) - - -def test_hist_density(s1_fix): - index = pd.IntervalIndex.from_breaks([-2, 0, 2, 3], closed="left") - pd.testing.assert_series_equal( - s1_fix.hist(bins=[-2, 0, 2, 3], stat="density"), - pd.Series([0.36, 0.08, 0.12], index=index), - check_names=False, - check_index_type=False, - ) - - -def test_hist_probability(s1_fix): - index = pd.IntervalIndex.from_breaks([-2, 0, 2, 3], closed="left") - pd.testing.assert_series_equal( - s1_fix.hist(bins=[-2, 0, 2, 3], stat="probability"), - pd.Series([0.642857, 0.142857, 0.214286], index=index), - check_names=False, - check_index_type=False, - ) - - -def test_quantiles(s1_fix): - assert (s1_fix.quantiles(4) == np.array([-1.75, -0.5, 0.25])).all() - - -def test_fractile(s1_fix): - assert list(map(s1().fractile, (0.25, 0.5, 0.75))) == [ - -1.75, - -0.5, - 0.25, - ] diff --git a/tox.ini b/tox.ini index 3c20a46..a89c868 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,28 @@ [tox] isolated_build=true -envlist = py{36,37,38,39}-pandas{0.24,0.25,1.0,1.1,1.2,1.3}-numpy{1.16,1.17,1.18,1.19,1.20,1.21} +envlist = py{36,37,38,39}-numpy{114,115,116,117,118,119,120,121}-pandas{024,025,10,11,12,13} +skipdist = true [testenv] -deps = +deps = + matplotlib + pytz pytest pytest-cov -commands = pytest \ No newline at end of file + pandas024: pandas>=0.24,<0.25 + pandas025: pandas>=0.25,<1 + pandas10: pandas>=1.0,<1.1 + pandas11: pandas>=1.1,<1.2 + pandas12: pandas>=1.2,<1.3 + pandas13: pandas>=1.3,<1.4 + numpy114: numpy>=1.14,<1.15 + numpy115: numpy>=1.15,<1.16 + numpy116: numpy>=1.16,<1.17 + numpy117: numpy>=1.17,<1.18 + numpy118: numpy>=1.18,<1.19 + numpy119: numpy>=1.19,<1.20 + numpy120: numpy>=1.20,<1.21 + numpy121: numpy>=1.21,<1.22 +skip_install = true +commands = + pytest tests/