breakout-detection 0.1.0 → 0.1.1

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: 8f81b5a2a9781787903f293322eef1ff6d08479cde2df25eea47c3023cd501bd
4
- data.tar.gz: 1839625455de87fca184eb2f1fdf8bc7a2e8809c8afd17b9fc9ec61d6d775b7a
3
+ metadata.gz: 6678a5f3c82af3a88809391a6c692f3d0af7d6d6acb335936a6a0712ed0a2176
4
+ data.tar.gz: d44d0f92be5412e5f49e095f9b3a1d0c564c30ac2e42911e276bc1092c10edd3
5
5
  SHA512:
6
- metadata.gz: 1fa14ae2cc9db547bdeb13a3232d87c96e239aa6dde618ea6b7fa9c53aaca5596b45223c3e7eeceb9a8aa0428de77c54c4e8275adf57b9767c13a889538981bb
7
- data.tar.gz: 4ba5ee1decd2ab9084c3d377ec6d37a743c7f5ff47bb48eb55bdd5ecb9bce57df534522643c448b00a35cdd39d1a429ebc1c036440b079046c321bbef5e193a1
6
+ metadata.gz: 343f0d3245d495d022b2953009f724d0bf83f2448280b4e9ad0b4ca678241ac57044b9d6b3efbb2d473f2fc61d0083e601fbae669b243196aba080e440296bce
7
+ data.tar.gz: 129bc113eae6cda7e081ad8cc15f7b01f772bd7504187b96ad0e2a3af583e256d7127049e1528c5de4c9eeb87dad2e9db00101c35e14def788243f073226c36a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.1.1 (2021-10-20)
2
+
3
+ - Added `plot` method
4
+
1
5
  ## 0.1.0 (2021-09-02)
2
6
 
