From ee623629a4c95f66bbebc55ba08ee3105a4ffd6b Mon Sep 17 00:00:00 2001 From: Peter Wildeford Date: Sun, 10 Nov 2024 13:31:19 -0500 Subject: [PATCH 01/13] Started third_kelly --- CHANGES.md | 2 +- pyproject.toml | 2 +- squigglepy/utils.py | 69 ++++++++++++++++++++++++++++++++++++++++--- squigglepy/version.py | 2 +- tests/test_utils.py | 46 +++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 317168d..9068b08 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ ## v0.29 -* ??? +* Added `third_kelly` as an alias for `kelly` with deference = 0.66. ## v0.28 diff --git a/pyproject.toml b/pyproject.toml index 21e597f..290f0db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "squigglepy" -version = "0.29-dev1" +version = "0.29-dev2" description = "Squiggle programming language for intuitive probabilistic estimation features in Python" authors = ["Peter Wildeford "] license = "MIT" diff --git a/squigglepy/utils.py b/squigglepy/utils.py index bd5f62d..3537a04 100644 --- a/squigglepy/utils.py +++ b/squigglepy/utils.py @@ -1020,10 +1020,8 @@ def kelly( "target": round(target, 2), "current": round(current, 2), "delta": round(target - current, 2), - "max_gain": round(target / market_price, 2), - "modeled_gain": round( - (adj_price * (target / market_price) + (1 - adj_price) * -target), 2 - ), + "max_gain": round(target / market_price - target, 2), + "modeled_gain": round((adj_price * (target / market_price) - target), 2), "expected_roi": round(expected_roi, 3), "expected_arr": round(expected_arr, 3) if expected_arr is not None else None, "resolve_date": resolve_date, @@ -1154,6 +1152,69 @@ def half_kelly(my_price, market_price, bankroll=1, resolve_date=None, current=0) ) +def third_kelly(my_price, market_price, bankroll=1, resolve_date=None, current=0): + """ + Alias for ``kelly`` where ``deference`` is 0.6666. + + Parameters + ---------- + my_price : float + The price (or probability) you give for the given event. + market_price : float + The price the market is giving for that event. + bankroll : float + How much money do you have to bet? Defaults to 1. + resolve_date : str or None + When will the event happen, the market resolve, and you get your money back? Used for + calculating expected ARR. Give in YYYY-MM-DD format. Defaults to None, which means + ARR is not calculated. + current : float + How much do you already have invested in this event? Used for calculating the + additional amount you should invest. Defaults to 0. + + Returns + ------- + dict + A dict of values specifying: + * ``my_price`` + * ``market_price`` + * ``deference`` + * ``adj_price`` : an adjustment to ``my_price`` once ``deference`` is taken + into account. + * ``delta_price`` : the absolute difference between ``my_price`` and ``market_price``. + * ``adj_delta_price`` : the absolute difference between ``adj_price`` and + ``market_price``. + * ``kelly`` : the kelly criterion indicating the percentage of ``bankroll`` + you should bet. + * ``target`` : the target amount of money you should have invested + * ``current`` + * ``delta`` : the amount of money you should invest given what you already + have invested + * ``max_gain`` : the amount of money you would gain if you win + * ``modeled_gain`` : the expected value you would win given ``adj_price`` + * ``expected_roi`` : the expected return on investment + * ``expected_arr`` : the expected ARR given ``resolve_date`` + * ``resolve_date`` + + Examples + -------- + >>> third_kelly(my_price=0.7, market_price=0.4, bankroll=100) + {'my_price': 0.7, 'market_price': 0.4, 'deference': 0.6666, 'adj_price': 0.50, + 'delta_price': 0.3, 'adj_delta_price': 0.1, 'kelly': 0.15, 'target': 12.5, + 'current': 0, 'delta': 12.5, 'max_gain': 31.25, 'modeled_gain': 8.28, + 'expected_roi': 0.188, 'expected_arr': None, 'resolve_date': None} + """ + # TODO: Update docstring + return kelly( + my_price=my_price, + market_price=market_price, + bankroll=bankroll, + resolve_date=resolve_date, + current=current, + deference=0.6666, + ) + + def quarter_kelly(my_price, market_price, bankroll=1, resolve_date=None, current=0): """ Alias for ``kelly`` where ``deference`` is 0.75. diff --git a/squigglepy/version.py b/squigglepy/version.py index 6ba181f..c35739b 100644 --- a/squigglepy/version.py +++ b/squigglepy/version.py @@ -1 +1 @@ -__version__ = "0.29-dev1" +__version__ = "0.29-dev2" diff --git a/tests/test_utils.py b/tests/test_utils.py index fd33cc9..d88754a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -781,6 +781,25 @@ def test_half_kelly(): assert obj["resolve_date"] is None +def test_third_kelly(): + obj = third_kelly(my_price=0.99, market_price=0.01) + assert obj["my_price"] == 0.99 + assert obj["market_price"] == 0.01 + assert obj["deference"] == 0.66 + assert obj["adj_price"] == 0.26 + assert obj["delta_price"] == 0.98 + assert obj["adj_delta_price"] == 0.24 + assert obj["kelly"] == 0.247 + assert obj["target"] == 0.25 + assert obj["current"] == 0 + assert obj["delta"] == 0.25 + assert obj["max_gain"] == 24.75 + assert obj["modeled_gain"] == 6.13 + assert obj["expected_roi"] == 24.5 + assert obj["expected_arr"] is None + assert obj["resolve_date"] is None + + def test_quarter_kelly(): obj = quarter_kelly(my_price=0.99, market_price=0.01) assert obj["my_price"] == 0.99 @@ -919,6 +938,33 @@ def test_kelly_with_resolve_date0pt5(): ) +def test_kelly_worked_example(): + half_year_from_today = datetime.now() + timedelta(days=int(round(365 * 0.5))) + half_year_from_today_str = half_year_from_today.strftime("%Y-%m-%d") + obj = sq.kelly(my_price=0.6, market_price=0.17, deference=0.66, bankroll=46288, resolve_date=half_year_from_today_str, current=7300) + assert obj["my_price"] == 0.6 + assert obj["market_price"] == 0.17 + assert obj["deference"] == 0.66 + assert obj["adj_price"] == 0.32 + assert obj["delta_price"] == 0.43 + assert obj["adj_delta_price"] == 0.15 + assert obj["kelly"] == 0.176 + assert obj["target"] == 8153.38 + assert obj["current"] == 7300 + assert obj["delta"] == 853.38 + assert obj["max_gain"] == 6767.31 + assert obj["modeled_gain"] == 1223 + assert obj["expected_roi"] == 0.86 + assert obj["expected_arr"] == 0.86 + assert obj["resolve_date"] == datetime( + half_year_from_today.year, + half_year_from_today.month, + half_year_from_today.day, + 0, + 0, + ) + + def test_extremize(): assert round(extremize(p=0.7, e=1), 3) == 0.7 assert round(extremize(p=0.7, e=1.73), 3) == 0.875 From 3218cdaecc2de5570c9e3a9c080b2a8582ad8384 Mon Sep 17 00:00:00 2001 From: Peter Wildeford Date: Sat, 25 Jan 2025 13:48:57 +0000 Subject: [PATCH 02/13] fix dependencies --- tests/test_utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index d88754a..0477773 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -26,6 +26,7 @@ kelly, full_kelly, half_kelly, + third_kelly, quarter_kelly, one_in, extremize, @@ -941,7 +942,14 @@ def test_kelly_with_resolve_date0pt5(): def test_kelly_worked_example(): half_year_from_today = datetime.now() + timedelta(days=int(round(365 * 0.5))) half_year_from_today_str = half_year_from_today.strftime("%Y-%m-%d") - obj = sq.kelly(my_price=0.6, market_price=0.17, deference=0.66, bankroll=46288, resolve_date=half_year_from_today_str, current=7300) + obj = kelly( + my_price=0.6, + market_price=0.17, + deference=0.66, + bankroll=46288, + resolve_date=half_year_from_today_str, + current=7300, + ) assert obj["my_price"] == 0.6 assert obj["market_price"] == 0.17 assert obj["deference"] == 0.66 From bc9dafaadffc2eedfd388d9b4fa53ff8ac4b0696 Mon Sep 17 00:00:00 2001 From: Peter Wildeford Date: Sat, 25 Jan 2025 13:49:00 +0000 Subject: [PATCH 03/13] update version --- pyproject.toml | 2 +- squigglepy/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 935189d..40e11d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "squigglepy" -version = "0.29-dev2" +version = "0.29-dev3" description = "Squiggle programming language for intuitive probabilistic estimation features in Python" authors = ["Peter Wildeford "] license = "MIT" diff --git a/squigglepy/version.py b/squigglepy/version.py index c35739b..aed89f1 100644 --- a/squigglepy/version.py +++ b/squigglepy/version.py @@ -1 +1 @@ -__version__ = "0.29-dev2" +__version__ = "0.29-dev3" From 67d0ed9e428b06dec6083689840dcf503412b88b Mon Sep 17 00:00:00 2001 From: Peter Wildeford Date: Fri, 31 Jan 2025 15:29:54 +0100 Subject: [PATCH 04/13] CHANGES --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 20a0a41..ca0a0bb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ ## v0.29 +* Fixes a bug where `max_gain` and `modeled_gain` were incorrect in kelly output. * Added `third_kelly` as an alias for `kelly` with deference = 0.66. * Allows Bernoulli distributions to be defined with p=0 or p=1 * Added a `Makefile` to help simplify testing and linting workflows From ac3dd2fcd6e2961179e4341bcb1a3d66d283036a Mon Sep 17 00:00:00 2001 From: Peter Wildeford Date: Fri, 31 Jan 2025 15:30:00 +0100 Subject: [PATCH 05/13] fix some tests --- tests/test_utils.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 0477773..f0c3030 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -737,8 +737,8 @@ def test_kelly_defaults(): assert obj["target"] == 0.99 assert obj["current"] == 0 assert obj["delta"] == 0.99 - assert obj["max_gain"] == 98.99 - assert obj["modeled_gain"] == 97.99 + assert obj["max_gain"] == 98 + assert obj["modeled_gain"] == 97.01 assert obj["expected_roi"] == 98 assert obj["expected_arr"] is None assert obj["resolve_date"] is None @@ -756,8 +756,8 @@ def test_full_kelly(): assert obj["target"] == 0.99 assert obj["current"] == 0 assert obj["delta"] == 0.99 - assert obj["max_gain"] == 98.99 - assert obj["modeled_gain"] == 97.99 + assert obj["max_gain"] == 98 + assert obj["modeled_gain"] == 97.01 assert obj["expected_roi"] == 98 assert obj["expected_arr"] is None assert obj["resolve_date"] is None @@ -775,8 +775,8 @@ def test_half_kelly(): assert obj["target"] == 0.49 assert obj["current"] == 0 assert obj["delta"] == 0.49 - assert obj["max_gain"] == 49.49 - assert obj["modeled_gain"] == 24.5 + assert obj["max_gain"] == 49 + assert obj["modeled_gain"] == 24.25 assert obj["expected_roi"] == 49 assert obj["expected_arr"] is None assert obj["resolve_date"] is None @@ -786,7 +786,7 @@ def test_third_kelly(): obj = third_kelly(my_price=0.99, market_price=0.01) assert obj["my_price"] == 0.99 assert obj["market_price"] == 0.01 - assert obj["deference"] == 0.66 + assert obj["deference"] == 0.667 assert obj["adj_price"] == 0.26 assert obj["delta_price"] == 0.98 assert obj["adj_delta_price"] == 0.24 @@ -872,7 +872,7 @@ def test_kelly_with_resolve_date(): assert obj["target"] == 0.99 assert obj["current"] == 0 assert obj["delta"] == 0.99 - assert obj["max_gain"] == 98.99 + assert obj["max_gain"] == 98 assert obj["modeled_gain"] == 97.99 assert obj["expected_roi"] == 98 assert obj["expected_arr"] == 99.258 @@ -899,7 +899,7 @@ def test_kelly_with_resolve_date2(): assert obj["target"] == 0.99 assert obj["current"] == 0 assert obj["delta"] == 0.99 - assert obj["max_gain"] == 98.99 + assert obj["max_gain"] == 98 assert obj["modeled_gain"] == 97.99 assert obj["expected_roi"] == 98 assert obj["expected_arr"] == 8.981 From 6a6ee2d7425365b211378c82b1a96a935e52b1a2 Mon Sep 17 00:00:00 2001 From: Peter Wildeford Date: Wed, 2 Apr 2025 11:55:27 +0200 Subject: [PATCH 06/13] add buckets --- CHANGES.md | 3 +- squigglepy/utils.py | 96 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ca0a0bb..e856ac2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,8 @@ ## v0.29 * Fixes a bug where `max_gain` and `modeled_gain` were incorrect in kelly output. -* Added `third_kelly` as an alias for `kelly` with deference = 0.66. +* Added `bucket_percentages` to more easily get the percentage of values within a bucket. (TODO: Add tests for this. Also maybe make a separate bucketing utility?) +* Added `third_kelly` as an alias for `kelly` with deference = 0.66. (TODO: Fix tests) * Allows Bernoulli distributions to be defined with p=0 or p=1 * Added a `Makefile` to help simplify testing and linting workflows diff --git a/squigglepy/utils.py b/squigglepy/utils.py index 3537a04..e1d5b93 100644 --- a/squigglepy/utils.py +++ b/squigglepy/utils.py @@ -5,6 +5,7 @@ from datetime import datetime from collections import Counter from collections.abc import Iterable +from typing import List, Tuple, Union, Optional import importlib import importlib.util @@ -1330,3 +1331,98 @@ def sharpe_ratio(returns, risk_free_rate=0): 0.7898 """ return (np.mean(returns) - risk_free_rate) / np.std(returns) + + +def bucket_percentages( + data: np.ndarray, + bins: Optional[Union[int, List[float], np.ndarray]] = None, + custom_bins: Optional[List[Tuple[float, float]]] = None, + normalize: bool = True, + as_percentage: bool = True, + labels: Optional[List[str]] = None +) -> dict: + """ + Calculate the percentage or count of values falling into specified buckets. + + Parameters: + ----------- + data : np.ndarray + Input array of values to categorize. + bins : int, list, or numpy array, optional + Either the number of equal-width bins or the bin edges. + Default is [-np.inf, 2, 4, 6, 8, 10, np.inf]. + custom_bins : list of tuples, optional + List of (low, high) tuples defining custom non-uniform bins. + Overrides the 'bins' parameter if provided. + normalize : bool, default=True + If True, return frequencies (counts divided by total length). + as_percentage : bool, default=True + If True and normalize is True, multiply frequencies by 100. + labels : list of str, optional + Custom labels for each bin. Length must match the number of bins. + + Returns: + -------- + dict + A dictionary mapping bin labels to their respective percentages or counts. + + Examples: + --------- + >>> import numpy as np + >>> data = np.random.normal(5, 2, 10000) + >>> + >>> # Basic usage with default bins + >>> bucket_percentages(data) + {'(-inf, 2)': 6.78, '[2, 4)': 24.52, '[4, 6)': 35.44, '[6, 8)': 23.39, '[8, 10)': 7.23, '[10, inf)': 2.64} + >>> + >>> # Custom bin edges + >>> bucket_percentages(data, bins=[0, 3, 6, 9, 12]) + {'[0, 3)': 11.97, '[3, 6)': 50.77, '[6, 9)': 33.0, '[9, 12)': 4.26} + >>> + >>> # Custom bin ranges and labels + >>> custom_bins = [(-np.inf, 0), (0, 5), (5, 10), (10, np.inf)] + >>> bucket_percentages(data, custom_bins=custom_bins, labels=['Negative', 'Low', 'Medium', 'High']) + {'Negative': 0.09, 'Low': 48.65, 'Medium': 48.62, 'High': 2.64} + >>> + >>> # Get raw counts instead of percentages + >>> bucket_percentages(data, bins=5, normalize=False, as_percentage=False) + {'[-1.64, 1.17)': 374, '[1.17, 3.97)': 2187, '[3.97, 6.78)': 4615, '[6.78, 9.58)': 2496, '[9.58, 12.39)': 328} + """ + + if custom_bins is not None: + # Use custom bin ranges + result = {} + for i, (low, high) in enumerate(custom_bins): + count = np.sum((data >= low) & (data < high)) + result[i] = count + + bin_edges = [b[0] for b in custom_bins] + [custom_bins[-1][1]] + bin_count = len(custom_bins) + else: + counts, bin_edges = np.histogram(data, bins=bins) + result = {i: count for i, count in enumerate(counts)} + bin_count = len(bin_edges) - 1 + + if normalize: + total = len(data) + result = {k: v / total * (100 if as_percentage else 1) for k, v in result.items()} + + if labels is None: + labels = [] + for i in range(bin_count): + if bin_edges[i] == -np.inf: + left_bracket = "(" + else: + left_bracket = "[" + + if bin_edges[i+1] == np.inf: + right_bracket = ")" + else: + right_bracket = ")" + + labels.append(f"{left_bracket}{bin_edges[i]}, {bin_edges[i+1]}{right_bracket}") + + if len(labels) != bin_count: + raise ValueError(f"Number of labels ({len(labels)}) must match number of bins ({bin_count})") + + return {labels[k]: v for k, v in result.items()} From 16bea624bd5d4aafadcbc36fb63a4bd53cae8652 Mon Sep 17 00:00:00 2001 From: Peter Wildeford Date: Wed, 16 Apr 2025 17:38:30 +0200 Subject: [PATCH 07/13] welcome Claude Code --- CLAUDE.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..83c77f4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,20 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build/Test/Lint Commands +- Install: `poetry install --with dev` +- Run all tests: `make test` or `pytest && pip3 install . && python3 tests/integration.py` +- Run single test: `pytest tests/test_file.py::test_function_name -v` +- Format code: `make format` or `black . && ruff check . --fix` +- Lint code: `make lint` or `ruff check .` + +## Style Guidelines +- Line length: 99 characters (configured for both Black and Ruff) +- Imports: stdlib first, third-party next, local imports last +- Naming: CamelCase for classes, snake_case for functions/vars, UPPER_CASE for constants +- Documentation: NumPy-style docstrings with examples, parameters, returns +- Type hints: Use throughout codebase +- Error handling: Validate inputs, use ValueError with descriptive messages +- Use operator overloading (`__add__`, `__mul__`, etc.) and custom operators (`@` for sampling) +- Tests: Descriptive names, unit tests match module structure, use hypothesis for property testing \ No newline at end of file From 0e0f557e7aeb630eeb41f0a9ef70df2953a62aac Mon Sep 17 00:00:00 2001 From: Peter Wildeford Date: Wed, 16 Apr 2025 17:38:49 +0200 Subject: [PATCH 08/13] add invlognorm --- CHANGES.md | 1 + pyproject.toml | 2 +- squigglepy/distributions.py | 154 +++++++++++++++++++++++++++++ squigglepy/samplers.py | 36 +++++++ squigglepy/version.py | 2 +- tests/integration.py | 4 +- tests/test_distributions.py | 190 ++++++++++++++++++++++++++++++++++++ 7 files changed, 386 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e856ac2..67c956d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,7 @@ ## v0.29 * Fixes a bug where `max_gain` and `modeled_gain` were incorrect in kelly output. +* Added `invlognorm` as a new distribution. * Added `bucket_percentages` to more easily get the percentage of values within a bucket. (TODO: Add tests for this. Also maybe make a separate bucketing utility?) * Added `third_kelly` as an alias for `kelly` with deference = 0.66. (TODO: Fix tests) * Allows Bernoulli distributions to be defined with p=0 or p=1 diff --git a/pyproject.toml b/pyproject.toml index 40e11d6..5a7c926 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "squigglepy" -version = "0.29-dev3" +version = "0.29-dev4" description = "Squiggle programming language for intuitive probabilistic estimation features in Python" authors = ["Peter Wildeford "] license = "MIT" diff --git a/squigglepy/distributions.py b/squigglepy/distributions.py index b25dbe8..4ec94d5 100644 --- a/squigglepy/distributions.py +++ b/squigglepy/distributions.py @@ -846,6 +846,92 @@ def __str__(self): return out +class InverseLognormalDistribution(ContinuousDistribution): + def __init__( + self, + x=None, + y=None, + norm_mean=None, + norm_sd=None, + lognorm_mean=None, + lognorm_sd=None, + credibility=90, + lclip=None, + rclip=None, + ): + super().__init__() + self.x = x + self.y = y + self.credibility = credibility + self.norm_mean = norm_mean + self.norm_sd = norm_sd + self.lognorm_mean = lognorm_mean + self.lognorm_sd = lognorm_sd + self.lclip = lclip + self.rclip = rclip + + if self.x is not None and self.y is not None and self.x > self.y: + raise ValueError("`high value` cannot be lower than `low value`") + if self.x is not None and self.x <= 0: + raise ValueError("inverse lognormal distribution must have values > 0") + + if (self.x is None or self.y is None) and self.norm_sd is None and self.lognorm_sd is None: + raise ValueError( + ("must define only one of x/y, norm_mean/norm_sd, " "or lognorm_mean/lognorm_sd") + ) + elif (self.x is not None or self.y is not None) and ( + self.norm_sd is not None or self.lognorm_sd is not None + ): + raise ValueError( + ("must define only one of x/y, norm_mean/norm_sd, " "or lognorm_mean/lognorm_sd") + ) + elif (self.norm_sd is not None or self.norm_mean is not None) and ( + self.lognorm_sd is not None or self.lognorm_mean is not None + ): + raise ValueError( + ("must define only one of x/y, norm_mean/norm_sd, " "or lognorm_mean/lognorm_sd") + ) + elif self.norm_sd is not None and self.norm_mean is None: + self.norm_mean = 0 + elif self.lognorm_sd is not None and self.lognorm_mean is None: + self.lognorm_mean = 1 + + if self.x is not None: + inv_x, inv_y = 1/self.y, 1/self.x # Note: x and y are swapped when inverted + self.norm_mean = (np.log(inv_x) + np.log(inv_y)) / 2 + cdf_value = 0.5 + 0.5 * (self.credibility / 100) + normed_sigma = scipy.stats.norm.ppf(cdf_value) + self.norm_sd = (np.log(inv_y) - self.norm_mean) / normed_sigma + + if self.lognorm_sd is None: + lognorm_mean = np.exp(self.norm_mean + self.norm_sd**2 / 2) + lognorm_var = (np.exp(self.norm_sd**2) - 1) * np.exp(2 * self.norm_mean + self.norm_sd**2) + + self.lognorm_mean = 1 / np.sqrt(lognorm_var + lognorm_mean**2) * np.exp(-self.norm_mean + self.norm_sd**2/2) + self.lognorm_sd = self.lognorm_mean * np.sqrt(np.exp(self.norm_sd**2) - 1) + + elif self.norm_sd is None: + # Start with the relationship between lognormal and its inverse + cv_squared = self.lognorm_sd**2 / self.lognorm_mean**2 # coefficient of variation squared + self.norm_sd = np.sqrt(np.log(1 + cv_squared)) + self.norm_mean = -np.log(self.lognorm_mean) - self.norm_sd**2/2 + + def __str__(self): + out = " invlognorm(lognorm_mean={}, lognorm_sd={}, norm_mean={}, norm_sd={}" + out = out.format( + round(self.lognorm_mean, 2), + round(self.lognorm_sd, 2), + round(self.norm_mean, 2), + round(self.norm_sd, 2), + ) + if self.lclip is not None: + out += ", lclip={}".format(self.lclip) + if self.rclip is not None: + out += ", rclip={}".format(self.rclip) + out += ")" + return out + + def lognorm( x=None, y=None, @@ -913,6 +999,74 @@ def lognorm( ) +def invlognorm( + x=None, + y=None, + credibility=90, + norm_mean=None, + norm_sd=None, + lognorm_mean=None, + lognorm_sd=None, + lclip=None, + rclip=None, +): + """ + Initialize an inverse lognormal distribution. + + This is a left-skewed distribution created by taking the reciprocal of a lognormal distribution. + + Can be defined either via a credible interval from ``x`` to ``y`` (use ``credibility`` or + it will default to being a 90% CI) or defined via underlying normal distribution parameters + or via the inverse lognormal mean and standard deviation. + + Parameters + ---------- + x : float + The low value of a credible interval defined by ``credibility``. Defaults to a 90% CI. + Must be a value greater than 0. + y : float + The high value of a credible interval defined by ``credibility``. Defaults to a 90% CI. + Must be a value greater than 0. + credibility : float + The range of the credibility interval. Defaults to 90. Ignored if the distribution is + defined instead by ``mean`` and ``sd``. + norm_mean : float or None + The mean of the underlying normal distribution. If not defined, defaults to 0. + norm_sd : float + The standard deviation of the underlying normal distribution. + lognorm_mean : float or None + The mean of the inverse lognormal distribution. If not defined, defaults to 1. + lognorm_sd : float + The standard deviation of the inverse lognormal distribution. + lclip : float or None + If not None, any value below ``lclip`` will be coerced to ``lclip``. + rclip : float or None + If not None, any value below ``rclip`` will be coerced to ``rclip``. + + Returns + ------- + InverseLognormalDistribution + + Examples + -------- + >>> invlognorm(1, 10) + invlognorm(lognorm_mean=0.3, lognorm_sd=0.13, norm_mean=-1.15, norm_sd=0.7) + >>> invlognorm(norm_mean=-1, norm_sd=2) + invlognorm(lognorm_mean=7.39, lognorm_sd=54.1, norm_mean=-1, norm_sd=2) + """ + return InverseLognormalDistribution( + x=x, + y=y, + credibility=credibility, + norm_mean=norm_mean, + norm_sd=norm_sd, + lognorm_mean=lognorm_mean, + lognorm_sd=lognorm_sd, + lclip=lclip, + rclip=rclip, + ) + + def to( x, y, credibility=90, lclip=None, rclip=None ) -> Union[LognormalDistribution, NormalDistribution]: diff --git a/squigglepy/samplers.py b/squigglepy/samplers.py index 684aed8..e33bec0 100644 --- a/squigglepy/samplers.py +++ b/squigglepy/samplers.py @@ -37,6 +37,7 @@ GeometricDistribution, LogTDistribution, LognormalDistribution, + InverseLognormalDistribution, MixtureDistribution, NormalDistribution, ParetoDistribution, @@ -113,6 +114,38 @@ def lognormal_sample(mean, sd, samples=1): return _simplify(_get_rng().lognormal(mean, sd, samples)) +def inverse_lognormal_sample(mean, sd, samples=1): + """ + Sample a random number according to an inverse lognormal distribution. + + This is equivalent to taking the reciprocal of samples from a lognormal distribution, + producing a left-skewed distribution (rather than the right-skewed lognormal). + + Parameters + ---------- + mean : float + The mean of the underlying normal distribution in log space. + sd : float + The standard deviation of the underlying normal distribution in log space. + samples : int + The number of samples to return. + + Returns + ------- + float + A random number sampled from an inverse lognormal distribution. + + Examples + -------- + >>> set_seed(42) + >>> inverse_lognormal_sample(0, 1) + 0.7373871753169729 + """ + # Generate lognormal samples and take their reciprocals + lognorm_samples = _get_rng().lognormal(mean, sd, samples) + return _simplify(1.0 / lognorm_samples) + + def t_sample(low=None, high=None, t=20, samples=1, credibility=90): """ Sample a random number according to a t-distribution. @@ -1047,6 +1080,9 @@ def run_dist(dist, pbar=None, tick=1): elif isinstance(dist, LognormalDistribution): samples = lognormal_sample(mean=dist.norm_mean, sd=dist.norm_sd, samples=n) + + elif isinstance(dist, InverseLognormalDistribution): + samples = inverse_lognormal_sample(mean=dist.norm_mean, sd=dist.norm_sd, samples=n) elif isinstance(dist, BinomialDistribution): samples = binomial_sample(n=dist.n, p=dist.p, samples=n) diff --git a/squigglepy/version.py b/squigglepy/version.py index aed89f1..7c3ad61 100644 --- a/squigglepy/version.py +++ b/squigglepy/version.py @@ -1 +1 @@ -__version__ = "0.29-dev3" +__version__ = "0.29-dev4" diff --git a/tests/integration.py b/tests/integration.py index 5fa490e..f94dd94 100644 --- a/tests/integration.py +++ b/tests/integration.py @@ -312,6 +312,7 @@ def move_days(days): sq.sample(sq.norm(-1.67, 1.67)) # This is equivalent to mean=0, sd=1 sq.sample(sq.norm(1, 3), n=100) sq.sample(sq.lognorm(1, 10)) + sq.sample(sq.invlognorm(1, 10)) sq.sample(sq.tdist(1, 10, t=5)) sq.sample(sq.triangular(1, 2, 3)) sq.sample(sq.pert(1, 2, 3, lam=2)) @@ -349,6 +350,7 @@ def move_days(days): ~sq.norm(-1.67, 1.67) sq.norm(1, 3) @ 100 ~sq.lognorm(1, 10) + ~sq.invlognorm(1, 10) ~sq.tdist(1, 10, t=5) ~sq.triangular(1, 2, 3) ~sq.pert(1, 2, 3, lam=2) @@ -366,7 +368,7 @@ def move_days(days): ~sq.discrete([[0.1, 0], [0.3, 1], [0.3, 2], [0.15, 3], [0.15, 4]]) ~sq.discrete([0, 1, 2]) ~sq.mixture([sq.norm(1, 3), sq.norm(4, 10), sq.lognorm(1, 10)], [0.3, 0.3, 0.4]) - ~sq.mixture([[0.3, sq.norm(1, 3)], [0.3, sq.norm(4, 10)], [0.4, sq.lognorm(1, 10)]]) + ~sq.mixture([[0.3, sq.norm(1, 3)], [0.3, sq.norm(4, 10)], [0.4, sq.invlognorm(1, 10)]]) ~sq.norm(1, 3) + ~sq.norm(4, 5) ~sq.norm(1, 3) - ~sq.norm(4, 5) ~sq.norm(1, 3) / ~sq.norm(4, 5) diff --git a/tests/test_distributions.py b/tests/test_distributions.py index 3539fb5..ccb7c6c 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -6,6 +6,7 @@ uniform, norm, lognorm, + invlognorm, binomial, beta, bernoulli, @@ -39,6 +40,7 @@ ExponentialDistribution, GammaDistribution, LogTDistribution, + InverseLognormalDistribution, LognormalDistribution, MixtureDistribution, NormalDistribution, @@ -405,6 +407,194 @@ def test_lognorm_passes_credibility(): assert obj.credibility == 80 +def test_invlognorm(): + assert isinstance(invlognorm(1, 2), InverseLognormalDistribution) + assert invlognorm(1, 2).x == 1 + assert invlognorm(1, 2).y == 2 + assert round(invlognorm(1, 2).norm_mean, 2) == -0.35 + assert round(invlognorm(1, 2).norm_sd, 2) == 0.21 + assert round(invlognorm(1, 2).lognorm_mean, 2) == 1.96 + assert round(invlognorm(1, 2).lognorm_sd, 2) == 0.42 + assert invlognorm(1, 2).credibility == 90 + assert invlognorm(1, 2).lclip is None + assert invlognorm(1, 2).rclip is None + assert str(invlognorm(1, 2)) == ( + " invlognorm(lognorm_mean=1.96, " + "lognorm_sd=0.42, norm_mean=-0.35, norm_sd=0.21)" + ) + + +def test_invlognorm_with_normmean_normsd(): + assert isinstance(invlognorm(norm_mean=1, norm_sd=2), InverseLognormalDistribution) + assert invlognorm(norm_mean=1, norm_sd=2).x is None + assert invlognorm(norm_mean=1, norm_sd=2).y is None + assert invlognorm(norm_mean=1, norm_sd=2).norm_mean == 1 + assert invlognorm(norm_mean=1, norm_sd=2).norm_sd == 2 + assert round(invlognorm(norm_mean=1, norm_sd=2).lognorm_mean, 2) == 0.02 + assert round(invlognorm(norm_mean=1, norm_sd=2).lognorm_sd, 2) == 0.13 + assert invlognorm(norm_mean=1, norm_sd=2).credibility == 90 + assert invlognorm(norm_mean=1, norm_sd=2).lclip is None + assert invlognorm(norm_mean=1, norm_sd=2).rclip is None + assert str(invlognorm(norm_mean=1, norm_sd=2)) == ( + " invlognorm(lognorm_mean=" + "0.02, lognorm_sd=0.13, norm_mean=1," + " norm_sd=2)" + ) + + +def test_invlognorm_with_lognormmean_lognormsd(): + assert isinstance(invlognorm(lognorm_mean=1, lognorm_sd=2), InverseLognormalDistribution) + assert invlognorm(lognorm_mean=1, lognorm_sd=2).x is None + assert invlognorm(lognorm_mean=1, lognorm_sd=2).y is None + assert round(invlognorm(lognorm_mean=1, lognorm_sd=2).norm_mean, 2) == -0.8 + assert round(invlognorm(lognorm_mean=1, lognorm_sd=2).norm_sd, 2) == 1.27 + assert invlognorm(lognorm_mean=1, lognorm_sd=2).lognorm_mean == 1 + assert invlognorm(lognorm_mean=1, lognorm_sd=2).lognorm_sd == 2 + assert invlognorm(lognorm_mean=1, lognorm_sd=2).credibility == 90 + assert invlognorm(lognorm_mean=1, lognorm_sd=2).lclip is None + assert invlognorm(lognorm_mean=1, lognorm_sd=2).rclip is None + assert str(invlognorm(lognorm_mean=1, lognorm_sd=2)) == ( + " invlognorm(lognorm_mean" "=1, lognorm_sd=2, norm_mean=-0.8, " "norm_sd=1.27)" + ) + + +def test_invlognorm_with_just_normsd_infers_zero_norm_mean(): + assert isinstance(invlognorm(norm_sd=2), InverseLognormalDistribution) + assert invlognorm(norm_sd=2).x is None + assert invlognorm(norm_sd=2).y is None + assert invlognorm(norm_sd=2).norm_mean == 0 + assert invlognorm(norm_sd=2).norm_sd == 2 + assert round(invlognorm(norm_sd=2).lognorm_mean, 2) == 0.14 + assert round(invlognorm(norm_sd=2).lognorm_sd, 2) == 0.99 + assert invlognorm(norm_sd=2).credibility == 90 + assert invlognorm(norm_sd=2).lclip is None + assert invlognorm(norm_sd=2).rclip is None + + +def test_invlognorm_with_just_invlognormsd_infers_unit_lognorm_mean(): + assert isinstance(invlognorm(lognorm_sd=2), InverseLognormalDistribution) + assert invlognorm(lognorm_sd=2).x is None + assert invlognorm(lognorm_sd=2).y is None + assert round(invlognorm(lognorm_sd=2).norm_mean, 2) == -0.8 + assert round(invlognorm(lognorm_sd=2).norm_sd, 2) == 1.27 + assert invlognorm(lognorm_sd=2).lognorm_mean == 1 + assert invlognorm(lognorm_sd=2).lognorm_sd == 2 + assert invlognorm(lognorm_sd=2).credibility == 90 + assert invlognorm(lognorm_sd=2).lclip is None + assert invlognorm(lognorm_sd=2).rclip is None + + +def test_invlognorm_blank_raises_value_error(): + with pytest.raises(ValueError) as execinfo: + invlognorm() + msg = "must define only one of x/y, norm_mean/norm_sd, or lognorm_mean/lognorm_sd" + assert msg in str(execinfo.value) + with pytest.raises(ValueError) as execinfo: + invlognorm(norm_mean=0) + assert msg in str(execinfo.value) + with pytest.raises(ValueError) as execinfo: + invlognorm(lognorm_mean=1) + assert msg in str(execinfo.value) + + +def test_invlognorm_overdefinition_value_error(): + with pytest.raises(ValueError) as execinfo: + invlognorm(x=1, y=2, norm_mean=3, norm_sd=4) + assert "must define only one of" in str(execinfo.value) + with pytest.raises(ValueError) as execinfo: + invlognorm(x=1, y=2, lognorm_mean=3, lognorm_sd=4) + assert "must define only one of" in str(execinfo.value) + with pytest.raises(ValueError) as execinfo: + invlognorm(norm_mean=1, norm_sd=2, lognorm_mean=3, lognorm_sd=4) + assert "must define only one of" in str(execinfo.value) + with pytest.raises(ValueError) as execinfo: + invlognorm(x=1, y=2, norm_mean=1, norm_sd=2, lognorm_mean=3, lognorm_sd=4) + assert "must define only one of" in str(execinfo.value) + + +def test_invlognorm_low_gt_high(): + with pytest.raises(ValueError) as execinfo: + invlognorm(10, 5) + assert "`high value` cannot be lower than `low value`" in str(execinfo.value) + + +def test_invlognorm_must_be_gt_0(): + with pytest.raises(ValueError) as execinfo: + invlognorm(0, 5) + assert "inverse lognormal distribution must have values > 0" in str(execinfo.value) + with pytest.raises(ValueError) as execinfo: + invlognorm(-5, 5) + assert "inverse lognormal distribution must have values > 0" in str(execinfo.value) + + +def test_invlognorm_passes_lclip_rclip(): + obj = invlognorm(1, 2, lclip=1) + assert isinstance(obj, InverseLognormalDistribution) + assert obj.lclip == 1 + assert obj.rclip is None + expected_str = ( + " invlognorm(lognorm_mean=1.96, lognorm_sd=0.42," + " norm_mean=-0.35, norm_sd=0.21, lclip=1)" + ) + assert str(obj) == expected_str + obj = invlognorm(1, 2, rclip=1) + assert isinstance(obj, InverseLognormalDistribution) + assert obj.lclip is None + assert obj.rclip == 1 + expected_str = ( + " invlognorm(lognorm_mean=1.96, lognorm_sd=0.42," + " norm_mean=-0.35, norm_sd=0.21, rclip=1)" + ) + assert str(obj) == expected_str + obj = invlognorm(1, 2, lclip=0, rclip=3) + assert isinstance(obj, InverseLognormalDistribution) + assert obj.lclip == 0 + assert obj.rclip == 3 + expected_str = ( + " invlognorm(lognorm_mean=1.96, lognorm_sd=0.42," + " norm_mean=-0.35, norm_sd=0.21, lclip=0, rclip=3)" + ) + assert str(obj) == expected_str + obj = invlognorm(norm_mean=1, norm_sd=2, lclip=0, rclip=3) + assert isinstance(obj, InverseLognormalDistribution) + assert obj.lclip == 0 + assert obj.rclip == 3 + expected_str = ( + " invlognorm(lognorm_mean=0.02, lognorm_sd=0.13," + " norm_mean=1, norm_sd=2, lclip=0, rclip=3)" + ) + assert str(obj) == expected_str + obj = invlognorm(norm_sd=2, lclip=0, rclip=3) + assert isinstance(obj, InverseLognormalDistribution) + assert obj.lclip == 0 + assert obj.rclip == 3 + expected_str = ( + " invlognorm(lognorm_mean=0.14, lognorm_sd=0.99," + " norm_mean=0, norm_sd=2, lclip=0, rclip=3)" + ) + assert str(obj) == expected_str + obj = invlognorm(lognorm_mean=1, lognorm_sd=2, lclip=0, rclip=3) + assert isinstance(obj, InverseLognormalDistribution) + assert obj.lclip == 0 + assert obj.rclip == 3 + expected_str = ( + " invlognorm(lognorm_mean=1, lognorm_sd=2," + " norm_mean=-0.8, norm_sd=1.27, lclip=0, rclip=3)" + ) + assert str(obj) == expected_str + obj = invlognorm(lognorm_sd=2, lclip=0, rclip=3) + assert isinstance(obj, InverseLognormalDistribution) + assert obj.lclip == 0 + assert obj.rclip == 3 + assert str(obj) == expected_str + + +def test_invlognorm_passes_credibility(): + obj = invlognorm(1, 2, credibility=80) + assert isinstance(obj, InverseLognormalDistribution) + assert obj.credibility == 80 + + def test_uniform(): assert isinstance(uniform(0, 1), UniformDistribution) assert uniform(0, 1).x == 0 From c1b43ec9663df537e1a15841ead572fb92f79122 Mon Sep 17 00:00:00 2001 From: Peter Wildeford Date: Wed, 16 Apr 2025 17:45:09 +0200 Subject: [PATCH 09/13] fix kelly tests --- tests/test_utils.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index f0c3030..62a8145 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -787,16 +787,16 @@ def test_third_kelly(): assert obj["my_price"] == 0.99 assert obj["market_price"] == 0.01 assert obj["deference"] == 0.667 - assert obj["adj_price"] == 0.26 + assert obj["adj_price"] == 0.34 assert obj["delta_price"] == 0.98 - assert obj["adj_delta_price"] == 0.24 - assert obj["kelly"] == 0.247 - assert obj["target"] == 0.25 + assert obj["adj_delta_price"] == 0.33 + assert obj["kelly"] == 0.33 + assert obj["target"] == 0.33 assert obj["current"] == 0 - assert obj["delta"] == 0.25 - assert obj["max_gain"] == 24.75 - assert obj["modeled_gain"] == 6.13 - assert obj["expected_roi"] == 24.5 + assert obj["delta"] == 0.33 + assert obj["max_gain"] == 32.67 + assert obj["modeled_gain"] == 10.78 + assert obj["expected_roi"] == 32.673 assert obj["expected_arr"] is None assert obj["resolve_date"] is None @@ -813,8 +813,8 @@ def test_quarter_kelly(): assert obj["target"] == 0.25 assert obj["current"] == 0 assert obj["delta"] == 0.25 - assert obj["max_gain"] == 24.75 - assert obj["modeled_gain"] == 6.13 + assert obj["max_gain"] == 24.5 + assert obj["modeled_gain"] == 6.06 assert obj["expected_roi"] == 24.5 assert obj["expected_arr"] is None assert obj["resolve_date"] is None @@ -832,8 +832,8 @@ def test_kelly_with_bankroll(): assert obj["target"] == 989.9 assert obj["current"] == 0 assert obj["delta"] == 989.9 - assert obj["max_gain"] == 98989.9 - assert obj["modeled_gain"] == 97990.1 + assert obj["max_gain"] == 98000 + assert obj["modeled_gain"] == 97010.1 assert obj["expected_roi"] == 98 assert obj["expected_arr"] is None assert obj["resolve_date"] is None @@ -851,8 +851,8 @@ def test_kelly_with_current(): assert obj["target"] == 989.9 assert obj["current"] == 100 assert obj["delta"] == 889.9 - assert obj["max_gain"] == 98989.9 - assert obj["modeled_gain"] == 97990.1 + assert obj["max_gain"] == 98000 + assert obj["modeled_gain"] == 97010.1 assert obj["expected_roi"] == 98 assert obj["expected_arr"] is None assert obj["resolve_date"] is None @@ -873,7 +873,7 @@ def test_kelly_with_resolve_date(): assert obj["current"] == 0 assert obj["delta"] == 0.99 assert obj["max_gain"] == 98 - assert obj["modeled_gain"] == 97.99 + assert obj["modeled_gain"] == 97.01 assert obj["expected_roi"] == 98 assert obj["expected_arr"] == 99.258 assert obj["resolve_date"] == datetime( @@ -900,7 +900,7 @@ def test_kelly_with_resolve_date2(): assert obj["current"] == 0 assert obj["delta"] == 0.99 assert obj["max_gain"] == 98 - assert obj["modeled_gain"] == 97.99 + assert obj["modeled_gain"] == 97.01 assert obj["expected_roi"] == 98 assert obj["expected_arr"] == 8.981 assert obj["resolve_date"] == datetime( @@ -926,8 +926,8 @@ def test_kelly_with_resolve_date0pt5(): assert obj["target"] == 0.99 assert obj["current"] == 0 assert obj["delta"] == 0.99 - assert obj["max_gain"] == 98.99 - assert obj["modeled_gain"] == 97.99 + assert obj["max_gain"] == 98 + assert obj["modeled_gain"] == 97.01 assert obj["expected_roi"] == 98 assert obj["expected_arr"] == 10575.628 assert obj["resolve_date"] == datetime( @@ -960,10 +960,10 @@ def test_kelly_worked_example(): assert obj["target"] == 8153.38 assert obj["current"] == 7300 assert obj["delta"] == 853.38 - assert obj["max_gain"] == 6767.31 - assert obj["modeled_gain"] == 1223 + assert obj["max_gain"] == 39807.68 + assert obj["modeled_gain"] == 7011.91 assert obj["expected_roi"] == 0.86 - assert obj["expected_arr"] == 0.86 + assert obj["expected_arr"] == 2.495 assert obj["resolve_date"] == datetime( half_year_from_today.year, half_year_from_today.month, From d4f9a957d9b31e7ecfe42d96eaa3a337abf5c039 Mon Sep 17 00:00:00 2001 From: Peter Wildeford Date: Wed, 16 Apr 2025 18:01:53 +0200 Subject: [PATCH 10/13] bucket_percentage tests --- CHANGES.md | 2 +- squigglepy/utils.py | 6 ----- tests/test_utils.py | 56 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 67c956d..816bcc9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ * Fixes a bug where `max_gain` and `modeled_gain` were incorrect in kelly output. * Added `invlognorm` as a new distribution. -* Added `bucket_percentages` to more easily get the percentage of values within a bucket. (TODO: Add tests for this. Also maybe make a separate bucketing utility?) +* Added `bucket_percentages` to more easily get the percentage of values within a bucket. * Added `third_kelly` as an alias for `kelly` with deference = 0.66. (TODO: Fix tests) * Allows Bernoulli distributions to be defined with p=0 or p=1 * Added a `Makefile` to help simplify testing and linting workflows diff --git a/squigglepy/utils.py b/squigglepy/utils.py index e1d5b93..6e768b8 100644 --- a/squigglepy/utils.py +++ b/squigglepy/utils.py @@ -1370,12 +1370,6 @@ def bucket_percentages( --------- >>> import numpy as np >>> data = np.random.normal(5, 2, 10000) - >>> - >>> # Basic usage with default bins - >>> bucket_percentages(data) - {'(-inf, 2)': 6.78, '[2, 4)': 24.52, '[4, 6)': 35.44, '[6, 8)': 23.39, '[8, 10)': 7.23, '[10, inf)': 2.64} - >>> - >>> # Custom bin edges >>> bucket_percentages(data, bins=[0, 3, 6, 9, 12]) {'[0, 3)': 11.97, '[3, 6)': 50.77, '[6, 9)': 33.0, '[9, 12)': 4.26} >>> diff --git a/tests/test_utils.py b/tests/test_utils.py index 62a8145..66b163e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -32,6 +32,7 @@ extremize, sharpe_ratio, normalize, + bucket_percentages, ) from ..squigglepy.rng import set_seed from ..squigglepy.distributions import bernoulli, beta, norm, dist_round, const @@ -993,3 +994,58 @@ def test_core_cuts(): def test_sharpe_ratio(): assert round(sharpe_ratio([0.04, -0.03, 0.05, 0.02, 0.03]), 4) == 0.7898 + + +def test_bucket_percentages(): + data = np.array([1, 3, 5, 7, 9, 11]) + result = bucket_percentages(data, bins=[-np.inf, 2, 4, 6, 8, 10, np.inf]) + assert len(result) == 6 + for val in result.values(): + assert abs(val - 16.67) < 0.01 + + +def test_bucket_percentages_custom_bins(): + data = np.array([1, 3, 5, 7, 9, 11]) + result = bucket_percentages(data, bins=[0, 5, 10, 15]) + expected = { + '[0, 5)': 33.33, + '[5, 10)': 50.0, + '[10, 15)': 16.67, + } + for key in result: + assert abs(result[key] - expected[key]) < 0.01 + + +def test_bucket_percentages_custom_ranges_and_labels(): + data = np.array([1, 3, 5, 7, 9, 11]) + custom_bins = [(-np.inf, 5), (5, 10), (10, np.inf)] + labels = ['Low', 'Medium', 'High'] + result = bucket_percentages(data, custom_bins=custom_bins, labels=labels) + expected = { + 'Low': 33.33, + 'Medium': 50.0, + 'High': 16.67, + } + for key in result: + assert abs(result[key] - expected[key]) < 0.01 + + +def test_bucket_percentages_counts(): + data = np.array([1, 3, 5, 7, 9, 11]) + result = bucket_percentages(data, bins=[0, 5, 10, 15], normalize=False, as_percentage=False) + expected = { + '[0, 5)': 2, + '[5, 10)': 3, + '[10, 15)': 1, + } + assert result == expected + + +def test_bucket_percentages_label_mismatch(): + data = np.array([1, 3, 5, 7, 9, 11]) + custom_bins = [(-np.inf, 5), (5, 10), (10, np.inf)] + labels = ['Low', 'Medium'] # Missing one label + + with pytest.raises(ValueError) as excinfo: + bucket_percentages(data, custom_bins=custom_bins, labels=labels) + assert "Number of labels" in str(excinfo.value) From d297b46f3b0f217fa581fae73ca8e6080d4a90ca Mon Sep 17 00:00:00 2001 From: Peter Wildeford Date: Wed, 16 Apr 2025 18:02:27 +0200 Subject: [PATCH 11/13] black --- squigglepy/distributions.py | 22 +++++++++++++++------- squigglepy/samplers.py | 4 ++-- squigglepy/utils.py | 34 ++++++++++++++++++---------------- tests/test_distributions.py | 4 +++- tests/test_utils.py | 24 ++++++++++++------------ 5 files changed, 50 insertions(+), 38 deletions(-) diff --git a/squigglepy/distributions.py b/squigglepy/distributions.py index 4ec94d5..ec33c5d 100644 --- a/squigglepy/distributions.py +++ b/squigglepy/distributions.py @@ -897,7 +897,7 @@ def __init__( self.lognorm_mean = 1 if self.x is not None: - inv_x, inv_y = 1/self.y, 1/self.x # Note: x and y are swapped when inverted + inv_x, inv_y = 1 / self.y, 1 / self.x # Note: x and y are swapped when inverted self.norm_mean = (np.log(inv_x) + np.log(inv_y)) / 2 cdf_value = 0.5 + 0.5 * (self.credibility / 100) normed_sigma = scipy.stats.norm.ppf(cdf_value) @@ -905,16 +905,24 @@ def __init__( if self.lognorm_sd is None: lognorm_mean = np.exp(self.norm_mean + self.norm_sd**2 / 2) - lognorm_var = (np.exp(self.norm_sd**2) - 1) * np.exp(2 * self.norm_mean + self.norm_sd**2) - - self.lognorm_mean = 1 / np.sqrt(lognorm_var + lognorm_mean**2) * np.exp(-self.norm_mean + self.norm_sd**2/2) + lognorm_var = (np.exp(self.norm_sd**2) - 1) * np.exp( + 2 * self.norm_mean + self.norm_sd**2 + ) + + self.lognorm_mean = ( + 1 + / np.sqrt(lognorm_var + lognorm_mean**2) + * np.exp(-self.norm_mean + self.norm_sd**2 / 2) + ) self.lognorm_sd = self.lognorm_mean * np.sqrt(np.exp(self.norm_sd**2) - 1) - + elif self.norm_sd is None: # Start with the relationship between lognormal and its inverse - cv_squared = self.lognorm_sd**2 / self.lognorm_mean**2 # coefficient of variation squared + cv_squared = ( + self.lognorm_sd**2 / self.lognorm_mean**2 + ) # coefficient of variation squared self.norm_sd = np.sqrt(np.log(1 + cv_squared)) - self.norm_mean = -np.log(self.lognorm_mean) - self.norm_sd**2/2 + self.norm_mean = -np.log(self.lognorm_mean) - self.norm_sd**2 / 2 def __str__(self): out = " invlognorm(lognorm_mean={}, lognorm_sd={}, norm_mean={}, norm_sd={}" diff --git a/squigglepy/samplers.py b/squigglepy/samplers.py index e33bec0..8edc48e 100644 --- a/squigglepy/samplers.py +++ b/squigglepy/samplers.py @@ -117,7 +117,7 @@ def lognormal_sample(mean, sd, samples=1): def inverse_lognormal_sample(mean, sd, samples=1): """ Sample a random number according to an inverse lognormal distribution. - + This is equivalent to taking the reciprocal of samples from a lognormal distribution, producing a left-skewed distribution (rather than the right-skewed lognormal). @@ -1080,7 +1080,7 @@ def run_dist(dist, pbar=None, tick=1): elif isinstance(dist, LognormalDistribution): samples = lognormal_sample(mean=dist.norm_mean, sd=dist.norm_sd, samples=n) - + elif isinstance(dist, InverseLognormalDistribution): samples = inverse_lognormal_sample(mean=dist.norm_mean, sd=dist.norm_sd, samples=n) diff --git a/squigglepy/utils.py b/squigglepy/utils.py index 6e768b8..a8a0ee0 100644 --- a/squigglepy/utils.py +++ b/squigglepy/utils.py @@ -1339,11 +1339,11 @@ def bucket_percentages( custom_bins: Optional[List[Tuple[float, float]]] = None, normalize: bool = True, as_percentage: bool = True, - labels: Optional[List[str]] = None + labels: Optional[List[str]] = None, ) -> dict: """ Calculate the percentage or count of values falling into specified buckets. - + Parameters: ----------- data : np.ndarray @@ -1360,47 +1360,47 @@ def bucket_percentages( If True and normalize is True, multiply frequencies by 100. labels : list of str, optional Custom labels for each bin. Length must match the number of bins. - + Returns: -------- dict A dictionary mapping bin labels to their respective percentages or counts. - + Examples: --------- >>> import numpy as np >>> data = np.random.normal(5, 2, 10000) >>> bucket_percentages(data, bins=[0, 3, 6, 9, 12]) {'[0, 3)': 11.97, '[3, 6)': 50.77, '[6, 9)': 33.0, '[9, 12)': 4.26} - >>> + >>> >>> # Custom bin ranges and labels >>> custom_bins = [(-np.inf, 0), (0, 5), (5, 10), (10, np.inf)] >>> bucket_percentages(data, custom_bins=custom_bins, labels=['Negative', 'Low', 'Medium', 'High']) {'Negative': 0.09, 'Low': 48.65, 'Medium': 48.62, 'High': 2.64} - >>> + >>> >>> # Get raw counts instead of percentages >>> bucket_percentages(data, bins=5, normalize=False, as_percentage=False) {'[-1.64, 1.17)': 374, '[1.17, 3.97)': 2187, '[3.97, 6.78)': 4615, '[6.78, 9.58)': 2496, '[9.58, 12.39)': 328} """ - + if custom_bins is not None: # Use custom bin ranges result = {} for i, (low, high) in enumerate(custom_bins): count = np.sum((data >= low) & (data < high)) result[i] = count - + bin_edges = [b[0] for b in custom_bins] + [custom_bins[-1][1]] bin_count = len(custom_bins) else: counts, bin_edges = np.histogram(data, bins=bins) result = {i: count for i, count in enumerate(counts)} bin_count = len(bin_edges) - 1 - + if normalize: total = len(data) result = {k: v / total * (100 if as_percentage else 1) for k, v in result.items()} - + if labels is None: labels = [] for i in range(bin_count): @@ -1408,15 +1408,17 @@ def bucket_percentages( left_bracket = "(" else: left_bracket = "[" - - if bin_edges[i+1] == np.inf: + + if bin_edges[i + 1] == np.inf: right_bracket = ")" else: right_bracket = ")" - + labels.append(f"{left_bracket}{bin_edges[i]}, {bin_edges[i+1]}{right_bracket}") - + if len(labels) != bin_count: - raise ValueError(f"Number of labels ({len(labels)}) must match number of bins ({bin_count})") - + raise ValueError( + f"Number of labels ({len(labels)}) must match number of bins ({bin_count})" + ) + return {labels[k]: v for k, v in result.items()} diff --git a/tests/test_distributions.py b/tests/test_distributions.py index ccb7c6c..325c721 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -454,7 +454,9 @@ def test_invlognorm_with_lognormmean_lognormsd(): assert invlognorm(lognorm_mean=1, lognorm_sd=2).lclip is None assert invlognorm(lognorm_mean=1, lognorm_sd=2).rclip is None assert str(invlognorm(lognorm_mean=1, lognorm_sd=2)) == ( - " invlognorm(lognorm_mean" "=1, lognorm_sd=2, norm_mean=-0.8, " "norm_sd=1.27)" + " invlognorm(lognorm_mean" + "=1, lognorm_sd=2, norm_mean=-0.8, " + "norm_sd=1.27)" ) diff --git a/tests/test_utils.py b/tests/test_utils.py index 66b163e..bdb63ff 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1008,9 +1008,9 @@ def test_bucket_percentages_custom_bins(): data = np.array([1, 3, 5, 7, 9, 11]) result = bucket_percentages(data, bins=[0, 5, 10, 15]) expected = { - '[0, 5)': 33.33, - '[5, 10)': 50.0, - '[10, 15)': 16.67, + "[0, 5)": 33.33, + "[5, 10)": 50.0, + "[10, 15)": 16.67, } for key in result: assert abs(result[key] - expected[key]) < 0.01 @@ -1019,12 +1019,12 @@ def test_bucket_percentages_custom_bins(): def test_bucket_percentages_custom_ranges_and_labels(): data = np.array([1, 3, 5, 7, 9, 11]) custom_bins = [(-np.inf, 5), (5, 10), (10, np.inf)] - labels = ['Low', 'Medium', 'High'] + labels = ["Low", "Medium", "High"] result = bucket_percentages(data, custom_bins=custom_bins, labels=labels) expected = { - 'Low': 33.33, - 'Medium': 50.0, - 'High': 16.67, + "Low": 33.33, + "Medium": 50.0, + "High": 16.67, } for key in result: assert abs(result[key] - expected[key]) < 0.01 @@ -1034,9 +1034,9 @@ def test_bucket_percentages_counts(): data = np.array([1, 3, 5, 7, 9, 11]) result = bucket_percentages(data, bins=[0, 5, 10, 15], normalize=False, as_percentage=False) expected = { - '[0, 5)': 2, - '[5, 10)': 3, - '[10, 15)': 1, + "[0, 5)": 2, + "[5, 10)": 3, + "[10, 15)": 1, } assert result == expected @@ -1044,8 +1044,8 @@ def test_bucket_percentages_counts(): def test_bucket_percentages_label_mismatch(): data = np.array([1, 3, 5, 7, 9, 11]) custom_bins = [(-np.inf, 5), (5, 10), (10, np.inf)] - labels = ['Low', 'Medium'] # Missing one label - + labels = ["Low", "Medium"] # Missing one label + with pytest.raises(ValueError) as excinfo: bucket_percentages(data, custom_bins=custom_bins, labels=labels) assert "Number of labels" in str(excinfo.value) From 0f8d12c2b37f2ad9cd2278ab4a7a51df350d84d2 Mon Sep 17 00:00:00 2001 From: Peter Wildeford Date: Wed, 16 Apr 2025 18:06:37 +0200 Subject: [PATCH 12/13] try adding setuptools --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 5a7c926..934355f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ repository = "https://github.com/rethinkpriorities/squigglepy" [tool.poetry.dependencies] python = ">=3.9,<3.12" +setuptools = "^69.0.0" numpy = "^1.24.3" scipy = "^1.10.1" tqdm = "^4.65.0" From e863ad3c45503fe59a42abc704cc6b4f94e25318 Mon Sep 17 00:00:00 2001 From: Peter Wildeford Date: Wed, 16 Apr 2025 18:10:24 +0200 Subject: [PATCH 13/13] new lockfile --- poetry.lock | 160 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 134 insertions(+), 26 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1eeab9e..64ca0bb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "ansi2html" @@ -6,6 +6,7 @@ version = "1.8.0" description = "" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "ansi2html-1.8.0-py3-none-any.whl", hash = "sha256:ef9cc9682539dbe524fbf8edad9c9462a308e04bce1170c32daa8fdfd0001785"}, {file = "ansi2html-1.8.0.tar.gz", hash = "sha256:38b82a298482a1fa2613f0f9c9beb3db72a8f832eeac58eb2e47bf32cd37f6d5"}, @@ -21,6 +22,7 @@ version = "23.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, @@ -31,7 +33,7 @@ cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] dev = ["attrs[docs,tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-no-zope = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.1.1) ; platform_python_implementation == \"CPython\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version < \"3.11\"", "pytest-xdist[psutil]"] [[package]] name = "black" @@ -39,6 +41,7 @@ version = "24.10.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, @@ -85,6 +88,7 @@ version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, @@ -96,6 +100,7 @@ version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["dev"] files = [ {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, @@ -180,6 +185,7 @@ version = "8.1.3" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, @@ -194,10 +200,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\" or platform_system == \"Windows\""} [[package]] name = "contourpy" @@ -205,6 +213,7 @@ version = "1.0.7" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:95c3acddf921944f241b6773b767f1cbce71d03307270e2d769fd584d5d1092d"}, {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc1464c97579da9f3ab16763c32e5c5d5bb5fa1ec7ce509a4ca6108b61b84fab"}, @@ -262,6 +271,7 @@ files = [ {file = "contourpy-1.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:64757f6460fc55d7e16ed4f1de193f362104285c667c112b50a804d482777edd"}, {file = "contourpy-1.0.7.tar.gz", hash = "sha256:d8165a088d31798b59e91117d1f5fc3df8168d8b48c4acc10fc0df0d0bdbcc5e"}, ] +markers = {main = "extra == \"plots\""} [package.dependencies] numpy = ">=1.16" @@ -279,6 +289,7 @@ version = "7.2.7" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, @@ -343,7 +354,7 @@ files = [ ] [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cycler" @@ -351,10 +362,12 @@ version = "0.11.0" description = "Composable style cycles" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, ] +markers = {main = "extra == \"plots\""} [[package]] name = "dash" @@ -362,6 +375,7 @@ version = "2.11.1" description = "A Python framework for building reactive web-apps. Developed by Plotly." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "dash-2.11.1-py3-none-any.whl", hash = "sha256:a5b55efc7f139538d95331fa0b5ee0d21600f7dafa9cce4fe4f887b773aba01a"}, {file = "dash-2.11.1.tar.gz", hash = "sha256:1acc4c634311cb306a1086fff315c1f2aaa3898bac362d93bc8db6b1a6474e5c"}, @@ -381,12 +395,12 @@ typing-extensions = ">=4.1.1" Werkzeug = "<2.3.0" [package.extras] -celery = ["celery[redis] (>=5.1.2)", "importlib-metadata (<5)", "redis (>=3.5.3)"] -ci = ["black (==21.6b0)", "black (==22.3.0)", "dash-dangerously-set-inner-html", "dash-flow-example (==0.0.5)", "flake8 (==3.9.2)", "flaky (==3.7.0)", "flask-talisman (==1.0.0)", "isort (==4.3.21)", "jupyterlab (<4.0.0)", "mimesis", "mock (==4.0.3)", "numpy", "openpyxl", "orjson (==3.5.4)", "orjson (==3.6.7)", "pandas (==1.1.5)", "pandas (>=1.4.0)", "preconditions", "pyarrow", "pyarrow (<3)", "pylint (==2.13.5)", "pytest-mock", "pytest-rerunfailures", "pytest-sugar (==0.9.6)", "xlrd (<2)", "xlrd (>=2.0.1)"] +celery = ["celery[redis] (>=5.1.2)", "importlib-metadata (<5) ; python_version < \"3.8\"", "redis (>=3.5.3)"] +ci = ["black (==21.6b0) ; python_version < \"3.7\"", "black (==22.3.0) ; python_version >= \"3.7\"", "dash-dangerously-set-inner-html", "dash-flow-example (==0.0.5)", "flake8 (==3.9.2)", "flaky (==3.7.0)", "flask-talisman (==1.0.0)", "isort (==4.3.21) ; python_version < \"3.7\"", "jupyterlab (<4.0.0)", "mimesis", "mock (==4.0.3)", "numpy", "openpyxl ; python_version >= \"3.8\"", "orjson (==3.5.4) ; python_version < \"3.7\"", "orjson (==3.6.7) ; python_version >= \"3.7\"", "pandas (==1.1.5) ; python_version < \"3.8\"", "pandas (>=1.4.0) ; python_version >= \"3.8\"", "preconditions", "pyarrow (<3) ; python_version < \"3.7\"", "pyarrow ; python_version >= \"3.7\"", "pylint (==2.13.5)", "pytest-mock", "pytest-rerunfailures", "pytest-sugar (==0.9.6)", "xlrd (<2) ; python_version < \"3.8\"", "xlrd (>=2.0.1) ; python_version >= \"3.8\""] compress = ["flask-compress"] dev = ["PyYAML (>=5.4.1)", "coloredlogs (>=15.0.1)", "fire (>=0.4.0)"] diskcache = ["diskcache (>=5.2.1)", "multiprocess (>=0.70.12)", "psutil (>=5.8.0)"] -testing = ["beautifulsoup4 (>=4.8.2)", "cryptography (<3.4)", "dash-testing-stub (>=0.0.2)", "lxml (>=4.6.2)", "multiprocess (>=0.70.12)", "percy (>=2.0.2)", "psutil (>=5.8.0)", "pytest (>=6.0.2)", "requests[security] (>=2.21.0)", "selenium (>=3.141.0,<=4.2.0)", "waitress (>=1.4.4)"] +testing = ["beautifulsoup4 (>=4.8.2)", "cryptography (<3.4) ; python_version < \"3.7\"", "dash-testing-stub (>=0.0.2)", "lxml (>=4.6.2)", "multiprocess (>=0.70.12)", "percy (>=2.0.2)", "psutil (>=5.8.0)", "pytest (>=6.0.2)", "requests[security] (>=2.21.0)", "selenium (>=3.141.0,<=4.2.0)", "waitress (>=1.4.4)"] [[package]] name = "dash-core-components" @@ -394,6 +408,7 @@ version = "2.0.0" description = "Core component suite for Dash" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "dash_core_components-2.0.0-py3-none-any.whl", hash = "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346"}, {file = "dash_core_components-2.0.0.tar.gz", hash = "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"}, @@ -405,6 +420,7 @@ version = "2.0.0" description = "Vanilla HTML components for Dash" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "dash_html_components-2.0.0-py3-none-any.whl", hash = "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"}, {file = "dash_html_components-2.0.0.tar.gz", hash = "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50"}, @@ -416,6 +432,7 @@ version = "5.0.0" description = "Dash table" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"}, {file = "dash_table-5.0.0.tar.gz", hash = "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308"}, @@ -427,6 +444,7 @@ version = "0.3.6" description = "serialize all of python" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, @@ -441,6 +459,8 @@ version = "1.1.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, @@ -455,6 +475,7 @@ version = "2.2.5" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "Flask-2.2.5-py3-none-any.whl", hash = "sha256:58107ed83443e86067e41eff4631b058178191a355886f8e479e347fa1285fdf"}, {file = "Flask-2.2.5.tar.gz", hash = "sha256:edee9b0a7ff26621bd5a8c10ff484ae28737a2410d99b0bb9a6850c7fb977aa0"}, @@ -477,6 +498,7 @@ version = "4.40.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "fonttools-4.40.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b802dcbf9bcff74672f292b2466f6589ab8736ce4dcf36f48eb994c2847c4b30"}, {file = "fonttools-4.40.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f6e3fa3da923063c286320e728ba2270e49c73386e3a711aa680f4b0747d692"}, @@ -513,20 +535,21 @@ files = [ {file = "fonttools-4.40.0-py3-none-any.whl", hash = "sha256:200729d12461e2038700d31f0d49ad5a7b55855dec7525074979a06b46f88505"}, {file = "fonttools-4.40.0.tar.gz", hash = "sha256:337b6e83d7ee73c40ea62407f2ce03b07c3459e213b6f332b94a69923b9e1cb9"}, ] +markers = {main = "extra == \"plots\""} [package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0) ; python_version <= \"3.11\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "scipy"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "scipy ; platform_python_implementation != \"PyPy\""] lxml = ["lxml (>=4.0,<5)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] -type1 = ["xattr"] +type1 = ["xattr ; sys_platform == \"darwin\""] ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.0.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] +unicode = ["unicodedata2 (>=15.0.0) ; python_version <= \"3.11\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] [[package]] name = "hypofuzz" @@ -534,6 +557,7 @@ version = "23.6.1" description = "Adaptive fuzzing for property-based tests" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "hypofuzz-23.6.1-py3-none-any.whl", hash = "sha256:111f55a05ba7d9c633bdcfc03a9972b593dc62813c0d25ca980cd7c641f31683"}, {file = "hypofuzz-23.6.1.tar.gz", hash = "sha256:34f8dde8a0f92ca4d246bb62b64bf4dc573bee21cebf4c94015e754cb165ce6b"}, @@ -559,6 +583,7 @@ version = "6.78.3" description = "A library for property-based testing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "hypothesis-6.78.3-py3-none-any.whl", hash = "sha256:6a853db06edca9867043cf6c14e5dff0e9ba8a7ad87b63d880016ca8469f42ba"}, {file = "hypothesis-6.78.3.tar.gz", hash = "sha256:e14ee83db4d2008d790b1d90cb80c5cee10b86c8babcee4c12585e068b682f4b"}, @@ -573,7 +598,7 @@ rich = {version = ">=9.0.0", optional = true, markers = "extra == \"cli\""} sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] -all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "importlib-metadata (>=3.6)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.16.0)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2023.3)"] +all = ["backports.zoneinfo (>=0.2.1) ; python_version < \"3.9\"", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "importlib-metadata (>=3.6) ; python_version < \"3.8\"", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.16.0)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2023.3) ; sys_platform == \"win32\""] cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] dateutil = ["python-dateutil (>=1.4)"] @@ -586,7 +611,7 @@ pandas = ["pandas (>=1.1)"] pytest = ["pytest (>=4.6)"] pytz = ["pytz (>=2014.1)"] redis = ["redis (>=3.0.0)"] -zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2023.3)"] +zoneinfo = ["backports.zoneinfo (>=0.2.1) ; python_version < \"3.9\"", "tzdata (>=2023.3) ; sys_platform == \"win32\""] [[package]] name = "idna" @@ -594,6 +619,7 @@ version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, @@ -605,6 +631,8 @@ version = "6.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version == \"3.9\"" files = [ {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, @@ -616,7 +644,7 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "importlib-resources" @@ -624,17 +652,19 @@ version = "5.12.0" description = "Read resources from Python packages" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, ] +markers = {main = "extra == \"plots\" and python_version == \"3.9\"", dev = "python_version == \"3.9\""} [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8 ; python_version < \"3.12\"", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\""] [[package]] name = "iniconfig" @@ -642,6 +672,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -653,6 +684,7 @@ version = "2.1.2" description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, @@ -664,6 +696,7 @@ version = "3.1.2" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, @@ -681,6 +714,7 @@ version = "1.4.4" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"}, {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"}, @@ -751,6 +785,7 @@ files = [ {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"}, {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, ] +markers = {main = "extra == \"plots\""} [[package]] name = "libcst" @@ -758,6 +793,7 @@ version = "1.0.1" description = "A concrete syntax tree with AST-like properties for Python 3.5, 3.6, 3.7, 3.8, 3.9, and 3.10 programs." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "libcst-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80423311f09fc5fc3270ede44d30d9d8d3c2d3dd50dbf703a581ca7346949fa6"}, {file = "libcst-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9d6dec2a3c443792e6af7c36fadc256e4ea586214c76b52f0d18118811dbe351"}, @@ -797,7 +833,7 @@ typing-extensions = ">=3.7.4.2" typing-inspect = ">=0.4.0" [package.extras] -dev = ["Sphinx (>=5.1.1)", "black (==23.3.0)", "build (>=0.10.0)", "coverage (>=4.5.4)", "fixit (==0.1.1)", "flake8 (>=3.7.8,<5)", "hypothesis (>=4.36.0)", "hypothesmith (>=0.0.4)", "jinja2 (==3.1.2)", "jupyter (>=1.0.0)", "maturin (>=0.8.3,<0.16)", "nbsphinx (>=0.4.2)", "prompt-toolkit (>=2.0.9)", "pyre-check (==0.9.10)", "setuptools-rust (>=1.5.2)", "setuptools-scm (>=6.0.1)", "slotscheck (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "ufmt (==2.1.0)", "usort (==1.0.7)"] +dev = ["Sphinx (>=5.1.1)", "black (==23.3.0)", "build (>=0.10.0)", "coverage (>=4.5.4)", "fixit (==0.1.1)", "flake8 (>=3.7.8,<5)", "hypothesis (>=4.36.0)", "hypothesmith (>=0.0.4)", "jinja2 (==3.1.2)", "jupyter (>=1.0.0)", "maturin (>=0.8.3,<0.16)", "nbsphinx (>=0.4.2)", "prompt-toolkit (>=2.0.9)", "pyre-check (==0.9.10) ; platform_system != \"Windows\"", "setuptools-rust (>=1.5.2)", "setuptools-scm (>=6.0.1)", "slotscheck (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "ufmt (==2.1.0)", "usort (==1.0.7)"] [[package]] name = "markdown-it-py" @@ -805,6 +841,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -829,6 +866,7 @@ version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, @@ -898,6 +936,7 @@ version = "3.7.1" description = "Python plotting package" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:95cbc13c1fc6844ab8812a525bbc237fa1470863ff3dace7352e910519e194b1"}, {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:08308bae9e91aca1ec6fd6dda66237eef9f6294ddb17f0d0b3c863169bf82353"}, @@ -941,6 +980,7 @@ files = [ {file = "matplotlib-3.7.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:97cc368a7268141afb5690760921765ed34867ffb9655dd325ed207af85c7529"}, {file = "matplotlib-3.7.1.tar.gz", hash = "sha256:7b73305f25eab4541bd7ee0b96d87e53ae9c9f1823be5659b806cd85786fe882"}, ] +markers = {main = "extra == \"plots\""} [package.dependencies] contourpy = ">=1.0.1" @@ -960,6 +1000,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -971,6 +1012,7 @@ version = "0.15.1" description = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "msgspec-0.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:217fa8a0eba122401c3fae3d07e1444556447ba3b9d65fc6647421b35430f2e2"}, {file = "msgspec-0.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cd1ae653ad3e3914dafa11156243e92a594e2c916a19dbbcf72102a1bef812c2"}, @@ -1004,10 +1046,10 @@ files = [ ] [package.extras] -dev = ["attrs", "coverage", "furo", "gcovr", "ipython", "msgpack", "mypy", "pre-commit", "pyright", "pytest", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "tomli", "tomli-w"] +dev = ["attrs", "coverage", "furo", "gcovr", "ipython", "msgpack", "mypy", "pre-commit", "pyright", "pytest", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "tomli ; python_version < \"3.11\"", "tomli-w"] doc = ["furo", "ipython", "sphinx", "sphinx-copybutton", "sphinx-design"] -test = ["attrs", "msgpack", "mypy", "pyright", "pytest", "pyyaml", "tomli", "tomli-w"] -toml = ["tomli", "tomli-w"] +test = ["attrs", "msgpack", "mypy", "pyright", "pytest", "pyyaml", "tomli ; python_version < \"3.11\"", "tomli-w"] +toml = ["tomli ; python_version < \"3.11\"", "tomli-w"] yaml = ["pyyaml"] [[package]] @@ -1016,6 +1058,7 @@ version = "0.70.14" description = "better multiprocessing and multithreading in python" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "multiprocess-0.70.14-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:560a27540daef4ce8b24ed3cc2496a3c670df66c96d02461a4da67473685adf3"}, {file = "multiprocess-0.70.14-pp37-pypy37_pp73-manylinux_2_24_i686.whl", hash = "sha256:bfbbfa36f400b81d1978c940616bc77776424e5e34cb0c94974b178d727cfcd5"}, @@ -1042,6 +1085,7 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -1053,6 +1097,7 @@ version = "1.5.6" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "nest_asyncio-1.5.6-py3-none-any.whl", hash = "sha256:b9a953fb40dceaa587d109609098db21900182b16440652454a146cffb06e8b8"}, {file = "nest_asyncio-1.5.6.tar.gz", hash = "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290"}, @@ -1064,6 +1109,7 @@ version = "1.24.3" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"}, {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"}, @@ -1101,10 +1147,12 @@ version = "23.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +markers = {main = "extra == \"plots\""} [[package]] name = "pandas" @@ -1112,6 +1160,7 @@ version = "2.0.2" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pandas-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ebb9f1c22ddb828e7fd017ea265a59d80461d5a79154b49a4207bd17514d122"}, {file = "pandas-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eb09a242184092f424b2edd06eb2b99d06dc07eeddff9929e8667d4ed44e181"}, @@ -1139,12 +1188,13 @@ files = [ {file = "pandas-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:77550c8909ebc23e56a89f91b40ad01b50c42cfbfab49b3393694a50549295ea"}, {file = "pandas-2.0.2.tar.gz", hash = "sha256:dd5476b6c3fe410ee95926873f377b856dbc4e81a9c605a0dc05aaccc6a7c6c6"}, ] +markers = {main = "extra == \"ecosystem\""} [package.dependencies] numpy = [ {version = ">=1.20.3", markers = "python_version < \"3.10\""}, {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.21.0", markers = "python_version == \"3.10\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -1179,6 +1229,7 @@ version = "0.3.0" description = "parallel graph management and execution in heterogeneous computing" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "pathos-0.3.0-py3-none-any.whl", hash = "sha256:b1f5a79b1c79a594330d451832642ee5bb61dd77dc75ba9e5c72087c77e8994c"}, {file = "pathos-0.3.0.tar.gz", hash = "sha256:24fa8db51fbd9284da8e191794097c4bb2aa3fce411090e57af6385e61b97e09"}, @@ -1196,6 +1247,7 @@ version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, @@ -1207,6 +1259,7 @@ version = "9.5.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, @@ -1275,6 +1328,7 @@ files = [ {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, ] +markers = {main = "extra == \"plots\""} [package.extras] docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] @@ -1286,6 +1340,7 @@ version = "3.5.3" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "platformdirs-3.5.3-py3-none-any.whl", hash = "sha256:0ade98a4895e87dc51d47151f7d2ec290365a585151d97b4d8d6312ed6132fed"}, {file = "platformdirs-3.5.3.tar.gz", hash = "sha256:e48fabd87db8f3a7df7150a4a5ea22c546ee8bc39bc2473244730d4b56d2cc4e"}, @@ -1301,6 +1356,7 @@ version = "5.15.0" description = "An open-source, interactive data visualization library for Python" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "plotly-5.15.0-py2.py3-none-any.whl", hash = "sha256:3508876bbd6aefb8a692c21a7128ca87ce42498dd041efa5c933ee44b55aab24"}, {file = "plotly-5.15.0.tar.gz", hash = "sha256:822eabe53997d5ebf23c77e1d1fcbf3bb6aa745eb05d532afd4b6f9a2e2ab02f"}, @@ -1316,6 +1372,7 @@ version = "1.0.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, @@ -1331,6 +1388,7 @@ version = "0.3.2" description = "utilities for filesystem exploration and automated builds" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "pox-0.3.2-py3-none-any.whl", hash = "sha256:56fe2f099ecd8a557b8948082504492de90e8598c34733c9b1fdeca8f7b6de61"}, {file = "pox-0.3.2.tar.gz", hash = "sha256:e825225297638d6e3d49415f8cfb65407a5d15e56f2fb7fe9d9b9e3050c65ee1"}, @@ -1342,6 +1400,7 @@ version = "1.7.6.6" description = "distributed and parallel python" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "ppft-1.7.6.6-py3-none-any.whl", hash = "sha256:f355d2caeed8bd7c9e4a860c471f31f7e66d1ada2791ab5458ea7dca15a51e41"}, {file = "ppft-1.7.6.6.tar.gz", hash = "sha256:f933f0404f3e808bc860745acb3b79cd4fe31ea19a20889a645f900415be60f1"}, @@ -1356,6 +1415,7 @@ version = "5.9.5" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] files = [ {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, @@ -1374,7 +1434,7 @@ files = [ ] [package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +test = ["enum34 ; python_version <= \"3.4\"", "ipaddress ; python_version < \"3.0\"", "mock ; python_version < \"3.0\"", "pywin32 ; sys_platform == \"win32\"", "wmi ; sys_platform == \"win32\""] [[package]] name = "pygments" @@ -1382,13 +1442,14 @@ version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, ] [package.extras] -plugins = ["importlib-metadata"] +plugins = ["importlib-metadata ; python_version < \"3.8\""] [[package]] name = "pyparsing" @@ -1396,10 +1457,12 @@ version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" +groups = ["main", "dev"] files = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] +markers = {main = "extra == \"plots\""} [package.extras] diagrams = ["jinja2", "railroad-diagrams"] @@ -1410,6 +1473,7 @@ version = "7.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-7.3.2-py3-none-any.whl", hash = "sha256:cdcbd012c9312258922f8cd3f1b62a6580fdced17db6014896053d47cddf9295"}, {file = "pytest-7.3.2.tar.gz", hash = "sha256:ee990a3cc55ba808b80795a79944756f315c67c12b56abd3ac993a7b8c17030b"}, @@ -1432,6 +1496,7 @@ version = "3.10.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, @@ -1449,10 +1514,12 @@ version = "2.8.2" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] +markers = {main = "extra == \"plots\" or extra == \"ecosystem\""} [package.dependencies] six = ">=1.5" @@ -1463,10 +1530,12 @@ version = "2023.3" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main", "dev"] files = [ {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] +markers = {main = "extra == \"ecosystem\""} [[package]] name = "pyyaml" @@ -1474,6 +1543,7 @@ version = "6.0" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, @@ -1523,6 +1593,7 @@ version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, @@ -1544,6 +1615,7 @@ version = "1.3.4" description = "Retrying" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "retrying-1.3.4-py3-none-any.whl", hash = "sha256:8cc4d43cb8e1125e0ff3344e9de678fefd85db3b750b81b2240dc0183af37b35"}, {file = "retrying-1.3.4.tar.gz", hash = "sha256:345da8c5765bd982b1d1915deb9102fd3d1f7ad16bd84a9700b85f64d24e8f3e"}, @@ -1558,6 +1630,7 @@ version = "13.4.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" +groups = ["dev"] files = [ {file = "rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"}, {file = "rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"}, @@ -1576,6 +1649,7 @@ version = "0.0.272" description = "An extremely fast Python linter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.0.272-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:ae9b57546e118660175d45d264b87e9b4c19405c75b587b6e4d21e6a17bf4fdf"}, {file = "ruff-0.0.272-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:1609b864a8d7ee75a8c07578bdea0a7db75a144404e75ef3162e0042bfdc100d"}, @@ -1602,6 +1676,7 @@ version = "1.10.1" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = "<3.12,>=3.8" +groups = ["main"] files = [ {file = "scipy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7354fd7527a4b0377ce55f286805b34e8c54b91be865bac273f527e1b839019"}, {file = "scipy-1.10.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4b3f429188c66603a1a5c549fb414e4d3bdc2a24792e061ffbd607d3d75fd84e"}, @@ -1640,6 +1715,7 @@ version = "0.12.2" description = "Statistical data visualization" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "seaborn-0.12.2-py3-none-any.whl", hash = "sha256:ebf15355a4dba46037dfd65b7350f014ceb1f13c05e814eda2c9f5fd731afc08"}, {file = "seaborn-0.12.2.tar.gz", hash = "sha256:374645f36509d0dcab895cba5b47daf0586f77bfe3b36c97c607db7da5be0139"}, @@ -1655,16 +1731,35 @@ dev = ["flake8", "flit", "mypy", "pandas-stubs", "pre-commit", "pytest", "pytest docs = ["ipykernel", "nbconvert", "numpydoc", "pydata_sphinx_theme (==0.10.0rc2)", "pyyaml", "sphinx-copybutton", "sphinx-design", "sphinx-issues"] stats = ["scipy (>=1.3)", "statsmodels (>=0.10)"] +[[package]] +name = "setuptools" +version = "69.5.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov ; platform_python_implementation != \"PyPy\"", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main", "dev"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +markers = {main = "extra == \"plots\" or extra == \"ecosystem\""} [[package]] name = "sortedcontainers" @@ -1672,6 +1767,7 @@ version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, @@ -1683,6 +1779,7 @@ version = "8.2.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "tenacity-8.2.2-py3-none-any.whl", hash = "sha256:2f277afb21b851637e8f52e6a613ff08734c347dc19ade928e519d7d2d8569b0"}, {file = "tenacity-8.2.2.tar.gz", hash = "sha256:43af037822bd0029025877f3b2d97cc4d7bb0c2991000a3d59d71517c5c969e0"}, @@ -1697,6 +1794,8 @@ version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -1708,6 +1807,7 @@ version = "4.65.0" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tqdm-4.65.0-py3-none-any.whl", hash = "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671"}, {file = "tqdm-4.65.0.tar.gz", hash = "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5"}, @@ -1728,6 +1828,7 @@ version = "4.6.3" description = "Backported and Experimental Type Hints for Python 3.7+" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, @@ -1739,6 +1840,7 @@ version = "0.9.0" description = "Runtime inspection utilities for typing module." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, @@ -1754,10 +1856,12 @@ version = "2023.3" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main", "dev"] files = [ {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, ] +markers = {main = "extra == \"ecosystem\""} [[package]] name = "urllib3" @@ -1765,13 +1869,14 @@ version = "2.0.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1782,6 +1887,7 @@ version = "2.2.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "Werkzeug-2.2.3-py3-none-any.whl", hash = "sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"}, {file = "Werkzeug-2.2.3.tar.gz", hash = "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe"}, @@ -1799,14 +1905,16 @@ version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] +markers = {main = "extra == \"plots\" and python_version == \"3.9\"", dev = "python_version == \"3.9\""} [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8 ; python_version < \"3.12\"", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\""] [extras] all = [] @@ -1814,6 +1922,6 @@ ecosystem = ["pandas"] plots = ["matplotlib"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.9,<3.12" -content-hash = "95f743356b3956905f791cc3067d80b8b33d72070502fdeaa005b7396645d312" +content-hash = "a678f950b77db8d35a9bf26c6d29e3a4f94306569984f9d939a1b889c3bde4fd"