rcas 0.2.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/CITATION.cff +17 -0
- data/DESIGN.md +783 -0
- data/LICENSE +21 -0
- data/MANUAL.md +6265 -0
- data/README.md +267 -0
- data/bin/rcas +9 -0
- data/bin/rcas-app +9 -0
- data/bin/rcas-chat +9 -0
- data/lib/rcas/algebraic.rb +481 -0
- data/lib/rcas/analysis.rb +966 -0
- data/lib/rcas/app/launcher.rb +203 -0
- data/lib/rcas/app/public/app.css +402 -0
- data/lib/rcas/app/public/app.js +449 -0
- data/lib/rcas/app/public/index.html +46 -0
- data/lib/rcas/app/server.rb +220 -0
- data/lib/rcas/app/window.rb +94 -0
- data/lib/rcas/app/worksheet.rb +290 -0
- data/lib/rcas/app.rb +168 -0
- data/lib/rcas/background.rb +758 -0
- data/lib/rcas/chat/assistant.rb +199 -0
- data/lib/rcas/chat/picker.rb +164 -0
- data/lib/rcas/chat/repl.rb +583 -0
- data/lib/rcas/chat/session.rb +137 -0
- data/lib/rcas/chat/settings.rb +71 -0
- data/lib/rcas/chat/style.rb +30 -0
- data/lib/rcas/chat/tool.rb +53 -0
- data/lib/rcas/chat/ui.rb +316 -0
- data/lib/rcas/chat/usage.rb +62 -0
- data/lib/rcas/chat/workspace.rb +132 -0
- data/lib/rcas/chat.rb +54 -0
- data/lib/rcas/coefficients.rb +170 -0
- data/lib/rcas/combinatorics.rb +274 -0
- data/lib/rcas/complex_parts.rb +160 -0
- data/lib/rcas/constants.rb +129 -0
- data/lib/rcas/core_ext.rb +35 -0
- data/lib/rcas/decide.rb +501 -0
- data/lib/rcas/decompositions.rb +241 -0
- data/lib/rcas/differentiate.rb +144 -0
- data/lib/rcas/discussion.rb +558 -0
- data/lib/rcas/distributions.rb +980 -0
- data/lib/rcas/dixon.rb +95 -0
- data/lib/rcas/docs.rb +321 -0
- data/lib/rcas/domains.rb +728 -0
- data/lib/rcas/expand.rb +174 -0
- data/lib/rcas/expression.rb +613 -0
- data/lib/rcas/factor.rb +605 -0
- data/lib/rcas/finite_field.rb +577 -0
- data/lib/rcas/fourier.rb +118 -0
- data/lib/rcas/fps.rb +678 -0
- data/lib/rcas/fraction.rb +126 -0
- data/lib/rcas/functions.rb +1136 -0
- data/lib/rcas/gcd.rb +112 -0
- data/lib/rcas/geometry.rb +266 -0
- data/lib/rcas/groebner.rb +162 -0
- data/lib/rcas/hold.rb +277 -0
- data/lib/rcas/hypothesis.rb +364 -0
- data/lib/rcas/inequalities.rb +689 -0
- data/lib/rcas/integral_functions.rb +260 -0
- data/lib/rcas/integrate.rb +1589 -0
- data/lib/rcas/integrate_substitutions.rb +434 -0
- data/lib/rcas/interpolate.rb +40 -0
- data/lib/rcas/irb.rb +146 -0
- data/lib/rcas/laplace.rb +159 -0
- data/lib/rcas/latex.rb +556 -0
- data/lib/rcas/lattice.rb +172 -0
- data/lib/rcas/linear_algebra.rb +117 -0
- data/lib/rcas/linear_program.rb +416 -0
- data/lib/rcas/lint.rb +79 -0
- data/lib/rcas/matrix.rb +531 -0
- data/lib/rcas/matrix_multiply.rb +202 -0
- data/lib/rcas/multimodular.rb +286 -0
- data/lib/rcas/named_polynomials.rb +274 -0
- data/lib/rcas/number_theory.rb +443 -0
- data/lib/rcas/numerics.rb +825 -0
- data/lib/rcas/ode.rb +488 -0
- data/lib/rcas/openmath/objects.rb +364 -0
- data/lib/rcas/openmath/phrasebook.rb +551 -0
- data/lib/rcas/openmath/popcorn.rb +518 -0
- data/lib/rcas/openmath/xml.rb +309 -0
- data/lib/rcas/openmath.rb +49 -0
- data/lib/rcas/petkovsek.rb +165 -0
- data/lib/rcas/piecewise.rb +488 -0
- data/lib/rcas/plot.rb +763 -0
- data/lib/rcas/plot3d.rb +419 -0
- data/lib/rcas/poly_matrix.rb +318 -0
- data/lib/rcas/poly_recurrence.rb +117 -0
- data/lib/rcas/polynomial.rb +466 -0
- data/lib/rcas/precision.rb +925 -0
- data/lib/rcas/printer.rb +150 -0
- data/lib/rcas/product.rb +155 -0
- data/lib/rcas/q_difference.rb +296 -0
- data/lib/rcas/q_functions.rb +158 -0
- data/lib/rcas/q_summation.rb +308 -0
- data/lib/rcas/q_zeilberger.rb +199 -0
- data/lib/rcas/random.rb +506 -0
- data/lib/rcas/rational_function.rb +186 -0
- data/lib/rcas/recurrence.rb +323 -0
- data/lib/rcas/render.rb +431 -0
- data/lib/rcas/results.rb +192 -0
- data/lib/rcas/scalar.rb +219 -0
- data/lib/rcas/series.rb +726 -0
- data/lib/rcas/simplify.rb +649 -0
- data/lib/rcas/solve.rb +2002 -0
- data/lib/rcas/special.rb +163 -0
- data/lib/rcas/statistics.rb +175 -0
- data/lib/rcas/steps.rb +835 -0
- data/lib/rcas/summation.rb +532 -0
- data/lib/rcas/trig.rb +264 -0
- data/lib/rcas/van_hoeij.rb +241 -0
- data/lib/rcas/vector.rb +175 -0
- data/lib/rcas/vector_calculus.rb +411 -0
- data/lib/rcas/version.rb +5 -0
- data/lib/rcas/zeilberger.rb +358 -0
- data/lib/rcas.rb +91 -0
- data/package.json +8 -0
- metadata +206 -0
data/lib/rcas/special.rb
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCAS
|
|
4
|
+
# The incomplete gamma and beta functions, numerically. They are what the
|
|
5
|
+
# chi-square, Student t and F distributions need for their CDFs; everything
|
|
6
|
+
# here returns Floats and is labelled as numeric where it surfaces.
|
|
7
|
+
#
|
|
8
|
+
# Special.gamma_p(2.0, 1.5) # regularized lower incomplete gamma P(a, x)
|
|
9
|
+
# Special.beta_i(0.3, 2.0, 5) # regularized incomplete beta I_x(a, b)
|
|
10
|
+
#
|
|
11
|
+
# P(a, x) by the series for x < a + 1 and by the continued fraction for
|
|
12
|
+
# Q(a, x) beyond; I_x(a, b) by the continued fraction with the symmetry
|
|
13
|
+
# I_x(a, b) = 1 - I_(1-x)(b, a) picking the fast side. Both are evaluated
|
|
14
|
+
# with the modified Lentz algorithm.
|
|
15
|
+
#
|
|
16
|
+
# Sources (keys: MANUAL.md, Sources): [AS64, §6.5, §26.5]; [PTVF07, §6.2,
|
|
17
|
+
# §6.4]; Lentz's method [Len76].
|
|
18
|
+
module Special
|
|
19
|
+
EPSILON = 1e-16
|
|
20
|
+
TINY = 1e-300
|
|
21
|
+
MAX_ITERATIONS = 300
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
def float(x, name)
|
|
26
|
+
v = x.is_a?(Numeric) ? x : Expression.lift(x).evalf
|
|
27
|
+
raise ArgumentError, "#{name}: a real number is needed, got #{x}" unless v.is_a?(Numeric) && !v.is_a?(Complex)
|
|
28
|
+
v.to_f
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def log_gamma(x) = Math.lgamma(x).first
|
|
32
|
+
|
|
33
|
+
# Regularized lower incomplete gamma P(a, x) = gamma(a, x) / Gamma(a).
|
|
34
|
+
def gamma_p(a, x)
|
|
35
|
+
a = float(a, "gamma_p")
|
|
36
|
+
x = float(x, "gamma_p")
|
|
37
|
+
raise ArgumentError, "gamma_p: a must be positive" unless a.positive?
|
|
38
|
+
return 0.0 if x <= 0
|
|
39
|
+
x < a + 1.0 ? gamma_series(a, x) : 1.0 - gamma_cf(a, x)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Regularized upper incomplete gamma Q(a, x) = 1 - P(a, x).
|
|
43
|
+
def gamma_q(a, x)
|
|
44
|
+
a = float(a, "gamma_q")
|
|
45
|
+
x = float(x, "gamma_q")
|
|
46
|
+
return 1.0 if x <= 0
|
|
47
|
+
x < a + 1.0 ? 1.0 - gamma_series(a, x) : gamma_cf(a, x)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The series needs about sqrt(a) terms near x = a, and stopping at a
|
|
51
|
+
# fixed 300 without a word gave ChiSquare(10**6 + 1).cdf(10**6 + 1) as
|
|
52
|
+
# 0.16 instead of 0.5 (third review, P-5): the budget grows with a, and
|
|
53
|
+
# running out of it is a refusal.
|
|
54
|
+
def budget(a) = MAX_ITERATIONS + (40 * Math.sqrt(a.abs)).ceil
|
|
55
|
+
|
|
56
|
+
def gamma_series(a, x)
|
|
57
|
+
ap = a
|
|
58
|
+
term = 1.0 / a
|
|
59
|
+
sum = term
|
|
60
|
+
converged = false
|
|
61
|
+
budget(a).times do
|
|
62
|
+
ap += 1
|
|
63
|
+
term *= x / ap
|
|
64
|
+
sum += term
|
|
65
|
+
if term.abs < sum.abs * EPSILON
|
|
66
|
+
converged = true
|
|
67
|
+
break
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
raise ArgumentError, "gamma_p: the series did not converge for a = #{a}, x = #{x}" unless converged
|
|
71
|
+
sum * Math.exp(log_prefactor(a, x))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# log(x**a * exp(-x) / Gamma(a)). For a large a the three terms are each
|
|
75
|
+
# of size a*log(a) and their sum is of size log(a): computed directly it
|
|
76
|
+
# loses nine digits at a = 10**6. Stirling's series for log Gamma(a)
|
|
77
|
+
# [AS64, 6.1.41] lets the large parts cancel on paper instead:
|
|
78
|
+
# a*(log1p(t) - t) - log(2*pi*a)/2 + ... with t = (x - a)/a.
|
|
79
|
+
def log_prefactor(a, x)
|
|
80
|
+
return -x + a * Math.log(x) - log_gamma(a) if a < 100
|
|
81
|
+
t = (x - a) / a
|
|
82
|
+
core = if t.abs < 0.1
|
|
83
|
+
# log1p(t) - t by its series, without the cancellation
|
|
84
|
+
(2..60).reduce(0.0) { |acc, k| acc + ((k.even? ? -1 : 1) * t**k / k) }
|
|
85
|
+
else
|
|
86
|
+
Math.log(1 + t) - t
|
|
87
|
+
end
|
|
88
|
+
stirling = 1.0 / (12 * a) - 1.0 / (360 * a**3) + 1.0 / (1260 * a**5)
|
|
89
|
+
a * core + 0.5 * Math.log(a) - 0.5 * Math.log(2 * Math::PI) - stirling
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Q(a, x) as a continued fraction (Lentz), for x >= a + 1.
|
|
93
|
+
def gamma_cf(a, x)
|
|
94
|
+
b = x + 1.0 - a
|
|
95
|
+
c = 1.0 / TINY
|
|
96
|
+
d = 1.0 / b
|
|
97
|
+
h = d
|
|
98
|
+
converged = false
|
|
99
|
+
(1..budget(a)).each do |i|
|
|
100
|
+
an = -i * (i - a)
|
|
101
|
+
b += 2.0
|
|
102
|
+
d = an * d + b
|
|
103
|
+
d = TINY if d.abs < TINY
|
|
104
|
+
c = b + an / c
|
|
105
|
+
c = TINY if c.abs < TINY
|
|
106
|
+
d = 1.0 / d
|
|
107
|
+
delta = d * c
|
|
108
|
+
h *= delta
|
|
109
|
+
if (delta - 1.0).abs < EPSILON
|
|
110
|
+
converged = true
|
|
111
|
+
break
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
raise ArgumentError, "gamma_q: the continued fraction did not converge for a = #{a}, x = #{x}" unless converged
|
|
115
|
+
Math.exp(log_prefactor(a, x)) * h
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Regularized incomplete beta I_x(a, b).
|
|
119
|
+
def beta_i(x, a, b)
|
|
120
|
+
x = float(x, "beta_i")
|
|
121
|
+
a = float(a, "beta_i")
|
|
122
|
+
b = float(b, "beta_i")
|
|
123
|
+
raise ArgumentError, "beta_i: a and b must be positive" unless a.positive? && b.positive?
|
|
124
|
+
return 0.0 if x <= 0
|
|
125
|
+
return 1.0 if x >= 1
|
|
126
|
+
front = Math.exp(log_gamma(a + b) - log_gamma(a) - log_gamma(b) + a * Math.log(x) + b * Math.log(1.0 - x))
|
|
127
|
+
x < (a + 1.0) / (a + b + 2.0) ? front * beta_cf(x, a, b) / a : 1.0 - front * beta_cf(1.0 - x, b, a) / b
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def beta_cf(x, a, b)
|
|
131
|
+
qab = a + b
|
|
132
|
+
qap = a + 1.0
|
|
133
|
+
qam = a - 1.0
|
|
134
|
+
c = 1.0
|
|
135
|
+
d = 1.0 - qab * x / qap
|
|
136
|
+
d = TINY if d.abs < TINY
|
|
137
|
+
d = 1.0 / d
|
|
138
|
+
h = d
|
|
139
|
+
(1..budget([a, b].max)).each do |m|
|
|
140
|
+
m2 = 2 * m
|
|
141
|
+
an = m * (b - m) * x / ((qam + m2) * (a + m2))
|
|
142
|
+
d = 1.0 + an * d
|
|
143
|
+
d = TINY if d.abs < TINY
|
|
144
|
+
c = 1.0 + an / c
|
|
145
|
+
c = TINY if c.abs < TINY
|
|
146
|
+
d = 1.0 / d
|
|
147
|
+
h *= d * c
|
|
148
|
+
an = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2))
|
|
149
|
+
d = 1.0 + an * d
|
|
150
|
+
d = TINY if d.abs < TINY
|
|
151
|
+
c = 1.0 + an / c
|
|
152
|
+
c = TINY if c.abs < TINY
|
|
153
|
+
d = 1.0 / d
|
|
154
|
+
delta = d * c
|
|
155
|
+
h *= delta
|
|
156
|
+
return h if (delta - 1.0).abs < EPSILON
|
|
157
|
+
end
|
|
158
|
+
# a continued fraction that has not settled is no value: StudentT with
|
|
159
|
+
# 10**12 degrees of freedom printed 0.84128 for 0.84134 (fourth review)
|
|
160
|
+
raise ArgumentError, "beta_i: the continued fraction did not converge for a = #{a}, b = #{b}, x = #{x}"
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCAS
|
|
4
|
+
# Descriptive statistics and least squares on lists of data, exact.
|
|
5
|
+
#
|
|
6
|
+
# mean([1, 2, 3, 4]) # => 5/2
|
|
7
|
+
# variance([2, 4, 4, 4, 5, 5, 7, 9]) # => 32/7 (sample variance, n - 1)
|
|
8
|
+
# quantile([1, 2, 3, 4], 1/4r) # => 7/4
|
|
9
|
+
# mean([a, b, c]) # => a/3 + b/3 + c/3
|
|
10
|
+
# linreg([1, 2, 3], [2, 4, 7], x) # => -2/3 + 5*x/2
|
|
11
|
+
#
|
|
12
|
+
# Conventions follow Maple, Mathematica and MuPAD: variance, standard
|
|
13
|
+
# deviation and covariance divide by n - 1 unless sample: false; skewness
|
|
14
|
+
# and kurtosis are the standardized third and fourth central moments
|
|
15
|
+
# (kurtosis 3 for a normal sample, not the excess); quantiles interpolate
|
|
16
|
+
# linearly between order statistics (definition 7 of [HF96], the default of
|
|
17
|
+
# R and Excel). Data may be symbolic wherever no ordering is needed.
|
|
18
|
+
#
|
|
19
|
+
# Sources (keys: MANUAL.md, Sources): [HF96]; least squares and the moment
|
|
20
|
+
# statistics are textbook [Ros14, ch. 7].
|
|
21
|
+
module Statistics
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def data(list, name)
|
|
25
|
+
list = list.to_a if list.is_a?(Range)
|
|
26
|
+
raise ArgumentError, "#{name}: a non-empty list of values is needed, got #{list.inspect}" unless list.is_a?(Array) && !list.empty?
|
|
27
|
+
list.map { |v| Expression.lift(v) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Sorted ascending by numeric value; symbolic data has no order.
|
|
31
|
+
def sorted(list, name)
|
|
32
|
+
values = data(list, name)
|
|
33
|
+
keyed = values.map do |v|
|
|
34
|
+
f = v.evalf
|
|
35
|
+
raise ArgumentError, "#{name}: #{v} is not a real number" unless f.is_a?(Numeric) && !f.is_a?(Complex)
|
|
36
|
+
[f, v]
|
|
37
|
+
end
|
|
38
|
+
# exact numbers compare exactly, anything else by Inequalities.compare
|
|
39
|
+
# where the Floats cannot tell: 1 and 1 + 10**-20 are one Float
|
|
40
|
+
# (third review, P-10)
|
|
41
|
+
keyed.sort do |(fa, a), (fb, b)|
|
|
42
|
+
if a.is_a?(Num) && b.is_a?(Num) && a.value.real? && b.value.real?
|
|
43
|
+
a.value <=> b.value
|
|
44
|
+
elsif (fa - fb).abs > 1e-9 * [1.0, fa.abs, fb.abs].max
|
|
45
|
+
fa <=> fb
|
|
46
|
+
else
|
|
47
|
+
Inequalities.compare(a, b) || (fa <=> fb)
|
|
48
|
+
end
|
|
49
|
+
end.map(&:last)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def size(list) = Num.new(list.size)
|
|
53
|
+
|
|
54
|
+
def mean(list)
|
|
55
|
+
values = data(list, "mean")
|
|
56
|
+
(values.reduce(:+) / size(values)).simplify
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def median(list)
|
|
60
|
+
values = sorted(list, "median")
|
|
61
|
+
n = values.size
|
|
62
|
+
return values[n / 2] if n.odd?
|
|
63
|
+
((values[n / 2 - 1] + values[n / 2]) / 2).simplify
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The most frequent value; a list when several tie.
|
|
67
|
+
def mode(list)
|
|
68
|
+
counts = frequencies(list)
|
|
69
|
+
top = counts.values.max
|
|
70
|
+
modes = counts.select { |_, c| c == top }.keys
|
|
71
|
+
modes.size == 1 ? modes.first : modes
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# { value => count }, ordered by value where the data is numeric.
|
|
75
|
+
def frequencies(list)
|
|
76
|
+
values = data(list, "frequencies").map(&:simplify)
|
|
77
|
+
counts = {}
|
|
78
|
+
values.each { |v| counts[v] = (counts[v] || 0) + 1 }
|
|
79
|
+
numeric = counts.keys.all? { |v| (f = v.evalf).is_a?(Numeric) && !f.is_a?(Complex) }
|
|
80
|
+
numeric ? counts.sort_by { |v, _| v.evalf }.to_h : counts
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def variance(list, sample: true)
|
|
84
|
+
values = data(list, "variance")
|
|
85
|
+
raise ArgumentError, "variance: at least two values are needed for a sample variance" if sample && values.size < 2
|
|
86
|
+
m = mean(values)
|
|
87
|
+
squares = values.map { |v| ((v - m)**2).expand }.reduce(:+)
|
|
88
|
+
(squares / Num.new(sample ? values.size - 1 : values.size)).simplify
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def stdev(list, sample: true) = RCAS.sqrt(variance(list, sample: sample)).simplify
|
|
92
|
+
|
|
93
|
+
# k-th central (or raw) moment, divided by n.
|
|
94
|
+
def moment(list, k, central: true)
|
|
95
|
+
values = data(list, "moment")
|
|
96
|
+
shift = central ? mean(values) : Num.new(0)
|
|
97
|
+
(values.map { |v| ((v - shift)**k).expand }.reduce(:+) / size(values)).simplify
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def skewness(list)
|
|
101
|
+
m2 = moment(list, 2)
|
|
102
|
+
(moment(list, 3) / m2**Rational(3, 2)).simplify
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def kurtosis(list)
|
|
106
|
+
m2 = moment(list, 2)
|
|
107
|
+
(moment(list, 4) / m2**2).simplify
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Definition 7 of [HF96]: h = (n - 1) p, linear interpolation between the
|
|
111
|
+
# order statistics x[floor h] and x[floor h + 1].
|
|
112
|
+
def quantile(list, p)
|
|
113
|
+
values = sorted(list, "quantile")
|
|
114
|
+
p = Expression.lift(p)
|
|
115
|
+
raise ArgumentError, "quantile: p must be a number in [0, 1], got #{p}" unless p.is_a?(Num) && p.value.real? && p.value.between?(0, 1)
|
|
116
|
+
h = (values.size - 1) * p.value
|
|
117
|
+
i = h.floor
|
|
118
|
+
return values[i] if i == h || i + 1 >= values.size
|
|
119
|
+
(values[i] + (values[i + 1] - values[i]) * Num.new(Simplify.normalize_number(h - i))).simplify
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def quartiles(list) = [quantile(list, Rational(1, 4)), median(list), quantile(list, Rational(3, 4))]
|
|
123
|
+
def iqr(list) = (quantile(list, Rational(3, 4)) - quantile(list, Rational(1, 4))).simplify
|
|
124
|
+
|
|
125
|
+
def geometric_mean(list)
|
|
126
|
+
values = data(list, "geometric_mean")
|
|
127
|
+
(values.reduce(:*)**Rational(1, values.size)).simplify
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def harmonic_mean(list)
|
|
131
|
+
values = data(list, "harmonic_mean")
|
|
132
|
+
(size(values) / values.map { |v| 1 / v }.reduce(:+)).simplify
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# ---- two variables ----------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
def pairs(xs, ys, name)
|
|
138
|
+
xs = data(xs, name)
|
|
139
|
+
ys = data(ys, name)
|
|
140
|
+
raise ArgumentError, "#{name}: the lists must have the same length (#{xs.size} and #{ys.size})" unless xs.size == ys.size
|
|
141
|
+
raise ArgumentError, "#{name}: at least two pairs are needed" if xs.size < 2
|
|
142
|
+
[xs, ys]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def covariance(xs, ys, sample: true)
|
|
146
|
+
xs, ys = pairs(xs, ys, "covariance")
|
|
147
|
+
mx = mean(xs)
|
|
148
|
+
my = mean(ys)
|
|
149
|
+
total = xs.zip(ys).map { |x, y| ((x - mx) * (y - my)).expand }.reduce(:+)
|
|
150
|
+
(total / Num.new(sample ? xs.size - 1 : xs.size)).simplify
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def correlation(xs, ys)
|
|
154
|
+
xs, ys = pairs(xs, ys, "correlation")
|
|
155
|
+
sx = stdev(xs)
|
|
156
|
+
sy = stdev(ys)
|
|
157
|
+
if Scalar.zero?(sx) || Scalar.zero?(sy)
|
|
158
|
+
raise ArgumentError, "correlation: undefined when one of the two series is constant"
|
|
159
|
+
end
|
|
160
|
+
(covariance(xs, ys) / (sx * sy)).simplify
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Least squares line as an expression in var: a + b*var.
|
|
164
|
+
def linreg(xs, ys, var = :x)
|
|
165
|
+
xs, ys = pairs(xs, ys, "linreg")
|
|
166
|
+
x = Expression.lift(var)
|
|
167
|
+
raise ArgumentError, "linreg: the third argument names the indeterminate, got #{x}" unless x.is_a?(Var)
|
|
168
|
+
vx = variance(xs)
|
|
169
|
+
raise ArgumentError, "linreg: all x values are equal" if Scalar.zero?(vx)
|
|
170
|
+
b = (covariance(xs, ys) / vx).simplify
|
|
171
|
+
a = (mean(ys) - b * mean(xs)).simplify
|
|
172
|
+
(a + b * x).simplify
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|