stl-rb 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +2 -2
- data/ext/stl/ext.cpp +2 -4
- data/ext/stl/stl.hpp +73 -44
- data/lib/stl/version.rb +1 -1
- data/lib/stl-rb.rb +1 -1
- data/lib/stl.rb +6 -2
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2aa2dc89bcf68293681a8fc62b7806ae94ba76561350e26679cbc776093dd2c6
|
4
|
+
data.tar.gz: 7c5e650e295730e29f344108bfdb802210c9db3b20b742adb7d64028c95e6c59
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 608fac4b9b1f16372f32e664a0887d2bb36e9842acbc51e6f524ec09f1cbcdbb680b1033235943e1c70ad0d8d093df76236005f064addddcc35d6d58eb233bf7
|
7
|
+
data.tar.gz: b931fd2c683e457c8845f824e3a5915775b1f671ac23ab4e70471f7424c45dc92bf58fb00e114d884b5d9ce859fbe5aac6314e5f11d8debfbfd82a923eb1ef6d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## 0.2.0 (2023-02-01)
|
2
|
+
|
3
|
+
- Raise error when `period` is less than 2
|
4
|
+
- Dropped support for Ruby < 2.7
|
5
|
+
|
6
|
+
## 0.1.3 (2021-12-16)
|
7
|
+
|
8
|
+
- Fixed installation error on macOS 12
|
9
|
+
|
1
10
|
## 0.1.2 (2021-10-24)
|
2
11
|
|
3
12
|
- Added `seasonal_strength` and `trend_strength` methods
|
data/README.md
CHANGED
@@ -9,7 +9,7 @@ Seasonal-trend decomposition for Ruby
|
|
9
9
|
Add this line to your application’s Gemfile:
|
10
10
|
|
11
11
|
```ruby
|
12
|
-
gem
|
12
|
+
gem "stl-rb"
|
13
13
|
```
|
14
14
|
|
15
15
|
## Getting Started
|
@@ -75,7 +75,7 @@ Stl.decompose(
|
|
75
75
|
Add [Vega](https://github.com/ankane/vega) to your application’s Gemfile:
|
76
76
|
|
77
77
|
```ruby
|
78
|
-
gem
|
78
|
+
gem "vega"
|
79
79
|
```
|
80
80
|
|
81
81
|
And use:
|
data/ext/stl/ext.cpp
CHANGED
data/ext/stl/stl.hpp
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
/*!
|
2
|
-
* STL C++ v0.1.
|
2
|
+
* STL C++ v0.1.2
|
3
3
|
* https://github.com/ankane/stl-cpp
|
4
4
|
* Unlicense OR MIT License
|
5
5
|
*
|
@@ -14,6 +14,7 @@
|
|
14
14
|
|
15
15
|
#include <algorithm>
|
16
16
|
#include <cmath>
|
17
|
+
#include <numeric>
|
17
18
|
#include <optional>
|
18
19
|
#include <stdexcept>
|
19
20
|
#include <vector>
|
@@ -91,14 +92,14 @@ void ess(const float* y, size_t n, size_t len, int ideg, size_t njump, bool user
|
|
91
92
|
return;
|
92
93
|
}
|
93
94
|
|
94
|
-
|
95
|
-
|
95
|
+
size_t nleft = 0;
|
96
|
+
size_t nright = 0;
|
96
97
|
|
97
98
|
auto newnj = std::min(njump, n - 1);
|
98
99
|
if (len >= n) {
|
99
100
|
nleft = 1;
|
100
101
|
nright = n;
|
101
|
-
for (
|
102
|
+
for (size_t i = 1; i <= n; i += newnj) {
|
102
103
|
auto ok = est(y, n, len, ideg, (float) i, &ys[i - 1], nleft, nright, res, userw, rw);
|
103
104
|
if (!ok) {
|
104
105
|
ys[i - 1] = y[i - 1];
|
@@ -108,7 +109,7 @@ void ess(const float* y, size_t n, size_t len, int ideg, size_t njump, bool user
|
|
108
109
|
auto nsh = (len + 1) / 2;
|
109
110
|
nleft = 1;
|
110
111
|
nright = len;
|
111
|
-
for (
|
112
|
+
for (size_t i = 1; i <= n; i++) { // fitted value at i
|
112
113
|
if (i > nsh && nright != n) {
|
113
114
|
nleft += 1;
|
114
115
|
nright += 1;
|
@@ -120,7 +121,7 @@ void ess(const float* y, size_t n, size_t len, int ideg, size_t njump, bool user
|
|
120
121
|
}
|
121
122
|
} else { // newnj greater than one, len less than n
|
122
123
|
auto nsh = (len + 1) / 2;
|
123
|
-
for (
|
124
|
+
for (size_t i = 1; i <= n; i += newnj) { // fitted value at i
|
124
125
|
if (i < nsh) {
|
125
126
|
nleft = 1;
|
126
127
|
nright = len;
|
@@ -139,7 +140,7 @@ void ess(const float* y, size_t n, size_t len, int ideg, size_t njump, bool user
|
|
139
140
|
}
|
140
141
|
|
141
142
|
if (newnj != 1) {
|
142
|
-
for (
|
143
|
+
for (size_t i = 1; i <= n - newnj; i += newnj) {
|
143
144
|
auto delta = (ys[i + newnj - 1] - ys[i - 1]) / ((float) newnj);
|
144
145
|
for (auto j = i + 1; j <= i + newnj - 1; j++) {
|
145
146
|
ys[j - 1] = ys[i - 1] + delta * ((float) (j - i));
|
@@ -167,7 +168,7 @@ void ma(const float* x, size_t n, size_t len, float* ave) {
|
|
167
168
|
auto v = 0.0;
|
168
169
|
|
169
170
|
// get the first average
|
170
|
-
for (
|
171
|
+
for (size_t i = 0; i < len; i++) {
|
171
172
|
v += x[i];
|
172
173
|
}
|
173
174
|
|
@@ -175,7 +176,7 @@ void ma(const float* x, size_t n, size_t len, float* ave) {
|
|
175
176
|
if (newn > 1) {
|
176
177
|
auto k = len;
|
177
178
|
auto m = 0;
|
178
|
-
for (
|
179
|
+
for (size_t j = 1; j < newn; j++) {
|
179
180
|
// window down the array
|
180
181
|
v = v - x[m] + x[k];
|
181
182
|
ave[j] = v / flen;
|
@@ -192,7 +193,7 @@ void fts(const float* x, size_t n, size_t np, float* trend, float* work) {
|
|
192
193
|
}
|
193
194
|
|
194
195
|
void rwts(const float* y, size_t n, const float* fit, float* rw) {
|
195
|
-
for (
|
196
|
+
for (size_t i = 0; i < n; i++) {
|
196
197
|
rw[i] = fabs(y[i] - fit[i]);
|
197
198
|
}
|
198
199
|
|
@@ -206,7 +207,7 @@ void rwts(const float* y, size_t n, const float* fit, float* rw) {
|
|
206
207
|
auto c9 = 0.999 * cmad;
|
207
208
|
auto c1 = 0.001 * cmad;
|
208
209
|
|
209
|
-
for (
|
210
|
+
for (size_t i = 0; i < n; i++) {
|
210
211
|
auto r = fabs(y[i] - fit[i]);
|
211
212
|
if (r <= c1) {
|
212
213
|
rw[i] = 1.0;
|
@@ -219,14 +220,14 @@ void rwts(const float* y, size_t n, const float* fit, float* rw) {
|
|
219
220
|
}
|
220
221
|
|
221
222
|
void ss(const float* y, size_t n, size_t np, size_t ns, int isdeg, size_t nsjump, bool userw, float* rw, float* season, float* work1, float* work2, float* work3, float* work4) {
|
222
|
-
for (
|
223
|
-
|
223
|
+
for (size_t j = 1; j <= np; j++) {
|
224
|
+
size_t k = (n - j) / np + 1;
|
224
225
|
|
225
|
-
for (
|
226
|
+
for (size_t i = 1; i <= k; i++) {
|
226
227
|
work1[i - 1] = y[(i - 1) * np + j - 1];
|
227
228
|
}
|
228
229
|
if (userw) {
|
229
|
-
for (
|
230
|
+
for (size_t i = 1; i <= k; i++) {
|
230
231
|
work3[i - 1] = rw[(i - 1) * np + j - 1];
|
231
232
|
}
|
232
233
|
}
|
@@ -243,25 +244,25 @@ void ss(const float* y, size_t n, size_t np, size_t ns, int isdeg, size_t nsjump
|
|
243
244
|
if (!ok) {
|
244
245
|
work2[k + 1] = work2[k];
|
245
246
|
}
|
246
|
-
for (
|
247
|
+
for (size_t m = 1; m <= k + 2; m++) {
|
247
248
|
season[(m - 1) * np + j - 1] = work2[m - 1];
|
248
249
|
}
|
249
250
|
}
|
250
251
|
}
|
251
252
|
|
252
253
|
void onestp(const float* y, size_t n, size_t np, size_t ns, size_t nt, size_t nl, int isdeg, int itdeg, int ildeg, size_t nsjump, size_t ntjump, size_t nljump, size_t ni, bool userw, float* rw, float* season, float* trend, float* work1, float* work2, float* work3, float* work4, float* work5) {
|
253
|
-
for (
|
254
|
-
for (
|
254
|
+
for (size_t j = 0; j < ni; j++) {
|
255
|
+
for (size_t i = 0; i < n; i++) {
|
255
256
|
work1[i] = y[i] - trend[i];
|
256
257
|
}
|
257
258
|
|
258
259
|
ss(work1, n, np, ns, isdeg, nsjump, userw, rw, work2, work3, work4, work5, season);
|
259
260
|
fts(work2, n + 2 * np, np, work3, work1);
|
260
261
|
ess(work3, n, nl, ildeg, nljump, false, work4, work1, work5);
|
261
|
-
for (
|
262
|
+
for (size_t i = 0; i < n; i++) {
|
262
263
|
season[i] = work2[np + i] - work1[i];
|
263
264
|
}
|
264
|
-
for (
|
265
|
+
for (size_t i = 0; i < n; i++) {
|
265
266
|
work1[i] = y[i] - season[i];
|
266
267
|
}
|
267
268
|
ess(work1, n, nt, itdeg, ntjump, userw, rw, trend, work3);
|
@@ -269,15 +270,6 @@ void onestp(const float* y, size_t n, size_t np, size_t ns, size_t nt, size_t nl
|
|
269
270
|
}
|
270
271
|
|
271
272
|
void stl(const float* y, size_t n, size_t np, size_t ns, size_t nt, size_t nl, int isdeg, int itdeg, int ildeg, size_t nsjump, size_t ntjump, size_t nljump, size_t ni, size_t no, float* rw, float* season, float* trend) {
|
272
|
-
auto work1 = std::vector<float>(n + 2 * np);
|
273
|
-
auto work2 = std::vector<float>(n + 2 * np);
|
274
|
-
auto work3 = std::vector<float>(n + 2 * np);
|
275
|
-
auto work4 = std::vector<float>(n + 2 * np);
|
276
|
-
auto work5 = std::vector<float>(n + 2 * np);
|
277
|
-
|
278
|
-
auto userw = false;
|
279
|
-
auto k = 0;
|
280
|
-
|
281
273
|
if (ns < 3) {
|
282
274
|
throw std::invalid_argument("seasonal_length must be at least 3");
|
283
275
|
}
|
@@ -311,13 +303,22 @@ void stl(const float* y, size_t n, size_t np, size_t ns, size_t nt, size_t nl, i
|
|
311
303
|
throw std::invalid_argument("low_pass_length must be odd");
|
312
304
|
}
|
313
305
|
|
306
|
+
auto work1 = std::vector<float>(n + 2 * np);
|
307
|
+
auto work2 = std::vector<float>(n + 2 * np);
|
308
|
+
auto work3 = std::vector<float>(n + 2 * np);
|
309
|
+
auto work4 = std::vector<float>(n + 2 * np);
|
310
|
+
auto work5 = std::vector<float>(n + 2 * np);
|
311
|
+
|
312
|
+
auto userw = false;
|
313
|
+
size_t k = 0;
|
314
|
+
|
314
315
|
while (true) {
|
315
316
|
onestp(y, n, np, ns, nt, nl, isdeg, itdeg, ildeg, nsjump, ntjump, nljump, ni, userw, rw, season, trend, work1.data(), work2.data(), work3.data(), work4.data(), work5.data());
|
316
317
|
k += 1;
|
317
318
|
if (k > no) {
|
318
319
|
break;
|
319
320
|
}
|
320
|
-
for (
|
321
|
+
for (size_t i = 0; i < n; i++) {
|
321
322
|
work1[i] = trend[i] + season[i];
|
322
323
|
}
|
323
324
|
rwts(y, n, work1.data(), rw);
|
@@ -325,18 +326,46 @@ void stl(const float* y, size_t n, size_t np, size_t ns, size_t nt, size_t nl, i
|
|
325
326
|
}
|
326
327
|
|
327
328
|
if (no <= 0) {
|
328
|
-
for (
|
329
|
+
for (size_t i = 0; i < n; i++) {
|
329
330
|
rw[i] = 1.0;
|
330
331
|
}
|
331
332
|
}
|
332
333
|
}
|
333
334
|
|
335
|
+
float var(const std::vector<float>& series) {
|
336
|
+
auto mean = std::accumulate(series.begin(), series.end(), 0.0) / series.size();
|
337
|
+
std::vector<float> tmp;
|
338
|
+
tmp.reserve(series.size());
|
339
|
+
for (auto v : series) {
|
340
|
+
tmp.push_back(pow(v - mean, 2));
|
341
|
+
}
|
342
|
+
return std::accumulate(tmp.begin(), tmp.end(), 0.0) / (series.size() - 1);
|
343
|
+
}
|
344
|
+
|
334
345
|
class StlResult {
|
335
346
|
public:
|
336
347
|
std::vector<float> seasonal;
|
337
348
|
std::vector<float> trend;
|
338
349
|
std::vector<float> remainder;
|
339
350
|
std::vector<float> weights;
|
351
|
+
|
352
|
+
inline float seasonal_strength() {
|
353
|
+
std::vector<float> sr;
|
354
|
+
sr.reserve(remainder.size());
|
355
|
+
for (size_t i = 0; i < remainder.size(); i++) {
|
356
|
+
sr.push_back(seasonal[i] + remainder[i]);
|
357
|
+
}
|
358
|
+
return std::max(0.0, 1.0 - var(remainder) / var(sr));
|
359
|
+
}
|
360
|
+
|
361
|
+
inline float trend_strength() {
|
362
|
+
std::vector<float> tr;
|
363
|
+
tr.reserve(remainder.size());
|
364
|
+
for (size_t i = 0; i < remainder.size(); i++) {
|
365
|
+
tr.push_back(trend[i] + remainder[i]);
|
366
|
+
}
|
367
|
+
return std::max(0.0, 1.0 - var(remainder) / var(tr));
|
368
|
+
}
|
340
369
|
};
|
341
370
|
|
342
371
|
class StlParams {
|
@@ -357,62 +386,62 @@ public:
|
|
357
386
|
inline StlParams seasonal_length(size_t ns) {
|
358
387
|
this->ns_ = ns;
|
359
388
|
return *this;
|
360
|
-
}
|
389
|
+
}
|
361
390
|
|
362
391
|
inline StlParams trend_length(size_t nt) {
|
363
392
|
this->nt_ = nt;
|
364
393
|
return *this;
|
365
|
-
}
|
394
|
+
}
|
366
395
|
|
367
396
|
inline StlParams low_pass_length(size_t nl) {
|
368
397
|
this->nl_ = nl;
|
369
398
|
return *this;
|
370
|
-
}
|
399
|
+
}
|
371
400
|
|
372
401
|
inline StlParams seasonal_degree(int isdeg) {
|
373
402
|
this->isdeg_ = isdeg;
|
374
403
|
return *this;
|
375
|
-
}
|
404
|
+
}
|
376
405
|
|
377
406
|
inline StlParams trend_degree(int itdeg) {
|
378
407
|
this->itdeg_ = itdeg;
|
379
408
|
return *this;
|
380
|
-
}
|
409
|
+
}
|
381
410
|
|
382
411
|
inline StlParams low_pass_degree(int ildeg) {
|
383
412
|
this->ildeg_ = ildeg;
|
384
413
|
return *this;
|
385
|
-
}
|
414
|
+
}
|
386
415
|
|
387
416
|
inline StlParams seasonal_jump(size_t nsjump) {
|
388
417
|
this->nsjump_ = nsjump;
|
389
418
|
return *this;
|
390
|
-
}
|
419
|
+
}
|
391
420
|
|
392
421
|
inline StlParams trend_jump(size_t ntjump) {
|
393
422
|
this->ntjump_ = ntjump;
|
394
423
|
return *this;
|
395
|
-
}
|
424
|
+
}
|
396
425
|
|
397
426
|
inline StlParams low_pass_jump(size_t nljump) {
|
398
427
|
this->nljump_ = nljump;
|
399
428
|
return *this;
|
400
|
-
}
|
429
|
+
}
|
401
430
|
|
402
431
|
inline StlParams inner_loops(bool ni) {
|
403
432
|
this->ni_ = ni;
|
404
433
|
return *this;
|
405
|
-
}
|
434
|
+
}
|
406
435
|
|
407
436
|
inline StlParams outer_loops(bool no) {
|
408
437
|
this->no_ = no;
|
409
438
|
return *this;
|
410
|
-
}
|
439
|
+
}
|
411
440
|
|
412
441
|
inline StlParams robust(bool robust) {
|
413
442
|
this->robust_ = robust;
|
414
443
|
return *this;
|
415
|
-
}
|
444
|
+
}
|
416
445
|
|
417
446
|
StlResult fit(const float* y, size_t n, size_t np);
|
418
447
|
StlResult fit(const std::vector<float>& y, size_t np);
|
@@ -468,7 +497,7 @@ StlResult StlParams::fit(const float* y, size_t n, size_t np) {
|
|
468
497
|
stl(y, n, newnp, newns, nt, nl, isdeg, itdeg, ildeg, nsjump, ntjump, nljump, ni, no, res.weights.data(), res.seasonal.data(), res.trend.data());
|
469
498
|
|
470
499
|
res.remainder.reserve(n);
|
471
|
-
for (
|
500
|
+
for (size_t i = 0; i < n; i++) {
|
472
501
|
res.remainder.push_back(y[i] - res.seasonal[i] - res.trend[i]);
|
473
502
|
}
|
474
503
|
|
data/lib/stl/version.rb
CHANGED
data/lib/stl-rb.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
require_relative "stl"
|
data/lib/stl.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# ext
|
2
|
-
|
2
|
+
require_relative "stl/ext"
|
3
3
|
|
4
4
|
# modules
|
5
|
-
|
5
|
+
require_relative "stl/version"
|
6
6
|
|
7
7
|
module Stl
|
8
8
|
class << self
|
@@ -13,6 +13,10 @@ module Stl
|
|
13
13
|
seasonal_jump: nil, trend_jump: nil, low_pass_jump: nil,
|
14
14
|
inner_loops: nil, outer_loops: nil, robust: false
|
15
15
|
)
|
16
|
+
if period < 2
|
17
|
+
raise ArgumentError, "period must be greater than 1"
|
18
|
+
end
|
19
|
+
|
16
20
|
params = StlParams.new
|
17
21
|
|
18
22
|
params.seasonal_length(seasonal_length) unless seasonal_length.nil?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stl-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-02-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rice
|
@@ -51,14 +51,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '2.
|
54
|
+
version: '2.7'
|
55
55
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
56
|
requirements:
|
57
57
|
- - ">="
|
58
58
|
- !ruby/object:Gem::Version
|
59
59
|
version: '0'
|
60
60
|
requirements: []
|
61
|
-
rubygems_version: 3.
|
61
|
+
rubygems_version: 3.4.1
|
62
62
|
signing_key:
|
63
63
|
specification_version: 4
|
64
64
|
summary: Seasonal-trend decomposition for Ruby
|