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/