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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +14 -0
- data/ext/anomaly_detection/anomaly_detection.cpp +1 -0
- data/lib/anomaly_detection/version.rb +1 -1
- data/lib/anomaly_detection.rb +65 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 94e6edb7ef4ce6db5fdb7b01ab40322bc8daceb222c0388abe19704ef2ca7f99
|
4
|
+
data.tar.gz: 41e4c55f5f42251a25fc337941ee85ba306df15ca925ebfe6d1d59129cc650cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a534a1903b14e7e3287b86b8da3936ee889f0e93690a55f5cbba5a384dfd06baab1ded6afb71acec6eb2461c0ac0d6645d28a5093ef33a4c28dce0319ef9ae75
|
7
|
+
data.tar.gz: 90fcd64ce191e8aaff1a46384e003f8eeea7b6e9732357fdd7726066bf4e3ad1a8fe91043042716f57e931b05c048d984c60178c05e38d127d21216fe5a2ee2f
|
data/CHANGELOG.md
CHANGED
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]; });
|
data/lib/anomaly_detection.rb
CHANGED
@@ -5,18 +5,72 @@ require "anomaly_detection/ext"
|
|
5
5
|
require "anomaly_detection/version"
|
6
6
|
|
7
7
|
module AnomalyDetection
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
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.
|
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-
|
11
|
+
date: 2021-10-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rice
|