diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..caba6e4 --- /dev/null +++ b/.flake8 @@ -0,0 +1,8 @@ +[flake8] +# Default line length in Black formatter is 88 +max-line-length = 88 +extend-ignore = + # No whitespace before ':' in [x : y] + E203, + # Line break before binary operator + W503 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f98d30..d9d3c35 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,13 +26,13 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + # exit-zero treats all errors as warnings. + flake8 . --count --exit-zero --statistics - name: Test with pytest run: | pytest diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 7103ddc..43de98f 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -1,6 +1,6 @@ # This workflow will install Poetry, resolve Python dependencies, run tests, # build and publish release candidate package versions to TestPyPI upon pushing -# tags of the form "*.*.*rc*". +# tags of the form "*.*.*". name: Publish to TestPyPI on: diff --git a/.vscode/settings.json b/.vscode/settings.json index a3a1838..e19736d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,10 @@ { + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, "python.testing.pytestArgs": [ "tests" ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true -} +} \ No newline at end of file diff --git a/README.md b/README.md index 0a8dad2..aa4f7a5 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,67 @@ -# `auto-period-finder` -[![PyPI Version](https://img.shields.io/pypi/v/auto-period-finder.svg?label=PyPI)](https://pypi.org/project/auto-period-finder/) -![PyPI - Python Version](https://img.shields.io/pypi/pyversions/auto-period-finder?label=Python) -![GitHub License](https://img.shields.io/github/license/iskandergaba/auto-period-finder?label=License) +
+

Pyriodicity

