rubystats 0.2.6 → 0.4.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/.github/workflows/test.yml +21 -0
- data/History.txt +31 -0
- data/README.rdoc +17 -6
- data/examples/uniform.rb +14 -0
- data/lib/rubystats/beta_distribution.rb +4 -0
- data/lib/rubystats/binomial_distribution.rb +42 -131
- data/lib/rubystats/cauchy_distribution.rb +50 -0
- data/lib/rubystats/exponential_distribution.rb +2 -2
- data/lib/rubystats/gamma_distribution.rb +70 -0
- data/lib/rubystats/lognormal_distribution.rb +59 -0
- data/lib/rubystats/modules.rb +6 -0
- data/lib/rubystats/multivariate_normal_distribution.rb +73 -0
- data/lib/rubystats/normal_distribution.rb +2 -2
- data/lib/rubystats/poisson_distribution.rb +78 -0
- data/lib/rubystats/probability_distribution.rb +3 -3
- data/lib/rubystats/student_t_distribution.rb +62 -0
- data/lib/rubystats/uniform_distribution.rb +70 -0
- data/lib/rubystats/version.rb +1 -1
- data/lib/rubystats/weibull_distribution.rb +56 -0
- data/lib/rubystats.rb +29 -0
- data/rubystats.gemspec +6 -0
- data/test/tc_beta.rb +21 -0
- data/test/tc_binomial.rb +14 -0
- data/test/tc_cauchy.rb +39 -0
- data/test/tc_exponential.rb +10 -0
- data/test/tc_gamma.rb +39 -0
- data/test/tc_lnorm.rb +45 -0
- data/test/tc_multivariate_normal.rb +53 -0
- data/test/tc_norm.rb +11 -1
- data/test/tc_poisson.rb +35 -0
- data/test/tc_studentt.rb +43 -0
- data/test/tc_unif.rb +48 -0
- data/test/tc_weibull.rb +51 -0
- metadata +28 -3
- data/.travis.yml +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5ce2923f406326fbac5468f2d90b3dd65ac7260
|
4
|
+
data.tar.gz: 71e668ad6712572d8e33145b2f6689bab25a9d03
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f56dbac4cb7ca98f9fea5932e22611d2a3631ba23316a90a25d26a30f3bf8005ab98caf2a7eb8efb6aa1ae8c161b6800c05af33ddee8e17950c8f8dc28b5e40
|
7
|
+
data.tar.gz: 4c9e4e0c71ce1aa6e5ad903a69bfe5c7051e6c3cd1f479d5708ce8e20ce4040cf52f009ce81bb272d7805000cd3d27c40bdb67caa84453a5e2f642074c123a12
|
@@ -0,0 +1,21 @@
|
|
1
|
+
on: [push, pull_request]
|
2
|
+
|
3
|
+
jobs:
|
4
|
+
test:
|
5
|
+
runs-on: ubuntu-latest
|
6
|
+
strategy:
|
7
|
+
matrix:
|
8
|
+
ruby-version: ['2.7', '3.0', '3.1', '3.2']
|
9
|
+
gemfile:
|
10
|
+
- Gemfile
|
11
|
+
env:
|
12
|
+
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
|
13
|
+
steps:
|
14
|
+
- uses: actions/checkout@v3
|
15
|
+
- name: Set up Ruby
|
16
|
+
uses: ruby/setup-ruby@v1
|
17
|
+
with:
|
18
|
+
ruby-version: ${{ matrix.ruby-version }}
|
19
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
20
|
+
- name: Run tests
|
21
|
+
run: bundle exec rake
|
data/History.txt
CHANGED
@@ -1,3 +1,34 @@
|
|
1
|
+
=== 0.3.0 / 2023-04-03
|
2
|
+
* Add support for ruby 3.1
|
3
|
+
|
4
|
+
=== 0.3.0 / 2017-12-01
|
5
|
+
* Uniform distribution
|
6
|
+
* added gamma distribution (mean, variance, pdf, cdf, rng)
|
7
|
+
* implemented multivariate normal distribution (mean,pdf,rng)
|
8
|
+
* cleaned up the code for the binomial distribution and created a module for discrete probability distributions
|
9
|
+
* implementation of poisson distribution (mean,variance,pdf,cdf,icdf,rng)
|
10
|
+
* fixed rng in Binomial and Poisson and added tests for their rng functions
|
11
|
+
* rewrote factorial function because of error with too many stack levels
|
12
|
+
* added student t distribution (mean, variance, pdf, rng)
|
13
|
+
* added weibull implementation (mean,pdf,cdf,icdf,rng)
|
14
|
+
* fix to prevent integer calculations when distributions are initialized (#10)
|
15
|
+
* Update beta_distribution.rb
|
16
|
+
* Added rng for beta distribution
|
17
|
+
* Corrected PDF calculation in README.rdoc
|
18
|
+
|
19
|
+
=== 0.2.6 / 2017-07-23
|
20
|
+
* Preserve the old API by setting constants manually
|
21
|
+
|
22
|
+
=== 0.2.5 / 2016-07-08
|
23
|
+
* refactoring to reduce warnings
|
24
|
+
* reactivate and fix test for normal distribution
|
25
|
+
* Use attr_reader to avoid initialization warnings.
|
26
|
+
* add test for normal distributed random numbers
|
27
|
+
|
28
|
+
=== 0.2.4 / 2016-01-31
|
29
|
+
* raise error when normal initialised with bad sigma
|
30
|
+
* changes for CI tests
|
31
|
+
|
1
32
|
=== 0.2.3 / 2008-07-06
|
2
33
|
* Fixing bug #21100 - problem with Beta distribution calculations when p and q values are small.
|
3
34
|
* Minor code cleanup for readability in a few places.
|
data/README.rdoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
= Rubystats
|
2
2
|
|
3
|
-
*
|
3
|
+
* https://github.com/phillbaker/rubystats
|
4
4
|
|
5
5
|
== DESCRIPTION:
|
6
6
|
|
@@ -37,19 +37,30 @@
|
|
37
37
|
This is beta-quality software. It works well according to my tests, but the API may change and other features may be added.
|
38
38
|
|
39
39
|
== FEATURES:
|
40
|
-
Classes for distributions:
|
40
|
+
Classes for continuous distributions:
|
41
41
|
|
42
|
-
* Normal
|
43
|
-
* Binomial
|
44
42
|
* Beta
|
43
|
+
* Cauchy
|
45
44
|
* Exponential
|
45
|
+
* Gamma
|
46
|
+
* Lognormal
|
47
|
+
* Multivariate Normal
|
48
|
+
* Normal
|
49
|
+
* Student t
|
50
|
+
* Uniform
|
51
|
+
* Weibull
|
52
|
+
|
53
|
+
Classes for discrete distributions:
|
54
|
+
|
55
|
+
* Binomial
|
56
|
+
* Poisson
|
46
57
|
|
47
58
|
Also includes Fisher's Exact Test
|
48
59
|
|
49
60
|
== SYNOPSIS:
|
50
61
|
=== Example: normal distribution with mean of 10 and standard deviation of 2
|
51
62
|
|
52
|
-
norm = Rubystats::NormalDistribution.new(10, 2)
|
63
|
+
norm = Rubystats::NormalDistribution.new(10.0, 2.0)
|
53
64
|
cdf = norm.cdf(11)
|
54
65
|
pdf = norm.pdf(11)
|
55
66
|
puts "CDF(11): #{cdf}"
|
@@ -57,7 +68,7 @@ Also includes Fisher's Exact Test
|
|
57
68
|
|
58
69
|
Output:
|
59
70
|
CDF(11): 0.691462461274013
|
60
|
-
PDF(11): 0.
|
71
|
+
PDF(11): 0.17603266338214973
|
61
72
|
|
62
73
|
=== Example: get some random numbers from a normal distribution
|
63
74
|
|
data/examples/uniform.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
2
|
+
require 'rubystats/uniform_distribution'
|
3
|
+
|
4
|
+
#uniform distribution with lower and upper bound of 0.0 and 1.0
|
5
|
+
unif = Rubystats::UniformDistribution.new(1.0, 6.0)
|
6
|
+
cdf = unif.cdf(2.5)
|
7
|
+
pdf = unif.pdf(2.5)
|
8
|
+
puts "CDF(2.5): #{cdf}"
|
9
|
+
puts "PDF(2.5): #{pdf}"
|
10
|
+
|
11
|
+
puts "Random numbers from the uniform distribution:"
|
12
|
+
10.times do
|
13
|
+
puts unif.rng
|
14
|
+
end
|
@@ -9,6 +9,7 @@ module Rubystats
|
|
9
9
|
include Rubystats::NumericalConstants
|
10
10
|
include Rubystats::SpecialMath
|
11
11
|
include Rubystats::ExtraMath
|
12
|
+
include Rubystats::MakeDiscrete
|
12
13
|
|
13
14
|
attr_reader :p, :n
|
14
15
|
attr_writer :p, :n
|
@@ -18,11 +19,11 @@ module Rubystats
|
|
18
19
|
if trials <= 0
|
19
20
|
raise ArgumentError.new("Error: trials must be greater than 0")
|
20
21
|
end
|
21
|
-
@n = trials
|
22
|
+
@n = trials.to_i
|
22
23
|
if prob < 0.0 || prob > 1.0
|
23
24
|
raise ArgumentError.new("prob must be between 0 and 1")
|
24
25
|
end
|
25
|
-
@p = prob
|
26
|
+
@p = prob.to_f
|
26
27
|
end
|
27
28
|
|
28
29
|
#returns the number of trials
|
@@ -45,79 +46,20 @@ module Rubystats
|
|
45
46
|
@n * @p * (1.0 - @p)
|
46
47
|
end
|
47
48
|
|
49
|
+
# Private methods below
|
50
|
+
|
51
|
+
private
|
52
|
+
|
48
53
|
# Probability density function of a binomial distribution (equivalent
|
49
54
|
# to R dbinom function).
|
50
55
|
# _x should be an integer
|
51
56
|
# returns the probability that a stochastic variable x has the value _x,
|
52
57
|
# i.e. P(x = _x)
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
for i in (0 ... _x.length)
|
57
|
-
check_range(_x[i], 0.0, @n)
|
58
|
-
pdf_vals[i] = binomial(@n, _x[i]) * (1-@p)**(@n-_x[i])
|
59
|
-
end
|
60
|
-
return pdf_vals
|
61
|
-
else
|
62
|
-
check_range(_x, 0.0, @n)
|
63
|
-
return binomial(@n, _x) * @p**_x * (1-@p)**(@n-_x)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# Cumulative binomial distribution function (equivalent to R pbinom function).
|
68
|
-
# _x should be integer-valued and can be single integer or array of integers
|
69
|
-
# returns single value or array containing probability that a stochastic
|
70
|
-
# variable x is less then X, i.e. P(x < _x).
|
71
|
-
def cdf(_x)
|
72
|
-
if _x.class == Array
|
73
|
-
pdf_vals = []
|
74
|
-
for i in (0 ..._x.length)
|
75
|
-
pdf_vals[i] = get_cdf(_x[i])
|
76
|
-
end
|
77
|
-
return pdf_vals
|
78
|
-
else
|
79
|
-
return get_cdf(_x)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# Inverse of the cumulative binomial distribution function
|
84
|
-
# (equivalent to R qbinom function).
|
85
|
-
# returns the value X for which P(x < _x).
|
86
|
-
def get_icdf(prob)
|
87
|
-
if prob.class == Array
|
88
|
-
inv_vals = []
|
89
|
-
for i in (0 ...prob.length)
|
90
|
-
check_range(prob[i])
|
91
|
-
inv_vals[i] = (find_root(prob[i], @n/2, 0.0, @n)).floor
|
92
|
-
end
|
93
|
-
return inv_vals
|
94
|
-
else
|
95
|
-
check_range(prob)
|
96
|
-
return (find_root(prob, @n/2, 0.0, @n)).floor
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Wrapper for binomial RNG function (equivalent to R rbinom function).
|
101
|
-
# returns random deviate given trials and p
|
102
|
-
def rng(num_vals = 1)
|
103
|
-
if num_vals < 1
|
104
|
-
raise "Error num_vals must be greater than or equal to 1"
|
105
|
-
end
|
106
|
-
if num_vals == 1
|
107
|
-
return get_rng
|
108
|
-
else
|
109
|
-
rand_vals = []
|
110
|
-
for i in (0 ...num_vals)
|
111
|
-
rand_vals[i] = get_rng
|
112
|
-
end
|
113
|
-
return rand_vals
|
114
|
-
end
|
58
|
+
def get_pdf(x)
|
59
|
+
check_range(x, 0, @n)
|
60
|
+
binomial(@n, x) * @p**x * (1-@p)**(@n-x)
|
115
61
|
end
|
116
62
|
|
117
|
-
# Private methods below
|
118
|
-
|
119
|
-
private
|
120
|
-
|
121
63
|
# Private shared function for getting cumulant for particular x
|
122
64
|
# param _x should be integer-valued
|
123
65
|
# returns the probability that a stochastic variable x is less than _x
|
@@ -128,71 +70,40 @@ module Rubystats
|
|
128
70
|
for i in (0 .. _x)
|
129
71
|
sum = sum + pdf(i)
|
130
72
|
end
|
131
|
-
|
73
|
+
sum
|
132
74
|
end
|
133
75
|
|
134
|
-
#
|
135
|
-
#
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
# port conforms to the Press et al. copyright.
|
146
|
-
def get_rng
|
147
|
-
nold = -1
|
148
|
-
pold = -1
|
149
|
-
p = (if @p <= 0.5 then @p else 1.0 - @p end)
|
150
|
-
am = @n * p
|
151
|
-
if @n < 25
|
152
|
-
bnl = 0.0
|
153
|
-
(1...@n).each do
|
154
|
-
if Kernel.rand < p
|
155
|
-
bnl = bnl.next
|
156
|
-
end
|
157
|
-
end
|
158
|
-
elsif am < 1.0
|
159
|
-
g = Math.exp(-am)
|
160
|
-
t = 1.0
|
161
|
-
for j in (0 ... @n)
|
162
|
-
t = t * Kernel.rand
|
163
|
-
break if t < g
|
164
|
-
end
|
165
|
-
bnl = (if j <= @n then j else @n end)
|
166
|
-
else
|
167
|
-
if n != nold
|
168
|
-
en = @n
|
169
|
-
oldg = log_gamma(en + 1.0)
|
170
|
-
nold = n
|
171
|
-
end
|
172
|
-
if p != pold
|
173
|
-
pc = 1.0 - p
|
174
|
-
plog = Math.log(p)
|
175
|
-
pclog = Math.log(pc)
|
176
|
-
pold = p
|
177
|
-
end
|
178
|
-
sq = Math.sqrt(2.0 * am * pc)
|
179
|
-
until Kernel.rand <= t do
|
180
|
-
until (em >= 0.0 || em < (en + 1.0)) do
|
181
|
-
angle = Pi * Kernel.rand
|
182
|
-
y = Math.tan(angle)
|
183
|
-
em = sq * y + am
|
184
|
-
end
|
185
|
-
em = em.floor
|
186
|
-
t = 1.2 * sq * (1.0 + y * y) *
|
187
|
-
Math.exp(oldg - log_gamma(em + 1.0) -
|
188
|
-
log_gamma(en - em + 1.0) + em * plog + (en - em) * pclog)
|
189
|
-
end
|
190
|
-
bnl = em
|
191
|
-
end
|
192
|
-
if p != @p
|
193
|
-
bnl = @n - bnl
|
194
|
-
end
|
195
|
-
return bnl
|
76
|
+
# Inverse of the cumulative binomial distribution function
|
77
|
+
# returns the value X for which P(x < _x).
|
78
|
+
def get_icdf(prob)
|
79
|
+
check_range(prob)
|
80
|
+
sum = 0.0
|
81
|
+
k = 0
|
82
|
+
until prob <= sum
|
83
|
+
sum += get_pdf(k)
|
84
|
+
k += 1
|
85
|
+
end
|
86
|
+
k - 1
|
196
87
|
end
|
88
|
+
|
89
|
+
# Private binomial RNG function
|
90
|
+
# Variation of Luc Devroye's "Second Waiting Time Method"
|
91
|
+
# on page 522 of his text "Non-Uniform Random Variate Generation."
|
92
|
+
# There are faster methods based on acceptance/rejection techniques,
|
93
|
+
# but they are substantially more complex to implement.
|
94
|
+
def get_rng
|
95
|
+
p = (@p <= 0.5) ? @p : (1.0 - @p)
|
96
|
+
log_q = Math.log(1.0 - p)
|
97
|
+
sum = 0.0
|
98
|
+
k = 0
|
99
|
+
loop do
|
100
|
+
sum += Math.log(Kernel.rand) / (@n - k)
|
101
|
+
if (sum < log_q)
|
102
|
+
return (p != @p) ? (@n - k) : k
|
103
|
+
end
|
104
|
+
k += 1
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
197
108
|
end
|
198
109
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubystats/probability_distribution'
|
2
|
+
module Rubystats
|
3
|
+
class CauchyDistribution < Rubystats::ProbabilityDistribution
|
4
|
+
|
5
|
+
def initialize(location=1.0,scale=1.0)
|
6
|
+
if scale <= 0.0
|
7
|
+
raise ArgumentError.new("Scale parameter in Cauchy distribution should be greater than zero.")
|
8
|
+
end
|
9
|
+
@location = location.to_f
|
10
|
+
@scale = scale.to_f
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def get_mean
|
16
|
+
Float::NAN
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_variance
|
20
|
+
Float::NAN
|
21
|
+
end
|
22
|
+
|
23
|
+
# Private method to obtain single PDF value.
|
24
|
+
# x should be greater than 0
|
25
|
+
# returns the probability that a stochastic variable x has the value X, i.e. P(x=X).
|
26
|
+
def get_pdf(x)
|
27
|
+
1.0 / (Math::PI * @scale * (1.0 + ((x - @location) / @scale)**2))
|
28
|
+
end
|
29
|
+
|
30
|
+
# Private method to obtain single CDF value.
|
31
|
+
# param x should be greater than 0
|
32
|
+
# return the probability that a stochastic variable x is less then X, i.e. P(x<X).
|
33
|
+
def get_cdf(x)
|
34
|
+
(1.0 / Math::PI) * Math.atan((x - @location) / @scale) + 0.5
|
35
|
+
end
|
36
|
+
|
37
|
+
# Private method to obtain single inverse CDF value.
|
38
|
+
# return the value X for which P(x<X).
|
39
|
+
def get_icdf(p)
|
40
|
+
check_range(p)
|
41
|
+
@location + @scale * Math.tan(Math::PI * (p - 0.5))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Private method to obtain single RNG value.
|
45
|
+
def get_rng
|
46
|
+
self.icdf(Kernel.rand)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -10,11 +10,11 @@ module Rubystats
|
|
10
10
|
include Rubystats::SpecialMath
|
11
11
|
include Rubystats::ExtraMath
|
12
12
|
|
13
|
-
def initialize(decay=1)
|
13
|
+
def initialize(decay=1.0)
|
14
14
|
if decay < 0.0
|
15
15
|
raise ArgumentError.new("Decay parameter should be positive.")
|
16
16
|
end
|
17
|
-
@rate = decay
|
17
|
+
@rate = decay.to_f
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'rubystats/normal_distribution'
|
2
|
+
require 'rubystats/probability_distribution'
|
3
|
+
module Rubystats
|
4
|
+
class GammaDistribution < Rubystats::ProbabilityDistribution
|
5
|
+
include Rubystats::NumericalConstants
|
6
|
+
include Rubystats::SpecialMath
|
7
|
+
|
8
|
+
def initialize(shape=1.0, scale=1.0)
|
9
|
+
if shape <= 0.0 || scale <= 0.0
|
10
|
+
raise ArgumentError.new("Input parameter should be greater than zero.")
|
11
|
+
end
|
12
|
+
@shape = shape.to_f
|
13
|
+
@scale = scale.to_f
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def get_mean
|
19
|
+
@scale * @shape
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_variance
|
23
|
+
@shape * (@scale)**2
|
24
|
+
end
|
25
|
+
|
26
|
+
# Private method to obtain single PDF value.
|
27
|
+
# x should be greater than or equal to 0.0
|
28
|
+
# returns the probability that a stochastic variable x has the value X, i.e. P(x=X).
|
29
|
+
def get_pdf(x)
|
30
|
+
check_range(x, 0.0, MAX_VALUE)
|
31
|
+
1.0 / (Math.gamma(@shape) * (@scale**@shape)) * (x**(@shape-1.0)) * Math.exp(-1.0 * x / @scale)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Private method to obtain single CDF value.
|
35
|
+
# param x should be greater than 0
|
36
|
+
# return the probability that a stochastic variable x is less then X, i.e. P(x<X).
|
37
|
+
def get_cdf(x)
|
38
|
+
check_range(x,0.0,MAX_VALUE)
|
39
|
+
@scale * incomplete_gamma(@shape, x/@scale) / Math.gamma(@shape)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Private method to obtain single inverse CDF value.
|
43
|
+
# return the value X for which P(x<X).
|
44
|
+
def get_icdf(p)
|
45
|
+
check_range(p)
|
46
|
+
raise "Inverse CDF for gamma not implemented yet."
|
47
|
+
end
|
48
|
+
|
49
|
+
# Private method to obtain single RNG value.
|
50
|
+
# Generate gamma random variate with
|
51
|
+
# Marsaglia's squeeze method.
|
52
|
+
def get_rng
|
53
|
+
raise "Gamma RNG not working for shape < 1" if @shape < 1.0
|
54
|
+
norm = Rubystats::NormalDistribution.new(0,1)
|
55
|
+
d = @shape - 1.0 / 3.0
|
56
|
+
c = 1.0 / Math.sqrt(9.0 * d)
|
57
|
+
MAX_ITERATIONS.times do
|
58
|
+
x = norm.rng
|
59
|
+
v = (1.0 + c * x)**(3.0)
|
60
|
+
next if v <= 0.0
|
61
|
+
u = Kernel.rand
|
62
|
+
if (u < 1.0 - 0.03331 * (x**4)) || (Math.log(u) < 0.5 * x**2 + d * (1.0 - v + Math.log(v)))
|
63
|
+
return (d * v) * @scale
|
64
|
+
end
|
65
|
+
end
|
66
|
+
raise "Gamma RNG not converged after max_iterations = #{MAX_ITERATIONS}"
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rubystats/probability_distribution'
|
2
|
+
require 'rubystats/normal_distribution'
|
3
|
+
# This class provides an object for encapsulating lognormal distributions
|
4
|
+
module Rubystats
|
5
|
+
class LognormalDistribution < Rubystats::ProbabilityDistribution
|
6
|
+
include Rubystats::SpecialMath
|
7
|
+
|
8
|
+
# Constructs a lognormal distribution.
|
9
|
+
def initialize(meanlog=0.0, sdlog=1.0)
|
10
|
+
raise "Argument Error: standard deviation for log-normal distribution must be positive." if sdlog < 0.0
|
11
|
+
@meanlog = meanlog.to_f
|
12
|
+
@sdlog = sdlog.to_f
|
13
|
+
@norm = Rubystats::NormalDistribution.new(@meanlog, @sdlog)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the mean of the distribution
|
17
|
+
def get_mean
|
18
|
+
return Math.exp(@meanlog + @sdlog**2 / 2.0)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the standard deviation of the distribution
|
22
|
+
def get_standard_deviation
|
23
|
+
return Math.sqrt(get_variance)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the variance of the distribution
|
27
|
+
def get_variance
|
28
|
+
return (Math.exp(@sdlog**2) - 1) * Math.exp(2.0 * @meanlog + @sdlog**2)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Obtain single PDF value
|
34
|
+
# Returns the probability that a stochastic variable x has the value X,
|
35
|
+
# i.e. P(x=X)
|
36
|
+
def get_pdf(x)
|
37
|
+
raise "Argument Error: x must be greater than zero" if x <= 0.0
|
38
|
+
return 1.0/x.to_f * @norm.pdf(Math.log(x.to_f))
|
39
|
+
end
|
40
|
+
|
41
|
+
# Obtain single CDF value
|
42
|
+
# Returns the probability that a stochastic variable x is less than X,
|
43
|
+
# i.e. P(x<X)
|
44
|
+
def get_cdf(x)
|
45
|
+
return 0.5 + 0.5 * Math.erf((Math.log(x.to_f) - @meanlog) / (NumericalConstants::SQRT2 * @sdlog))
|
46
|
+
end
|
47
|
+
|
48
|
+
# Obtain single inverse CDF value.
|
49
|
+
# returns the value X for which P(x<X).
|
50
|
+
def get_icdf(p)
|
51
|
+
raise "method 'get_icdf' not implemented for log-normal"
|
52
|
+
end
|
53
|
+
|
54
|
+
# returns single random number from log normal
|
55
|
+
def get_rng
|
56
|
+
return Math.exp(@norm.rng)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/rubystats/modules.rb
CHANGED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'rubystats/probability_distribution'
|
2
|
+
require 'rubystats/normal_distribution'
|
3
|
+
require 'matrix'
|
4
|
+
|
5
|
+
module Rubystats
|
6
|
+
module MultivariateDistribution
|
7
|
+
#override probability_distribution pdf function to work with multivariate input variables
|
8
|
+
def pdf(x)
|
9
|
+
get_pdf(x)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
class MultivariateNormalDistribution < Rubystats::ProbabilityDistribution
|
13
|
+
include Rubystats::NumericalConstants
|
14
|
+
include Rubystats::MultivariateDistribution
|
15
|
+
|
16
|
+
def initialize(mu=[0.0,0.0],sigma=[[1.0,0.0],[0.0,1.0]])
|
17
|
+
raise "dimensions of mu vector and sigma matrix doesn't match" if mu.size != sigma.size
|
18
|
+
sigma.each{|row| raise "row dim of sigma does not match mu vector" if row.size != mu.size }
|
19
|
+
|
20
|
+
mu_f = mu.collect{|x| x.to_f }
|
21
|
+
sigma_f = sigma.collect{|row| row.collect{|x| x.to_f}}
|
22
|
+
|
23
|
+
@mu = Vector.elements(mu_f)
|
24
|
+
@sigma = Matrix.rows(sigma_f)
|
25
|
+
u, d, u_inv = @sigma.eigensystem
|
26
|
+
@sigma_inv = u * (1/d) * u_inv
|
27
|
+
@a = u * (d)**(0.5)
|
28
|
+
|
29
|
+
@pdf_factor = 1.0 / Math.sqrt((TWO_PI * @sigma).determinant.to_f)
|
30
|
+
@stdnorm = Rubystats::NormalDistribution.new(0.0,1.0)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def get_mean
|
36
|
+
@mu.to_a
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_variance
|
40
|
+
raise "variance for multivariate normal distribution not implemented"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Private method to obtain single PDF value.
|
44
|
+
# x should be greater than 0
|
45
|
+
# returns the probability that a stochastic variable x has the value X, i.e. P(x=X).
|
46
|
+
def get_pdf(x)
|
47
|
+
d = Vector.elements(x) - @mu
|
48
|
+
@pdf_factor * Math.exp(-0.5 * d.inner_product(@sigma_inv*d).to_f)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Private method to obtain single CDF value.
|
52
|
+
# param x should be greater than 0
|
53
|
+
# return the probability that a stochastic variable x is less then X, i.e. P(x<X).
|
54
|
+
def get_cdf(x)
|
55
|
+
raise "cdf for multivariate normal distribution not implemented"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Private method to obtain single inverse CDF value.
|
59
|
+
# return the value X for which P(x<X).
|
60
|
+
def get_icdf(p)
|
61
|
+
check_range(p)
|
62
|
+
raise "inverse cdf for multivariate normal distribution not implemented"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Private method to obtain single RNG value.
|
66
|
+
def get_rng
|
67
|
+
z = Vector.elements(@mu.collect{ @stdnorm.rng })
|
68
|
+
(@mu + @a * z).to_a
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
@@ -11,11 +11,11 @@ module Rubystats
|
|
11
11
|
# Constructs a normal distribution (defaults to zero mean and
|
12
12
|
# unity variance).
|
13
13
|
def initialize(mu=0.0, sigma=1.0)
|
14
|
-
@mean = mu
|
14
|
+
@mean = mu.to_f
|
15
15
|
if sigma <= 0.0
|
16
16
|
raise "error, invalid sigma #{sigma}, should be > 0"
|
17
17
|
end
|
18
|
-
@stdev = sigma
|
18
|
+
@stdev = sigma.to_f
|
19
19
|
@variance = sigma**2
|
20
20
|
@pdf_denominator = SQRT2PI * Math.sqrt(@variance)
|
21
21
|
@cdf_denominator = SQRT2 * Math.sqrt(@variance)
|