stl-rb 0.4.1 → 0.4.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c2dd80347b3360994ebdefd94b6649d1c43d1e8b9d0bfae1a81df87b3ef6b27
4
- data.tar.gz: 9bd7ddf6a35529a2365fe55e19e025d0c94a122d5eede5db0a8fbc1fcc53ba91
3
+ metadata.gz: c47963b84db78ea882aa50f4a6a18c18ee84e9acc802d0db125772f80531b179
4
+ data.tar.gz: 9c9e732f43bc586dbac041be2a3c4cc59d552681a59f8185cb873f6800bf9787
5
5
  SHA512:
6
- metadata.gz: 4517e2450a79e93177c55623a34e6d9bc1f9a2e366cd92060fc08ef43a7f768a8c09df5b47eb21b7aa5fd41084526e325eb0d3b6064331193d1e1e583f46068d
7
- data.tar.gz: 1a482b74b0f6bd586f4c58974f125a0484eca0c7f36cfe91d26ab52a0c75a1634ade87b1ce14416d3ada5f04e3623c1b8d7e507267ecdd40388e11418c922485
6
+ metadata.gz: f434fe76d8498d8eaf6560fc464f84e10ea1aa1addf32a5fbf432e1f1435ae31d5936b2b49b64a3220bdf298b379074d97b4f9863e6e6a4a10a800ac6caa2746
7
+ data.tar.gz: 4aa67ef012521f9a810f6aa15586e0f9010c4a87b0f7aacbf0fb8eef6aeb47af06b0707d5268b0b9bb5c2f57cd0d295de47930907e24658db9e6d5091ab16b01
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.4.2 (2026-08-08)
2
+
3
+ - Added support for MSTL
4
+
1
5
  ## 0.4.1 (2026-05-04)
2
6
 
3
7
  - Fixed error with certain options
data/README.md CHANGED
@@ -18,9 +18,9 @@ Decompose a time series
18
18
 
19
19
  ```ruby
20
20
  series = {
21
- Date.parse("2025-01-01") => 100,
22
- Date.parse("2025-01-02") => 150,
23
- Date.parse("2025-01-03") => 136,
21
+ Date.parse("2026-01-01") => 100,
22
+ Date.parse("2026-01-02") => 150,
23
+ Date.parse("2026-01-03") => 136,
24
24
  # ...
25
25
  }
26
26
 
@@ -47,6 +47,14 @@ Use robustness iterations
47
47
  Stl.decompose(series, period: 7, robust: true)
48
48
  ```
49
49
 
50
+ ## Multiple Seasonality
51
+
52
+ Specify multiple periods
53
+
54
+ ```ruby
55
+ Stl.decompose(series, period: [7, 365])
56
+ ```
57
+
50
58
  ## Options
51
59
 
52
60
  Pass options
@@ -66,7 +74,9 @@ Stl.decompose(
66
74
  low_pass_jump: 1, # skipping value for low-pass smoothing
67
75
  inner_loops: 2, # number of loops for updating the seasonal and trend components
68
76
  outer_loops: 0, # number of iterations of robust fitting
69
- robust: false # if robustness iterations are to be used
77
+ robust: false, # if robustness iterations are to be used
78
+ iterations: 2, # number of iterations for MSTL
79
+ lambda: 0.5 # lambda for Box-Cox transformation for MSTL
70
80
  )
