PySDKit 0.4.34__tar.gz → 0.4.35__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pysdkit-0.4.34 → pysdkit-0.4.35}/PKG-INFO +2 -2
- {pysdkit-0.4.34 → pysdkit-0.4.35}/PySDKit.egg-info/PKG-INFO +2 -2
- {pysdkit-0.4.34 → pysdkit-0.4.35}/PySDKit.egg-info/SOURCES.txt +1 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/README.md +1 -1
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/__init__.py +6 -1
- pysdkit-0.4.35/pysdkit/tests/test_mstl.py +160 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_stl.py +64 -57
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tsa/__init__.py +10 -1
- pysdkit-0.4.35/pysdkit/tsa/_mstl.py +286 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tsa/_stl.py +141 -94
- pysdkit-0.4.34/pysdkit/tsa/_mstl.py +0 -25
- {pysdkit-0.4.34 → pysdkit-0.4.35}/LICENSE +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/PySDKit.egg-info/dependency_links.txt +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/PySDKit.egg-info/requires.txt +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/PySDKit.egg-info/top_level.txt +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pyproject.toml +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_alif/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_alif/alif.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_alif/data/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_alif/data/prefixed_double_filter.npy +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_alif/iterative_filtering.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_apmd/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_apmd/apmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/_find_extrema.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/_prepare_points.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/_splines.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/ceemdan.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/eemd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/efd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/emd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/hht/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/hht/frequency.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/hht/hht.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/memd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/remd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/tvf_emd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd2d/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd2d/bemd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd2d/bmemd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd2d/emd2d.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_esmd/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_esmd/esmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_ewt/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_ewt/ewt.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_ewt/ewt2d.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_faemd/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_faemd/extrema.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_faemd/faemd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_faemd/faemd2d.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_faemd/faemd3d.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_faemd/filter.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_fmd/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_fmd/fmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_fmd/nfmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_gdmd/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_gdmd/gdmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_hvd/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_hvd/hvd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_itd/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_itd/itd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_jmd/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_jmd/jmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_jmd/mjmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_jmd/sjmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_lmd/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_lmd/lmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_lmd/rlmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_osd/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_osd/swd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_ssa/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_ssa/ssa.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/acmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/avmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/ba_acmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/base.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/mvmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/ovmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/stvmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/svmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/vmd_c.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/vmd_f.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/vme.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd2d/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd2d/cvmd2d.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd2d/vmd2d.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vncmd/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vncmd/avncmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vncmd/incmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vncmd/vncmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_add_noise.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_cube.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_generator.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_image.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_models.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_test_univariate.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_time_series.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/texture.txt +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/entropy/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/entropy/_permutation_entropy.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/entropy/_sample_entropy.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/models/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/models/_base.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/models/_kmeans.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/models/_knn.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/models/_pca.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/plot/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/plot/_fourier_spectra.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/plot/_functions.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/plot/_plot_images.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/plot/_plot_imfs.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/plot/_plot_signal.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/data/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_cube.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_image.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_signal.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/models/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/models/test_knn.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/models/test_pca.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/run_all.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_alif.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_apmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_avncmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_ceemdan.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_emd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_ewt.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_ewt2d.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_faemd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_faemd2d.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_faemd3d.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_gdmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_itd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_jmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_lmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_moving_decomp.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_ovmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_rlmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_sjmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_ssa.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_stvmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_svmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_swd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_vmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_vmd2d.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_vncmd.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tsa/_dtw.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tsa/_moving_decomp.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/__init__.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_cite.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_correlation.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_diagnalization.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_differ.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_fft.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_function.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_hilbert.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_instantaneous.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_kernel_matrix.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_mirror.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_process.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_smooth1d.py +0 -0
- {pysdkit-0.4.34 → pysdkit-0.4.35}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySDKit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.35
|
|
4
4
|
Summary: A Python library for signal decomposition algorithms with a unified interface.
|
|
5
5
|
Author: josefinez, Deeksha Manjunath, Yuan Feng, JacktheFowler
|
|
6
6
|
Author-email: Whenxuan Wang <wwhenxuan@gmail.com>, RuiZhe Wang <3133986068@qq.com>, WenTong Zhao <23049200290@stu.xidian.edu.cn>
|
|
@@ -189,7 +189,7 @@ plot_IMFs_amplitude_spectra(IMFs, smooth="exp") # use exp smooth
|
|
|
189
189
|
| [`STNBMD`]() (Short-Time Narrow-Band Mode Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X16002443?via%3Dihub) | [[code]](https://ww2.mathworks.cn/matlabcentral/fileexchange/56226-short-time-narrow-band-mode-decomposition-stnbmd-toolbox) | ✖️ |
|
|
190
190
|
| [`SWD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/swd.py) (Swarm Decomposition) | [[paper]](https://doi.org/10.1016/j.sigpro.2016.09.004) | [[code]](https://github.com/gkaposto/Swarm-Decomposition) | ✔️ |
|
|
191
191
|
| [`STL`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/tsa/_stl.py) (Seasonal-Trend decomposition using LOESS) | [[paper]](https://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/stl_decomposition.html) | ✔️ |
|
|
192
|
-
| [`MSTL`]() (Multivariate Seasonal-Trend decomposition using LOESS) | [[paper]](https://
|
|
192
|
+
| [`MSTL`]() (Multivariate Seasonal-Trend decomposition using LOESS) | [[paper]](https://arxiv.org/abs/2107.13462) | [[code]](https://github.com/KishManani/MSTL) | ✔️ |
|
|
193
193
|
|
|
194
194
|
## Acknowledgements 🎖️ <a id="Acknowledgements"></a>
|
|
195
195
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySDKit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.35
|
|
4
4
|
Summary: A Python library for signal decomposition algorithms with a unified interface.
|
|
5
5
|
Author: josefinez, Deeksha Manjunath, Yuan Feng, JacktheFowler
|
|
6
6
|
Author-email: Whenxuan Wang <wwhenxuan@gmail.com>, RuiZhe Wang <3133986068@qq.com>, WenTong Zhao <23049200290@stu.xidian.edu.cn>
|
|
@@ -189,7 +189,7 @@ plot_IMFs_amplitude_spectra(IMFs, smooth="exp") # use exp smooth
|
|
|
189
189
|
| [`STNBMD`]() (Short-Time Narrow-Band Mode Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X16002443?via%3Dihub) | [[code]](https://ww2.mathworks.cn/matlabcentral/fileexchange/56226-short-time-narrow-band-mode-decomposition-stnbmd-toolbox) | ✖️ |
|
|
190
190
|
| [`SWD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/swd.py) (Swarm Decomposition) | [[paper]](https://doi.org/10.1016/j.sigpro.2016.09.004) | [[code]](https://github.com/gkaposto/Swarm-Decomposition) | ✔️ |
|
|
191
191
|
| [`STL`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/tsa/_stl.py) (Seasonal-Trend decomposition using LOESS) | [[paper]](https://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/stl_decomposition.html) | ✔️ |
|
|
192
|
-
| [`MSTL`]() (Multivariate Seasonal-Trend decomposition using LOESS) | [[paper]](https://
|
|
192
|
+
| [`MSTL`]() (Multivariate Seasonal-Trend decomposition using LOESS) | [[paper]](https://arxiv.org/abs/2107.13462) | [[code]](https://github.com/KishManani/MSTL) | ✔️ |
|
|
193
193
|
|
|
194
194
|
## Acknowledgements 🎖️ <a id="Acknowledgements"></a>
|
|
195
195
|
|
|
@@ -135,7 +135,7 @@ plot_IMFs_amplitude_spectra(IMFs, smooth="exp") # use exp smooth
|
|
|
135
135
|
| [`STNBMD`]() (Short-Time Narrow-Band Mode Decomposition) | [[paper]](https://www.sciencedirect.com/science/article/pii/S0022460X16002443?via%3Dihub) | [[code]](https://ww2.mathworks.cn/matlabcentral/fileexchange/56226-short-time-narrow-band-mode-decomposition-stnbmd-toolbox) | ✖️ |
|
|
136
136
|
| [`SWD`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/_osd/swd.py) (Swarm Decomposition) | [[paper]](https://doi.org/10.1016/j.sigpro.2016.09.004) | [[code]](https://github.com/gkaposto/Swarm-Decomposition) | ✔️ |
|
|
137
137
|
| [`STL`](https://github.com/wwhenxuan/PySDKit/blob/main/pysdkit/tsa/_stl.py) (Seasonal-Trend decomposition using LOESS) | [[paper]](https://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/stl_decomposition.html) | ✔️ |
|
|
138
|
-
| [`MSTL`]() (Multivariate Seasonal-Trend decomposition using LOESS) | [[paper]](https://
|
|
138
|
+
| [`MSTL`]() (Multivariate Seasonal-Trend decomposition using LOESS) | [[paper]](https://arxiv.org/abs/2107.13462) | [[code]](https://github.com/KishManani/MSTL) | ✔️ |
|
|
139
139
|
|
|
140
140
|
## Acknowledgements 🎖️ <a id="Acknowledgements"></a>
|
|
141
141
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
A Python library for signal decomposition algorithms.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
__version__ = "0.4.
|
|
5
|
+
__version__ = "0.4.35"
|
|
6
6
|
|
|
7
7
|
# Empirical Mode Decomposition
|
|
8
8
|
from ._emd import EMD
|
|
@@ -118,6 +118,9 @@ from .tsa import Moving_Decomp
|
|
|
118
118
|
# Seasonal-Trend decomposition using LOESS (STL)
|
|
119
119
|
from .tsa import STL
|
|
120
120
|
|
|
121
|
+
# Multiple Seasonal-Trend decomposition using LOESS (MSTL)
|
|
122
|
+
from .tsa import MSTL
|
|
123
|
+
|
|
121
124
|
# Hilbert-Huang Transform
|
|
122
125
|
from ._emd import HHT
|
|
123
126
|
|
|
@@ -182,6 +185,7 @@ Successive Jump and Mode Decomposition | SJMD / SMJMD
|
|
|
182
185
|
Feature Mode Decomposition | FMD
|
|
183
186
|
Moving Average Decomposition | Moving
|
|
184
187
|
Seasonal-Trend decomposition using LOESS | STL
|
|
188
|
+
Multiple Seasonal-Trend decomposition (LOESS) | MSTL
|
|
185
189
|
Hilbert-Huang Transform | HHT
|
|
186
190
|
_______________________________________________________________
|
|
187
191
|
"""
|
|
@@ -235,6 +239,7 @@ __all__ = [
|
|
|
235
239
|
"FMD",
|
|
236
240
|
"Moving_Decomp",
|
|
237
241
|
"STL",
|
|
242
|
+
"MSTL",
|
|
238
243
|
"models",
|
|
239
244
|
"data",
|
|
240
245
|
"entropy",
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Unit tests for Multiple Seasonal-Trend decomposition using LOESS (MSTL).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import unittest
|
|
7
|
+
import warnings
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from pysdkit import MSTL
|
|
12
|
+
from pysdkit.tsa import MSTLResult, STL
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _multi_seasonal_series(
|
|
16
|
+
n: int = 500,
|
|
17
|
+
periods=(24, 168),
|
|
18
|
+
seed: int = 0,
|
|
19
|
+
) -> tuple:
|
|
20
|
+
"""Synthetic series with trend + two seasonal sine waves + noise."""
|
|
21
|
+
rng = np.random.default_rng(seed)
|
|
22
|
+
t = np.arange(n, dtype=float)
|
|
23
|
+
trend = 0.0001 * t**2 + 50.0
|
|
24
|
+
seas = [amp * np.sin(2 * np.pi * t / p) for p, amp in zip(periods, (5.0, 10.0))]
|
|
25
|
+
noise = 0.3 * rng.standard_normal(n)
|
|
26
|
+
y = trend + sum(seas) + noise
|
|
27
|
+
return y, trend, seas
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestMSTL(unittest.TestCase):
|
|
31
|
+
"""Automated tests for MSTL."""
|
|
32
|
+
|
|
33
|
+
def setUp(self) -> None:
|
|
34
|
+
self.periods = (24, 24 * 7)
|
|
35
|
+
self.y, self.true_trend, self.true_seas = _multi_seasonal_series(
|
|
36
|
+
n=500, periods=self.periods, seed=0
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def test_fit_transform_shape(self) -> None:
|
|
40
|
+
"""Verify output shapes for two seasonal periods."""
|
|
41
|
+
mstl = MSTL(periods=self.periods, iterate=2)
|
|
42
|
+
result = mstl.fit_transform(self.y)
|
|
43
|
+
|
|
44
|
+
self.assertIsInstance(result, MSTLResult)
|
|
45
|
+
self.assertEqual(result.seasonal.shape, (2, self.y.size))
|
|
46
|
+
self.assertEqual(result.trend.shape, self.y.shape)
|
|
47
|
+
self.assertEqual(result.resid.shape, self.y.shape)
|
|
48
|
+
self.assertEqual(result.periods, tuple(sorted(self.periods)))
|
|
49
|
+
|
|
50
|
+
def test_reconstruction(self) -> None:
|
|
51
|
+
"""observed ≈ sum(seasonals) + trend + resid on the transformed scale."""
|
|
52
|
+
result = MSTL(periods=self.periods).fit_transform(self.y)
|
|
53
|
+
reconstructed = result.seasonal.sum(axis=0) + result.trend + result.resid
|
|
54
|
+
self.assertTrue(np.allclose(result.observed, reconstructed, atol=1e-8))
|
|
55
|
+
|
|
56
|
+
def test_default_call(self) -> None:
|
|
57
|
+
"""__call__ should match fit_transform."""
|
|
58
|
+
mstl = MSTL(periods=self.periods)
|
|
59
|
+
a = mstl(self.y)
|
|
60
|
+
b = mstl.fit_transform(self.y)
|
|
61
|
+
self.assertTrue(np.allclose(a.trend, b.trend))
|
|
62
|
+
self.assertTrue(np.allclose(a.seasonal, b.seasonal))
|
|
63
|
+
self.assertTrue(np.allclose(a.resid, b.resid))
|
|
64
|
+
|
|
65
|
+
def test_str(self) -> None:
|
|
66
|
+
self.assertIn("MSTL", str(MSTL(periods=24)))
|
|
67
|
+
|
|
68
|
+
def test_single_period_matches_stl_identity(self) -> None:
|
|
69
|
+
"""With one period, MSTL should still reconstruct exactly."""
|
|
70
|
+
period = 24
|
|
71
|
+
y, _, _ = _multi_seasonal_series(n=240, periods=(period, 48), seed=1)
|
|
72
|
+
# Keep only daily-like oscillation for a cleaner single-period case
|
|
73
|
+
t = np.arange(y.size, dtype=float)
|
|
74
|
+
y = 0.01 * t + 3 * np.sin(2 * np.pi * t / period) + 0.1 * np.random.default_rng(0).standard_normal(y.size)
|
|
75
|
+
|
|
76
|
+
result = MSTL(periods=period, iterate=5).fit_transform(y)
|
|
77
|
+
self.assertEqual(result.seasonal.ndim, 1)
|
|
78
|
+
self.assertEqual(result.seasonal.shape, y.shape)
|
|
79
|
+
reconstructed = result.seasonal + result.trend + result.resid
|
|
80
|
+
self.assertTrue(np.allclose(result.observed, reconstructed, atol=1e-8))
|
|
81
|
+
|
|
82
|
+
def test_default_windows(self) -> None:
|
|
83
|
+
"""Paper default windows are 7 + 4*i."""
|
|
84
|
+
mstl = MSTL(periods=(24, 168))
|
|
85
|
+
_, windows = mstl._prepare_periods_windows(nobs=500)
|
|
86
|
+
self.assertEqual(windows, (11, 15))
|
|
87
|
+
|
|
88
|
+
def test_periods_sorted_ascending(self) -> None:
|
|
89
|
+
"""Longer periods should not be extracted before shorter ones."""
|
|
90
|
+
result = MSTL(periods=(168, 24)).fit_transform(self.y)
|
|
91
|
+
self.assertEqual(result.periods, (24, 168))
|
|
92
|
+
|
|
93
|
+
def test_custom_windows(self) -> None:
|
|
94
|
+
result = MSTL(periods=(24, 168), windows=(11, 15)).fit_transform(self.y)
|
|
95
|
+
reconstructed = result.seasonal.sum(0) + result.trend + result.resid
|
|
96
|
+
self.assertTrue(np.allclose(result.observed, reconstructed, atol=1e-8))
|
|
97
|
+
|
|
98
|
+
def test_recovers_seasonal_energy(self) -> None:
|
|
99
|
+
"""Extracted seasonals should correlate with the planted components."""
|
|
100
|
+
result = MSTL(periods=self.periods, iterate=2, stl_kwargs={"seasonal_deg": 0}).fit_transform(
|
|
101
|
+
self.y
|
|
102
|
+
)
|
|
103
|
+
# periods are sorted ascending -> seasonal[0] ~ daily, seasonal[1] ~ weekly
|
|
104
|
+
corr_daily = np.corrcoef(result.seasonal[0], self.true_seas[0])[0, 1]
|
|
105
|
+
corr_weekly = np.corrcoef(result.seasonal[1], self.true_seas[1])[0, 1]
|
|
106
|
+
self.assertGreater(corr_daily, 0.8, msg=f"daily corr={corr_daily}")
|
|
107
|
+
self.assertGreater(corr_weekly, 0.8, msg=f"weekly corr={corr_weekly}")
|
|
108
|
+
|
|
109
|
+
corr_trend = np.corrcoef(result.trend, self.true_trend)[0, 1]
|
|
110
|
+
self.assertGreater(corr_trend, 0.9, msg=f"trend corr={corr_trend}")
|
|
111
|
+
|
|
112
|
+
def test_boxcox_auto(self) -> None:
|
|
113
|
+
"""Box-Cox('auto') should run on positive data and reconstruct."""
|
|
114
|
+
y = self.y - self.y.min() + 1.0
|
|
115
|
+
result = MSTL(periods=self.periods, lmbda="auto", iterate=1).fit_transform(y)
|
|
116
|
+
self.assertIsNotNone(result.lmbda)
|
|
117
|
+
reconstructed = result.seasonal.sum(0) + result.trend + result.resid
|
|
118
|
+
self.assertTrue(np.allclose(result.observed, reconstructed, atol=1e-8))
|
|
119
|
+
|
|
120
|
+
def test_boxcox_rejects_nonpositive(self) -> None:
|
|
121
|
+
y = self.y.copy()
|
|
122
|
+
y[0] = -1.0
|
|
123
|
+
with self.assertRaises(ValueError):
|
|
124
|
+
MSTL(periods=24, lmbda=0.0).fit_transform(y)
|
|
125
|
+
|
|
126
|
+
def test_invalid_periods(self) -> None:
|
|
127
|
+
with self.assertRaises(ValueError):
|
|
128
|
+
MSTL(periods=1)
|
|
129
|
+
with self.assertRaises(ValueError):
|
|
130
|
+
MSTL(periods=(24, 24), windows=(11,))
|
|
131
|
+
|
|
132
|
+
def test_invalid_windows(self) -> None:
|
|
133
|
+
with self.assertRaises(ValueError):
|
|
134
|
+
MSTL(periods=(24, 168), windows=(10, 15)) # even window
|
|
135
|
+
|
|
136
|
+
def test_long_period_warning(self) -> None:
|
|
137
|
+
y = np.random.default_rng(0).standard_normal(50)
|
|
138
|
+
with warnings.catch_warnings(record=True) as caught:
|
|
139
|
+
warnings.simplefilter("always")
|
|
140
|
+
with self.assertRaises(ValueError):
|
|
141
|
+
# both periods >= n/2 -> nothing left
|
|
142
|
+
MSTL(periods=(40, 45)).fit_transform(y)
|
|
143
|
+
self.assertTrue(any(issubclass(w.category, UserWarning) for w in caught))
|
|
144
|
+
|
|
145
|
+
def test_stl_kwargs_robust(self) -> None:
|
|
146
|
+
y = self.y.copy()
|
|
147
|
+
y[100] += 20.0
|
|
148
|
+
result = MSTL(
|
|
149
|
+
periods=self.periods, iterate=1, stl_kwargs={"robust": True}
|
|
150
|
+
).fit_transform(y)
|
|
151
|
+
self.assertTrue(np.all(np.isfinite(result.resid)))
|
|
152
|
+
self.assertEqual(result.weights.shape, y.shape)
|
|
153
|
+
|
|
154
|
+
def test_uses_pysdkit_stl(self) -> None:
|
|
155
|
+
"""MSTL must be built on the local STL class (no statsmodels dependency)."""
|
|
156
|
+
self.assertIs(MSTL.__init__.__globals__.get("STL", STL), STL)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
unittest.main()
|
|
@@ -1,57 +1,68 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Unit tests for Seasonal-Trend decomposition using LOESS (STL).
|
|
4
|
+
"""
|
|
5
|
+
|
|
2
6
|
import unittest
|
|
7
|
+
|
|
3
8
|
import numpy as np
|
|
9
|
+
|
|
4
10
|
from pysdkit import STL
|
|
5
11
|
from pysdkit.tsa import STLResult
|
|
6
12
|
from pysdkit.data import generate_time_series
|
|
7
13
|
|
|
8
14
|
|
|
9
15
|
class TestSTL(unittest.TestCase):
|
|
10
|
-
"""Test whether STL decomposition runs normally"""
|
|
16
|
+
"""Test whether STL decomposition runs normally."""
|
|
11
17
|
|
|
12
18
|
def setUp(self) -> None:
|
|
13
|
-
"""Create time series data for testing"""
|
|
19
|
+
"""Create time series data for testing."""
|
|
14
20
|
np.random.seed(42)
|
|
15
|
-
|
|
16
|
-
self.period = 12 # seasonal period
|
|
21
|
+
self.period = 12
|
|
17
22
|
self.data = generate_time_series(
|
|
18
|
-
duration=
|
|
23
|
+
duration=240,
|
|
19
24
|
periodicities=np.array([self.period]),
|
|
20
25
|
num_harmonics=np.array([2]),
|
|
21
26
|
std=np.array([0.5]),
|
|
27
|
+
seed=42,
|
|
22
28
|
)
|
|
23
|
-
# Add a linear trend
|
|
24
29
|
self.trend = np.linspace(0, 10, len(self.data))
|
|
25
|
-
self.data
|
|
30
|
+
self.data = self.data + self.trend
|
|
26
31
|
|
|
27
32
|
def test_fit_transform(self) -> None:
|
|
28
|
-
"""Verify
|
|
29
|
-
# Create an algorithm instance object
|
|
33
|
+
"""Verify reconstruction identity observed = seasonal + trend + resid."""
|
|
30
34
|
stl = STL(period=self.period)
|
|
31
35
|
result = stl.fit_transform(self.data)
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
self.assertIsInstance(result, STLResult, "Wrong return result type")
|
|
35
|
-
|
|
36
|
-
# Verify component relationship: observed = seasonal + trend + resid
|
|
37
|
+
self.assertIsInstance(result, STLResult)
|
|
37
38
|
reconstructed = result.seasonal + result.trend + result.resid
|
|
38
39
|
self.assertTrue(
|
|
39
40
|
np.allclose(result.observed, reconstructed, atol=1e-10),
|
|
40
41
|
"Reconstructed series after decomposition does not match",
|
|
41
42
|
)
|
|
43
|
+
self.assertEqual(len(result.observed), len(self.data))
|
|
44
|
+
self.assertTrue(np.all(np.isfinite(result.seasonal)))
|
|
45
|
+
self.assertTrue(np.all(np.isfinite(result.trend)))
|
|
46
|
+
self.assertTrue(np.all(np.isfinite(result.resid)))
|
|
42
47
|
|
|
43
48
|
def test_default_call(self) -> None:
|
|
44
|
-
"""Verify that
|
|
45
|
-
# Create an algorithm instance object
|
|
49
|
+
"""Verify that __call__ matches fit_transform."""
|
|
46
50
|
stl = STL(period=self.period)
|
|
47
|
-
|
|
51
|
+
a = stl(self.data)
|
|
52
|
+
b = stl.fit_transform(self.data)
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
self.
|
|
51
|
-
self.
|
|
54
|
+
self.assertIsInstance(a, STLResult)
|
|
55
|
+
self.assertEqual(len(a.observed), len(self.data))
|
|
56
|
+
self.assertTrue(np.allclose(a.seasonal, b.seasonal))
|
|
57
|
+
self.assertTrue(np.allclose(a.trend, b.trend))
|
|
58
|
+
self.assertTrue(np.allclose(a.resid, b.resid))
|
|
59
|
+
|
|
60
|
+
def test_str(self) -> None:
|
|
61
|
+
"""Verify the human-readable algorithm name."""
|
|
62
|
+
self.assertIn("STL", str(STL(period=12)))
|
|
52
63
|
|
|
53
64
|
def test_robust_mode(self) -> None:
|
|
54
|
-
"""Verify that robust and non-robust modes both
|
|
65
|
+
"""Verify that robust and non-robust modes both reconstruct."""
|
|
55
66
|
data_with_outliers = self.data.copy()
|
|
56
67
|
outlier_indices = [10, 30, 50, 70, 90]
|
|
57
68
|
data_with_outliers[outlier_indices] += 10.0
|
|
@@ -64,39 +75,32 @@ class TestSTL(unittest.TestCase):
|
|
|
64
75
|
np.allclose(result.observed, reconstructed, atol=1e-10),
|
|
65
76
|
f"Reconstruction failed for robust={robust}",
|
|
66
77
|
)
|
|
67
|
-
self.assertEqual(len(result.resid), len(data_with_outliers))
|
|
68
|
-
# Outlier residuals should remain finite in both modes
|
|
69
78
|
self.assertTrue(np.all(np.isfinite(result.resid[outlier_indices])))
|
|
79
|
+
self.assertEqual(result.weights.shape, result.observed.shape)
|
|
70
80
|
|
|
71
81
|
def test_seasonal_component(self) -> None:
|
|
72
|
-
"""Verify
|
|
82
|
+
"""Verify that the seasonal component shows the expected periodicity."""
|
|
73
83
|
stl = STL(period=self.period, seasonal=7)
|
|
74
84
|
result = stl(self.data)
|
|
75
85
|
|
|
76
|
-
# Verify seasonal component periodicity
|
|
77
86
|
seasonal = result.seasonal
|
|
78
87
|
autocorr = np.correlate(seasonal, seasonal, mode="full")
|
|
79
88
|
autocorr = autocorr[len(autocorr) // 2 :]
|
|
89
|
+
autocorr = autocorr / (autocorr[0] + 1e-12)
|
|
80
90
|
|
|
81
|
-
|
|
82
|
-
self.assertGreater(
|
|
83
|
-
autocorr[0], autocorr[1], "Wrong autocorrelation peak in seasonal component"
|
|
84
|
-
)
|
|
91
|
+
self.assertGreater(autocorr[0], autocorr[1])
|
|
85
92
|
self.assertGreater(
|
|
86
93
|
autocorr[self.period],
|
|
87
|
-
autocorr[self.period
|
|
94
|
+
autocorr[self.period // 2],
|
|
88
95
|
"Seasonal component periodicity is not evident",
|
|
89
96
|
)
|
|
90
97
|
|
|
91
98
|
def test_trend_component(self) -> None:
|
|
92
|
-
"""Verify smoothness of the trend
|
|
99
|
+
"""Verify smoothness / correlation of the recovered trend."""
|
|
93
100
|
stl = STL(period=self.period, trend=25)
|
|
94
101
|
result = stl(self.data)
|
|
95
102
|
|
|
96
|
-
# Compute first differences of the trend component
|
|
97
103
|
trend_diff = np.diff(result.trend)
|
|
98
|
-
|
|
99
|
-
# Trend component should be smoother than the raw data
|
|
100
104
|
data_diff = np.diff(self.data)
|
|
101
105
|
self.assertLess(
|
|
102
106
|
np.std(trend_diff),
|
|
@@ -104,50 +108,53 @@ class TestSTL(unittest.TestCase):
|
|
|
104
108
|
"Trend component is not smooth enough",
|
|
105
109
|
)
|
|
106
110
|
|
|
107
|
-
# Trend component should correlate with the added linear trend
|
|
108
111
|
corr = np.corrcoef(result.trend, self.trend)[0, 1]
|
|
109
112
|
self.assertGreater(
|
|
110
|
-
corr,
|
|
113
|
+
corr,
|
|
114
|
+
0.9,
|
|
115
|
+
"Trend component correlation with expected trend is insufficient",
|
|
111
116
|
)
|
|
112
117
|
|
|
113
118
|
def test_parameter_validation(self) -> None:
|
|
114
|
-
"""Verify parameter validation logic"""
|
|
115
|
-
# Invalid period
|
|
119
|
+
"""Verify parameter validation logic."""
|
|
116
120
|
with self.assertRaises(ValueError):
|
|
117
121
|
STL(period=1)
|
|
118
122
|
|
|
119
|
-
#
|
|
123
|
+
# seasonal must be odd and >= 3
|
|
124
|
+
with self.assertRaises(ValueError):
|
|
125
|
+
STL(period=12, seasonal=4)
|
|
120
126
|
with self.assertRaises(ValueError):
|
|
121
|
-
STL(period=12, seasonal=
|
|
127
|
+
STL(period=12, seasonal=2)
|
|
122
128
|
|
|
123
|
-
#
|
|
129
|
+
# trend must be odd and > period
|
|
130
|
+
with self.assertRaises(ValueError):
|
|
131
|
+
STL(period=12, trend=12)
|
|
132
|
+
with self.assertRaises(ValueError):
|
|
133
|
+
STL(period=12, trend=11)
|
|
134
|
+
|
|
135
|
+
# insufficient length
|
|
124
136
|
stl = STL(period=12)
|
|
125
137
|
with self.assertRaises(ValueError):
|
|
126
|
-
stl.fit_transform(np.random.rand(10))
|
|
138
|
+
stl.fit_transform(np.random.rand(10))
|
|
127
139
|
|
|
128
140
|
def test_different_iterations(self) -> None:
|
|
129
141
|
"""Verify that different iteration counts still yield valid decompositions."""
|
|
130
142
|
configs = [
|
|
131
|
-
STL(period=self.period),
|
|
132
|
-
STL(period=self.period),
|
|
133
|
-
STL(period=self.period, robust=True),
|
|
143
|
+
(STL(period=self.period), dict()),
|
|
144
|
+
(STL(period=self.period), dict(inner_iter=5)),
|
|
145
|
+
(STL(period=self.period, robust=True), dict(outer_iter=5)),
|
|
134
146
|
]
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
lambda stl: stl.fit_transform(self.data, inner_iter=5),
|
|
138
|
-
lambda stl: stl.fit_transform(self.data, outer_iter=15),
|
|
139
|
-
]
|
|
140
|
-
|
|
141
|
-
for stl, call in zip(configs, calls):
|
|
142
|
-
result = call(stl)
|
|
147
|
+
for stl, kwargs in configs:
|
|
148
|
+
result = stl.fit_transform(self.data, **kwargs)
|
|
143
149
|
reconstructed = result.seasonal + result.trend + result.resid
|
|
144
|
-
self.assertTrue(
|
|
145
|
-
np.allclose(result.observed, reconstructed, atol=1e-10),
|
|
146
|
-
"Reconstructed series after decomposition does not match",
|
|
147
|
-
)
|
|
148
|
-
self.assertEqual(len(result.observed), len(self.data))
|
|
150
|
+
self.assertTrue(np.allclose(result.observed, reconstructed, atol=1e-10))
|
|
149
151
|
self.assertTrue(np.all(np.isfinite(result.resid)))
|
|
150
152
|
|
|
153
|
+
def test_mean_seasonal_near_zero(self) -> None:
|
|
154
|
+
"""Seasonal component should be approximately zero-mean after low-pass removal."""
|
|
155
|
+
result = STL(period=self.period, seasonal=13).fit_transform(self.data)
|
|
156
|
+
self.assertLess(abs(np.mean(result.seasonal)), 1.0)
|
|
157
|
+
|
|
151
158
|
|
|
152
159
|
if __name__ == "__main__":
|
|
153
160
|
unittest.main()
|
|
@@ -6,10 +6,19 @@ Created on 2025/02/10 13:25:06
|
|
|
6
6
|
This module is used to store some valuable signal processing methods in time series analysis (TSA)
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
__all__ = [
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Moving_Decomp",
|
|
11
|
+
"dtw_distance",
|
|
12
|
+
"STLResult",
|
|
13
|
+
"STL",
|
|
14
|
+
"MSTLResult",
|
|
15
|
+
"MSTL",
|
|
16
|
+
]
|
|
10
17
|
|
|
11
18
|
from ._moving_decomp import Moving_Decomp
|
|
12
19
|
|
|
13
20
|
from ._dtw import dtw_distance
|
|
14
21
|
|
|
15
22
|
from ._stl import STL, STLResult
|
|
23
|
+
|
|
24
|
+
from ._mstl import MSTL, MSTLResult
|