statistical 0.1.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 +7 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +111 -0
- data/.travis.yml +7 -0
- data/CONTRIBUTING.md +73 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +37 -0
- data/Rakefile +43 -0
- data/bin/console +11 -0
- data/bin/distribution +53 -0
- data/bin/setup +8 -0
- data/data/template/distribution.erb +84 -0
- data/data/template/rng.erb +53 -0
- data/data/template/spec.erb +142 -0
- data/lib/core_extensions.rb +35 -0
- data/lib/statistical.rb +7 -0
- data/lib/statistical/distribution.rb +36 -0
- data/lib/statistical/distribution/bernoulli.rb +29 -0
- data/lib/statistical/distribution/exponential.rb +85 -0
- data/lib/statistical/distribution/laplace.rb +101 -0
- data/lib/statistical/distribution/two_point.rb +144 -0
- data/lib/statistical/distribution/uniform.rb +98 -0
- data/lib/statistical/distribution/uniform_discrete.rb +133 -0
- data/lib/statistical/distribution/weibull.rb +99 -0
- data/lib/statistical/helpers.rb +132 -0
- data/lib/statistical/rng.rb +37 -0
- data/lib/statistical/rng/bernoulli.rb +29 -0
- data/lib/statistical/rng/exponential.rb +56 -0
- data/lib/statistical/rng/laplace.rb +57 -0
- data/lib/statistical/rng/two_point.rb +70 -0
- data/lib/statistical/rng/uniform.rb +62 -0
- data/lib/statistical/rng/uniform_discrete.rb +78 -0
- data/lib/statistical/rng/weibull.rb +58 -0
- data/lib/statistical/version.rb +3 -0
- data/statistical.gemspec +28 -0
- metadata +165 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'statistical/exceptions'
|
2
|
+
require 'statistical/distribution/<%= distribution%>'
|
3
|
+
|
4
|
+
module Statistical
|
5
|
+
# Companion RNG class for the continuous uniform distribution. Requires a
|
6
|
+
# distrbution object of the corresponding distribution
|
7
|
+
#
|
8
|
+
# @author Vaibhav Yenamandra
|
9
|
+
#
|
10
|
+
# @attr_reader [Numeric] lower The lower bound of the uniform distribution.
|
11
|
+
# @attr_reader [Numeric] upper The upper bound of the uniform distribution.
|
12
|
+
module Rng
|
13
|
+
class <%= distribution.capitalize.camelcase %>
|
14
|
+
attr_reader :generator, # other params go here
|
15
|
+
|
16
|
+
def initialize(dobj = nil, seed = Random.new_seed)
|
17
|
+
unless dobj.nil? || dobj.is_a?(Statistical::Distribution::<%= distribution.capitalize.camelcase %>)
|
18
|
+
raise TypeError, "Expected Distribution object or nil, found #{dobj.class}"
|
19
|
+
end
|
20
|
+
dobj = Statistical::Distribution::<%= distribution.capitalize.camelcase %>.new if dobj.nil?
|
21
|
+
@generator = Rng::Uniform.new(Distribution::Uniform.new, seed)
|
22
|
+
# Map other parameters here
|
23
|
+
@sdist = dobj
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return the next random number from the sequence
|
27
|
+
#
|
28
|
+
# @return next random number in the sequence
|
29
|
+
def rand
|
30
|
+
end
|
31
|
+
|
32
|
+
# Compare against another rng to see if they are the same
|
33
|
+
#
|
34
|
+
# @return true if and only if, source distributions are the same and the
|
35
|
+
# prng has the same initial state
|
36
|
+
def eql?(other)
|
37
|
+
return false unless other.is_a?(self.class)
|
38
|
+
# Compare parameters and other stuff here
|
39
|
+
@generator == other.generator
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return the type of the source distribution
|
43
|
+
#
|
44
|
+
# @return source distribution's type
|
45
|
+
def type
|
46
|
+
@sdist.class
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :==, :eql?
|
50
|
+
private :eql?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'statistical/rng/<%= distribution%>'
|
3
|
+
require 'statistical/distribution/<%= distribution %>'
|
4
|
+
|
5
|
+
describe Statistical::Rng::<%= distribution.capitalize.camelcase %> do
|
6
|
+
it 'passes the G-test at a 95% significance level' do
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '.new' do
|
10
|
+
context 'when called with no arguments' do
|
11
|
+
fail
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when parameters are specified' do
|
15
|
+
fail
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when initialized with a seed' do
|
19
|
+
it 'should be repeatable for the same arguments' do
|
20
|
+
fail
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#rand' do
|
26
|
+
it 'returns a number between 0 and 1 by default' do
|
27
|
+
fail
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns a bounded value when bounds are specified' do
|
31
|
+
fail
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#==' do
|
36
|
+
context 'when compared against another uniform distribution' do
|
37
|
+
it 'should return true if the bounds and seed are the same' do
|
38
|
+
fail
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should return false if bounds / seed differ' do
|
42
|
+
fail
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when compared against any other distribution class' do
|
47
|
+
it 'should return false if classes are different' do
|
48
|
+
fail
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
describe Statistical::Distribution::<%= distribution.capitalize.camelcase %> do
|
56
|
+
describe '.new' do
|
57
|
+
context 'when called with no arguments' do
|
58
|
+
fail
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when upper and lower bounds are specified' do
|
62
|
+
fail
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
describe '#pdf' do
|
68
|
+
context 'when called with x < lower_bound' do
|
69
|
+
fail
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'when called with x > upper_bound' do
|
73
|
+
fail
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when called with x in [lower_bound, upper_bound]' do
|
77
|
+
fail
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#cdf' do
|
82
|
+
context 'when called with x < lower' do
|
83
|
+
fail
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'when called with x > upper' do
|
87
|
+
fail
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'when called with x in [lower, upper]' do
|
91
|
+
fail
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#quantile' do
|
96
|
+
context 'when called for x > 1' do
|
97
|
+
fail
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'when called for x < 0' do
|
101
|
+
fail
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when called for x in [0, 1]' do
|
105
|
+
fail
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#p_value' do
|
110
|
+
it 'should be the same as #quantile' do
|
111
|
+
fail
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#mean' do
|
116
|
+
it 'should return the correct mean' do
|
117
|
+
fail
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#variance' do
|
122
|
+
it 'should return the correct variance' do
|
123
|
+
fail
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe '#==' do
|
128
|
+
context 'when compared against another Uniform distribution' do
|
129
|
+
it 'returns `true` if they have the same parameters' do
|
130
|
+
fail
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'returns `false` if they have different parameters' do
|
134
|
+
fail
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'when compared against any distribution type' do
|
139
|
+
fail
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# All core class modifications go here
|
2
|
+
String.class_eval do
|
3
|
+
# Convert CamelCase to snake_case
|
4
|
+
def snakecase
|
5
|
+
gsub(/::/, '/')
|
6
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
7
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
8
|
+
.tr('-', '_')
|
9
|
+
.downcase
|
10
|
+
end
|
11
|
+
|
12
|
+
# convert snake_case to CamelCase
|
13
|
+
def camelcase
|
14
|
+
split('_').map(&:capitalize).join
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Array.class_eval do
|
19
|
+
def sum
|
20
|
+
inject(:+)
|
21
|
+
end
|
22
|
+
|
23
|
+
def mean
|
24
|
+
sum.fdiv(count)
|
25
|
+
end
|
26
|
+
|
27
|
+
def variance
|
28
|
+
map { |x| (x - mean)**2}.sum.fdiv(count)
|
29
|
+
end
|
30
|
+
|
31
|
+
def comprehend(&block)
|
32
|
+
return self if block.nil?
|
33
|
+
collect(&block).compact
|
34
|
+
end
|
35
|
+
end
|
data/lib/statistical.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'statistical/distribution/uniform'
|
2
|
+
require 'statistical/distribution/uniform_discrete'
|
3
|
+
require 'statistical/distribution/two_point'
|
4
|
+
require 'statistical/distribution/bernoulli'
|
5
|
+
require 'statistical/distribution/exponential'
|
6
|
+
require 'statistical/distribution/laplace'
|
7
|
+
require 'statistical/distribution/weibull'
|
8
|
+
|
9
|
+
module Statistical
|
10
|
+
# Factory module used to create instances of various distributions classes
|
11
|
+
# nested under itself
|
12
|
+
module Distribution
|
13
|
+
# @private
|
14
|
+
# No need to document this
|
15
|
+
# Dynamically add constants when called
|
16
|
+
def self.const_missing(cname)
|
17
|
+
const_set(cname, make_classmap) if cname == :DISTRIBUTION_TYPES
|
18
|
+
end
|
19
|
+
|
20
|
+
# Create a distribution identified by the type hash
|
21
|
+
# @raise ArgumentError if `type` was not found
|
22
|
+
def self.create(type = :uniform, *args, &block)
|
23
|
+
raise ArgumentError unless DISTRIBUTION_TYPES.include?(type)
|
24
|
+
DISTRIBUTION_TYPES[type].new(*args, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.make_classmap
|
28
|
+
dist_klasses = constants.select { |k| const_get(k).is_a?(Class)}
|
29
|
+
keylist = dist_klasses.map { |k| k.to_s.snakecase.to_sym}
|
30
|
+
klasses = dist_klasses.map { |k| const_get(k)}
|
31
|
+
return Hash[keylist.zip(klasses)].freeze
|
32
|
+
end
|
33
|
+
|
34
|
+
private_class_method :make_classmap
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'statistical/distribution/two_point'
|
2
|
+
|
3
|
+
module Statistical
|
4
|
+
module Distribution
|
5
|
+
# This is a convenience class implemented for syntactic sugar.
|
6
|
+
# `TwoPoint.new(p)` already mimics and is infact the behaviour of this class
|
7
|
+
#
|
8
|
+
# @note The states used to represent success & failure must be Numeric.
|
9
|
+
# Using it on generic state lables can cause strange outcomes!
|
10
|
+
#
|
11
|
+
# @note state_failure < state_sucesss, for the sake of sanity.
|
12
|
+
#
|
13
|
+
# @author Vaibhav Yenamandra
|
14
|
+
# @attr_reader [Float] p probability of the success state (= 1)
|
15
|
+
# @attr_reader [Float] q probability of the failure state (= 0)
|
16
|
+
class Bernoulli < TwoPoint
|
17
|
+
# This is probably the best but the least descriptive variable name
|
18
|
+
# attr_reader @p, @q
|
19
|
+
|
20
|
+
# Returns a new instance of the Bernoulli distribution
|
21
|
+
#
|
22
|
+
# @param [Float] prob_success The probability of success
|
23
|
+
def initialize(prob_success = 0.5)
|
24
|
+
super(prob_success)
|
25
|
+
self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'statistical/helpers'
|
2
|
+
|
3
|
+
module Statistical
|
4
|
+
module Distribution
|
5
|
+
# Abstraction of common statistical properties of the exponential
|
6
|
+
# distribution
|
7
|
+
#
|
8
|
+
# @author Vaibhav Yenamandra
|
9
|
+
# @attr_reader [Numeric] rate Rate parameter controlling the
|
10
|
+
# exponential distribution. Same as `λ` in the canonical version
|
11
|
+
class Exponential
|
12
|
+
attr_reader :rate, :support
|
13
|
+
|
14
|
+
# Returns a new `Statistical::Distribution::Uniform` instance
|
15
|
+
#
|
16
|
+
# @param [Numeric] rate Rate parameter of the exponential distribution.
|
17
|
+
# Same as `λ` in the canonical version
|
18
|
+
# @return `Statistical::Distribution::Exponential` instance
|
19
|
+
def initialize(rate = 1)
|
20
|
+
@rate = rate
|
21
|
+
@support = Domain[0.0, Float::INFINITY, :right_open]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns value of probability density function at a point.
|
25
|
+
#
|
26
|
+
# @param [Numeric] x A real valued point
|
27
|
+
# @return [Float] Probility density function evaluated at x
|
28
|
+
def pdf(x)
|
29
|
+
return [@rate * Math.exp(-@rate * x), 0.0, 0.0][@support <=> x]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns value of cumulative density function at a point.
|
33
|
+
#
|
34
|
+
# @param [Numeric] x A real valued point
|
35
|
+
# @return [Float] Probability mass function evaluated at x
|
36
|
+
def cdf(x)
|
37
|
+
return [1 - Math.exp(-@rate * x), 1.0, 0.0][@support <=> x]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns value of inverse CDF for a given probability
|
41
|
+
#
|
42
|
+
# @see #p_value
|
43
|
+
#
|
44
|
+
# @param [Float] p a value within [0, 1]
|
45
|
+
# @return Inverse CDF for valid p
|
46
|
+
# @raise [RangeError] if p > 1 or p < 0
|
47
|
+
def quantile(p)
|
48
|
+
raise RangeError, "`p` must be in [0, 1], found: #{p}" if p < 0 || p > 1
|
49
|
+
return Math.log(1 - p) / -@rate
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the mean value for the calling instance. Calculated mean, and
|
53
|
+
# not inferred from simulations
|
54
|
+
#
|
55
|
+
# @return Mean of the distribution
|
56
|
+
def mean
|
57
|
+
return 1.0 / @rate
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the expected value of variance for the calling instance.
|
61
|
+
#
|
62
|
+
# @return Variance of the distribution
|
63
|
+
def variance
|
64
|
+
return (1.0 / @rate)**2
|
65
|
+
end
|
66
|
+
|
67
|
+
# Compares two distribution instances and returns a boolean outcome
|
68
|
+
# Available publicly as #==
|
69
|
+
#
|
70
|
+
# @private
|
71
|
+
#
|
72
|
+
# @param other A distribution object (preferred)
|
73
|
+
# @return [Boolean] true if-and-only-if two instances are of the same
|
74
|
+
# class and have the same parameters.
|
75
|
+
def eql?(other)
|
76
|
+
return other.is_a?(self.class) && @rate == other.rate
|
77
|
+
end
|
78
|
+
|
79
|
+
alias :== :eql?
|
80
|
+
alias :p_value :quantile
|
81
|
+
|
82
|
+
private :eql?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'statistical/helpers'
|
2
|
+
|
3
|
+
module Statistical
|
4
|
+
module Distribution
|
5
|
+
# Say something useful about this class.
|
6
|
+
#
|
7
|
+
# @note Any caveats you want to talk about go here...
|
8
|
+
#
|
9
|
+
# @author Vaibhav Yenamandra
|
10
|
+
# @attr_reader [Numeric] scale Scale parameter of the Laplace distribution.
|
11
|
+
# @attr_reader [Numeric] location Location parameter to determine where the
|
12
|
+
# distribution is centered / where the mean lies at
|
13
|
+
class Laplace
|
14
|
+
attr_reader :scale, :location, :support
|
15
|
+
|
16
|
+
# Returns a new instance of the Laplace distribution; also known as
|
17
|
+
# the two-sided exponential distribution
|
18
|
+
#
|
19
|
+
# @param [Numeric] scale Scale parameter of the Laplace distribution.
|
20
|
+
# @param [Numeric] location Location parameter to determine where the
|
21
|
+
# distribution is centered / where the mean lies at
|
22
|
+
# @return `Statistical::Distribution::Laplace` instance
|
23
|
+
def initialize(location = 0, scale = 1)
|
24
|
+
@scale = scale
|
25
|
+
@location = location
|
26
|
+
@support = Domain[-Float::INFINITY, Float::INFINITY, :full_open]
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns value of probability density function at a point.
|
31
|
+
#
|
32
|
+
# @param [Numeric] x A real valued point
|
33
|
+
# @return [Float] Probility density function evaluated at x
|
34
|
+
def pdf(x)
|
35
|
+
x_ = (x - @location).abs.fdiv(@scale)
|
36
|
+
return Math.exp(- x_).fdiv(2 * @scale)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns value of cumulative density function at a point.
|
40
|
+
#
|
41
|
+
# @param [Numeric] x A real valued point
|
42
|
+
# @return [Float] Cumulative density function evaluated at x
|
43
|
+
def cdf(x)
|
44
|
+
return [0.5,
|
45
|
+
1.0 - 0.5 * Math.exp((@location - x).fdiv(@scale)),
|
46
|
+
0.5 * Math.exp((x - @location).fdiv(@scale))
|
47
|
+
][x <=> @location]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns value of inverse CDF for a given probability
|
51
|
+
#
|
52
|
+
# @see #p_value
|
53
|
+
#
|
54
|
+
# @param [Numeric] p a value within [0, 1]
|
55
|
+
# @return Inverse CDF for valid p
|
56
|
+
# @raise [RangeError] if p > 1 or p < 0
|
57
|
+
def quantile(p)
|
58
|
+
raise RangeError, "`p` must be in [0, 1], found: #{p}" if p < 0 || p > 1
|
59
|
+
|
60
|
+
return [@location,
|
61
|
+
@location - @scale * Math.log(2 * (1.0 - p)),
|
62
|
+
@scale * Math.log(2 * p) + @location
|
63
|
+
][p <=> 0.5]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the expected mean value for the calling instance.
|
67
|
+
#
|
68
|
+
# @return Mean of the distribution
|
69
|
+
def mean
|
70
|
+
return @location
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the expected value of variance for the calling instance.
|
74
|
+
#
|
75
|
+
# @return Variance of the distribution
|
76
|
+
def variance
|
77
|
+
return 2 * @scale * @scale
|
78
|
+
end
|
79
|
+
|
80
|
+
# Compares two distribution instances and returns a boolean outcome
|
81
|
+
# Available publicly as #==
|
82
|
+
#
|
83
|
+
# @private
|
84
|
+
#
|
85
|
+
# @param other A distribution object (preferred)
|
86
|
+
# @return [Boolean] true if-and-only-if two instances are of the same
|
87
|
+
# class and have the same parameters.
|
88
|
+
def eql?(other)
|
89
|
+
return false unless other.is_a?(self.class) &&
|
90
|
+
other.scale == @scale &&
|
91
|
+
other.location == @location
|
92
|
+
return true
|
93
|
+
end
|
94
|
+
|
95
|
+
alias :== :eql?
|
96
|
+
alias :p_value :quantile
|
97
|
+
|
98
|
+
private :eql?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|