mc_forecast 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +12 -1
- data/lib/mc_forecast/simulation.rb +36 -11
- data/lib/mc_forecast/version.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82e81b59b88ffa6eadba70aef437ecec5c040b890b19d7f64f8237eb59fc7c56
|
4
|
+
data.tar.gz: 729b9f9d931b338f5860427d1aaba73aea35c847eedb018d72c39a7486c7f551
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ce13d962a1c9e82f52e101387f6c8627fdac52187000e5ceb5a25bc7513bc9929cd94d6fc6faaf5196f7bcc90336bb69be642c774abe93a205b43464bbc20f1
|
7
|
+
data.tar.gz: ea2d982df7f53a36558969c1d8361ea477d817895f92a4118cb23472eaa3066b85ce9efffcc12f804894facf1303f8dffe68c2b2bb9fd8865125d0ba516a41fd
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
[](https://github.com/DaanVanVugt/ruby-mc/actions/workflows/ci.yml)
|
6
6
|
[](https://codeclimate.com/github/DaanVanVugt/ruby-mc)
|
7
7
|
|
8
|
-
|
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
|
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
|
-
# {
|
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
|
-
|
34
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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(
|
69
|
+
def quantile_indices(count, quantiles)
|
45
70
|
quantiles.map do |q|
|
46
|
-
(q * (
|
71
|
+
(q * (count - 1)).round.to_i.clamp(0, count - 1)
|
47
72
|
end
|
48
73
|
end
|
49
74
|
end
|
data/lib/mc_forecast/version.rb
CHANGED
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.
|
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:
|
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
|