statistical 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|