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.
Files changed (166) hide show
  1. {pysdkit-0.4.34 → pysdkit-0.4.35}/PKG-INFO +2 -2
  2. {pysdkit-0.4.34 → pysdkit-0.4.35}/PySDKit.egg-info/PKG-INFO +2 -2
  3. {pysdkit-0.4.34 → pysdkit-0.4.35}/PySDKit.egg-info/SOURCES.txt +1 -0
  4. {pysdkit-0.4.34 → pysdkit-0.4.35}/README.md +1 -1
  5. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/__init__.py +6 -1
  6. pysdkit-0.4.35/pysdkit/tests/test_mstl.py +160 -0
  7. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_stl.py +64 -57
  8. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tsa/__init__.py +10 -1
  9. pysdkit-0.4.35/pysdkit/tsa/_mstl.py +286 -0
  10. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tsa/_stl.py +141 -94
  11. pysdkit-0.4.34/pysdkit/tsa/_mstl.py +0 -25
  12. {pysdkit-0.4.34 → pysdkit-0.4.35}/LICENSE +0 -0
  13. {pysdkit-0.4.34 → pysdkit-0.4.35}/PySDKit.egg-info/dependency_links.txt +0 -0
  14. {pysdkit-0.4.34 → pysdkit-0.4.35}/PySDKit.egg-info/requires.txt +0 -0
  15. {pysdkit-0.4.34 → pysdkit-0.4.35}/PySDKit.egg-info/top_level.txt +0 -0
  16. {pysdkit-0.4.34 → pysdkit-0.4.35}/pyproject.toml +0 -0
  17. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_alif/__init__.py +0 -0
  18. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_alif/alif.py +0 -0
  19. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_alif/data/__init__.py +0 -0
  20. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_alif/data/prefixed_double_filter.npy +0 -0
  21. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_alif/iterative_filtering.py +0 -0
  22. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_apmd/__init__.py +0 -0
  23. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_apmd/apmd.py +0 -0
  24. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/__init__.py +0 -0
  25. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/_find_extrema.py +0 -0
  26. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/_prepare_points.py +0 -0
  27. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/_splines.py +0 -0
  28. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/ceemdan.py +0 -0
  29. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/eemd.py +0 -0
  30. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/efd.py +0 -0
  31. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/emd.py +0 -0
  32. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/hht/__init__.py +0 -0
  33. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/hht/frequency.py +0 -0
  34. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/hht/hht.py +0 -0
  35. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/memd.py +0 -0
  36. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/remd.py +0 -0
  37. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd/tvf_emd.py +0 -0
  38. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd2d/__init__.py +0 -0
  39. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd2d/bemd.py +0 -0
  40. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd2d/bmemd.py +0 -0
  41. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_emd2d/emd2d.py +0 -0
  42. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_esmd/__init__.py +0 -0
  43. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_esmd/esmd.py +0 -0
  44. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_ewt/__init__.py +0 -0
  45. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_ewt/ewt.py +0 -0
  46. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_ewt/ewt2d.py +0 -0
  47. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_faemd/__init__.py +0 -0
  48. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_faemd/extrema.py +0 -0
  49. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_faemd/faemd.py +0 -0
  50. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_faemd/faemd2d.py +0 -0
  51. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_faemd/faemd3d.py +0 -0
  52. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_faemd/filter.py +0 -0
  53. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_fmd/__init__.py +0 -0
  54. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_fmd/fmd.py +0 -0
  55. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_fmd/nfmd.py +0 -0
  56. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_gdmd/__init__.py +0 -0
  57. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_gdmd/gdmd.py +0 -0
  58. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_hvd/__init__.py +0 -0
  59. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_hvd/hvd.py +0 -0
  60. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_itd/__init__.py +0 -0
  61. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_itd/itd.py +0 -0
  62. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_jmd/__init__.py +0 -0
  63. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_jmd/jmd.py +0 -0
  64. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_jmd/mjmd.py +0 -0
  65. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_jmd/sjmd.py +0 -0
  66. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_lmd/__init__.py +0 -0
  67. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_lmd/lmd.py +0 -0
  68. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_lmd/rlmd.py +0 -0
  69. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_osd/__init__.py +0 -0
  70. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_osd/swd.py +0 -0
  71. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_ssa/__init__.py +0 -0
  72. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_ssa/ssa.py +0 -0
  73. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/__init__.py +0 -0
  74. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/acmd.py +0 -0
  75. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/avmd.py +0 -0
  76. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/ba_acmd.py +0 -0
  77. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/base.py +0 -0
  78. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/mvmd.py +0 -0
  79. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/ovmd.py +0 -0
  80. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/stvmd.py +0 -0
  81. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/svmd.py +0 -0
  82. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/vmd_c.py +0 -0
  83. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/vmd_f.py +0 -0
  84. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd/vme.py +0 -0
  85. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd2d/__init__.py +0 -0
  86. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd2d/cvmd2d.py +0 -0
  87. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vmd2d/vmd2d.py +0 -0
  88. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vncmd/__init__.py +0 -0
  89. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vncmd/avncmd.py +0 -0
  90. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vncmd/incmd.py +0 -0
  91. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/_vncmd/vncmd.py +0 -0
  92. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/__init__.py +0 -0
  93. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_add_noise.py +0 -0
  94. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_cube.py +0 -0
  95. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_generator.py +0 -0
  96. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_image.py +0 -0
  97. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_models.py +0 -0
  98. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_test_univariate.py +0 -0
  99. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/_time_series.py +0 -0
  100. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/data/texture.txt +0 -0
  101. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/entropy/__init__.py +0 -0
  102. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/entropy/_approxiamte_entropy.py +0 -0
  103. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/entropy/_permutation_entropy.py +0 -0
  104. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/entropy/_sample_entropy.py +0 -0
  105. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/models/__init__.py +0 -0
  106. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/models/_base.py +0 -0
  107. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/models/_kmeans.py +0 -0
  108. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/models/_knn.py +0 -0
  109. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/models/_pca.py +0 -0
  110. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/plot/__init__.py +0 -0
  111. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/plot/_fourier_spectra.py +0 -0
  112. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/plot/_functions.py +0 -0
  113. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/plot/_plot_images.py +0 -0
  114. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/plot/_plot_imfs.py +0 -0
  115. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/plot/_plot_signal.py +0 -0
  116. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/__init__.py +0 -0
  117. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/data/__init__.py +0 -0
  118. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_cube.py +0 -0
  119. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_image.py +0 -0
  120. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_signal.py +0 -0
  121. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/data/test_generator_univariate_signal.py +0 -0
  122. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/models/__init__.py +0 -0
  123. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/models/test_knn.py +0 -0
  124. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/models/test_pca.py +0 -0
  125. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/run_all.py +0 -0
  126. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_alif.py +0 -0
  127. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_apmd.py +0 -0
  128. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_avncmd.py +0 -0
  129. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_ceemdan.py +0 -0
  130. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_emd.py +0 -0
  131. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_ewt.py +0 -0
  132. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_ewt2d.py +0 -0
  133. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_faemd.py +0 -0
  134. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_faemd2d.py +0 -0
  135. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_faemd3d.py +0 -0
  136. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_gdmd.py +0 -0
  137. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_itd.py +0 -0
  138. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_jmd.py +0 -0
  139. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_lmd.py +0 -0
  140. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_moving_decomp.py +0 -0
  141. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_ovmd.py +0 -0
  142. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_rlmd.py +0 -0
  143. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_sjmd.py +0 -0
  144. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_ssa.py +0 -0
  145. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_stvmd.py +0 -0
  146. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_svmd.py +0 -0
  147. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_swd.py +0 -0
  148. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_vmd.py +0 -0
  149. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_vmd2d.py +0 -0
  150. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tests/test_vncmd.py +0 -0
  151. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tsa/_dtw.py +0 -0
  152. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/tsa/_moving_decomp.py +0 -0
  153. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/__init__.py +0 -0
  154. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_cite.py +0 -0
  155. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_correlation.py +0 -0
  156. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_diagnalization.py +0 -0
  157. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_differ.py +0 -0
  158. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_fft.py +0 -0
  159. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_function.py +0 -0
  160. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_hilbert.py +0 -0
  161. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_instantaneous.py +0 -0
  162. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_kernel_matrix.py +0 -0
  163. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_mirror.py +0 -0
  164. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_process.py +0 -0
  165. {pysdkit-0.4.34 → pysdkit-0.4.35}/pysdkit/utils/_smooth1d.py +0 -0
  166. {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.34
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://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/mstl_decomposition.html) | ✖️ |
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.34
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://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/mstl_decomposition.html) | ✖️ |
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
 
@@ -123,6 +123,7 @@ pysdkit/tests/test_itd.py
123
123
  pysdkit/tests/test_jmd.py
124
124
  pysdkit/tests/test_lmd.py
125
125
  pysdkit/tests/test_moving_decomp.py
126
+ pysdkit/tests/test_mstl.py
126
127
  pysdkit/tests/test_ovmd.py
127
128
  pysdkit/tests/test_rlmd.py
128
129
  pysdkit/tests/test_sjmd.py
@@ -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://www.nniiem.ru/file/news/2016/stl-statistical-model.pdf) | [[code]](https://www.statsmodels.org/stable/examples/notebooks/generated/mstl_decomposition.html) | ✖️ |
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.34"
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
- # Generate a base time series
16
- self.period = 12 # seasonal period
21
+ self.period = 12
17
22
  self.data = generate_time_series(
18
- duration=120,
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 += self.trend
30
+ self.data = self.data + self.trend
26
31
 
27
32
  def test_fit_transform(self) -> None:
28
- """Verify that time series decomposition runs normally"""
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
- # Verify the return type
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 the call method can run normally"""
45
- # Create an algorithm instance object
49
+ """Verify that __call__ matches fit_transform."""
46
50
  stl = STL(period=self.period)
47
- result = stl(self.data)
51
+ a = stl(self.data)
52
+ b = stl.fit_transform(self.data)
48
53
 
49
- # Verify basic output
50
- self.assertIsInstance(result, STLResult, "Wrong return result type")
51
- self.assertEqual(len(result.observed), len(self.data), "Data length mismatch")
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 run and reconstruct."""
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 periodicity of the seasonal component"""
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
- # Peaks should appear at period points
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 - 1],
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 component"""
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, 0.9, "Trend component correlation with expected trend is insufficient"
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
- # Invalid seasonal parameter
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=4) # must be >= 7 and odd
127
+ STL(period=12, seasonal=2)
122
128
 
123
- # Insufficient data length
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)) # 10 < 2*12
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
- calls = [
136
- lambda stl: stl(self.data),
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__ = ["Moving_Decomp", "dtw_distance", "STLResult", "STL"]
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