71
81
  ```
72
82
 
@@ -105,6 +115,7 @@ This library was ported from the [Fortran implementation](https://www.netlib.org
105
115
  ## References
106
116
 
107
117
  - [STL: A Seasonal-Trend Decomposition Procedure Based on Loess](https://www.scb.se/contentassets/ca21efb41fee47d293bbee5bf7be7fb3/stl-a-seasonal-trend-decomposition-procedure-based-on-loess.pdf)
118
+ - [MSTL: A Seasonal-Trend Decomposition Algorithm for Time Series with Multiple Seasonal Patterns](https://arxiv.org/pdf/2107.13462.pdf)
108
119
  - [Measuring strength of trend and seasonality](https://otexts.com/fpp2/seasonal-strength.html)
109
120
 
110
121
  ## History
data/ext/stl/ext.cpp CHANGED
@@ -33,6 +33,13 @@ void Init_ext() {
33
33
  .define_attr("outer_loops", &stl::StlParams::outer_loops)
34
34
  .define_attr("robust", &stl::StlParams::robust);
35
35
 
36
+ Rice::define_class_under<stl::MstlParams>(rb_mStl, "MstlParams")
37
+ .define_constructor(Rice::Constructor<stl::MstlParams>())
38
+ .define_attr("iterations", &stl::MstlParams::iterations)
39
+ .define_attr("lambda", &stl::MstlParams::lambda)
40
+ .define_attr("seasonal_lengths", &stl::MstlParams::seasonal_lengths)
41
+ .define_attr("stl_params", &stl::MstlParams::stl_params);
42
+
36
43
  rb_mStl
37
44
  .define_singleton_function(
38
45
  "_decompose",
@@ -48,5 +55,22 @@ void Init_ext() {
48
55
  ret[Rice::Symbol("weights")] = to_a(fit.weights());
49
56
  }
50
57
  return ret;
58
+ })
59
+ .define_singleton_function(
60
+ "_decompose_mstl",
61
+ [](Rice::Array rb_series, const std::vector<size_t>& periods, const stl::MstlParams& params) {
62
+ std::vector<float> series = rb_series.to_vector<float>();
63
+ stl::Mstl fit{series, periods, params};
64
+
65
+ Rice::Array seasonal;
66
+ for (const auto& s : fit.seasonal()) {
67
+ seasonal.push(to_a(s), false);
68
+ }
69
+
70
+ Rice::Hash ret;
71
+ ret[Rice::Symbol("seasonal")] = seasonal;
72
+ ret[Rice::Symbol("trend")] = to_a(fit.trend());
73
+ ret[Rice::Symbol("remainder")] = to_a(fit.remainder());
74
+ return ret;
51
75
  });
52
76
  }
data/lib/stl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Stl
2
- VERSION = "0.4.1"
2
+ VERSION = "0.4.2"
3
3
  end
data/lib/stl.rb CHANGED
@@ -11,15 +11,18 @@ module Stl
11
11
  seasonal_length: nil, trend_length: nil, low_pass_length: nil,
12
12
  seasonal_degree: nil, trend_degree: nil, low_pass_degree: nil,
13
13
  seasonal_jump: nil, trend_jump: nil, low_pass_jump: nil,
14
- inner_loops: nil, outer_loops: nil, robust: false
14
+ inner_loops: nil, outer_loops: nil, robust: false,
15
+ iterations: nil, lambda: nil
15
16
  )
16
- if period < 2
17
+ mstl = period.is_a?(Array)
18
+
19
+ if !mstl && period < 2
17
20
  raise ArgumentError, "period must be greater than 1"
18
21
  end
19
22
 
20
23
  params = StlParams.new
21
24
 
22
- params.seasonal_length = seasonal_length unless seasonal_length.nil?
25
+ params.seasonal_length = seasonal_length if !seasonal_length.nil? && !(mstl && seasonal_length.is_a?(Array))
23
26
  params.trend_length = trend_length unless trend_length.nil?
24
27
  params.low_pass_length = low_pass_length unless low_pass_length.nil?
25
28
 
@@ -42,12 +45,27 @@ module Stl
42
45
  y = series
43
46
  end
44
47
 
45
- _decompose(y, period, params, outer_loops.nil? ? robust : outer_loops > 0)
48
+ if mstl
49
+ mstl_params = MstlParams.new
50
+ mstl_params.iterations = iterations unless iterations.nil?
51
+ mstl_params.lambda = lambda unless lambda.nil?
52
+ mstl_params.seasonal_lengths = seasonal_length if seasonal_length.is_a?(Array)
53
+ mstl_params.stl_params = params
54
+ _decompose_mstl(y, period, mstl_params)
55
+ else
56
+ raise ArgumentError, "iterations requires MSTL" unless iterations.nil?
57
+ raise ArgumentError, "lambda requires MSTL" unless lambda.nil?
58
+ _decompose(y, period, params, outer_loops.nil? ? robust : outer_loops > 0)
59
+ end
46
60
  end
47
61
 
48
62
  def plot(series, result)
49
63
  require "vega"
50
64
 
65
+ if mstl?(result)
66
+ raise "not implemented yet"
67
+ end
68
+
51
69
  data =
52
70
  if series.is_a?(Hash)
53
71
  series.sort_by { |k, _| k }.map.with_index do |s, i|
@@ -101,13 +119,17 @@ module Stl
101
119
  end
102
120
 
103
121
  def seasonal_strength(result)
104
- sr = result[:seasonal].zip(result[:remainder]).map { |a, b| a + b }
105
- [0, 1 - var(result[:remainder]) / var(sr)].max
122
+ if mstl?(result)
123
+ result[:seasonal].map do |s|
124
+ strength(s, result[:remainder])
125
+ end
126
+ else
127
+ strength(result[:seasonal], result[:remainder])
128
+ end
106
129
  end
107
130
 
108
131
  def trend_strength(result)
109
- tr = result[:trend].zip(result[:remainder]).map { |a, b| a + b }
110
- [0, 1 - var(result[:remainder]) / var(tr)].max
132
+ strength(result[:trend], result[:remainder])
111
133
  end
112
134
 
113
135
  private
@@ -120,9 +142,18 @@ module Stl
120
142
  end
121
143
  end
122
144
 
145
+ def strength(component, remainder)
146
+ sr = component.zip(remainder).map { |a, b| a + b }
147
+ [0, 1 - var(remainder) / var(sr)].max
148
+ end
149
+
123
150
  def var(series)
124
151
  mean = series.sum / series.size.to_f
125
152
  series.sum { |v| (v - mean) ** 2 } / (series.size.to_f - 1)
126
153
  end
154
+
155
+ def mstl?(result)
156
+ result[:seasonal][0].is_a?(Array)
157
+ end
127
158
  end
128
159
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stl-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
@@ -55,7 +55,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
55
  - !ruby/object:Gem::Version
56
56
  version: '0'
57
57
  requirements: []
58
- rubygems_version: 4.0.6
58
+ rubygems_version: 4.0.16
59
59
  specification_version: 4
60
60
  summary: Seasonal-trend decomposition for Ruby
61
61
  test_files: []