3
7
  - First release
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  :fire: [BreakoutDetection](https://github.com/twitter/BreakoutDetection) for Ruby
4
4
 
5
- Learn more about [how it works](https://blog.twitter.com/engineering/en_us/a/2014/breakout-detection-in-the-wild)
5
+ Learn [how it works](https://blog.twitter.com/engineering/en_us/a/2014/breakout-detection-in-the-wild)
6
6
 
7
7
  [![Build Status](https://github.com/ankane/breakout/workflows/build/badge.svg?branch=master)](https://github.com/ankane/breakout/actions)
8
8
 
@@ -60,6 +60,20 @@ Breakout.detect(
60
60
  )
61
61
  ```
62
62
 
63
+ ## Plotting
64
+
65
+ Add [Vega](https://github.com/ankane/vega) to your application’s Gemfile:
66
+
67
+ ```ruby
68
+ gem 'vega'
69
+ ```
70
+
71
+ And use:
72
+
73
+ ```ruby
74
+ Breakout.plot(series, breakouts)
75
+ ```
76
+
63
77
  ## Credits
64
78
 
65
79
  This library uses the C++ code from the [BreakoutDetection](https://github.com/twitter/BreakoutDetection) R package and is available under the same license.
@@ -50,7 +50,7 @@ std::vector<int> EDM_multi(const std::vector<double>& Z, int min_size = 24, doub
50
50
  for (int i = min_size - 1; i < s; ++i)
51
51
  insert_element(right_min, right_max, Z[i]);
52
52
 
53
- // Iterate over possible locations for the penultiamte change
53
+ // Iterate over possible locations for the penultimate change
54
54
  for (int t = min_size; t < s - min_size + 1; ++t) { // modify limits to deal with min_size
55
55
  insert_element(left_min, left_max, Z[t - 1]); // insert element into left tree
56
56
  remove_element(right_min, right_max, Z[t - 1]); // remove element from right tree
@@ -1,7 +1,7 @@
1
1
  /*
2
- Penalizes based on percent chagne in the statistic value.
3
- Linear penalty means that each new breakout must result in an at least X% increast
4
- Quadratic penalty means that each new brekaout must result in at least an (X*k)% increase for k breakouts
2
+ Penalizes based on percent change in the statistic value.
3
+ Linear penalty means that each new breakout must result in an at least X% increase
4
+ Quadratic penalty means that each new breakout must result in at least an (X*k)% increase for k breakouts
5
5
  */
6
6
 
7
7
  #include <algorithm>
@@ -355,7 +355,6 @@ void BackwardUpdate(std::vector<double>& Z, Information& info, int& tau1, double
355
355
  index /= 2;
356
356
  }
357
357
  }
358
- double qb = std::pow(GetQuantile(info.B, quant), alpha);
359
358
  // Move tau2 from the end of the time series to the front.
360
359
  // Update the statistic value along the way
361
360
  tau2 = N;
@@ -366,7 +365,7 @@ void BackwardUpdate(std::vector<double>& Z, Information& info, int& tau1, double
366
365
  --info.B[index];
367
366
  index /= 2;
368
367
  }
369
- qb = std::pow(GetQuantile(info.B, quant), alpha);
368
+ double qb = std::pow(GetQuantile(info.B, quant), alpha);
370
369
 
371
370
  double stat = 2 * qc - qa - qb;
372
371
  stat *= (double)(tau2 - tau1) * tau1 / tau2;
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  Robust estimation of 2[mean(X)-mean(Y)]^2 time normalization factor
3
3
  This is the E-Divisive E-statistic when alpha = 2
4
- Instead of calculating mean(X) we calculate median(X), and similarly for Y
4
+ Instead of calculating mean(X), we calculate median(X), and similarly for Y
5
5
  */
6
6
 
7
7
  #include <algorithm>
@@ -1,3 +1,3 @@
1
1
  module Breakout
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/lib/breakout.rb CHANGED
@@ -5,24 +5,78 @@ require "breakout/ext"
5
5
  require "breakout/version"
6
6
 
7
7
  module Breakout
8
- def self.detect(series, min_size: 30, method: "multi", alpha: 2, beta: nil, degree: 1, percent: nil, exact: true)
9
- raise ArgumentError, "min_size must be at least 2" if min_size < 2
10
- raise ArgumentError, "beta and percent cannot be passed together" unless beta.nil? || percent.nil?
11
- raise ArgumentError, "alpha must be between 0 and 2" if alpha < 0 || alpha > 2
12
- raise ArgumentError, "degree must be 0, 1, or 2" unless [0, 1, 2].include?(degree)
13
- raise ArgumentError, "Bad method" unless ["amoc", "multi"].include?(method)
14
-
15
- return [] if series.size < min_size
16
-
17
- if series.is_a?(Hash)
18
- sorted = series.sort_by { |k, _| k }
19
- z = sorted.map(&:last)
20
- else
21
- z = series
8
+ class << self
9
+ def detect(series, min_size: 30, method: "multi", alpha: 2, beta: nil, degree: 1, percent: nil, exact: true)
10
+ raise ArgumentError, "min_size must be at least 2" if min_size < 2
11
+ raise ArgumentError, "beta and percent cannot be passed together" unless beta.nil? || percent.nil?
12
+ raise ArgumentError, "alpha must be between 0 and 2" if alpha < 0 || alpha > 2
13
+ raise ArgumentError, "degree must be 0, 1, or 2" unless [0, 1, 2].include?(degree)
14
+ raise ArgumentError, "method must be amoc or multi" unless ["amoc", "multi"].include?(method)
15
+
16
+ return [] if series.size < min_size
17
+
18
+ if series.is_a?(Hash)
19
+ sorted = series.sort_by { |k, _| k }
20
+ z = sorted.map(&:last)
21
+ else
22
+ z = series
23
+ end
24
+
25
+ res = _detect(z, min_size, method, alpha, beta, degree, percent, exact)
26
+ res.map! { |i| sorted[i][0] } if series.is_a?(Hash)
27
+ res
28
+ end
29
+
30
+ def plot(series, breakouts)
31
+ require "vega"
32
+
33
+ data =
34
+ if series.is_a?(Hash)
35
+ series.map { |k, v| {x: iso8601(k), y: v, breakout: breakouts.include?(k)} }
36
+ else
37
+ series.map.with_index { |v, i| {x: i, y: v, breakout: breakouts.include?(i)} }
38
+ end
39
+
40
+ if series.is_a?(Hash)
41
+ x = {field: "x", type: "temporal"}
42
+ x["scale"] = {type: "utc"} if series.keys.first.is_a?(Date)
43
+ else
44
+ x = {field: "x", type: "quantitative"}
45
+ end
46
+
47
+ Vega.lite
48
+ .data(data)
49
+ .layer([
50
+ {
51
+ mark: {type: "line"},
52
+ encoding: {
53
+ x: x,
54
+ y: {field: "y", type: "quantitative", scale: {zero: false}},
55
+ color: {value: "#fa9088"}
56
+ }
57
+ },
58
+ {
59
+ transform: [{"filter": "datum.breakout == true"}],
60
+ mark: {type: "rule"},
61
+ encoding: {
62
+ x: x,
63
+ color: {value: "#19c7ca"},
64
+ strokeWidth: {value: 2},
65
+ strokeDash: {value: [6, 6]}
66
+ }
67
+ }
68
+ ])
69
+ .config(axis: {title: nil, labelFontSize: 12})
22
70
  end
23
71
 
24
- res = _detect(z, min_size, method, alpha, beta, degree, percent, exact)
25
- res.map! { |i| sorted[i][0] } if series.is_a?(Hash)
26
- res
72
+ private
73
+
74
+ def iso8601(v)
75
+ if v.is_a?(Date)
76
+ v.strftime("%Y-%m-%d")
77
+ else
78
+ v.strftime("%Y-%m-%dT%H:%M:%S.%L%z")
79
+ end
80
+ end
27
81
  end
28
82
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: breakout-detection
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
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-09-03 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