spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
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);