-## About `auto-period-finder` -`auto-period-finder` is an autocorrelation function (ACF) based seasonality periods automatic finder for univariate time series. +[![PyPI Version](https://img.shields.io/pypi/v/pyriodicity.svg?label=PyPI)](https://pypi.org/project/pyriodicity/) +![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pyriodicity?label=Python) +![GitHub License](https://img.shields.io/github/license/iskandergaba/pyriodicity?label=License) + +
+ + +## About Pyriodicity +Pyriodicity provides intuitive and easy-to-use Python implementation for periodicity (seasonality) detection in univariate time series. Pyriodicity supports the following detection methods: +- [Autocorrelation Function (ACF)](https://otexts.com/fpp3/acf.html) +- [Autoperiod]( https://doi.org/10.1137/1.9781611972757.40) +- [Fast Fourier Transform (FFT)](https://otexts.com/fpp3/useful-predictors.html#fourier-series) ## Installation -To install the latest version of `auto-period-finder`, simply run: +To install the latest version of `pyriodicity`, simply run: ```shell -pip install auto-period-finder +pip install pyriodicity ``` ## Example -Start by loading a timeseries dataset with a frequency. We can use `co2` emissions sample dataset from `statsmodels` +Start by loading a the `co2` timeseries emissions sample data from [`statsmodels`](https://www.statsmodels.org) ```python from statsmodels.datasets import co2 data = co2.load().data ``` -You can resample the data to whatever frequency you want. - +You can then resample the data to whatever frequency you want. In this example, we downsample the data to a monthly frequency ```python data = data.resample("ME").mean().ffill() ``` -Use `AutoPeriodFinder` to find the list of seasonality periods based on ACF. +Use `Autoperiod` to find the list of periods based in this data (if any). ```python -from auto_period_finder import AutoPeriodFinder -period_finder = AutoPeriodFinder(data) -periods = period_finder.fit() +from pyriodicity import Autoperiod +autoperiod = Autoperiod(data) +periods = autoperiod.fit() ``` -You can also find the most prominent period either ACF-wise: +There are multiple parameters you can play with should you wish to. For example, you can specify a lower percentile value for a more lenient detection ```python -strongest_period_acf = period_finder.fit_find_strongest_acf() +autoperiod.fit(percentile=90) ``` -or variance-wise: +Or increase the number of random data permutations for a better power threshold estimation ```python -strongest_period_var = period_finder.fit_find_strongest_var() +autoperiod.fit(k=300) ``` -You can learn more about calculating seasonality component through variance from [here](OTexts.com/fpp3/stlfeatures.html). +Alternatively, you can use other periodicity detection methods such as `ACFPeriodicityDetector` and `FFTPeriodicityDetector` and compare results and performances. -## How to Get Started -This project is built and published using [Poetry](https://python-poetry.org). To setup development environment for this project you can follow these steps: +## Development Environment Setup +This project is built and published using [Poetry](https://python-poetry.org). To setup a development environment for this project you can follow these steps: -1. First, you need to install [Python](https://www.python.org) of one of the compatible versions indicated above. -2. Install Poetry. You can follow this [guide](https://python-poetry.org/docs/#installing-with-the-official-installer) and use their official installer. +1. Install one of the compatible [Python](https://www.python.org) versions indicated above. +2. Install [Poetry](https://python-poetry.org/docs/#installing-with-pipx). 3. Navigate to the root folder and install dependencies in a virtual environment: ```shell poetry install ``` -4. If everything worked properly, you should have `auto-period-finder-geinoPPi-py3.10` environment activated. You can verify this by running: +4. If everything worked properly, you should have an environment under the name `pyriodicity-py3.*` activated. You can verify this by running: ```shell poetry env list ``` @@ -62,34 +69,18 @@ poetry env list ```shell poetry run pytest ``` -6. To export the list detailed list of dependencies, run the following command: +6. To export the detailed dependency list, consider running the following: ```shell +# Add poetry-plugin-export plugin to poetry poetry self add poetry-plugin-export -poetry export --output requirements.txt -``` - -## ACF-Based Seasonality Period Detection Explained -An easy and quick way to find seasonality periods of a univariate time series is to check its autocorrelation function (ACF) and look for specific charecteristics in lag values that we will detail in a second. You can read more information about time series ACF [here](https://otexts.com/fpp3/acf.html), but intuitively, An autocorrelation coefficient $r_k$ measures the the linear relationship between $k$-lagged values of a given time series. In simpler terms, $r_k$ measures how similar/dissimilar time series values that $k$-length apart from each other. The set of $r_k$ values for each lag $k$ makes ACF. Equipped with this information, I developed a package for finding time series seasonality periods automatically using ACF information. -Simply put, given a univariate time series $T$, the algorithm finds, iteratively, lag values $k$ such that: -1. $1 \lt k \leq \frac{\lvert T \rvert}{2}$ -2. Autocorrelation coefficients $r_q$ are local maxima where $q \in \{k, 2k, 3k, ...\}$ -3. $\forall p \in P, \forall n \in \mathbb{N}, k \neq n \times p$, where $P$ is the list of already found periods. - -The list of such $k$ values constitute the set of found seasonality periods $P$. To understand this further, consider this hypothetical time series of hourly frequency that has clear weekly seasonality below - -[![Time series with a weekly seasonality](https://raw.githubusercontent.com/iskandergaba/auto-period-finder/master/assets/images/timeseries.png)](https://raw.githubusercontent.com/iskandergaba/auto-period-finder/master/assets/images/timeseries.png) - -Now let's look at the corresponding ACF for the time series above: - -[![Autocorrelation function of a time series with a weekly seasonality](https://raw.githubusercontent.com/iskandergaba/auto-period-finder/master/assets/images/acf.png)](https://raw.githubusercontent.com/iskandergaba/auto-period-finder/master/assets/images/acf.png) - -You can see that the autocorrelation coefficient for lag value 168 hours (i.e. one week) is a local maximum (red-border square). Similarly, autocorrelation coefficient for lag values that are multiples of 168 (gray-border squares). We can therefore conclude that this time series has a weekly seasonality period. +# Export the package dependencies to requirements.txt +poetry export --output requirements.txt -### Notes -- The first condition is needed because a seasonality period cannot neither be 1 (a trivial case), nor greater than half the length of the target time series (by definition, a seasonality has to manifest itself at least twice in a given time series). -- The third condition favors eliminating redundant seasonality periods that are multiples of each others. The algorithm does allow, however, finding seasonality periods that divide already found seasonality periods. -- The periods detection uses `argmax` on the ACF to select seasonality period candidates before checking they satisfy the conditions discussed above. Therefore, the list of seasonality periods are returned in the descending order of their corresponding ACF coefficients. +# If you wish to export all the dependencies, including those needed for testing, run the following command +poetry export --with test --output requirements-dev.txt +``` ## References -- [1] Hyndman, R.J., & Athanasopoulos, G. (2021) Forecasting: principles and practice, 3rd edition, OTexts: Melbourne, Australia. [OTexts.com/fpp3](https://otexts.com/fpp3). Accessed on 12-25-2023. +- [1] Hyndman, R.J., & Athanasopoulos, G. (2021) Forecasting: principles and practice, 3rd edition, OTexts: Melbourne, Australia. [OTexts.com/fpp3](https://otexts.com/fpp3). Accessed on 09-15-2024. +- [2] Vlachos, M., Yu, P., & Castelli, V. (2005). On periodicity detection and Structural Periodic similarity. Proceedings of the 2005 SIAM International Conference on Data Mining. [doi.org/10.1137/1.9781611972757.40](https://doi.org/10.1137/1.9781611972757.40). diff --git a/assets/images/acf.png b/assets/images/acf.png deleted file mode 100644 index fdf78fc..0000000 Binary files a/assets/images/acf.png and /dev/null differ diff --git a/assets/images/timeseries.png b/assets/images/timeseries.png deleted file mode 100644 index 0ac81af..0000000 Binary files a/assets/images/timeseries.png and /dev/null differ diff --git a/auto_period_finder/__init__.py b/auto_period_finder/__init__.py deleted file mode 100644 index d498034..0000000 --- a/auto_period_finder/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from auto_period_finder.finder import * diff --git a/auto_period_finder/finder.py b/auto_period_finder/finder.py deleted file mode 100644 index af3d1e7..0000000 --- a/auto_period_finder/finder.py +++ /dev/null @@ -1,323 +0,0 @@ -import math -from enum import Enum -from typing import Dict, Optional, Union - -import numpy as np -from statsmodels.tools.typing import ArrayLike1D -from statsmodels.tsa.seasonal import STL, seasonal_decompose -from statsmodels.tsa.stattools import acf - - -class Decomposer(Enum): - """ - Seasonality decomposition method. - """ - - STL = 1 - MOVING_AVERAGES = 2 - - -class AutoPeriodFinder: - """ - Autocorrelation function (ACF) based seasonality periods automatic finder. - - Find the periods of a given time series using its ACF. A time delta - is considered a period if: - 1- It is a local maximum of ACF.\n - 2- Its multiples are also local maxima.\n - 3- It is not a multiple of an already discovered period. For example, - it is redundant to return a 2 year seasonality period if we have already - found a 1 year period. The inverse, however, is not necessarily true. - - Parameters - ---------- - endog : array_like - Data to be investigated. Must be squeezable to 1-d. - acf_kwargs: dict, optional - Arguments to pass to the ACF. - - See Also - -------- - statsmodels.tsa.stattools.acf - Autocorrelation function. - statsmodels.tsa.seasonal.STL - Season-Trend decomposition using LOESS. - statsmodels.tsa.seasonal.seasonal_decompose - Seasonal decomposition using moving averages. - - References - ---------- - .. [1] Hyndman, R.J., & Athanasopoulos, G. (2021) - Forecasting: principles and practice, 3rd edition, OTexts: Melbourne, Australia. - OTexts.com/fpp3/stlfeatures.html. Accessed on 12-23-2023. - - Examples - -------- - Start by loading a timeseries dataset with a frequency. - - >>> from statsmodels.datasets import co2 - >>> data = co2.load().data - - You can resample the data to whatever frequency you want. - - >>> data = data.resample("M").mean().ffill() - - Use AutoPeriodFinder to find the list of seasonality periods based on ACF. - - >>> period_finder = AutoPeriodFinder(data) - >>> periods = period_finder.fit() - - You can also find the most prominent period either ACF-wise or variance-wise. - - >>> strongest_period_acf = period_finder.fit_find_strongest_acf() - >>> strongest_period_var = period_finder.fit_find_strongest_var() - """ - - def __init__( - self, - endog: ArrayLike1D, - acf_kwargs: Optional[Dict[str, Union[int, bool, None]]] = None, - ): - self.y = self.__to_1d_array(endog) - self._acf_kwargs = self.__remove_overloaded_acf_kwargs( - acf_kwargs if acf_kwargs else {} - ) - - def fit( - self, - vicinity_radius: Optional[Union[int, None]] = None, - max_period_count: Optional[Union[int, None]] = None, - ) -> list: - """ - Find seasonality periods of the given time series automatically. - - Parameters - ---------- - vicinity_radius : int, optional, default = None - How many data points, before and after, a period candidate - value to consider for satisfying the periodicity conditions. - Essentially, the algorithm will verify that at least one point - in the vicinity (defined by this parameter) of every multiple - of the candidate value is a local maximum. - This helps mitigate the effects of the forward and backward - noise shifts of the period value. It is also effective - at reducing the number of detected period values that are - too tightly bunched together. - max_period_count : int, optional, default = None - Maximum number of periods to look for. - - Returns - ------- - list - List of periods. - """ - return self.__find_periods( - self.y, vicinity_radius, max_period_count, self._acf_kwargs - ) - - def fit_find_strongest_acf( - self, vicinity_radius: Optional[Union[int, None]] = None - ) -> int: - """ - Find the strongest seasonality period ACF-wise of the given time series. - - Parameters - ---------- - vicinity_radius : int, optional, default = None - How many data points, before and after, a period candidate - value to consider for satisfying the periodicity conditions. - Essentially, the algorithm will verify that at least one point - in the vicinity (defined by this parameter) of every multiple - of the candidate value is a local maximum. - This helps mitigate the effects of the forward and backward - noise shifts of the period value. It is also effective - at reducing the number of detected period values that are - too tightly bunched together. - - Returns - ------- - int - The strongest seasonality period ACF-wise. - """ - periods = self.fit(vicinity_radius=vicinity_radius, max_period_count=1) - if len(periods) == 0: - return None - else: - return periods[0] - - def fit_find_strongest_var( - self, - vicinity_radius: Optional[Union[int, None]] = None, - max_period_count: Optional[Union[int, None]] = None, - decomposer: Optional[Decomposer] = Decomposer.MOVING_AVERAGES, - decomposer_kwargs: Optional[Dict[str, Union[int, bool, None]]] = None, - ) -> int: - """ - Find the strongest seasonality period variance-wise of the given time series - using seasonal decomposition. - - Parameters - ---------- - vicinity_radius : int, optional, default = None - How many data points, before and after, a period candidate - value to consider for satisfying the periodicity conditions. - Essentially, the algorithm will verify that at least one point - in the vicinity (defined by this parameter) of every multiple - of the candidate value is a local maximum. - This helps mitigate the effects of the forward and backward - noise shifts of the period value. It is also effective - at reducing the number of detected period values that are - too tightly bunched together. - max_period_count : int, optional, default = None - Maximum number of periods to look for. - decomposer: Decomposer. optional, default = Decomposer.MOVING_AVERAGE_DECOMPOSER - The seasonality decomposer that returns DecomposeResult to be used to - determine the strongest seasonality period. The possible values are - [Decomposer.MOVING_AVERAGE_DECOMPOSER, Decomposer.STL]. - decomposer_kwargs: dict, optional - Arguments to pass to the decomposer. - - Returns - ------- - int - The strongest seasonality period. - """ - periods = self.fit( - vicinity_radius=vicinity_radius, max_period_count=max_period_count - ) - if len(periods) == 0: - return None - elif len(periods) == 1: - return periods[0] - else: - if decomposer == Decomposer.STL: - decomposer_kwargs = self.__remove_overloaded_stl_kwargs( - decomposer_kwargs if decomposer_kwargs else {} - ) - decomps = { - p: STL(self.y, p, **decomposer_kwargs).fit() for p in periods - } - elif decomposer == Decomposer.MOVING_AVERAGES: - decomposer_kwargs = self.__remove_overloaded_seasonal_decompose_kwargs( - decomposer_kwargs if decomposer_kwargs else {} - ) - decomps = { - p: seasonal_decompose(self.y, period=p, **decomposer_kwargs) - for p in periods - } - else: - raise ValueError("Invalid seasonality decomposer: " + decomposer) - strengths = { - p: self.__seasonality_strength(d.seasonal, d.resid) - for p, d in decomps.items() - } - return max(strengths, key=strengths.get) - - def __find_periods( - self, - y: ArrayLike1D, - vicinity_radius: Optional[Union[int, None]], - max_period_count: Optional[Union[int, None]], - acf_kwargs: Dict[str, Union[int, bool, None]], - ) -> list: - periods = [] - acf_arr = np.array(acf(y, nlags=len(y), **acf_kwargs)) - acf_arr_work = acf_arr.copy() - - # Eliminate the trivial seasonality period of 0 and its vicinity - vicinity_radius = 0 if vicinity_radius is None else vicinity_radius - acf_arr_work[0 : vicinity_radius + 1] = -1 - - while True: - # i is a period candidate: It cannot be greater than half the timeseries length - i = acf_arr_work[: (acf_arr_work.size - vicinity_radius - 1) // 2].argmax() - - # No more periods left or the maximum number of periods has been found - if acf_arr_work[i] == -1 or ( - max_period_count is not None and len(periods) == max_period_count - ): - return periods - - # Check that i and all of its multiples are local maxima - period = self.__get_period(acf_arr, i, vicinity_radius) - if period is not None: - # Add to period return list - periods.append(period) - # Ignore i and its multiples - for offset in np.arange(-vicinity_radius, vicinity_radius + 1): - acf_arr_work[ - [i * j + offset for j in np.arange(1, len(acf_arr_work) // i)] - ] = -1 - - # Not a period, ignore it - else: - acf_arr_work[ - [ - i + offset - for offset in np.arange(-vicinity_radius, vicinity_radius + 1) - ] - ] = -1 - - @staticmethod - def __get_period(acf_arr, lag, vicinity_radius=0): - # Lag value vicinity offset range array - vicinity = np.arange(-vicinity_radius, vicinity_radius + 1) - # Possible lag value multipliers - multipliers = np.arange( - 2, math.ceil((len(acf_arr) - vicinity_radius - 1) / lag) - ) - # The total number of local maxima found - local_maxima_count = 0 - # Lag value accumulator for local maxima found at the corresponding multiplier indices - lag_value_acc = np.zeros(len(multipliers), dtype=np.int64) - for offset in vicinity: - multiple_is_local_maxima = [ - acf_arr[lag * j + offset - 1] < acf_arr[lag * j + offset] - and acf_arr[lag * j + offset] > acf_arr[lag * j + offset + 1] - for j in multipliers - ] - local_maxima_count += multiple_is_local_maxima.count(True) - lag_value_acc[multiple_is_local_maxima] = ( - lag_value_acc[multiple_is_local_maxima] + lag + offset - ) - - # No lag value in the vicinity is a period - if any(f == 0 for f in lag_value_acc): - return None - # Return the average lag value - else: - return np.sum(lag_value_acc) // local_maxima_count - - @staticmethod - def __seasonality_strength(seasonal, resid): - return max(0, 1 - np.var(resid) / np.var(seasonal + resid)) - - @staticmethod - def __remove_overloaded_acf_kwargs(acf_kwargs: Dict) -> Dict: - args = ["x", "nlags", "qstat", "alpha", "bartlett_confint"] - for arg in args: - acf_kwargs.pop(arg, None) - return acf_kwargs - - @staticmethod - def __remove_overloaded_stl_kwargs(stl_kwargs: Dict) -> Dict: - args = ["endog", "period"] - for arg in args: - stl_kwargs.pop(arg, None) - return stl_kwargs - - @staticmethod - def __remove_overloaded_seasonal_decompose_kwargs( - seasonal_decompose_kwargs: Dict, - ) -> Dict: - args = ["x", "period"] - for arg in args: - seasonal_decompose_kwargs.pop(arg, None) - return seasonal_decompose_kwargs - - @staticmethod - def __to_1d_array(x): - y = np.ascontiguousarray(np.squeeze(np.asarray(x)), dtype=np.double) - if y.ndim != 1: - raise ValueError("y must be a 1d array") - return y diff --git a/poetry.lock b/poetry.lock index f32bd88..56a0866 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "colorama" @@ -13,13 +13,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -38,103 +38,120 @@ files = [ [[package]] name = "numpy" -version = "1.26.4" +version = "2.1.1" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, + {file = "numpy-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8a0e34993b510fc19b9a2ce7f31cb8e94ecf6e924a40c0c9dd4f62d0aac47d9"}, + {file = "numpy-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dd86dfaf7c900c0bbdcb8b16e2f6ddf1eb1fe39c6c8cca6e94844ed3152a8fd"}, + {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:5889dd24f03ca5a5b1e8a90a33b5a0846d8977565e4ae003a63d22ecddf6782f"}, + {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:59ca673ad11d4b84ceb385290ed0ebe60266e356641428c845b39cd9df6713ab"}, + {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ce49a34c44b6de5241f0b38b07e44c1b2dcacd9e36c30f9c2fcb1bb5135db7"}, + {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913cc1d311060b1d409e609947fa1b9753701dac96e6581b58afc36b7ee35af6"}, + {file = "numpy-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:caf5d284ddea7462c32b8d4a6b8af030b6c9fd5332afb70e7414d7fdded4bfd0"}, + {file = "numpy-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:57eb525e7c2a8fdee02d731f647146ff54ea8c973364f3b850069ffb42799647"}, + {file = "numpy-2.1.1-cp310-cp310-win32.whl", hash = "sha256:9a8e06c7a980869ea67bbf551283bbed2856915f0a792dc32dd0f9dd2fb56728"}, + {file = "numpy-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d10c39947a2d351d6d466b4ae83dad4c37cd6c3cdd6d5d0fa797da56f710a6ae"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d07841fd284718feffe7dd17a63a2e6c78679b2d386d3e82f44f0108c905550"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5613cfeb1adfe791e8e681128f5f49f22f3fcaa942255a6124d58ca59d9528f"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0b8cc2715a84b7c3b161f9ebbd942740aaed913584cae9cdc7f8ad5ad41943d0"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b49742cdb85f1f81e4dc1b39dcf328244f4d8d1ded95dea725b316bd2cf18c95"}, + {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8d5f8a8e3bc87334f025194c6193e408903d21ebaeb10952264943a985066ca"}, + {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d51fc141ddbe3f919e91a096ec739f49d686df8af254b2053ba21a910ae518bf"}, + {file = "numpy-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:98ce7fb5b8063cfdd86596b9c762bf2b5e35a2cdd7e967494ab78a1fa7f8b86e"}, + {file = "numpy-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:24c2ad697bd8593887b019817ddd9974a7f429c14a5469d7fad413f28340a6d2"}, + {file = "numpy-2.1.1-cp311-cp311-win32.whl", hash = "sha256:397bc5ce62d3fb73f304bec332171535c187e0643e176a6e9421a6e3eacef06d"}, + {file = "numpy-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:ae8ce252404cdd4de56dcfce8b11eac3c594a9c16c231d081fb705cf23bd4d9e"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c803b7934a7f59563db459292e6aa078bb38b7ab1446ca38dd138646a38203e"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6435c48250c12f001920f0751fe50c0348f5f240852cfddc5e2f97e007544cbe"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3269c9eb8745e8d975980b3a7411a98976824e1fdef11f0aacf76147f662b15f"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521"}, + {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b"}, + {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b9cd92c8f8e7b313b80e93cedc12c0112088541dcedd9197b5dee3738c1201"}, + {file = "numpy-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:afd9c680df4de71cd58582b51e88a61feed4abcc7530bcd3d48483f20fc76f2a"}, + {file = "numpy-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8661c94e3aad18e1ea17a11f60f843a4933ccaf1a25a7c6a9182af70610b2313"}, + {file = "numpy-2.1.1-cp312-cp312-win32.whl", hash = "sha256:950802d17a33c07cba7fd7c3dcfa7d64705509206be1606f196d179e539111ed"}, + {file = "numpy-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:3fc5eabfc720db95d68e6646e88f8b399bfedd235994016351b1d9e062c4b270"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:046356b19d7ad1890c751b99acad5e82dc4a02232013bd9a9a712fddf8eb60f5"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e5a9cb2be39350ae6c8f79410744e80154df658d5bea06e06e0ac5bb75480d5"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d4c57b68c8ef5e1ebf47238e99bf27657511ec3f071c465f6b1bccbef12d4136"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:8ae0fd135e0b157365ac7cc31fff27f07a5572bdfc38f9c2d43b2aff416cc8b0"}, + {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981707f6b31b59c0c24bcda52e5605f9701cb46da4b86c2e8023656ad3e833cb"}, + {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ca4b53e1e0b279142113b8c5eb7d7a877e967c306edc34f3b58e9be12fda8df"}, + {file = "numpy-2.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e097507396c0be4e547ff15b13dc3866f45f3680f789c1a1301b07dadd3fbc78"}, + {file = "numpy-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7506387e191fe8cdb267f912469a3cccc538ab108471291636a96a54e599556"}, + {file = "numpy-2.1.1-cp313-cp313-win32.whl", hash = "sha256:251105b7c42abe40e3a689881e1793370cc9724ad50d64b30b358bbb3a97553b"}, + {file = "numpy-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:f212d4f46b67ff604d11fff7cc62d36b3e8714edf68e44e9760e19be38c03eb0"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:920b0911bb2e4414c50e55bd658baeb78281a47feeb064ab40c2b66ecba85553"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bab7c09454460a487e631ffc0c42057e3d8f2a9ddccd1e60c7bb8ed774992480"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:cea427d1350f3fd0d2818ce7350095c1a2ee33e30961d2f0fef48576ddbbe90f"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:e30356d530528a42eeba51420ae8bf6c6c09559051887196599d96ee5f536468"}, + {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8dfa9e94fc127c40979c3eacbae1e61fda4fe71d84869cc129e2721973231ef"}, + {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910b47a6d0635ec1bd53b88f86120a52bf56dcc27b51f18c7b4a2e2224c29f0f"}, + {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:13cc11c00000848702322af4de0147ced365c81d66053a67c2e962a485b3717c"}, + {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53e27293b3a2b661c03f79aa51c3987492bd4641ef933e366e0f9f6c9bf257ec"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7be6a07520b88214ea85d8ac8b7d6d8a1839b0b5cb87412ac9f49fa934eb15d5"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:52ac2e48f5ad847cd43c4755520a2317f3380213493b9d8a4c5e37f3b87df504"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a95ca3560a6058d6ea91d4629a83a897ee27c00630aed9d933dff191f170cd"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:99f4a9ee60eed1385a86e82288971a51e71df052ed0b2900ed30bc840c0f2e39"}, + {file = "numpy-2.1.1.tar.gz", hash = "sha256:d0cf7d55b1051387807405b3898efafa862997b4cba8aa5dbe657be794afeafd"}, ] [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] name = "pandas" -version = "2.2.1" +version = "2.2.2" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8df8612be9cd1c7797c93e1c5df861b2ddda0b48b08f2c3eaa0702cf88fb5f88"}, - {file = "pandas-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0f573ab277252ed9aaf38240f3b54cfc90fff8e5cab70411ee1d03f5d51f3944"}, - {file = "pandas-2.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f02a3a6c83df4026e55b63c1f06476c9aa3ed6af3d89b4f04ea656ccdaaaa359"}, - {file = "pandas-2.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c38ce92cb22a4bea4e3929429aa1067a454dcc9c335799af93ba9be21b6beb51"}, - {file = "pandas-2.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c2ce852e1cf2509a69e98358e8458775f89599566ac3775e70419b98615f4b06"}, - {file = "pandas-2.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53680dc9b2519cbf609c62db3ed7c0b499077c7fefda564e330286e619ff0dd9"}, - {file = "pandas-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:94e714a1cca63e4f5939cdce5f29ba8d415d85166be3441165edd427dc9f6bc0"}, - {file = "pandas-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f821213d48f4ab353d20ebc24e4faf94ba40d76680642fb7ce2ea31a3ad94f9b"}, - {file = "pandas-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c70e00c2d894cb230e5c15e4b1e1e6b2b478e09cf27cc593a11ef955b9ecc81a"}, - {file = "pandas-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97fbb5387c69209f134893abc788a6486dbf2f9e511070ca05eed4b930b1b02"}, - {file = "pandas-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101d0eb9c5361aa0146f500773395a03839a5e6ecde4d4b6ced88b7e5a1a6403"}, - {file = "pandas-2.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7d2ed41c319c9fb4fd454fe25372028dfa417aacb9790f68171b2e3f06eae8cd"}, - {file = "pandas-2.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:af5d3c00557d657c8773ef9ee702c61dd13b9d7426794c9dfeb1dc4a0bf0ebc7"}, - {file = "pandas-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:06cf591dbaefb6da9de8472535b185cba556d0ce2e6ed28e21d919704fef1a9e"}, - {file = "pandas-2.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:88ecb5c01bb9ca927ebc4098136038519aa5d66b44671861ffab754cae75102c"}, - {file = "pandas-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:04f6ec3baec203c13e3f8b139fb0f9f86cd8c0b94603ae3ae8ce9a422e9f5bee"}, - {file = "pandas-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a935a90a76c44fe170d01e90a3594beef9e9a6220021acfb26053d01426f7dc2"}, - {file = "pandas-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c391f594aae2fd9f679d419e9a4d5ba4bce5bb13f6a989195656e7dc4b95c8f0"}, - {file = "pandas-2.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9d1265545f579edf3f8f0cb6f89f234f5e44ba725a34d86535b1a1d38decbccc"}, - {file = "pandas-2.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11940e9e3056576ac3244baef2fedade891977bcc1cb7e5cc8f8cc7d603edc89"}, - {file = "pandas-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:4acf681325ee1c7f950d058b05a820441075b0dd9a2adf5c4835b9bc056bf4fb"}, - {file = "pandas-2.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9bd8a40f47080825af4317d0340c656744f2bfdb6819f818e6ba3cd24c0e1397"}, - {file = "pandas-2.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df0c37ebd19e11d089ceba66eba59a168242fc6b7155cba4ffffa6eccdfb8f16"}, - {file = "pandas-2.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:739cc70eaf17d57608639e74d63387b0d8594ce02f69e7a0b046f117974b3019"}, - {file = "pandas-2.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d3558d263073ed95e46f4650becff0c5e1ffe0fc3a015de3c79283dfbdb3df"}, - {file = "pandas-2.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4aa1d8707812a658debf03824016bf5ea0d516afdea29b7dc14cf687bc4d4ec6"}, - {file = "pandas-2.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:76f27a809cda87e07f192f001d11adc2b930e93a2b0c4a236fde5429527423be"}, - {file = "pandas-2.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:1ba21b1d5c0e43416218db63037dbe1a01fc101dc6e6024bcad08123e48004ab"}, - {file = "pandas-2.2.1.tar.gz", hash = "sha256:0ab90f87093c13f3e8fa45b48ba9f39181046e8f3317d3aadb2fffbb1b978572"}, + {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, + {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, + {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, + {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, + {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, + {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, + {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, ] [package.dependencies] numpy = [ - {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, - {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, - {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -185,13 +202,13 @@ test = ["pytest", "pytest-cov", "scipy"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -236,56 +253,64 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2024.1" +version = "2024.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, ] [[package]] name = "scipy" -version = "1.13.0" +version = "1.14.1" description = "Fundamental algorithms for scientific computing in Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "scipy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba419578ab343a4e0a77c0ef82f088238a93eef141b2b8017e46149776dfad4d"}, - {file = "scipy-1.13.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:22789b56a999265431c417d462e5b7f2b487e831ca7bef5edeb56efe4c93f86e"}, - {file = "scipy-1.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f1432ba070e90d42d7fd836462c50bf98bd08bed0aa616c359eed8a04e3922"}, - {file = "scipy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8434f6f3fa49f631fae84afee424e2483289dfc30a47755b4b4e6b07b2633a4"}, - {file = "scipy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:dcbb9ea49b0167de4167c40eeee6e167caeef11effb0670b554d10b1e693a8b9"}, - {file = "scipy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:1d2f7bb14c178f8b13ebae93f67e42b0a6b0fc50eba1cd8021c9b6e08e8fb1cd"}, - {file = "scipy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fbcf8abaf5aa2dc8d6400566c1a727aed338b5fe880cde64907596a89d576fa"}, - {file = "scipy-1.13.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5e4a756355522eb60fcd61f8372ac2549073c8788f6114449b37e9e8104f15a5"}, - {file = "scipy-1.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5acd8e1dbd8dbe38d0004b1497019b2dbbc3d70691e65d69615f8a7292865d7"}, - {file = "scipy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ff7dad5d24a8045d836671e082a490848e8639cabb3dbdacb29f943a678683d"}, - {file = "scipy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4dca18c3ffee287ddd3bc8f1dabaf45f5305c5afc9f8ab9cbfab855e70b2df5c"}, - {file = "scipy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:a2f471de4d01200718b2b8927f7d76b5d9bde18047ea0fa8bd15c5ba3f26a1d6"}, - {file = "scipy-1.13.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d0de696f589681c2802f9090fff730c218f7c51ff49bf252b6a97ec4a5d19e8b"}, - {file = "scipy-1.13.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:b2a3ff461ec4756b7e8e42e1c681077349a038f0686132d623fa404c0bee2551"}, - {file = "scipy-1.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf9fe63e7a4bf01d3645b13ff2aa6dea023d38993f42aaac81a18b1bda7a82a"}, - {file = "scipy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e7626dfd91cdea5714f343ce1176b6c4745155d234f1033584154f60ef1ff42"}, - {file = "scipy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:109d391d720fcebf2fbe008621952b08e52907cf4c8c7efc7376822151820820"}, - {file = "scipy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:8930ae3ea371d6b91c203b1032b9600d69c568e537b7988a3073dfe4d4774f21"}, - {file = "scipy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5407708195cb38d70fd2d6bb04b1b9dd5c92297d86e9f9daae1576bd9e06f602"}, - {file = "scipy-1.13.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:ac38c4c92951ac0f729c4c48c9e13eb3675d9986cc0c83943784d7390d540c78"}, - {file = "scipy-1.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c74543c4fbeb67af6ce457f6a6a28e5d3739a87f62412e4a16e46f164f0ae5"}, - {file = "scipy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28e286bf9ac422d6beb559bc61312c348ca9b0f0dae0d7c5afde7f722d6ea13d"}, - {file = "scipy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33fde20efc380bd23a78a4d26d59fc8704e9b5fd9b08841693eb46716ba13d86"}, - {file = "scipy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:45c08bec71d3546d606989ba6e7daa6f0992918171e2a6f7fbedfa7361c2de1e"}, - {file = "scipy-1.13.0.tar.gz", hash = "sha256:58569af537ea29d3f78e5abd18398459f195546bb3be23d16677fb26616cc11e"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, + {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, + {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, + {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, + {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, + {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, + {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, + {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, + {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, + {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, ] [package.dependencies] -numpy = ">=1.22.4,<2.3" +numpy = ">=1.23.5,<2.3" [package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] -doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] -test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "six" @@ -300,55 +325,47 @@ files = [ [[package]] name = "statsmodels" -version = "0.14.1" +version = "0.14.2" description = "Statistical computations and models for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "statsmodels-0.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:43af9c0b07c9d72f275cf14ea54a481a3f20911f0b443181be4769def258fdeb"}, - {file = "statsmodels-0.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a16975ab6ad505d837ba9aee11f92a8c5b49c4fa1ff45b60fe23780b19e5705e"}, - {file = "statsmodels-0.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e278fe74da5ed5e06c11a30851eda1af08ef5af6be8507c2c45d2e08f7550dde"}, - {file = "statsmodels-0.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0564d92cb05b219b4538ed09e77d96658a924a691255e1f7dd23ee338df441b"}, - {file = "statsmodels-0.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5385e22e72159a09c099c4fb975f350a9f3afeb57c1efce273b89dcf1fe44c0f"}, - {file = "statsmodels-0.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:0a8aae75a2e08ebd990e5fa394f8e32738b55785cb70798449a3f4207085e667"}, - {file = "statsmodels-0.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b69a63ad6c979a6e4cde11870ffa727c76a318c225a7e509f031fbbdfb4e416a"}, - {file = "statsmodels-0.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7562cb18a90a114f39fab6f1c25b9c7b39d9cd5f433d0044b430ca9d44a8b52c"}, - {file = "statsmodels-0.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3abaca4b963259a2bf349c7609cfbb0ce64ad5fb3d92d6f08e21453e4890248"}, - {file = "statsmodels-0.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f727fe697f6406d5f677b67211abe5a55101896abdfacdb3f38410405f6ad8"}, - {file = "statsmodels-0.14.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6838ac6bdb286daabb5e91af90fd4258f09d0cec9aace78cc441cb2b17df428"}, - {file = "statsmodels-0.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:709bfcef2dbe66f705b17e56d1021abad02243ee1a5d1efdb90f9bad8b06a329"}, - {file = "statsmodels-0.14.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f32a7cd424cf33304a54daee39d32cccf1d0265e652c920adeaeedff6d576457"}, - {file = "statsmodels-0.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f8c30181c084173d662aaf0531867667be2ff1bee103b84feb64f149f792dbd2"}, - {file = "statsmodels-0.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de2b97413913d52ad6342dece2d653e77f78620013b7705fad291d4e4266ccb"}, - {file = "statsmodels-0.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3420f88289c593ba2bca33619023059c476674c160733bd7d858564787c83d3"}, - {file = "statsmodels-0.14.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c008e16096f24f0514e53907890ccac6589a16ad6c81c218f2ee6752fdada555"}, - {file = "statsmodels-0.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:bc0351d279c4e080f0ce638a3d886d312aa29eade96042e3ba0a73771b1abdfb"}, - {file = "statsmodels-0.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf293ada63b2859d95210165ad1dfcd97bd7b994a5266d6fbeb23659d8f0bf68"}, - {file = "statsmodels-0.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44ca8cb88fa3d3a4ffaff1fb8eb0e98bbf83fc936fcd9b9eedee258ecc76696a"}, - {file = "statsmodels-0.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d5373d176239993c095b00d06036690a50309a4e00c2da553b65b840f956ae6"}, - {file = "statsmodels-0.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a532dfe899f8b6632cd8caa0b089b403415618f51e840d1817a1e4b97e200c73"}, - {file = "statsmodels-0.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:4fe0a60695952b82139ae8750952786a700292f9e0551d572d7685070944487b"}, - {file = "statsmodels-0.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04293890f153ffe577e60a227bd43babd5f6c1fc50ea56a3ab1862ae85247a95"}, - {file = "statsmodels-0.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e70a2e93d54d40b2cb6426072acbc04f35501b1ea2569f6786964adde6ca572"}, - {file = "statsmodels-0.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab3a73d16c0569adbba181ebb967e5baaa74935f6d2efe86ac6fc5857449b07d"}, - {file = "statsmodels-0.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eefa5bcff335440ee93e28745eab63559a20cd34eea0375c66d96b016de909b3"}, - {file = "statsmodels-0.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:bc43765710099ca6a942b5ffa1bac7668965052542ba793dd072d26c83453572"}, - {file = "statsmodels-0.14.1.tar.gz", hash = "sha256:2260efdc1ef89f39c670a0bd8151b1d0843567781bcafec6cda0534eb47a94f6"}, + {file = "statsmodels-0.14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df5d6f95c46f0341da6c79ee7617e025bf2b371e190a8e60af1ae9cabbdb7a97"}, + {file = "statsmodels-0.14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a87ef21fadb445b650f327340dde703f13aec1540f3d497afb66324499dea97a"}, + {file = "statsmodels-0.14.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5827a12e3ede2b98a784476d61d6bec43011fedb64aa815f2098e0573bece257"}, + {file = "statsmodels-0.14.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f2b7611a61adb7d596a6d239abdf1a4d5492b931b00d5ed23d32844d40e48e"}, + {file = "statsmodels-0.14.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c254c66142f1167b4c7d031cf8db55294cc62ff3280e090fc45bd10a7f5fd029"}, + {file = "statsmodels-0.14.2-cp310-cp310-win_amd64.whl", hash = "sha256:0e46e9d59293c1af4cc1f4e5248f17e7e7bc596bfce44d327c789ac27f09111b"}, + {file = "statsmodels-0.14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:50fcb633987779e795142f51ba49fb27648d46e8a1382b32ebe8e503aaabaa9e"}, + {file = "statsmodels-0.14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:876794068abfaeed41df71b7887000031ecf44fbfa6b50d53ccb12ebb4ab747a"}, + {file = "statsmodels-0.14.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a91f6c4943de13e3ce2e20ee3b5d26d02bd42300616a421becd53756f5deb37"}, + {file = "statsmodels-0.14.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4864a1c4615c5ea5f2e3b078a75bdedc90dd9da210a37e0738e064b419eccee2"}, + {file = "statsmodels-0.14.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afbd92410e0df06f3d8c4e7c0e2e71f63f4969531f280fb66059e2ecdb6e0415"}, + {file = "statsmodels-0.14.2-cp311-cp311-win_amd64.whl", hash = "sha256:8e004cfad0e46ce73fe3f3812010c746f0d4cfd48e307b45c14e9e360f3d2510"}, + {file = "statsmodels-0.14.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:eb0ba1ad3627705f5ae20af6b2982f500546d43892543b36c7bca3e2f87105e7"}, + {file = "statsmodels-0.14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:90fd2f0110b73fc3fa5a2f21c3ca99b0e22285cccf38e56b5b8fd8ce28791b0f"}, + {file = "statsmodels-0.14.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac780ad9ff552773798829a0b9c46820b0faa10e6454891f5e49a845123758ab"}, + {file = "statsmodels-0.14.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55d1742778400ae67acb04b50a2c7f5804182f8a874bd09ca397d69ed159a751"}, + {file = "statsmodels-0.14.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f870d14a587ea58a3b596aa994c2ed889cc051f9e450e887d2c83656fc6a64bf"}, + {file = "statsmodels-0.14.2-cp312-cp312-win_amd64.whl", hash = "sha256:f450fcbae214aae66bd9d2b9af48e0f8ba1cb0e8596c6ebb34e6e3f0fec6542c"}, + {file = "statsmodels-0.14.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:201c3d00929c4a67cda1fe05b098c8dcf1b1eeefa88e80a8f963a844801ed59f"}, + {file = "statsmodels-0.14.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9edefa4ce08e40bc1d67d2f79bc686ee5e238e801312b5a029ee7786448c389a"}, + {file = "statsmodels-0.14.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c78a7601fdae1aa32104c5ebff2e0b72c26f33e870e2f94ab1bcfd927ece9b"}, + {file = "statsmodels-0.14.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f36494df7c03d63168fccee5038a62f469469ed6a4dd6eaeb9338abedcd0d5f5"}, + {file = "statsmodels-0.14.2-cp39-cp39-win_amd64.whl", hash = "sha256:8875823bdd41806dc853333cc4e1b7ef9481bad2380a999e66ea42382cf2178d"}, + {file = "statsmodels-0.14.2.tar.gz", hash = "sha256:890550147ad3a81cda24f0ba1a5c4021adc1601080bd00e191ae7cd6feecd6ad"}, ] [package.dependencies] -numpy = [ - {version = ">=1.22.3,<2", markers = "python_version == \"3.10\" and platform_system == \"Windows\" and platform_python_implementation != \"PyPy\""}, - {version = ">=1.18,<2", markers = "python_version != \"3.10\" or platform_system != \"Windows\" or platform_python_implementation == \"PyPy\""}, -] +numpy = ">=1.22.3" packaging = ">=21.3" -pandas = ">=1.0,<2.1.0 || >2.1.0" -patsy = ">=0.5.4" -scipy = ">=1.4,<1.9.2 || >1.9.2" +pandas = ">=1.4,<2.1.0 || >2.1.0" +patsy = ">=0.5.6" +scipy = ">=1.8,<1.9.2 || >1.9.2" [package.extras] build = ["cython (>=0.29.33)"] -develop = ["colorama", "cython (>=0.29.33)", "cython (>=0.29.33,<4.0.0)", "flake8", "isort", "joblib", "matplotlib (>=3)", "oldest-supported-numpy (>=2022.4.18)", "pytest (>=7.3.0)", "pytest-cov", "pytest-randomly", "pytest-xdist", "pywinpty", "setuptools-scm[toml] (>=8.0,<9.0)"] +develop = ["colorama", "cython (>=0.29.33)", "cython (>=3.0.10,<4)", "flake8", "isort", "joblib", "matplotlib (>=3)", "pytest (>=7.3.0,<8)", "pytest-cov", "pytest-randomly", "pytest-xdist", "pywinpty", "setuptools-scm[toml] (>=8.0,<9.0)"] docs = ["ipykernel", "jupyter-client", "matplotlib", "nbconvert", "nbformat", "numpydoc", "pandas-datareader", "sphinx"] [[package]] @@ -376,4 +393,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "a01d12e4b8e2f3d5c4f30ab0c176f91d98ebaa0153bdf6424b795da371c3b9ce" +content-hash = "dc0fd42c12c8b550aedcb29a31fa1998c98d9d911b84329f647eb07714f4586f" diff --git a/pyproject.toml b/pyproject.toml index 19d3356..5f9d711 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,23 +1,24 @@ [tool.poetry] -name = "auto-period-finder" -version = "0.0.11" -description = "An autocorrelation function-based seasonality periods automatic finder for univariate time series." +name = "pyriodicity" +version = "0.2.0" +description = "Pyriodicity provides intuitive and easy-to-use Python implementation for periodicity (seasonality) detection in univariate time series." license = "MIT" readme = ["README.md"] -repository = "https://github.com/iskandergaba/auto-period-finder" +repository = "https://github.com/iskandergaba/pyriodicity" authors = ["Iskander Gaba "] -keywords = ["time", "series", "forecast", "analysis", "seasonality", "trend", "decomposition"] +keywords = ["signal", "time", "series", "forecast", "analysis", "seasonality", "trend", "decomposition"] [tool.poetry.dependencies] python = "^3.10" -statsmodels = "^0.14.1" +scipy = "^1.14.0" [tool.poetry.group.test.dependencies] pytest = "^7.4.3" +statsmodels = "^0.14.2" [build-system] requires = ["poetry-core"] diff --git a/pyriodicity/__init__.py b/pyriodicity/__init__.py new file mode 100644 index 0000000..018d74a --- /dev/null +++ b/pyriodicity/__init__.py @@ -0,0 +1,7 @@ +from .detectors import ( + ACFPeriodicityDetector, + Autoperiod, + FFTPeriodicityDetector, +) + +__all__ = ["ACFPeriodicityDetector", "Autoperiod", "FFTPeriodicityDetector"] diff --git a/pyriodicity/detectors/__init__.py b/pyriodicity/detectors/__init__.py new file mode 100644 index 0000000..1449dbc --- /dev/null +++ b/pyriodicity/detectors/__init__.py @@ -0,0 +1,9 @@ +from .acf import ACFPeriodicityDetector +from .autoperiod import Autoperiod +from .fft import FFTPeriodicityDetector + +__all__ = [ + "ACFPeriodicityDetector", + "Autoperiod", + "FFTPeriodicityDetector", +] diff --git a/pyriodicity/detectors/acf.py b/pyriodicity/detectors/acf.py new file mode 100644 index 0000000..7254443 --- /dev/null +++ b/pyriodicity/detectors/acf.py @@ -0,0 +1,117 @@ +from typing import Callable, Optional, Union + +from numpy.typing import ArrayLike, NDArray +from scipy.signal import argrelmax + +from pyriodicity.tools import acf, apply_window, detrend, to_1d_array + + +class ACFPeriodicityDetector: + """ + Autocorrelation function (ACF) based periodicity detector. + + Find the periods in a given signal or series using its ACF. A lag value + is considered a period if it is a local maximum of the ACF. + + Parameters + ---------- + endog : array_like + Data to be investigated. Must be squeezable to 1-d. + + References + ---------- + .. [1] Hyndman, R.J., & Athanasopoulos, G. (2021) + Forecasting: principles and practice, 3rd edition, OTexts: Melbourne, Australia. + OTexts.com/fpp3/acf.html. Accessed on 09-15-2024. + + Examples + -------- + Start by loading a timeseries datasets. + + >>> from statsmodels.datasets import co2 + >>> data = co2.load().data + + You can resample the data to whatever frequency you want. + + >>> data = data.resample("ME").mean().ffill() + + Use ACFPeriodicityDetector to find the list of seasonality periods using the ACF. + + >>> acf_detector = ACFPeriodicityDetector(data) + >>> periods = acf_detector.fit() + + You can get the most prominent period by setting max_period_count to 1 + + >>> acf_detector.fit(max_period_count=1) + + You can also use a different correlation function like Spearman + + >>> acf_detector.fit(correlation_func="spearman") + """ + + def __init__(self, endog: ArrayLike): + self.y = to_1d_array(endog) + + def fit( + self, + max_period_count: Optional[int] = None, + detrend_func: Optional[Union[str, Callable[[ArrayLike], NDArray]]] = "linear", + window_func: Optional[Union[str, float, tuple]] = None, + correlation_func: Optional[str] = "pearson", + ) -> NDArray: + """ + Find periods in the given series. + + Parameters + ---------- + max_period_count : int, optional, default = None + Maximum number of periods to look for. + detrend_func : str, callable, default = None + The kind of detrending to be applied on the series. It can either be + 'linear' or 'constant' if it the parameter is of 'str' type, or a + custom function that returns a detrended series. + window_func : float, str, tuple optional, default = None + Window function to be applied to the time series. Check + 'window' parameter documentation for scipy.signal.get_window + function for more information on the accepted formats of this + parameter. + correlation_func : str, default = 'pearson' + The correlation function to be used to calculate the ACF of the time + series. Possible values are ['pearson', 'spearman', 'kendall']. + + See Also + -------- + scipy.signal.detrend + Remove linear trend along axis from data. + scipy.signal.get_window + Return a window of a given length and type. + scipy.stats.kendalltau + Calculate Kendall's tau, a correlation measure for ordinal data. + scipy.stats.pearsonr + Pearson correlation coefficient and p-value for testing non-correlation. + scipy.stats.spearmanr + Calculate a Spearman correlation coefficient with associated p-value. + + + Returns + ------- + NDArray + List of detected periods. + """ + # Detrend data + self.y = self.y if detrend_func is None else detrend(self.y, detrend_func) + + # Apply window on data + self.y = self.y if window_func is None else apply_window(self.y, window_func) + + # Compute the ACF + acf_arr = acf(self.y, len(self.y) // 2, correlation_func) + + # Find the local argmax of the first half of the ACF array + local_argmax = argrelmax(acf_arr)[0] + + # Argsort the local maxima in the ACF array in a descending order + periods = local_argmax[acf_arr[local_argmax].argsort()][::-1] + + # Return the requested maximum count of detected periods + return periods[:max_period_count] diff --git a/pyriodicity/detectors/autoperiod.py b/pyriodicity/detectors/autoperiod.py new file mode 100644 index 0000000..e39f3e1 --- /dev/null +++ b/pyriodicity/detectors/autoperiod.py @@ -0,0 +1,242 @@ +from typing import Callable, Optional, Union + +import numpy as np +from numpy.typing import ArrayLike, NDArray +from scipy.signal import argrelmax, periodogram +from scipy.stats import linregress + +from pyriodicity.tools import acf, apply_window, detrend, to_1d_array + + +class Autoperiod: + """ + Autoperiod periodicity detector. + + Find the periods in a given signal or series using Autoperiod. + + Parameters + ---------- + endog : array_like + Data to be investigated. Must be squeezable to 1-d. + + References + ---------- + .. [1] Vlachos, M., Yu, P., & Castelli, V. (2005). + On periodicity detection and Structural Periodic similarity. + Proceedings of the 2005 SIAM International Conference on Data Mining. + https://doi.org/10.1137/1.9781611972757.40 + + Examples + -------- + Start by loading a timeseries dataset. + + >>> from statsmodels.datasets import co2 + >>> data = co2.load().data + + You can resample the data to whatever frequency you want. + + >>> data = data.resample("ME").mean().ffill() + + Use Autoperiod to find the list of periods in the data. + + >>> autoperiod = Autoperiod(data) + >>> periods = autoperiod.fit() + + You can specify a lower percentile value for a more lenient detection + + >>> autoperiod.fit(percentile=90) + + Or increase the number of random data permutations for a better power threshold estimation + + >>> autoperiod.fit(k=300) + """ + + def __init__(self, endog: ArrayLike): + self.y = to_1d_array(endog) + + def fit( + self, + k: int = 100, + percentile: int = 95, + detrend_func: Optional[Union[str, Callable[[ArrayLike], NDArray]]] = "linear", + window_func: Optional[Union[str, float, tuple]] = None, + correlation_func: Optional[str] = "pearson", + ) -> NDArray: + """ + Find periods in the given series. + + Parameters + ---------- + k : int, optional, default = 100 + The number of times the data is randomly permuted while estimating the + power threshold. + percentile : int, optional, default = 95 + Percentage for the percentile parameter used in computing the power + threshold. Value must be between 0 and 100 inclusive. + detrend_func : str, callable, default = None + The kind of detrending to be applied on the series. It can either be + 'linear' or 'constant' if it the parameter is of 'str' type, or a + custom function that returns a detrended series. + window_func : float, str, tuple optional, default = None + Window function to be applied to the time series. Check + 'window' parameter documentation for scipy.signal.get_window + function for more information on the accepted formats of this + parameter. + correlation_func : str, default = 'pearson' + The correlation function to be used to calculate the ACF of the time + series. Possible values are ['pearson', 'spearman', 'kendall']. + + See Also + -------- + scipy.signal.detrend + Remove linear trend along axis from data. + scipy.signal.get_window + Return a window of a given length and type. + scipy.stats.kendalltau + Calculate Kendall's tau, a correlation measure for ordinal data. + scipy.stats.pearsonr + Pearson correlation coefficient and p-value for testing non-correlation. + scipy.stats.spearmanr + Calculate a Spearman correlation coefficient with associated p-value. + + Returns + ------- + NDArray + List of detected periods. + """ + # Detrend data + self.y = self.y if detrend_func is None else detrend(self.y, detrend_func) + # Apply window on data + self.y = self.y if window_func is None else apply_window(self.y, window_func) + + # Compute the power threshold + p_threshold = self._power_threshold(self.y, k, percentile) + + # Find period hints + freq, power = periodogram(self.y, window=None, detrend=None) + period_hints = np.array( + [ + 1 / f + for f, p in zip(freq, power) + if f >= 1 / len(freq) and p >= p_threshold + ] + ) + + # Compute the ACF + length = len(self.y) + acf_arr = acf(self.y, nlags=length, correlation_func=correlation_func) + + # Validate period hints + period_hints_valid = [] + for p in period_hints: + q = length / p + start = np.floor((p + length / (q + 1)) / 2 - 1).astype(int) + end = np.ceil((p + length / (q - 1)) / 2 + 1).astype(int) + + splits = [ + self._split(np.arange(len(acf_arr)), acf_arr, start, end, i) + for i in range(start + 2, end) + ] + line1, line2, _ = splits[ + np.array([error for _, _, error in splits]).argmin() + ] + + if line1.slope > 0 > line2.slope: + period_hints_valid.append(p) + + period_hints_valid = np.array(period_hints_valid) + + # Return the closest ACF peak for each valid period hint + local_argmax = argrelmax(acf_arr)[0] + return np.array( + list( + { + min(local_argmax, key=lambda x: abs(x - p)) + for p in period_hints_valid + } + ) + ) + + @staticmethod + def _power_threshold(y: ArrayLike, k: int, p: int) -> float: + """ + Compute the power threshold as the p-th percentile of the maximum + power values of the periodogram of k permutations of the data. + + Parameters + ---------- + y : array_like + Data to be investigated. Must be squeezable to 1-d. + k : int + The number of times the data is randomly permuted to compute + the maximum power values. + p : int + The percentile value used to compute the power threshold. + It determines the cutoff point in the sorted list of the maximum + power values from the periodograms of the permuted data. + Value must be between 0 and 100 inclusive. + + See Also + -------- + scipy.signal.periodogram + Estimate power spectral density using a periodogram. + + Returns + ------- + float + Power threshold of the target data. + """ + max_powers = [] + while len(max_powers) < k: + _, power_p = periodogram( + np.random.permutation(y), window=None, detrend=None + ) + max_powers.append(power_p.max()) + max_powers.sort() + return np.percentile(max_powers, p) + + @staticmethod + def _split(x: ArrayLike, y: ArrayLike, start: int, end: int, split: int) -> tuple: + """ + Approximate a function at [start, end] with two line segments at + [start, split - 1] and [split, end]. + + Parameters + ---------- + x : array_like + The x-coordinates of the data points. + y : array_like + The y-coordinates of the data points. + start : int + The start index of the data points to be approximated. + end : int + The end index of the data points to be approximated. + split : int + The split index of the data points to be approximated. + + See Also + -------- + scipy.stats.linregress + Calculate a linear least-squares regression for two sets of measurements. + + Returns + ------- + linregress + The first line segment. + linregress + The second line segment. + float + The error of the approximation. + """ + x1, y1, x2, y2 = ( + x[start:split], + y[start:split], + x[split : end + 1], + y[split : end + 1], + ) + line1 = linregress(x1, y1) + line2 = linregress(x2, y2) + error = np.sum(np.abs(y1 - (line1.intercept + line1.slope * x1))) + np.sum( + np.abs(y2 - (line2.intercept + line2.slope * x2)) + ) + return line1, line2, error diff --git a/pyriodicity/detectors/fft.py b/pyriodicity/detectors/fft.py new file mode 100644 index 0000000..b2520c1 --- /dev/null +++ b/pyriodicity/detectors/fft.py @@ -0,0 +1,110 @@ +from typing import Callable, Optional, Union + +import numpy as np +from numpy.typing import ArrayLike, NDArray + +from pyriodicity.tools import apply_window, detrend, to_1d_array + + +class FFTPeriodicityDetector: + """ + Fast Fourier Transform (FFT) based periodicity detector. + + Find the periods in a given signal or series using FFT. + + Parameters + ---------- + endog : array_like + Data to be investigated. Must be squeezable to 1-d. + + References + ---------- + .. [1] Hyndman, R.J., & Athanasopoulos, G. (2021) + Forecasting: principles and practice, 3rd edition, OTexts: Melbourne, Australia. + OTexts.com/fpp3/useful-predictors.html#fourier-series. Accessed on 09-15-2024. + + Examples + -------- + Start by loading a timeseries dataset. + + >>> from statsmodels.datasets import co2 + >>> data = co2.load().data + + You can resample the data to whatever frequency you want. + + >>> data = data.resample("ME").mean().ffill() + + Use FFTPeriodicityDetector to find the list of periods using FFT, ordered + by corresponding frequency amplitudes in a descending order. + + >>> fft_detector = FFTPeriodicityDetector(data) + >>> periods = fft_detector.fit() + + You can optionally specify a window function for pre-processing. + + >>> periods = fft_detector.fit(window_func="blackman") + """ + + def __init__(self, endog: ArrayLike): + self.y = to_1d_array(endog) + + def fit( + self, + max_period_count: Optional[int] = None, + detrend_func: Optional[Union[str, Callable[[ArrayLike], NDArray]]] = "linear", + window_func: Optional[Union[float, str, tuple]] = None, + ) -> NDArray: + """ + Find periods in the given series. + + Parameters + ---------- + max_period_count : int, optional, default = None + Maximum number of periods to look for. + detrend_func : str, callable, default = None + The kind of detrending to be applied on the series. It can either be + 'linear' or 'constant' if it the parameter is of 'str' type, or a + custom function that returns a detrended series. + window_func : float, str, tuple optional, default = None + Window function to be applied to the time series. Check + 'window' parameter documentation for scipy.signal.get_window + function for more information on the accepted formats of this + parameter. + + See Also + -------- + numpy.fft + Discrete Fourier Transform. + scipy.signal.detrend + Remove linear trend along axis from data. + scipy.signal.get_window + Return a window of a given length and type. + + Returns + ------- + NDArray + List of detected periods. + """ + # Detrend data + self.y = self.y if detrend_func is None else detrend(self.y, detrend_func) + + # Apply the window function on the data + self.y = ( + self.y + if window_func is None + else apply_window(self.y, window_func=window_func) + ) + + # Compute DFT and ignore the zero frequency + freqs = np.fft.rfftfreq(len(self.y), d=1)[1:] + ft = np.fft.rfft(self.y)[1:] + + # Compute periods and their respective amplitudes + periods = np.round(1 / freqs) + amps = abs(ft) + + # A period cannot be greater than half the length of the series + filter = periods < len(self.y) // 2 + + # Return periods in descending order of their corresponding amplitudes + return periods[filter][np.argsort(-amps[filter])][:max_period_count] diff --git a/pyriodicity/tools/__init__.py b/pyriodicity/tools/__init__.py new file mode 100644 index 0000000..bdd219a --- /dev/null +++ b/pyriodicity/tools/__init__.py @@ -0,0 +1,17 @@ +from ._tools import ( + acf, + apply_window, + detrend, + remove_overloaded_kwargs, + seasonality_strength, + to_1d_array, +) + +__all__ = [ + "acf", + "apply_window", + "detrend", + "remove_overloaded_kwargs", + "seasonality_strength", + "to_1d_array", +] diff --git a/pyriodicity/tools/_tools.py b/pyriodicity/tools/_tools.py new file mode 100644 index 0000000..ba8dcbf --- /dev/null +++ b/pyriodicity/tools/_tools.py @@ -0,0 +1,57 @@ +from typing import Callable, Dict, List, Optional, Union + +import numpy as np +from numpy.typing import ArrayLike, NDArray +from scipy.signal import detrend as _detrend +from scipy.signal import get_window +from scipy.stats import kendalltau, pearsonr, spearmanr + + +@staticmethod +def to_1d_array(x: ArrayLike) -> NDArray: + y = np.ascontiguousarray(np.squeeze(np.asarray(x)), dtype=np.double) + if y.ndim != 1: + raise ValueError("y must be a 1d array") + return y + + +@staticmethod +def remove_overloaded_kwargs(kwargs: Dict, args: List) -> Dict: + for arg in args: + kwargs.pop(arg, None) + return kwargs + + +@staticmethod +def seasonality_strength(seasonal: ArrayLike, resid: ArrayLike) -> float: + return max(0, 1 - np.var(resid) / np.var(seasonal + resid)) + + +@staticmethod +def apply_window(x: ArrayLike, window_func: Union[str, float, tuple]) -> NDArray: + return x * get_window(window=window_func, Nx=len(x)) + + +@staticmethod +def detrend( + x: ArrayLike, + method: Union[str, Callable[[ArrayLike], NDArray]], +) -> NDArray: + if isinstance(method, str): + return _detrend(x, type=method) + return method(x) + + +@staticmethod +def acf( + x: ArrayLike, + nlags: int, + correlation_func: Optional[str] = "pearson", +) -> NDArray: + if not 0 < nlags <= len(x): + raise ValueError("nlags must be a postive integer less than the data length") + if correlation_func == "spearman": + return np.array([spearmanr(x, np.roll(x, l)).statistic for l in range(nlags)]) + elif correlation_func == "kendall": + return np.array([kendalltau(x, np.roll(x, l)).statistic for l in range(nlags)]) + return np.array([pearsonr(x, np.roll(x, l)).statistic for l in range(nlags)]) diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..ac2210b --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,179 @@ +colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and sys_platform == "win32" \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 +exceptiongroup==1.2.2 ; python_version >= "3.10" and python_version < "3.11" \ + --hash=sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b \ + --hash=sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc +iniconfig==2.0.0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ + --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 +numpy==2.1.1 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:046356b19d7ad1890c751b99acad5e82dc4a02232013bd9a9a712fddf8eb60f5 \ + --hash=sha256:0b8cc2715a84b7c3b161f9ebbd942740aaed913584cae9cdc7f8ad5ad41943d0 \ + --hash=sha256:0d07841fd284718feffe7dd17a63a2e6c78679b2d386d3e82f44f0108c905550 \ + --hash=sha256:13cc11c00000848702322af4de0147ced365c81d66053a67c2e962a485b3717c \ + --hash=sha256:13ce49a34c44b6de5241f0b38b07e44c1b2dcacd9e36c30f9c2fcb1bb5135db7 \ + --hash=sha256:24c2ad697bd8593887b019817ddd9974a7f429c14a5469d7fad413f28340a6d2 \ + --hash=sha256:251105b7c42abe40e3a689881e1793370cc9724ad50d64b30b358bbb3a97553b \ + --hash=sha256:2ca4b53e1e0b279142113b8c5eb7d7a877e967c306edc34f3b58e9be12fda8df \ + --hash=sha256:3269c9eb8745e8d975980b3a7411a98976824e1fdef11f0aacf76147f662b15f \ + --hash=sha256:397bc5ce62d3fb73f304bec332171535c187e0643e176a6e9421a6e3eacef06d \ + --hash=sha256:3fc5eabfc720db95d68e6646e88f8b399bfedd235994016351b1d9e062c4b270 \ + --hash=sha256:50a95ca3560a6058d6ea91d4629a83a897ee27c00630aed9d933dff191f170cd \ + --hash=sha256:52ac2e48f5ad847cd43c4755520a2317f3380213493b9d8a4c5e37f3b87df504 \ + --hash=sha256:53e27293b3a2b661c03f79aa51c3987492bd4641ef933e366e0f9f6c9bf257ec \ + --hash=sha256:57eb525e7c2a8fdee02d731f647146ff54ea8c973364f3b850069ffb42799647 \ + --hash=sha256:5889dd24f03ca5a5b1e8a90a33b5a0846d8977565e4ae003a63d22ecddf6782f \ + --hash=sha256:59ca673ad11d4b84ceb385290ed0ebe60266e356641428c845b39cd9df6713ab \ + --hash=sha256:6435c48250c12f001920f0751fe50c0348f5f240852cfddc5e2f97e007544cbe \ + --hash=sha256:6e5a9cb2be39350ae6c8f79410744e80154df658d5bea06e06e0ac5bb75480d5 \ + --hash=sha256:7be6a07520b88214ea85d8ac8b7d6d8a1839b0b5cb87412ac9f49fa934eb15d5 \ + --hash=sha256:7c803b7934a7f59563db459292e6aa078bb38b7ab1446ca38dd138646a38203e \ + --hash=sha256:7dd86dfaf7c900c0bbdcb8b16e2f6ddf1eb1fe39c6c8cca6e94844ed3152a8fd \ + --hash=sha256:8661c94e3aad18e1ea17a11f60f843a4933ccaf1a25a7c6a9182af70610b2313 \ + --hash=sha256:8ae0fd135e0b157365ac7cc31fff27f07a5572bdfc38f9c2d43b2aff416cc8b0 \ + --hash=sha256:910b47a6d0635ec1bd53b88f86120a52bf56dcc27b51f18c7b4a2e2224c29f0f \ + --hash=sha256:913cc1d311060b1d409e609947fa1b9753701dac96e6581b58afc36b7ee35af6 \ + --hash=sha256:920b0911bb2e4414c50e55bd658baeb78281a47feeb064ab40c2b66ecba85553 \ + --hash=sha256:950802d17a33c07cba7fd7c3dcfa7d64705509206be1606f196d179e539111ed \ + --hash=sha256:981707f6b31b59c0c24bcda52e5605f9701cb46da4b86c2e8023656ad3e833cb \ + --hash=sha256:98ce7fb5b8063cfdd86596b9c762bf2b5e35a2cdd7e967494ab78a1fa7f8b86e \ + --hash=sha256:99f4a9ee60eed1385a86e82288971a51e71df052ed0b2900ed30bc840c0f2e39 \ + --hash=sha256:9a8e06c7a980869ea67bbf551283bbed2856915f0a792dc32dd0f9dd2fb56728 \ + --hash=sha256:ae8ce252404cdd4de56dcfce8b11eac3c594a9c16c231d081fb705cf23bd4d9e \ + --hash=sha256:afd9c680df4de71cd58582b51e88a61feed4abcc7530bcd3d48483f20fc76f2a \ + --hash=sha256:b49742cdb85f1f81e4dc1b39dcf328244f4d8d1ded95dea725b316bd2cf18c95 \ + --hash=sha256:b5613cfeb1adfe791e8e681128f5f49f22f3fcaa942255a6124d58ca59d9528f \ + --hash=sha256:bab7c09454460a487e631ffc0c42057e3d8f2a9ddccd1e60c7bb8ed774992480 \ + --hash=sha256:c8a0e34993b510fc19b9a2ce7f31cb8e94ecf6e924a40c0c9dd4f62d0aac47d9 \ + --hash=sha256:caf5d284ddea7462c32b8d4a6b8af030b6c9fd5332afb70e7414d7fdded4bfd0 \ + --hash=sha256:cea427d1350f3fd0d2818ce7350095c1a2ee33e30961d2f0fef48576ddbbe90f \ + --hash=sha256:d0cf7d55b1051387807405b3898efafa862997b4cba8aa5dbe657be794afeafd \ + --hash=sha256:d10c39947a2d351d6d466b4ae83dad4c37cd6c3cdd6d5d0fa797da56f710a6ae \ + --hash=sha256:d2b9cd92c8f8e7b313b80e93cedc12c0112088541dcedd9197b5dee3738c1201 \ + --hash=sha256:d4c57b68c8ef5e1ebf47238e99bf27657511ec3f071c465f6b1bccbef12d4136 \ + --hash=sha256:d51fc141ddbe3f919e91a096ec739f49d686df8af254b2053ba21a910ae518bf \ + --hash=sha256:e097507396c0be4e547ff15b13dc3866f45f3680f789c1a1301b07dadd3fbc78 \ + --hash=sha256:e30356d530528a42eeba51420ae8bf6c6c09559051887196599d96ee5f536468 \ + --hash=sha256:e8d5f8a8e3bc87334f025194c6193e408903d21ebaeb10952264943a985066ca \ + --hash=sha256:e8dfa9e94fc127c40979c3eacbae1e61fda4fe71d84869cc129e2721973231ef \ + --hash=sha256:f212d4f46b67ff604d11fff7cc62d36b3e8714edf68e44e9760e19be38c03eb0 \ + --hash=sha256:f7506387e191fe8cdb267f912469a3cccc538ab108471291636a96a54e599556 \ + --hash=sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521 \ + --hash=sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b +packaging==24.1 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ + --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 +pandas==2.2.2 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863 \ + --hash=sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2 \ + --hash=sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1 \ + --hash=sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad \ + --hash=sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db \ + --hash=sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76 \ + --hash=sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51 \ + --hash=sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32 \ + --hash=sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08 \ + --hash=sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b \ + --hash=sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4 \ + --hash=sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921 \ + --hash=sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288 \ + --hash=sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee \ + --hash=sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0 \ + --hash=sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24 \ + --hash=sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99 \ + --hash=sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151 \ + --hash=sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd \ + --hash=sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce \ + --hash=sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57 \ + --hash=sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef \ + --hash=sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54 \ + --hash=sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a \ + --hash=sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238 \ + --hash=sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23 \ + --hash=sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772 \ + --hash=sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce \ + --hash=sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad +patsy==0.5.6 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:19056886fd8fa71863fa32f0eb090267f21fb74be00f19f5c70b2e9d76c883c6 \ + --hash=sha256:95c6d47a7222535f84bff7f63d7303f2e297747a598db89cf5c67f0c0c7d2cdb +pluggy==1.5.0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ + --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 +pytest==7.4.4 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280 \ + --hash=sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8 +python-dateutil==2.9.0.post0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 +pytz==2024.2 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a \ + --hash=sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725 +scipy==1.14.1 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e \ + --hash=sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79 \ + --hash=sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37 \ + --hash=sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5 \ + --hash=sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675 \ + --hash=sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d \ + --hash=sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f \ + --hash=sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310 \ + --hash=sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617 \ + --hash=sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e \ + --hash=sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e \ + --hash=sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417 \ + --hash=sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d \ + --hash=sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94 \ + --hash=sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad \ + --hash=sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8 \ + --hash=sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0 \ + --hash=sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69 \ + --hash=sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066 \ + --hash=sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3 \ + --hash=sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5 \ + --hash=sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07 \ + --hash=sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2 \ + --hash=sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389 \ + --hash=sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d \ + --hash=sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84 \ + --hash=sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2 \ + --hash=sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3 \ + --hash=sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73 \ + --hash=sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06 \ + --hash=sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc \ + --hash=sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1 \ + --hash=sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2 +six==1.16.0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 +statsmodels==0.14.2 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:0e46e9d59293c1af4cc1f4e5248f17e7e7bc596bfce44d327c789ac27f09111b \ + --hash=sha256:10f2b7611a61adb7d596a6d239abdf1a4d5492b931b00d5ed23d32844d40e48e \ + --hash=sha256:201c3d00929c4a67cda1fe05b098c8dcf1b1eeefa88e80a8f963a844801ed59f \ + --hash=sha256:29c78a7601fdae1aa32104c5ebff2e0b72c26f33e870e2f94ab1bcfd927ece9b \ + --hash=sha256:4864a1c4615c5ea5f2e3b078a75bdedc90dd9da210a37e0738e064b419eccee2 \ + --hash=sha256:50fcb633987779e795142f51ba49fb27648d46e8a1382b32ebe8e503aaabaa9e \ + --hash=sha256:55d1742778400ae67acb04b50a2c7f5804182f8a874bd09ca397d69ed159a751 \ + --hash=sha256:5827a12e3ede2b98a784476d61d6bec43011fedb64aa815f2098e0573bece257 \ + --hash=sha256:7a91f6c4943de13e3ce2e20ee3b5d26d02bd42300616a421becd53756f5deb37 \ + --hash=sha256:876794068abfaeed41df71b7887000031ecf44fbfa6b50d53ccb12ebb4ab747a \ + --hash=sha256:8875823bdd41806dc853333cc4e1b7ef9481bad2380a999e66ea42382cf2178d \ + --hash=sha256:890550147ad3a81cda24f0ba1a5c4021adc1601080bd00e191ae7cd6feecd6ad \ + --hash=sha256:8e004cfad0e46ce73fe3f3812010c746f0d4cfd48e307b45c14e9e360f3d2510 \ + --hash=sha256:90fd2f0110b73fc3fa5a2f21c3ca99b0e22285cccf38e56b5b8fd8ce28791b0f \ + --hash=sha256:9edefa4ce08e40bc1d67d2f79bc686ee5e238e801312b5a029ee7786448c389a \ + --hash=sha256:a87ef21fadb445b650f327340dde703f13aec1540f3d497afb66324499dea97a \ + --hash=sha256:ac780ad9ff552773798829a0b9c46820b0faa10e6454891f5e49a845123758ab \ + --hash=sha256:afbd92410e0df06f3d8c4e7c0e2e71f63f4969531f280fb66059e2ecdb6e0415 \ + --hash=sha256:c254c66142f1167b4c7d031cf8db55294cc62ff3280e090fc45bd10a7f5fd029 \ + --hash=sha256:df5d6f95c46f0341da6c79ee7617e025bf2b371e190a8e60af1ae9cabbdb7a97 \ + --hash=sha256:eb0ba1ad3627705f5ae20af6b2982f500546d43892543b36c7bca3e2f87105e7 \ + --hash=sha256:f36494df7c03d63168fccee5038a62f469469ed6a4dd6eaeb9338abedcd0d5f5 \ + --hash=sha256:f450fcbae214aae66bd9d2b9af48e0f8ba1cb0e8596c6ebb34e6e3f0fec6542c \ + --hash=sha256:f870d14a587ea58a3b596aa994c2ed889cc051f9e450e887d2c83656fc6a64bf +tomli==2.0.1 ; python_version >= "3.10" and python_version < "3.11" \ + --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ + --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f +tzdata==2024.1 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd \ + --hash=sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252 diff --git a/requirements.txt b/requirements.txt index cf36276..9a1dbd8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,141 +1,88 @@ -numpy==1.26.4 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b \ - --hash=sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818 \ - --hash=sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20 \ - --hash=sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0 \ - --hash=sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010 \ - --hash=sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a \ - --hash=sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea \ - --hash=sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c \ - --hash=sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71 \ - --hash=sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110 \ - --hash=sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be \ - --hash=sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a \ - --hash=sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a \ - --hash=sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5 \ - --hash=sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed \ - --hash=sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd \ - --hash=sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c \ - --hash=sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e \ - --hash=sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0 \ - --hash=sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c \ - --hash=sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a \ - --hash=sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b \ - --hash=sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0 \ - --hash=sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6 \ - --hash=sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2 \ - --hash=sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a \ - --hash=sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30 \ - --hash=sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218 \ - --hash=sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5 \ - --hash=sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07 \ - --hash=sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2 \ - --hash=sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4 \ - --hash=sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764 \ - --hash=sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef \ - --hash=sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3 \ - --hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f -packaging==24.0 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \ - --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 -pandas==2.2.1 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:04f6ec3baec203c13e3f8b139fb0f9f86cd8c0b94603ae3ae8ce9a422e9f5bee \ - --hash=sha256:06cf591dbaefb6da9de8472535b185cba556d0ce2e6ed28e21d919704fef1a9e \ - --hash=sha256:0ab90f87093c13f3e8fa45b48ba9f39181046e8f3317d3aadb2fffbb1b978572 \ - --hash=sha256:0f573ab277252ed9aaf38240f3b54cfc90fff8e5cab70411ee1d03f5d51f3944 \ - --hash=sha256:101d0eb9c5361aa0146f500773395a03839a5e6ecde4d4b6ced88b7e5a1a6403 \ - --hash=sha256:11940e9e3056576ac3244baef2fedade891977bcc1cb7e5cc8f8cc7d603edc89 \ - --hash=sha256:1ba21b1d5c0e43416218db63037dbe1a01fc101dc6e6024bcad08123e48004ab \ - --hash=sha256:4aa1d8707812a658debf03824016bf5ea0d516afdea29b7dc14cf687bc4d4ec6 \ - --hash=sha256:4acf681325ee1c7f950d058b05a820441075b0dd9a2adf5c4835b9bc056bf4fb \ - --hash=sha256:53680dc9b2519cbf609c62db3ed7c0b499077c7fefda564e330286e619ff0dd9 \ - --hash=sha256:739cc70eaf17d57608639e74d63387b0d8594ce02f69e7a0b046f117974b3019 \ - --hash=sha256:76f27a809cda87e07f192f001d11adc2b930e93a2b0c4a236fde5429527423be \ - --hash=sha256:7d2ed41c319c9fb4fd454fe25372028dfa417aacb9790f68171b2e3f06eae8cd \ - --hash=sha256:88ecb5c01bb9ca927ebc4098136038519aa5d66b44671861ffab754cae75102c \ - --hash=sha256:8df8612be9cd1c7797c93e1c5df861b2ddda0b48b08f2c3eaa0702cf88fb5f88 \ - --hash=sha256:94e714a1cca63e4f5939cdce5f29ba8d415d85166be3441165edd427dc9f6bc0 \ - --hash=sha256:9bd8a40f47080825af4317d0340c656744f2bfdb6819f818e6ba3cd24c0e1397 \ - --hash=sha256:9d1265545f579edf3f8f0cb6f89f234f5e44ba725a34d86535b1a1d38decbccc \ - --hash=sha256:a935a90a76c44fe170d01e90a3594beef9e9a6220021acfb26053d01426f7dc2 \ - --hash=sha256:af5d3c00557d657c8773ef9ee702c61dd13b9d7426794c9dfeb1dc4a0bf0ebc7 \ - --hash=sha256:c2ce852e1cf2509a69e98358e8458775f89599566ac3775e70419b98615f4b06 \ - --hash=sha256:c38ce92cb22a4bea4e3929429aa1067a454dcc9c335799af93ba9be21b6beb51 \ - --hash=sha256:c391f594aae2fd9f679d419e9a4d5ba4bce5bb13f6a989195656e7dc4b95c8f0 \ - --hash=sha256:c70e00c2d894cb230e5c15e4b1e1e6b2b478e09cf27cc593a11ef955b9ecc81a \ - --hash=sha256:df0c37ebd19e11d089ceba66eba59a168242fc6b7155cba4ffffa6eccdfb8f16 \ - --hash=sha256:e97fbb5387c69209f134893abc788a6486dbf2f9e511070ca05eed4b930b1b02 \ - --hash=sha256:f02a3a6c83df4026e55b63c1f06476c9aa3ed6af3d89b4f04ea656ccdaaaa359 \ - --hash=sha256:f821213d48f4ab353d20ebc24e4faf94ba40d76680642fb7ce2ea31a3ad94f9b \ - --hash=sha256:f9d3558d263073ed95e46f4650becff0c5e1ffe0fc3a015de3c79283dfbdb3df -patsy==0.5.6 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:19056886fd8fa71863fa32f0eb090267f21fb74be00f19f5c70b2e9d76c883c6 \ - --hash=sha256:95c6d47a7222535f84bff7f63d7303f2e297747a598db89cf5c67f0c0c7d2cdb -python-dateutil==2.9.0.post0 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ - --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 -pytz==2024.1 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812 \ - --hash=sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319 -scipy==1.13.0 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:05f1432ba070e90d42d7fd836462c50bf98bd08bed0aa616c359eed8a04e3922 \ - --hash=sha256:09c74543c4fbeb67af6ce457f6a6a28e5d3739a87f62412e4a16e46f164f0ae5 \ - --hash=sha256:0fbcf8abaf5aa2dc8d6400566c1a727aed338b5fe880cde64907596a89d576fa \ - --hash=sha256:109d391d720fcebf2fbe008621952b08e52907cf4c8c7efc7376822151820820 \ - --hash=sha256:1d2f7bb14c178f8b13ebae93f67e42b0a6b0fc50eba1cd8021c9b6e08e8fb1cd \ - --hash=sha256:1e7626dfd91cdea5714f343ce1176b6c4745155d234f1033584154f60ef1ff42 \ - --hash=sha256:22789b56a999265431c417d462e5b7f2b487e831ca7bef5edeb56efe4c93f86e \ - --hash=sha256:28e286bf9ac422d6beb559bc61312c348ca9b0f0dae0d7c5afde7f722d6ea13d \ - --hash=sha256:33fde20efc380bd23a78a4d26d59fc8704e9b5fd9b08841693eb46716ba13d86 \ - --hash=sha256:45c08bec71d3546d606989ba6e7daa6f0992918171e2a6f7fbedfa7361c2de1e \ - --hash=sha256:4dca18c3ffee287ddd3bc8f1dabaf45f5305c5afc9f8ab9cbfab855e70b2df5c \ - --hash=sha256:5407708195cb38d70fd2d6bb04b1b9dd5c92297d86e9f9daae1576bd9e06f602 \ - --hash=sha256:58569af537ea29d3f78e5abd18398459f195546bb3be23d16677fb26616cc11e \ - --hash=sha256:5e4a756355522eb60fcd61f8372ac2549073c8788f6114449b37e9e8104f15a5 \ - --hash=sha256:6bf9fe63e7a4bf01d3645b13ff2aa6dea023d38993f42aaac81a18b1bda7a82a \ - --hash=sha256:8930ae3ea371d6b91c203b1032b9600d69c568e537b7988a3073dfe4d4774f21 \ - --hash=sha256:9ff7dad5d24a8045d836671e082a490848e8639cabb3dbdacb29f943a678683d \ - --hash=sha256:a2f471de4d01200718b2b8927f7d76b5d9bde18047ea0fa8bd15c5ba3f26a1d6 \ - --hash=sha256:ac38c4c92951ac0f729c4c48c9e13eb3675d9986cc0c83943784d7390d540c78 \ - --hash=sha256:b2a3ff461ec4756b7e8e42e1c681077349a038f0686132d623fa404c0bee2551 \ - --hash=sha256:b5acd8e1dbd8dbe38d0004b1497019b2dbbc3d70691e65d69615f8a7292865d7 \ - --hash=sha256:b8434f6f3fa49f631fae84afee424e2483289dfc30a47755b4b4e6b07b2633a4 \ - --hash=sha256:ba419578ab343a4e0a77c0ef82f088238a93eef141b2b8017e46149776dfad4d \ - --hash=sha256:d0de696f589681c2802f9090fff730c218f7c51ff49bf252b6a97ec4a5d19e8b \ - --hash=sha256:dcbb9ea49b0167de4167c40eeee6e167caeef11effb0670b554d10b1e693a8b9 -six==1.16.0 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 -statsmodels==0.14.1 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:04293890f153ffe577e60a227bd43babd5f6c1fc50ea56a3ab1862ae85247a95 \ - --hash=sha256:0a8aae75a2e08ebd990e5fa394f8e32738b55785cb70798449a3f4207085e667 \ - --hash=sha256:0d5373d176239993c095b00d06036690a50309a4e00c2da553b65b840f956ae6 \ - --hash=sha256:2260efdc1ef89f39c670a0bd8151b1d0843567781bcafec6cda0534eb47a94f6 \ - --hash=sha256:2de2b97413913d52ad6342dece2d653e77f78620013b7705fad291d4e4266ccb \ - --hash=sha256:3e70a2e93d54d40b2cb6426072acbc04f35501b1ea2569f6786964adde6ca572 \ - --hash=sha256:43af9c0b07c9d72f275cf14ea54a481a3f20911f0b443181be4769def258fdeb \ - --hash=sha256:44ca8cb88fa3d3a4ffaff1fb8eb0e98bbf83fc936fcd9b9eedee258ecc76696a \ - --hash=sha256:4fe0a60695952b82139ae8750952786a700292f9e0551d572d7685070944487b \ - --hash=sha256:5385e22e72159a09c099c4fb975f350a9f3afeb57c1efce273b89dcf1fe44c0f \ - --hash=sha256:709bfcef2dbe66f705b17e56d1021abad02243ee1a5d1efdb90f9bad8b06a329 \ - --hash=sha256:7562cb18a90a114f39fab6f1c25b9c7b39d9cd5f433d0044b430ca9d44a8b52c \ - --hash=sha256:a16975ab6ad505d837ba9aee11f92a8c5b49c4fa1ff45b60fe23780b19e5705e \ - --hash=sha256:a532dfe899f8b6632cd8caa0b089b403415618f51e840d1817a1e4b97e200c73 \ - --hash=sha256:ab3a73d16c0569adbba181ebb967e5baaa74935f6d2efe86ac6fc5857449b07d \ - --hash=sha256:b0f727fe697f6406d5f677b67211abe5a55101896abdfacdb3f38410405f6ad8 \ - --hash=sha256:b3abaca4b963259a2bf349c7609cfbb0ce64ad5fb3d92d6f08e21453e4890248 \ - --hash=sha256:b6838ac6bdb286daabb5e91af90fd4258f09d0cec9aace78cc441cb2b17df428 \ - --hash=sha256:b69a63ad6c979a6e4cde11870ffa727c76a318c225a7e509f031fbbdfb4e416a \ - --hash=sha256:bc0351d279c4e080f0ce638a3d886d312aa29eade96042e3ba0a73771b1abdfb \ - --hash=sha256:bc43765710099ca6a942b5ffa1bac7668965052542ba793dd072d26c83453572 \ - --hash=sha256:bf293ada63b2859d95210165ad1dfcd97bd7b994a5266d6fbeb23659d8f0bf68 \ - --hash=sha256:c008e16096f24f0514e53907890ccac6589a16ad6c81c218f2ee6752fdada555 \ - --hash=sha256:c0564d92cb05b219b4538ed09e77d96658a924a691255e1f7dd23ee338df441b \ - --hash=sha256:c3420f88289c593ba2bca33619023059c476674c160733bd7d858564787c83d3 \ - --hash=sha256:e278fe74da5ed5e06c11a30851eda1af08ef5af6be8507c2c45d2e08f7550dde \ - --hash=sha256:eefa5bcff335440ee93e28745eab63559a20cd34eea0375c66d96b016de909b3 \ - --hash=sha256:f32a7cd424cf33304a54daee39d32cccf1d0265e652c920adeaeedff6d576457 \ - --hash=sha256:f8c30181c084173d662aaf0531867667be2ff1bee103b84feb64f149f792dbd2 -tzdata==2024.1 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd \ - --hash=sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252 +numpy==2.1.1 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:046356b19d7ad1890c751b99acad5e82dc4a02232013bd9a9a712fddf8eb60f5 \ + --hash=sha256:0b8cc2715a84b7c3b161f9ebbd942740aaed913584cae9cdc7f8ad5ad41943d0 \ + --hash=sha256:0d07841fd284718feffe7dd17a63a2e6c78679b2d386d3e82f44f0108c905550 \ + --hash=sha256:13cc11c00000848702322af4de0147ced365c81d66053a67c2e962a485b3717c \ + --hash=sha256:13ce49a34c44b6de5241f0b38b07e44c1b2dcacd9e36c30f9c2fcb1bb5135db7 \ + --hash=sha256:24c2ad697bd8593887b019817ddd9974a7f429c14a5469d7fad413f28340a6d2 \ + --hash=sha256:251105b7c42abe40e3a689881e1793370cc9724ad50d64b30b358bbb3a97553b \ + --hash=sha256:2ca4b53e1e0b279142113b8c5eb7d7a877e967c306edc34f3b58e9be12fda8df \ + --hash=sha256:3269c9eb8745e8d975980b3a7411a98976824e1fdef11f0aacf76147f662b15f \ + --hash=sha256:397bc5ce62d3fb73f304bec332171535c187e0643e176a6e9421a6e3eacef06d \ + --hash=sha256:3fc5eabfc720db95d68e6646e88f8b399bfedd235994016351b1d9e062c4b270 \ + --hash=sha256:50a95ca3560a6058d6ea91d4629a83a897ee27c00630aed9d933dff191f170cd \ + --hash=sha256:52ac2e48f5ad847cd43c4755520a2317f3380213493b9d8a4c5e37f3b87df504 \ + --hash=sha256:53e27293b3a2b661c03f79aa51c3987492bd4641ef933e366e0f9f6c9bf257ec \ + --hash=sha256:57eb525e7c2a8fdee02d731f647146ff54ea8c973364f3b850069ffb42799647 \ + --hash=sha256:5889dd24f03ca5a5b1e8a90a33b5a0846d8977565e4ae003a63d22ecddf6782f \ + --hash=sha256:59ca673ad11d4b84ceb385290ed0ebe60266e356641428c845b39cd9df6713ab \ + --hash=sha256:6435c48250c12f001920f0751fe50c0348f5f240852cfddc5e2f97e007544cbe \ + --hash=sha256:6e5a9cb2be39350ae6c8f79410744e80154df658d5bea06e06e0ac5bb75480d5 \ + --hash=sha256:7be6a07520b88214ea85d8ac8b7d6d8a1839b0b5cb87412ac9f49fa934eb15d5 \ + --hash=sha256:7c803b7934a7f59563db459292e6aa078bb38b7ab1446ca38dd138646a38203e \ + --hash=sha256:7dd86dfaf7c900c0bbdcb8b16e2f6ddf1eb1fe39c6c8cca6e94844ed3152a8fd \ + --hash=sha256:8661c94e3aad18e1ea17a11f60f843a4933ccaf1a25a7c6a9182af70610b2313 \ + --hash=sha256:8ae0fd135e0b157365ac7cc31fff27f07a5572bdfc38f9c2d43b2aff416cc8b0 \ + --hash=sha256:910b47a6d0635ec1bd53b88f86120a52bf56dcc27b51f18c7b4a2e2224c29f0f \ + --hash=sha256:913cc1d311060b1d409e609947fa1b9753701dac96e6581b58afc36b7ee35af6 \ + --hash=sha256:920b0911bb2e4414c50e55bd658baeb78281a47feeb064ab40c2b66ecba85553 \ + --hash=sha256:950802d17a33c07cba7fd7c3dcfa7d64705509206be1606f196d179e539111ed \ + --hash=sha256:981707f6b31b59c0c24bcda52e5605f9701cb46da4b86c2e8023656ad3e833cb \ + --hash=sha256:98ce7fb5b8063cfdd86596b9c762bf2b5e35a2cdd7e967494ab78a1fa7f8b86e \ + --hash=sha256:99f4a9ee60eed1385a86e82288971a51e71df052ed0b2900ed30bc840c0f2e39 \ + --hash=sha256:9a8e06c7a980869ea67bbf551283bbed2856915f0a792dc32dd0f9dd2fb56728 \ + --hash=sha256:ae8ce252404cdd4de56dcfce8b11eac3c594a9c16c231d081fb705cf23bd4d9e \ + --hash=sha256:afd9c680df4de71cd58582b51e88a61feed4abcc7530bcd3d48483f20fc76f2a \ + --hash=sha256:b49742cdb85f1f81e4dc1b39dcf328244f4d8d1ded95dea725b316bd2cf18c95 \ + --hash=sha256:b5613cfeb1adfe791e8e681128f5f49f22f3fcaa942255a6124d58ca59d9528f \ + --hash=sha256:bab7c09454460a487e631ffc0c42057e3d8f2a9ddccd1e60c7bb8ed774992480 \ + --hash=sha256:c8a0e34993b510fc19b9a2ce7f31cb8e94ecf6e924a40c0c9dd4f62d0aac47d9 \ + --hash=sha256:caf5d284ddea7462c32b8d4a6b8af030b6c9fd5332afb70e7414d7fdded4bfd0 \ + --hash=sha256:cea427d1350f3fd0d2818ce7350095c1a2ee33e30961d2f0fef48576ddbbe90f \ + --hash=sha256:d0cf7d55b1051387807405b3898efafa862997b4cba8aa5dbe657be794afeafd \ + --hash=sha256:d10c39947a2d351d6d466b4ae83dad4c37cd6c3cdd6d5d0fa797da56f710a6ae \ + --hash=sha256:d2b9cd92c8f8e7b313b80e93cedc12c0112088541dcedd9197b5dee3738c1201 \ + --hash=sha256:d4c57b68c8ef5e1ebf47238e99bf27657511ec3f071c465f6b1bccbef12d4136 \ + --hash=sha256:d51fc141ddbe3f919e91a096ec739f49d686df8af254b2053ba21a910ae518bf \ + --hash=sha256:e097507396c0be4e547ff15b13dc3866f45f3680f789c1a1301b07dadd3fbc78 \ + --hash=sha256:e30356d530528a42eeba51420ae8bf6c6c09559051887196599d96ee5f536468 \ + --hash=sha256:e8d5f8a8e3bc87334f025194c6193e408903d21ebaeb10952264943a985066ca \ + --hash=sha256:e8dfa9e94fc127c40979c3eacbae1e61fda4fe71d84869cc129e2721973231ef \ + --hash=sha256:f212d4f46b67ff604d11fff7cc62d36b3e8714edf68e44e9760e19be38c03eb0 \ + --hash=sha256:f7506387e191fe8cdb267f912469a3cccc538ab108471291636a96a54e599556 \ + --hash=sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521 \ + --hash=sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b +scipy==1.14.1 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e \ + --hash=sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79 \ + --hash=sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37 \ + --hash=sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5 \ + --hash=sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675 \ + --hash=sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d \ + --hash=sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f \ + --hash=sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310 \ + --hash=sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617 \ + --hash=sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e \ + --hash=sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e \ + --hash=sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417 \ + --hash=sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d \ + --hash=sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94 \ + --hash=sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad \ + --hash=sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8 \ + --hash=sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0 \ + --hash=sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69 \ + --hash=sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066 \ + --hash=sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3 \ + --hash=sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5 \ + --hash=sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07 \ + --hash=sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2 \ + --hash=sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389 \ + --hash=sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d \ + --hash=sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84 \ + --hash=sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2 \ + --hash=sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3 \ + --hash=sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73 \ + --hash=sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06 \ + --hash=sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc \ + --hash=sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1 \ + --hash=sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2 diff --git a/tests/detectors/__init__.py b/tests/detectors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/detectors/test_acf.py b/tests/detectors/test_acf.py new file mode 100644 index 0000000..30538eb --- /dev/null +++ b/tests/detectors/test_acf.py @@ -0,0 +1,96 @@ +from statsmodels.datasets import co2 + +from pyriodicity import ACFPeriodicityDetector + + +def test_co2_daily_acf_default(): + data = co2.load().data.resample("D").mean().ffill() + acf_detector = ACFPeriodicityDetector(data) + periods = acf_detector.fit() + assert len(periods) > 0 + + +def test_co2_weekly_acf_default(): + data = co2.load().data.resample("W").mean().ffill() + acf_detector = ACFPeriodicityDetector(data) + periods = acf_detector.fit() + assert len(periods) > 0 + + +def test_co2_monthly_acf_default(): + data = co2.load().data.resample("ME").mean().ffill() + acf_detector = ACFPeriodicityDetector(data) + periods = acf_detector.fit() + assert len(periods) > 0 + + +def test_co2_daily_acf_max_period_count_one(): + data = co2.load().data.resample("D").mean().ffill() + acf_detector = ACFPeriodicityDetector(data) + periods = acf_detector.fit(max_period_count=1) + assert len(periods) == 1 + assert periods[0] == 364 + + +def test_co2_weekly_acf_max_period_count_one(): + data = co2.load().data.resample("W").mean().ffill() + acf_detector = ACFPeriodicityDetector(data) + periods = acf_detector.fit(max_period_count=1) + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_acf_max_period_count_one(): + data = co2.load().data.resample("ME").mean().ffill() + acf_detector = ACFPeriodicityDetector(data) + periods = acf_detector.fit(max_period_count=1) + assert len(periods) == 1 + assert periods[0] == 12 + + +def test_co2_daily_acf_max_period_count_one_correlation_func_spearman(): + data = co2.load().data.resample("D").mean().ffill() + acf_detector = ACFPeriodicityDetector(data) + periods = acf_detector.fit(max_period_count=1, correlation_func="spearman") + assert len(periods) == 1 + assert periods[0] == 364 + + +def test_co2_weekly_acf_max_period_count_one_correlation_func_spearman(): + data = co2.load().data.resample("W").mean().ffill() + acf_detector = ACFPeriodicityDetector(data) + periods = acf_detector.fit(max_period_count=1, correlation_func="spearman") + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_acf_max_period_count_one_correlation_func_spearman(): + data = co2.load().data.resample("ME").mean().ffill() + acf_detector = ACFPeriodicityDetector(data) + periods = acf_detector.fit(max_period_count=1, correlation_func="spearman") + assert len(periods) == 1 + assert periods[0] == 12 + + +def test_co2_daily_acf_max_period_count_one_window_func_blackman(): + data = co2.load().data.resample("D").mean().ffill() + acf_detector = ACFPeriodicityDetector(data) + periods = acf_detector.fit(max_period_count=1, window_func="blackman") + assert len(periods) == 1 + assert periods[0] == 364 + + +def test_co2_weekly_acf_max_period_count_one_window_func_blackman(): + data = co2.load().data.resample("W").mean().ffill() + acf_detector = ACFPeriodicityDetector(data) + periods = acf_detector.fit(max_period_count=1, window_func="blackman") + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_acf_max_period_count_one_window_func_blackman(): + data = co2.load().data.resample("ME").mean().ffill() + acf_detector = ACFPeriodicityDetector(data) + periods = acf_detector.fit(max_period_count=1, window_func="blackman") + assert len(periods) == 1 + assert periods[0] == 12 diff --git a/tests/detectors/test_autoperiod.py b/tests/detectors/test_autoperiod.py new file mode 100644 index 0000000..582403d --- /dev/null +++ b/tests/detectors/test_autoperiod.py @@ -0,0 +1,103 @@ +import numpy as np +from statsmodels.datasets import co2 + +from pyriodicity import Autoperiod + + +def test_co2_daily_autoperiod_default(): + data = co2.load().data.resample("D").mean().ffill() + autoperiod = Autoperiod(data) + periods = autoperiod.fit() + assert len(periods) == 1 + assert periods[0] == 364 + + +def test_co2_weekly_autoperiod_default(): + data = co2.load().data.resample("W").mean().ffill() + autoperiod = Autoperiod(data) + periods = autoperiod.fit() + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_autoperiod_default(): + data = co2.load().data.resample("ME").mean().ffill() + autoperiod = Autoperiod(data) + periods = autoperiod.fit() + assert len(periods) == 1 + assert periods[0] == 12 + + +def test_co2_daily_autoperiod_detrend_func_constant(): + data = co2.load().data.resample("D").mean().ffill() + autoperiod = Autoperiod(data) + periods = autoperiod.fit(detrend_func="constant") + assert len(periods) == 0 + + +def test_co2_weekly_autoperiod_detrend_func_constant(): + data = co2.load().data.resample("W").mean().ffill() + autoperiod = Autoperiod(data) + periods = autoperiod.fit(detrend_func="constant") + assert len(periods) == 0 + + +def test_co2_monthly_autoperiod_detrend_func_constant(): + data = co2.load().data.resample("ME").mean().ffill() + autoperiod = Autoperiod(data) + periods = autoperiod.fit(detrend_func="constant") + assert len(periods) == 0 + + +def test_co2_daily_autoperiod_detrend_func_constant_window_func_blackman(): + data = co2.load().data.resample("D").mean().ffill() + autoperiod = Autoperiod(data) + periods = autoperiod.fit(detrend_func="constant", window_func="blackman") + assert len(periods) == 1 + assert periods[0] == 364 + + +def test_co2_weekly_autoperiod_detrend_func_constant_window_func_blackman(): + data = co2.load().data.resample("ME").mean().ffill() + autoperiod = Autoperiod(data) + periods = autoperiod.fit(detrend_func="constant", window_func="blackman") + assert len(periods) == 1 + assert periods[0] == 12 + + +def test_co2_monthly_autoperiod_detrend_func_constant_window_func_blackman(): + data = co2.load().data.resample("ME").mean().ffill() + autoperiod = Autoperiod(data) + periods = autoperiod.fit(detrend_func="constant", window_func="blackman") + assert len(periods) == 1 + assert periods[0] == 12 + + +def test_co2_daily_autoperiod_detrend_func_custom_window_func_blackmanharris(): + data = co2.load().data.resample("D").mean().ffill() + autoperiod = Autoperiod(data) + periods = autoperiod.fit( + detrend_func=lambda x: x - np.median(x), window_func="blackmanharris" + ) + assert len(periods) == 1 + assert periods[0] == 364 + + +def test_co2_weekly_autoperiod_detrend_func_custom_window_func_blackmanharris(): + data = co2.load().data.resample("W").mean().ffill() + autoperiod = Autoperiod(data) + periods = autoperiod.fit( + detrend_func=lambda x: x - np.median(x), window_func="blackmanharris" + ) + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_autoperiod_detrend_func_custom_window_func_blackmanharris(): + data = co2.load().data.resample("ME").mean().ffill() + autoperiod = Autoperiod(data) + periods = autoperiod.fit( + detrend_func=lambda x: x - np.median(x), window_func="blackmanharris" + ) + assert len(periods) == 1 + assert periods[0] == 12 diff --git a/tests/detectors/test_fft.py b/tests/detectors/test_fft.py new file mode 100644 index 0000000..f658b1f --- /dev/null +++ b/tests/detectors/test_fft.py @@ -0,0 +1,233 @@ +from statsmodels.datasets import co2 + +from pyriodicity import FFTPeriodicityDetector + + +def test_co2_monthly_fft_find_all_periods(): + data = co2.load().data.resample("ME").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit() + assert len(periods) != 0 + + +def test_co2_monthly_fft_find_first_two_periods(): + data = co2.load().data.resample("ME").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=2) + assert len(periods) == 2 + + +def test_co2_daily_fft_find_strongest_period(): + data = co2.load().data.resample("D").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1) + assert len(periods) == 1 + assert periods[0] == 363 + + +def test_co2_weekly_fft_find_strongest_period(): + data = co2.load().data.resample("W").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1) + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_fft_find_strongest_period(): + data = co2.load().data.resample("ME").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1) + assert len(periods) == 1 + assert periods[0] == 12 + + +def test_co2_daily_fft_find_strongest_period_window_func_barthann(): + data = co2.load().data.resample("D").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="barthann") + assert len(periods) == 1 + assert periods[0] == 363 + + +def test_co2_weekly_fft_find_strongest_period_window_func_barthann(): + data = co2.load().data.resample("W").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="barthann") + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_fft_find_strongest_period_window_func_barthann(): + data = co2.load().data.resample("ME").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="barthann") + assert len(periods) == 1 + assert periods[0] == 12 + + +def test_co2_daily_fft_find_strongest_period_window_func_bartlett(): + data = co2.load().data.resample("D").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="bartlett") + assert len(periods) == 1 + assert periods[0] == 363 + + +def test_co2_weekly_fft_find_strongest_period_window_func_bartlett(): + data = co2.load().data.resample("W").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="bartlett") + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_fft_find_strongest_period_window_func_bartlett(): + data = co2.load().data.resample("ME").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="bartlett") + assert len(periods) == 1 + assert periods[0] == 12 + + +def test_co2_daily_fft_find_strongest_period_window_func_blackman(): + data = co2.load().data.resample("D").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="blackman") + assert len(periods) == 1 + assert periods[0] == 363 + + +def test_co2_weekly_fft_find_strongest_period_window_func_blackman(): + data = co2.load().data.resample("W").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="blackman") + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_fft_find_strongest_period_window_func_blackman(): + data = co2.load().data.resample("ME").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="blackman") + assert len(periods) == 1 + assert periods[0] == 12 + + +def test_co2_daily_fft_find_strongest_period_window_func_blackmanharris(): + data = co2.load().data.resample("D").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="blackmanharris") + assert len(periods) == 1 + assert periods[0] == 363 + + +def test_co2_weekly_fft_find_strongest_period_window_func_blackmanharris(): + data = co2.load().data.resample("W").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="blackmanharris") + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_fft_find_strongest_period_window_func_blackmanharris(): + data = co2.load().data.resample("ME").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="blackmanharris") + assert len(periods) == 1 + assert periods[0] == 12 + + +def test_co2_daily_fft_find_strongest_period_window_func_boxcar(): + data = co2.load().data.resample("D").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="boxcar") + assert len(periods) == 1 + assert periods[0] == 363 + + +def test_co2_weekly_fft_find_strongest_period_window_func_boxcar(): + data = co2.load().data.resample("W").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="boxcar") + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_fft_find_strongest_period_window_func_boxcar(): + data = co2.load().data.resample("ME").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="boxcar") + assert len(periods) == 1 + assert periods[0] == 12 + + +def test_co2_daily_fft_find_strongest_period_window_func_hamming(): + data = co2.load().data.resample("D").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="hamming") + assert len(periods) == 1 + assert periods[0] == 363 + + +def test_co2_weekly_fft_find_strongest_period_window_func_hamming(): + data = co2.load().data.resample("W").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="hamming") + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_fft_find_strongest_period_window_func_hamming(): + data = co2.load().data.resample("ME").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="hamming") + assert len(periods) == 1 + assert periods[0] == 12 + + +def test_co2_daily_fft_find_strongest_period_window_func_hann(): + data = co2.load().data.resample("D").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="hann") + assert len(periods) == 1 + assert periods[0] == 363 + + +def test_co2_weekly_fft_find_strongest_period_window_func_hann(): + data = co2.load().data.resample("W").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="hann") + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_fft_find_strongest_period_window_func_hann(): + data = co2.load().data.resample("ME").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="hann") + assert len(periods) == 1 + assert periods[0] == 12 + + +def test_co2_daily_fft_find_strongest_period_window_func_tukey(): + data = co2.load().data.resample("D").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="tukey") + assert len(periods) == 1 + assert periods[0] == 363 + + +def test_co2_weekly_fft_find_strongest_period_window_func_tukey(): + data = co2.load().data.resample("W").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="tukey") + assert len(periods) == 1 + assert periods[0] == 52 + + +def test_co2_monthly_fft_find_strongest_period_window_func_tukey(): + data = co2.load().data.resample("ME").mean().ffill() + fft_detector = FFTPeriodicityDetector(data) + periods = fft_detector.fit(max_period_count=1, window_func="tukey") + assert len(periods) == 1 + assert periods[0] == 12 diff --git a/tests/test_finder.py b/tests/test_finder.py deleted file mode 100644 index 1f0803d..0000000 --- a/tests/test_finder.py +++ /dev/null @@ -1,51 +0,0 @@ -from statsmodels.datasets import co2 - -from auto_period_finder import AutoPeriodFinder, Decomposer - - -def test_find_all_periods(): - data = co2.load().data.resample("ME").mean().ffill() - period_finder = AutoPeriodFinder(data) - periods = period_finder.fit() - assert len(periods) != 0 - - -def test_find_first_two_periods(): - data = co2.load().data.resample("ME").mean().ffill() - period_finder = AutoPeriodFinder(data) - periods = period_finder.fit(max_period_count=2) - assert len(periods) == 2 - - -def test_find_strongest_period_acf_wise(): - data = co2.load().data.resample("ME").mean().ffill() - period_finder = AutoPeriodFinder(data) - periods = period_finder.fit(max_period_count=1) - strongest_period_acf = period_finder.fit_find_strongest_acf() - assert len(periods) == 1 - assert periods[0] == strongest_period_acf - - -def test_find_strongest_period_var_wise_stl_default(): - data = co2.load().data.resample("ME").mean().ffill() - period_finder = AutoPeriodFinder(data) - strongest_period_var = period_finder.fit_find_strongest_var( - decomposer=Decomposer.STL - ) - assert strongest_period_var == 180 - - -def test_find_strongest_period_var_wise_stl_custom(): - data = co2.load().data.resample("ME").mean().ffill() - period_finder = AutoPeriodFinder(data) - strongest_period_var = period_finder.fit_find_strongest_var( - decomposer=Decomposer.STL, decomposer_kwargs={"seasonal_deg": 0} - ) - assert strongest_period_var == 180 - - -def test_find_strongest_period_var_wise_moving_averages(): - data = co2.load().data.resample("ME").mean().ffill() - period_finder = AutoPeriodFinder(data) - strongest_period_var = period_finder.fit_find_strongest_var() - assert strongest_period_var == 180