commit
message
Added Formulas.FDIST$LEFTTAILED (aka F.DIST)
author
Ben Vogt <[email protected]>
date
2017-02-19 20:39:26
stats
3 file(s) changed,
183 insertions(+),
1 deletions(-)
files
src/RawFormulas/Math.ts
src/RawFormulas/RawFormulas.ts
tests/FormulasTest.ts
1diff --git a/src/RawFormulas/Math.ts b/src/RawFormulas/Math.ts
2index 7bbb51b..5118f85 100644
3--- a/src/RawFormulas/Math.ts
4+++ b/src/RawFormulas/Math.ts
5@@ -1145,6 +1145,169 @@ var DEVSQ = function (...values) : number {
6 return result;
7 };
8
9+/**
10+ * Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x.
11+ * Alternately called Fisher-Snedecor distribution or Snecdor's F distribution.
12+ * @param values[0] x - The input to the F probability distribution function. The value at which to evaluate the function.
13+ * Must be a positive number.
14+ * @param values[1] degrees_freedom1 - The numerator degrees of freedom.
15+ * @param values[2] degrees_freedom2 - The denominator degrees of freedom.
16+ * @param values[3] cumulative - Logical value that determines the form of the function. If true returns the cumulative
17+ * distribution function. If false returns the probability density function.
18+ * @returns {number|undefined|boolean} left-tailed F probability distribution
19+ * @constructor
20+ * TODO: This function should be stricter in its return type.
21+ */
22+var FDIST$LEFTTAILED = function (...values) : number|undefined|boolean {
23+ function gammaln(x) {
24+ var j = 0;
25+ var cof = [
26+ 76.18009172947146, -86.50532032941677, 24.01409824083091,
27+ -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5
28+ ];
29+ var ser = 1.000000000190015;
30+ var xx, y, tmp;
31+ tmp = (y = xx = x) + 5.5;
32+ tmp -= (xx + 0.5) * Math.log(tmp);
33+ for (; j < 6; j++)
34+ ser += cof[j] / ++y;
35+ return Math.log(2.5066282746310005 * ser / xx) - tmp;
36+ }
37+ function gammafn(x) {
38+ var p = [-1.716185138865495, 24.76565080557592, -379.80425647094563,
39+ 629.3311553128184, 866.9662027904133, -31451.272968848367,
40+ -36144.413418691176, 66456.14382024054
41+ ];
42+ var q = [-30.8402300119739, 315.35062697960416, -1015.1563674902192,
43+ -3107.771671572311, 22538.118420980151, 4755.8462775278811,
44+ -134659.9598649693, -115132.2596755535];
45+ var fact;
46+ var n = 0;
47+ var xden = 0;
48+ var xnum = 0;
49+ var y = x;
50+ var i, z, yi, res;
51+ if (y <= 0) {
52+ res = y % 1 + 3.6e-16;
53+ if (res) {
54+ fact = (!(y & 1) ? 1 : -1) * Math.PI / Math.sin(Math.PI * res);
55+ y = 1 - y;
56+ } else {
57+ return Infinity;
58+ }
59+ }
60+ yi = y;
61+ if (y < 1) {
62+ z = y++;
63+ } else {
64+ z = (y -= n = (y | 0) - 1) - 1;
65+ }
66+ for (i = 0; i < 8; ++i) {
67+ xnum = (xnum + p[i]) * z;
68+ xden = xden * z + q[i];
69+ }
70+ res = xnum / xden + 1;
71+ if (yi < y) {
72+ res /= yi;
73+ } else if (yi > y) {
74+ for (i = 0; i < n; ++i) {
75+ res *= y;
76+ y++;
77+ }
78+ }
79+ if (fact) {
80+ res = fact / res;
81+ }
82+ return res;
83+ }
84+ function betacf(x, a, b) {
85+ var fpmin = 1e-30;
86+ var m = 1;
87+ var qab = a + b;
88+ var qap = a + 1;
89+ var qam = a - 1;
90+ var c = 1;
91+ var d = 1 - qab * x / qap;
92+ var m2, aa, del, h;
93+
94+ // These q's will be used in factors that occur in the coefficients
95+ if (Math.abs(d) < fpmin)
96+ d = fpmin;
97+ d = 1 / d;
98+ h = d;
99+
100+ for (; m <= 100; m++) {
101+ m2 = 2 * m;
102+ aa = m * (b - m) * x / ((qam + m2) * (a + m2));
103+ // One step (the even one) of the recurrence
104+ d = 1 + aa * d;
105+ if (Math.abs(d) < fpmin)
106+ d = fpmin;
107+ c = 1 + aa / c;
108+ if (Math.abs(c) < fpmin)
109+ c = fpmin;
110+ d = 1 / d;
111+ h *= d * c;
112+ aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
113+ // Next step of the recurrence (the odd one)
114+ d = 1 + aa * d;
115+ if (Math.abs(d) < fpmin)
116+ d = fpmin;
117+ c = 1 + aa / c;
118+ if (Math.abs(c) < fpmin)
119+ c = fpmin;
120+ d = 1 / d;
121+ del = d * c;
122+ h *= del;
123+ if (Math.abs(del - 1.0) < 3e-7)
124+ break;
125+ }
126+
127+ return h;
128+ }
129+ function ibeta(x, a, b) {
130+ // Factors in front of the continued fraction.
131+ var bt = (x === 0 || x === 1) ? 0 :
132+ Math.exp(gammaln(a + b) - gammaln(a) -
133+ gammaln(b) + a * Math.log(x) + b *
134+ Math.log(1 - x));
135+ if (x < 0 || x > 1)
136+ return false;
137+ if (x < (a + 1) / (a + b + 2))
138+ // Use continued fraction directly.
139+ return bt * betacf(x, a, b) / a;
140+ // else use continued fraction after making the symmetry transformation.
141+ return 1 - bt * betacf(1 - x, b, a) / b;
142+ }
143+ function cdf(x, df1, df2) {
144+ return ibeta((df1 * x) / (df1 * x + df2), df1 / 2, df2 / 2);
145+ }
146+ function pdf(x, df1, df2) {
147+ if (x < 0) {
148+ return undefined;
149+ }
150+ return Math.sqrt((Math.pow(df1 * x, df1) * Math.pow(df2, df2)) /
151+ (Math.pow(df1 * x + df2, df1 + df2))) /
152+ (x * betafn(df1/2, df2/2));
153+ }
154+ function betafn(x, y) {
155+ // ensure arguments are positive
156+ if (x <= 0 || y <= 0)
157+ return undefined;
158+ // make sure x + y doesn't exceed the upper limit of usable values
159+ return (x + y > 170) ? Math.exp(x/*TODO: y?*/) : gammafn(x) * gammafn(y) / gammafn(x + y);
160+ }
161+ ArgsChecker.checkLength(values, 4);
162+ var x = TypeCaster.firstValueAsNumber(values[0]);
163+ if (x < 0) {
164+ throw new CellError(ERRORS.NUM_ERROR, "Function F.DIST parameter 1 value is " + x + ". It should be greater than or equal to 0.");
165+ }
166+ var d1 = TypeCaster.firstValueAsNumber(values[1]);
167+ var d2 = TypeCaster.firstValueAsNumber(values[2]);
168+ var cumulative = TypeCaster.firstValueAsBoolean(values[3]);
169+ return (cumulative) ? cdf(x, d1, d2) : pdf(x, d1, d2);
170+};
171+
172
173 export {
174 ABS,
175@@ -1166,6 +1329,7 @@ export {
176 COS,
177 DEVSQ,
178 EVEN,
179+ FDIST$LEFTTAILED,
180 INT,
181 ISEVEN,
182 ISODD,
183diff --git a/src/RawFormulas/RawFormulas.ts b/src/RawFormulas/RawFormulas.ts
184index 653e7ec..c98e1c2 100644
185--- a/src/RawFormulas/RawFormulas.ts
186+++ b/src/RawFormulas/RawFormulas.ts
187@@ -21,6 +21,7 @@ import {
188 COS,
189 DEVSQ,
190 EVEN,
191+ FDIST$LEFTTAILED,
192 INT,
193 ISEVEN,
194 ISODD,
195@@ -121,7 +122,7 @@ var EOMONTH = function (start_date, months) {
196 };
197 var EXPONDIST = Formula["EXPONDIST"];
198 var __COMPLEX = {
199- "F.DIST": Formula["FDIST"],
200+ "F.DIST": FDIST$LEFTTAILED,
201 "F.INV": Formula["FINV"]
202 };
203 var YEARFRAC = Formula["YEARFRAC"];
204diff --git a/tests/FormulasTest.ts b/tests/FormulasTest.ts
205index 37c05e1..560f282 100644
206--- a/tests/FormulasTest.ts
207+++ b/tests/FormulasTest.ts
208@@ -968,8 +968,25 @@ assertEquals(EXPONDIST(4, 0.5, false), 0.06766764161830635);
209
210 assertEquals(FALSE(), false);
211
212+// Test F.DIST
213 assertEquals(__COMPLEX["F.DIST"](15.35, 7, 6, false), 0.0003451054686025578);
214 assertEquals(__COMPLEX["F.DIST"](15.35, 7, 6, true), 0.9980694465675269);
215+assertEquals(__COMPLEX["F.DIST"](15.35, 7, 6, 1), 0.9980694465675269);
216+assertEquals(__COMPLEX["F.DIST"](15.35, "7", [6], 1), 0.9980694465675269);
217+assertEquals(__COMPLEX["F.DIST"](15.35, "7", [6], 10), 0.9980694465675269);
218+catchAndAssertEquals(function() {
219+ __COMPLEX["F.DIST"](15.35, 7, 6, "10");
220+}, ERRORS.VALUE_ERROR);
221+catchAndAssertEquals(function() {
222+ __COMPLEX["F.DIST"](-15.35, 7, 6, 1);
223+}, ERRORS.NUM_ERROR);
224+catchAndAssertEquals(function() {
225+ __COMPLEX["F.DIST"](15.35, 7, 6);
226+}, ERRORS.NA_ERROR);
227+catchAndAssertEquals(function() {
228+ __COMPLEX["F.DIST"]();
229+}, ERRORS.NA_ERROR);
230+
231
232 assertEquals(__COMPLEX["F.INV"](0.42, 2, 3), 0.6567804059458624);
233