commit
message
[FORECAST] formula added and tested
author
Ben Vogt <[email protected]>
date
2017-06-27 01:17:50
stats
8 file(s) changed,
144 insertions(+),
7 deletions(-)
files
DOCS.md
TODO.md
dist/Formulas/AllFormulas.js
dist/Formulas/Statistical.js
src/Formulas/AllFormulas.ts
src/Formulas/Statistical.ts
tests/Formulas/StatisticalTest.ts
tests/SheetFormulaTest.ts
1diff --git a/DOCS.md b/DOCS.md
2index eff56de..f6eb8c2 100644
3--- a/DOCS.md
4+++ b/DOCS.md
5@@ -1564,6 +1564,17 @@
6 @returns {number}
7 @constructor
8 ```
9+
10+### FORECAST
11+
12+```
13+ Calculates the a future value using existing x-values and y-values. Any text values will be ignored.
14+@param x - The data point for which you would like to predict the value.
15+@param rangeY - Dependent range of values.
16+@param rangeX - Independent range of values.
17+@returns {number}
18+@constructor TODO: This formula will fail to parse since the first argument is followed by an argument that is an array. TODO (continued) This is a known issue.
19+```
20 ## Text
21
22
23diff --git a/TODO.md b/TODO.md
24index 29ceb4f..61a3592 100644
25--- a/TODO.md
26+++ b/TODO.md
27@@ -37,7 +37,7 @@ For example 64 tbs to a qt.
28 * N
29 * TYPE
30 * CELL - Requires changes to parser.js
31-* IFERROR
32+* IFERROR - Requires changes to parser.js
33 * ADDRESS
34 * COLUMN - Requires changes to parser.js
35 * COLUMNS
36@@ -49,8 +49,8 @@ For example 64 tbs to a qt.
37 * OFFSET
38 * ROW - Requires changes to parser.js
39 * ROWS
40-* VLOOKUP
41-* COUNTBLANK
42+* VLOOKUP - Requires changes to parser.js
43+* COUNTBLANK - Requires changes to parser.js
44 * MULTINOMIAL
45 * SERIESSUM
46 * SUBTOTAL
47@@ -64,7 +64,6 @@ For example 64 tbs to a qt.
48 * COVAR
49 * CRITBINOM
50 * F.DIST.RT
51-* FORECAST
52 * GEOMEAN
53 * HARMEAN
54 * HYPGEOMDIST
55diff --git a/dist/Formulas/AllFormulas.js b/dist/Formulas/AllFormulas.js
56index e6ee74c..a6d8af0 100644
57--- a/dist/Formulas/AllFormulas.js
58+++ b/dist/Formulas/AllFormulas.js
59@@ -142,6 +142,7 @@ exports.SMALL = Statistical_1.SMALL;
60 exports.LARGE = Statistical_1.LARGE;
61 exports.KURT = Statistical_1.KURT;
62 exports.INTERCEPT = Statistical_1.INTERCEPT;
63+exports.FORECAST = Statistical_1.FORECAST;
64 var Text_1 = require("./Text");
65 exports.ARABIC = Text_1.ARABIC;
66 exports.CHAR = Text_1.CHAR;
67diff --git a/dist/Formulas/Statistical.js b/dist/Formulas/Statistical.js
68index 601f723..e74fa2b 100644
69--- a/dist/Formulas/Statistical.js
70+++ b/dist/Formulas/Statistical.js
71@@ -889,3 +889,46 @@ var INTERCEPT = function (rangeY, rangeX) {
72 return yMean - b * xMean;
73 };
74 exports.INTERCEPT = INTERCEPT;
75+/**
76+ * Calculates the a future value using existing x-values and y-values. Any text values will be ignored.
77+ * @param x - The data point for which you would like to predict the value.
78+ * @param rangeY - Dependent range of values.
79+ * @param rangeX - Independent range of values.
80+ * @returns {number}
81+ * @constructor
82+ * TODO: This formula will fail to parse since the first argument is followed by an argument that is an array.
83+ * TODO (continued) This is a known issue.
84+ */
85+var FORECAST = function (x, rangeY, rangeX) {
86+ ArgsChecker_1.ArgsChecker.checkLength(arguments, 3, "FORECAST");
87+ x = TypeConverter_1.TypeConverter.firstValueAsNumber(x);
88+ var dataX = Filter_1.Filter.flattenAndThrow(rangeX).filter(function (value) {
89+ return typeof value !== "string";
90+ }).map(function (value) {
91+ return TypeConverter_1.TypeConverter.valueToNumber(value);
92+ });
93+ var dataY = Filter_1.Filter.flattenAndThrow(rangeY).filter(function (value) {
94+ return typeof value !== "string";
95+ }).map(function (value) {
96+ return TypeConverter_1.TypeConverter.valueToNumber(value);
97+ });
98+ if (dataX.length !== dataY.length) {
99+ throw new Errors_1.NAError("FORECAST has mismatched argument count " + dataX.length + " vs " + dataY.length + ".");
100+ }
101+ var xMean = MathHelpers_1.mean(dataX);
102+ var yMean = MathHelpers_1.mean(dataY);
103+ var n = dataX.length;
104+ var num = 0;
105+ var den = 0;
106+ for (var i = 0; i < n; i++) {
107+ num += (dataX[i] - xMean) * (dataY[i] - yMean);
108+ den += Math.pow(dataX[i] - xMean, 2);
109+ }
110+ if (den === 0) {
111+ throw new Errors_1.DivZeroError("Evaluation of function FORECAST caused a divide by zero error.");
112+ }
113+ var b = num / den;
114+ var a = yMean - b * xMean;
115+ return a + b * x;
116+};
117+exports.FORECAST = FORECAST;
118diff --git a/src/Formulas/AllFormulas.ts b/src/Formulas/AllFormulas.ts
119index 9e5031f..18a08c3 100644
120--- a/src/Formulas/AllFormulas.ts
121+++ b/src/Formulas/AllFormulas.ts
122@@ -147,7 +147,8 @@ import {
123 SMALL,
124 LARGE,
125 KURT,
126- INTERCEPT
127+ INTERCEPT,
128+ FORECAST
129 } from "./Statistical";
130 import {
131 ARABIC,
132@@ -363,5 +364,6 @@ export {
133 SMALL,
134 LARGE,
135 KURT,
136- INTERCEPT
137+ INTERCEPT,
138+ FORECAST
139 }
140\ No newline at end of file
141diff --git a/src/Formulas/Statistical.ts b/src/Formulas/Statistical.ts
142index 735463c..c93c961 100644
143--- a/src/Formulas/Statistical.ts
144+++ b/src/Formulas/Statistical.ts
145@@ -873,6 +873,52 @@ var INTERCEPT = function (rangeY, rangeX) {
146 };
147
148
149+/**
150+ * Calculates the a future value using existing x-values and y-values. Any text values will be ignored.
151+ * @param x - The data point for which you would like to predict the value.
152+ * @param rangeY - Dependent range of values.
153+ * @param rangeX - Independent range of values.
154+ * @returns {number}
155+ * @constructor
156+ * TODO: This formula will fail to parse since the first argument is followed by an argument that is an array.
157+ * TODO (continued) This is a known issue.
158+ */
159+var FORECAST = function (x, rangeY, rangeX) {
160+ ArgsChecker.checkLength(arguments, 3, "FORECAST");
161+ x = TypeConverter.firstValueAsNumber(x);
162+ var dataX = Filter.flattenAndThrow(rangeX).filter(function (value) {
163+ return typeof value !== "string";
164+ }).map(function (value) {
165+ return TypeConverter.valueToNumber(value);
166+ });
167+ var dataY = Filter.flattenAndThrow(rangeY).filter(function (value) {
168+ return typeof value !== "string";
169+ }).map(function (value) {
170+ return TypeConverter.valueToNumber(value);
171+ });
172+
173+ if (dataX.length !== dataY.length) {
174+ throw new NAError("FORECAST has mismatched argument count " + dataX.length + " vs " + dataY.length + ".");
175+ }
176+
177+ var xMean = mean(dataX);
178+ var yMean = mean(dataY);
179+ var n = dataX.length;
180+ var num = 0;
181+ var den = 0;
182+ for (var i = 0; i < n; i++) {
183+ num += (dataX[i] - xMean) * (dataY[i] - yMean);
184+ den += Math.pow(dataX[i] - xMean, 2);
185+ }
186+ if (den === 0) {
187+ throw new DivZeroError("Evaluation of function FORECAST caused a divide by zero error.");
188+ }
189+ var b = num / den;
190+ var a = yMean - b * xMean;
191+ return a + b * x;
192+};
193+
194+
195 export {
196 AVERAGE,
197 AVERAGEA,
198@@ -905,5 +951,6 @@ export {
199 SMALL,
200 LARGE,
201 KURT,
202- INTERCEPT
203+ INTERCEPT,
204+ FORECAST
205 }
206\ No newline at end of file
207diff --git a/tests/Formulas/StatisticalTest.ts b/tests/Formulas/StatisticalTest.ts
208index f02874a..98a65ba 100644
209--- a/tests/Formulas/StatisticalTest.ts
210+++ b/tests/Formulas/StatisticalTest.ts
211@@ -30,7 +30,8 @@ import {
212 SMALL,
213 LARGE,
214 KURT,
215- INTERCEPT
216+ INTERCEPT,
217+ FORECAST
218 } from "../../src/Formulas/Statistical";
219 import * as ERRORS from "../../src/Errors";
220 import {
221@@ -671,4 +672,25 @@ test("INTERCEPT", function() {
222 catchAndAssertEquals(function() {
223 INTERCEPT.apply(this, [[1, 2, 3], [1, 2, 3], [1, 2, 3]]);
224 }, ERRORS.NA_ERROR);
225-});
226\ No newline at end of file
227+});
228+
229+test("FORCAST", function() {
230+ assertEquals(FORECAST(0, [1, 2, 3, 4], [10, 20, 33, 44]), 0.1791776688042246);
231+ assertEquals(FORECAST(1, [1, 2, 3, 4], [10, 20, 33, 44]), 0.2659373821199545);
232+ assertEquals(FORECAST(22, [1, 2, 3, 4], [10, 20, 33, 44]), 2.087891361750283);
233+ assertEquals(FORECAST(-10, [1, 2, 3, 4], [10, 20, 33, 44]), -0.6884194643530746);
234+ assertEquals(FORECAST(0, [true, 2, 3, 4], [10, 20, 33, "ignore", 44]), 0.1791776688042246);
235+ assertEquals(FORECAST(0, [1, 2], [10, 20]), 0);
236+ catchAndAssertEquals(function() {
237+ FORECAST(0, [1], [10])
238+ }, ERRORS.DIV_ZERO_ERROR);
239+ catchAndAssertEquals(function() {
240+ FORECAST(0, [1, "ignore"], [10, "ignore"])
241+ }, ERRORS.DIV_ZERO_ERROR);
242+ catchAndAssertEquals(function() {
243+ FORECAST.apply(this, [[1, 2, 3]]);
244+ }, ERRORS.NA_ERROR);
245+ catchAndAssertEquals(function() {
246+ FORECAST.apply(this, [0, [1, 2, 3], [1, 2, 3], [1, 2, 3]]);
247+ }, ERRORS.NA_ERROR);
248+});
249diff --git a/tests/SheetFormulaTest.ts b/tests/SheetFormulaTest.ts
250index a07d65f..ef16a66 100644
251--- a/tests/SheetFormulaTest.ts
252+++ b/tests/SheetFormulaTest.ts
253@@ -715,6 +715,14 @@ test("Sheet LARGE", function(){
254 assertFormulaEquals('=LARGE([1, 2], 2)', 1);
255 });
256
257+test("Sheet INTERCEPT", function(){
258+ assertFormulaEquals('=INTERCEPT([1, 2, 3, 4], [10, 20, 33, 44])', 0.1791776688042246);
259+});
260+
261+test("Sheet FORECAST", function(){
262+ assertFormulaEquals('=FORECAST([0], [1, 2, 3, 4], [10, 20, 33, 44])', 0.1791776688042246);
263+});
264+
265 test("Sheet *", function(){
266 assertFormulaEquals('= 10 * 10', 100);
267 assertFormulaEquals('= 10 * 0', 0);