mc_forecast 0.1.0 → 0.2.0

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: 4f0d344256cf08700cd6d8475d0ac19ec44079cae9699f7a037fc1b934ee5c4d
4
- data.tar.gz: dca662a52e4bc902ddf944e47e59882f43d86629cf5792aa19d737fa430f8d59
3
+ metadata.gz: 82e81b59b88ffa6eadba70aef437ecec5c040b890b19d7f64f8237eb59fc7c56
4
+ data.tar.gz: 729b9f9d931b338f5860427d1aaba73aea35c847eedb018d72c39a7486c7f551
5
5
  SHA512:
6
- metadata.gz: a066871adf87c23610e0f08938934f5e1abdb630e477aea65e86195446952e279df54028c9ea12017e37669371203d6f22c53d21c1433aa69cf88c51ccb7b6b1
7
- data.tar.gz: 3c294be97dc9040d7cde19ae90c0508d02fe2f6eb777cb38ff109ac7a7971ca94b25b1fbf47cd53190cdcb5eb2f2a3558198361ffa54d6d9fdd43b7004d63982
6
+ metadata.gz: 4ce13d962a1c9e82f52e101387f6c8627fdac52187000e5ceb5a25bc7513bc9929cd94d6fc6faaf5196f7bcc90336bb69be642c774abe93a205b43464bbc20f1
7
+ data.tar.gz: ea2d982df7f53a36558969c1d8361ea477d817895f92a4118cb23472eaa3066b85ce9efffcc12f804894facf1303f8dffe68c2b2bb9fd8865125d0ba516a41fd
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/DaanVanVugt/ruby-mc/ci.yml)](https://github.com/DaanVanVugt/ruby-mc/actions/workflows/ci.yml)
6
6
  [![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/DaanVanVugt/ruby-mc)](https://codeclimate.com/github/DaanVanVugt/ruby-mc)
7
7
 
8
- TODO: Description of this gem goes here.
8
+ Use Monte-Carlo methods for business forecasting. Define transition methods (for example a month-based one) and keep track of events you are interested in. Automatically generates a 95% confidence interval and mean values.
9
9
 
10
10
  ---
11
11
 
@@ -23,6 +23,17 @@ gem install mc_forecast
23
23
 
24
24
  ```ruby
25
25
  require "mc_forecast"
26
+ # all arguments optional
27
+ e = McForecast::Simulation.new.run(init_state: nil, steps: 1, trials: 1_000) do |_state, _step, _trial|
28
+ events = {}
29
+ events[:coin] = rand > 0.5 ? 1 : 0
30
+
31
+ # block should return a new state and a hash of events
32
+ [nil, events]
33
+ end
34
+ # e[:coin][:mean][0] ~ 0.5
35
+ # e[:coin][:quantiles][0.025][0] ~ 0
36
+ # e[:coin][:quantiles][0.975][0] ~ 1
26
37
  ```
27
38
 
28
39
  ## Support
@@ -1,9 +1,11 @@
1
+ require "deep_dup"
2
+
1
3
  module McForecast
2
4
  class Simulation
3
- def run(init_state: nil, trials: 1_000, steps: 1, quantiles: [0.025, 0.975])
5
+ def run(init_state: nil, trials: 1_000, steps: 1, quantiles: [0.025, 0.16, 0.84, 0.975])
4
6
  events = {}
5
7
  (0..trials - 1).each do |trial|
6
- state = init_state.dup # this is not a deepcopy!
8
+ state = DeepDup.deep_dup(init_state)
7
9
  (0..steps - 1).each do |step|
8
10
  state, e = yield state, step, trial
9
11
  e.each_pair do |k, v|
@@ -22,7 +24,11 @@ module McForecast
22
24
 
23
25
  # Return an analysis of the events, containing:
24
26
  # { event_name:
25
- # { mean: [...], # per step
27
+ # {
28
+ # sum: { mean: ...,
29
+ # quantiles:
30
+ # { 0.025: ..., 0.975: ... }},
31
+ # mean: [...], # per step
26
32
  # quantiles:
27
33
  # { 0.025: [...],
28
34
  # 0.975: [...]
@@ -30,20 +36,39 @@ module McForecast
30
36
  def analyze(events, quantiles)
31
37
  events.transform_values do |steps| # array(steps) of arrays(trials)
32
38
  {
33
- mean: steps.map { |trials| Rational(trials.sum || 0, trials.length) },
34
- quantiles: Array.new(steps.length)
39
+ sum: sum(steps, quantiles),
40
+ # besides the total sum we may want to have a sum for multiples of our base period
41
+ # (or week/month/quarter/year but that gets a bit complicated)
42
+ mean: steps.map { |trials| (trials.sum || 0).to_f / trials.length },
43
+ quantiles: quantiles.zip(step_quantiles(quantiles, steps)).to_h
35
44
  }
45
+ end
46
+ end
47
+
48
+ def sum(steps, quantiles)
49
+ sums = steps.transpose.map(&:sum) # gives a sum of this event, per trial
50
+ {
51
+ mean: (sums.sum || 0).to_f / steps[0].length,
52
+ # sort all of the sums, and take the elements closest to the chosen quantiles, and then make a nice hash
53
+ quantiles: quantiles.zip(sums.sort.values_at(*quantile_indices(steps[0].length, quantiles))).to_h
54
+ }
55
+ end
36
56
 
37
- require 'byebug'
38
- puts(steps.map do |trials|
39
- [quantiles, trials.sort.values_at(*quantile_indices(trials.length, quantiles))]
40
- end)
57
+ def step_quantiles(quantiles, steps)
58
+ if quantiles.any?
59
+ # only need to sort if we request answers on any quantiles
60
+ steps.map do |trials|
61
+ # could avoid sorting with some creativity, but probably fine for our data lengths so far
62
+ trials.sort.values_at(*quantile_indices(trials.length, quantiles))
63
+ end.transpose # a[step][]
64
+ else
65
+ []
41
66
  end
42
67
  end
43
68
 
44
- def quantile_indices(n_trials, quantiles)
69
+ def quantile_indices(count, quantiles)
45
70
  quantiles.map do |q|
46
- (q * (n_trials - 1)).round.to_i.clamp(0, n_trials - 1)
71
+ (q * (count - 1)).round.to_i.clamp(0, count - 1)
47
72
  end
48
73
  end
49
74
  end
@@ -1,3 +1,3 @@
1
1
  module McForecast
2
- VERSION = "0.1.0".freeze
2
+ VERSION = "0.2.0".freeze
3
3
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mc_forecast
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daan van Vugt
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-12-08 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-03-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: deep_dup
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description:
14
28
  email:
15
29
  - dvanvugt@ignitioncomputing.com