statistical 0.1.0

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