anomaly_detection 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e8882d64f5b2fe33406fa583404a3efead153d47a434ac6f5a1ec21d10666389
4
- data.tar.gz: f0544946e1cb9011e32c7f3f0a8ae4fb90f9d1a2c7acca67c5f9e8f27f8780af
3
+ metadata.gz: 94e6edb7ef4ce6db5fdb7b01ab40322bc8daceb222c0388abe19704ef2ca7f99
4
+ data.tar.gz: 41e4c55f5f42251a25fc337941ee85ba306df15ca925ebfe6d1d59129cc650cc
5
5
  SHA512:
6
- metadata.gz: d30c25bf7a1a7069b7ba7c430e928677f5636261cdb1bc4ff24745103bbb2769ad065a2f368b32440eb1b5fce6483e206667b462542912f44f60a9b61494f360
7
- data.tar.gz: 4a3aba5aeed9e5488c77448d38f331cb3241db6cbf518fc5c4f8d00ca480ce13e53819e65b9a66970e8ca175349db00a4ae6b5b6bc8833f82a44219f840419b9
6
+ metadata.gz: a534a1903b14e7e3287b86b8da3936ee889f0e93690a55f5cbba5a384dfd06baab1ded6afb71acec6eb2461c0ac0d6645d28a5093ef33a4c28dce0319ef9ae75
7
+ data.tar.gz: 90fcd64ce191e8aaff1a46384e003f8eeea7b6e9732357fdd7726066bf4e3ad1a8fe91043042716f57e931b05c048d984c60178c05e38d127d21216fe5a2ee2f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.1.2 (2021-10-20)
2
+
3
+ - Added `plot` method
4
+
1
5
  ## 0.1.1 (2021-10-17)
2
6
 
3
7
  - Added `verbose` option
data/README.md CHANGED
@@ -58,6 +58,20 @@ AnomalyDetection.detect(
58
58
  )
59
59
  ```
60
60
 
61
+ ## Plotting [unreleased]
62
+
63
+ Add [Vega](https://github.com/ankane/vega) to your application’s Gemfile:
64
+
65
+ ```ruby
66
+ gem 'vega'
67
+ ```
68
+
69
+ And use:
70
+
71
+ ```ruby
72
+ AnomalyDetection.plot(series, anomalies)
73
+ ```
74
+
61
75
  ## Credits
62
76
 
63
77
  This library was ported from the [AnomalyDetection](https://github.com/twitter/AnomalyDetection) R package and is available under the same license. It uses [cdflib](https://people.sc.fsu.edu/~jburkardt/cpp_src/cdflib/cdflib.html) for the quantile function.
@@ -70,6 +70,7 @@ std::vector<size_t> detect_anoms(const std::vector<float>& data, int num_obs_per
70
70
  auto max_outliers = (size_t) n * k;
71
71
 
72
72
  // Sort data for fast median
73
+ // Use stable sort for indexes for deterministic results
73
74
  std::vector<size_t> indexes(n);
74
75
  std::iota(indexes.begin(), indexes.end(), 0);
75
76
  std::stable_sort(indexes.begin(), indexes.end(), [&data2](size_t a, size_t b) { return data2[a] < data2[b]; });
@@ -1,3 +1,3 @@
1
1
  module AnomalyDetection
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
@@ -5,18 +5,72 @@ require "anomaly_detection/ext"
5
5
  require "anomaly_detection/version"
6
6
 
7
7
  module AnomalyDetection
8
- def self.detect(series, period:, max_anoms: 0.1, alpha: 0.05, direction: "both", verbose: false)
9
- raise ArgumentError, "series must contain at least 2 periods" if series.size < period * 2
10
-
11
- if series.is_a?(Hash)
12
- sorted = series.sort_by { |k, _| k }
13
- x = sorted.map(&:last)
14
- else
15
- x = series
8
+ class << self
9
+ def detect(series, period:, max_anoms: 0.1, alpha: 0.05, direction: "both", plot: false, verbose: false)
10
+ raise ArgumentError, "series must contain at least 2 periods" if series.size < period * 2
11
+
12
+ if series.is_a?(Hash)
13
+ sorted = series.sort_by { |k, _| k }
14
+ x = sorted.map(&:last)
15
+ else
16
+ x = series
17
+ end
18
+
19
+ res = _detect(x, period, max_anoms, alpha, direction, verbose)
20
+ res.map! { |i| sorted[i][0] } if series.is_a?(Hash)
21
+ res
16
22
  end
17
23
 
18
- res = _detect(x, period, max_anoms, alpha, direction, verbose)
19
- res.map! { |i| sorted[i][0] } if series.is_a?(Hash)
20
- res
24
+ # TODO add tooltips
25
+ def plot(series, anomalies)
26
+ require "vega"
27
+
28
+ data =
29
+ if series.is_a?(Hash)
30
+ series.map { |k, v| {x: iso8601(k), y: v, anomaly: anomalies.include?(k)} }
31
+ else
32
+ series.map.with_index { |v, i| {x: i, y: v, anomaly: anomalies.include?(i)} }
33
+ end
34
+
35
+ if series.is_a?(Hash)
36
+ x = {field: "x", type: "temporal"}
37
+ x["scale"] = {type: "utc"} if series.keys.first.is_a?(Date)
38
+ else
39
+ x = {field: "x", type: "quantitative"}
40
+ end
41
+
42
+ Vega.lite
43
+ .data(data)
44
+ .layer([
45
+ {
46
+ mark: {type: "line"},
47
+ encoding: {
48
+ x: x,
49
+ y: {field: "y", type: "quantitative", scale: {zero: false}},
50
+ color: {value: "#fa9088"}
51
+ }
52
+ },
53
+ {
54
+ transform: [{"filter": "datum.anomaly == true"}],
55
+ mark: {type: "point", size: 200},
56
+ encoding: {
57
+ x: x,
58
+ y: {field: "y", type: "quantitative"},
59
+ color: {value: "#19c7ca"}
60
+ }
61
+ }
62
+ ])
63
+ .config(axis: {title: nil, labelFontSize: 12})
64
+ end
65
+
66
+ private
67
+
68
+ def iso8601(v)
69
+ if v.is_a?(Date)
70
+ v.strftime("%Y-%m-%d")
71
+ else
72
+ v.strftime("%Y-%m-%dT%H:%M:%S.%L%z")
73
+ end
74
+ end
21
75
  end
22
76
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anomaly_detection
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-18 00:00:00.000000000 Z
11
+ date: 2021-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rice