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.
@@ -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
@@ -0,0 +1,7 @@
1
+ require 'statistical/version'
2
+ require 'statistical/distribution'
3
+ require 'statistical/rng'
4
+ require 'core_extensions'
5
+
6
+ module Statistical
7
+ end
@@ -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