spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
[GROWTH] formula added and tested
author
Ben Vogt <[email protected]>
date
2017-06-19 02:42:30
stats
8 file(s) changed, 213 insertions(+), 7 deletions(-)
files
DOCS.md
TODO.md
dist/Formulas/AllFormulas.js
dist/Formulas/Range.js
src/Formulas/AllFormulas.ts
src/Formulas/Range.ts
tests/Formulas/RangeTest.ts
tests/SheetFormulaTest.ts
  1diff --git a/DOCS.md b/DOCS.md
  2index 6a63693..bf2004c 100644
  3--- a/DOCS.md
  4+++ b/DOCS.md
  5@@ -1244,7 +1244,19 @@
  6 @param range - to get frequency for. 
  7 @param bins - or classes. 
  8 @returns {Array<number>} 
  9-@constructor
 10+@constructor TODO: Returns ColumnArray (values stacked in Y-direction)
 11+```
 12+
 13+### GROWTH 
 14+
 15+```
 16+  Given partial data with exponential growth, fits and ideal exponential growth trend, and predicts future values. For more information see: https:xkcd.com1102 
 17+@param knownY - The range or array containing the dependent, y, values that are known, and will be used to fit an ideal exponential growth curve. 
 18+@param knownX - OPTIONAL - The range or values of the independent variables that correspond to knownY. 
 19+@param newX - OPTIONAL - The range, values, or data-points to return the y-values on the ideal curve fit. 
 20+@param shouldUseConstant - OPTIONAL - True by default. Given an exponential function y = bm^x, should this function calculate b? 
 21+@returns {Array} 
 22+@constructor TODO: Returns RowArray (values stacked in X-direction)
 23 ```
 24 ## Statistical
 25 
 26diff --git a/TODO.md b/TODO.md
 27index c8fb1fb..81ab8e0 100644
 28--- a/TODO.md
 29+++ b/TODO.md
 30@@ -130,7 +130,6 @@ For example 64 tbs to a qt.
 31 * TEXT
 32 * UPPER
 33 * VALUE
 34-* GROWTH
 35 * LINEST
 36 * LOGEST
 37 * MDETERM
 38diff --git a/dist/Formulas/AllFormulas.js b/dist/Formulas/AllFormulas.js
 39index 8e019cd..a167626 100644
 40--- a/dist/Formulas/AllFormulas.js
 41+++ b/dist/Formulas/AllFormulas.js
 42@@ -75,6 +75,7 @@ exports.MROUND = Math_1.MROUND;
 43 exports.FACTDOUBLE = Math_1.FACTDOUBLE;
 44 var Range_1 = require("./Range");
 45 exports.FREQUENCY = Range_1.FREQUENCY;
 46+exports.GROWTH = Range_1.GROWTH;
 47 var Info_1 = require("./Info");
 48 exports.NA = Info_1.NA;
 49 exports.ISTEXT = Info_1.ISTEXT;
 50diff --git a/dist/Formulas/Range.js b/dist/Formulas/Range.js
 51index 493a0b1..559f833 100644
 52--- a/dist/Formulas/Range.js
 53+++ b/dist/Formulas/Range.js
 54@@ -3,12 +3,14 @@ exports.__esModule = true;
 55 var ArgsChecker_1 = require("../Utilities/ArgsChecker");
 56 var Filter_1 = require("../Utilities/Filter");
 57 var TypeConverter_1 = require("../Utilities/TypeConverter");
 58+var Errors_1 = require("../Errors");
 59 /**
 60  * Calculates the frequency distribution of a range into specified classes or "bins".
 61  * @param range - to get frequency for.
 62  * @param bins - or classes.
 63  * @returns {Array<number>}
 64  * @constructor
 65+ * TODO: Returns ColumnArray (values stacked in Y-direction)
 66  */
 67 var FREQUENCY = function (range, bins) {
 68     ArgsChecker_1.ArgsChecker.checkLength(arguments, 2, "FREQUENCY");
 69@@ -54,3 +56,78 @@ var FREQUENCY = function (range, bins) {
 70     return r;
 71 };
 72 exports.FREQUENCY = FREQUENCY;
 73+/**
 74+ * Given partial data with exponential growth, fits and ideal exponential growth trend, and predicts future values. For
 75+ * more information see: https://xkcd.com/1102/
 76+ * @param knownY - The range or array containing the dependent, y, values that are known, and will be used to fit an
 77+ * ideal exponential growth curve.
 78+ * @param knownX - OPTIONAL - The range or values of the independent variables that correspond to knownY.
 79+ * @param newX - OPTIONAL - The range, values, or data-points to return the y-values on the ideal curve fit.
 80+ * @param shouldUseConstant - OPTIONAL - True by default. Given an exponential function y = b*m^x, should this function
 81+ * calculate b?
 82+ * @returns {Array}
 83+ * @constructor
 84+ * TODO: Returns RowArray (values stacked in X-direction)
 85+ */
 86+var GROWTH = function (knownY, knownX, newX, shouldUseConstant) {
 87+    // Credits: Ilmari Karonen, FormulaJs (https://github.com/sutoiku/formula.js/)
 88+    knownY = Filter_1.Filter.flattenAndThrow(knownY).map(function (value) {
 89+        if (typeof value !== "number") {
 90+            throw new Errors_1.ValueError("Function GROWTH parameter 1 expects number values. But '" + value + "' is " + (typeof value)
 91+                + " and cannot be coerced to a number.");
 92+        }
 93+        return value;
 94+    });
 95+    // Default values for optional parameters:
 96+    if (arguments.length < 2) {
 97+        knownX = [];
 98+        for (var i = 1; i <= knownY.length; i++) {
 99+            knownX.push(i);
100+        }
101+    }
102+    if (arguments.length < 3) {
103+        newX = [];
104+        for (var i = 1; i <= knownY.length; i++) {
105+            newX.push(i);
106+        }
107+    }
108+    if (arguments.length < 4) {
109+        shouldUseConstant = true || shouldUseConstant;
110+    }
111+    // Calculate sums over the data:
112+    var n = knownY.length;
113+    var avg_x = 0;
114+    var avg_y = 0;
115+    var avg_xy = 0;
116+    var avg_xx = 0;
117+    for (i = 0; i < n; i++) {
118+        var x = knownX[i];
119+        var y = Math.log(knownY[i]);
120+        avg_x += x;
121+        avg_y += y;
122+        avg_xy += x * y;
123+        avg_xx += x * x;
124+    }
125+    avg_x /= n;
126+    avg_y /= n;
127+    avg_xy /= n;
128+    avg_xx /= n;
129+    // Compute linear regression coefficients:
130+    var beta;
131+    var alpha;
132+    if (shouldUseConstant) {
133+        beta = (avg_xy - avg_x * avg_y) / (avg_xx - avg_x * avg_x);
134+        alpha = avg_y - beta * avg_x;
135+    }
136+    else {
137+        beta = avg_xy / avg_xx;
138+        alpha = 0;
139+    }
140+    // Compute and return result array:
141+    var new_y = [];
142+    for (i = 0; i < newX.length; i++) {
143+        new_y.push(Math.exp(alpha + beta * newX[i]));
144+    }
145+    return new_y;
146+};
147+exports.GROWTH = GROWTH;
148diff --git a/src/Formulas/AllFormulas.ts b/src/Formulas/AllFormulas.ts
149index 6946d58..692b758 100644
150--- a/src/Formulas/AllFormulas.ts
151+++ b/src/Formulas/AllFormulas.ts
152@@ -73,7 +73,8 @@ import {
153   FACTDOUBLE
154 } from "./Math";
155 import {
156-  FREQUENCY
157+  FREQUENCY,
158+  GROWTH
159 } from "./Range";
160 import {
161   NA,
162@@ -343,5 +344,6 @@ export {
163   ISNONTEXT,
164   MROUND,
165   FACTDOUBLE,
166-  FREQUENCY
167+  FREQUENCY,
168+  GROWTH
169 }
170\ No newline at end of file
171diff --git a/src/Formulas/Range.ts b/src/Formulas/Range.ts
172index 599103d..22f8e28 100644
173--- a/src/Formulas/Range.ts
174+++ b/src/Formulas/Range.ts
175@@ -3,6 +3,7 @@ import {
176 } from "../Utilities/ArgsChecker";
177 import {Filter} from "../Utilities/Filter";
178 import {TypeConverter} from "../Utilities/TypeConverter";
179+import {ValueError} from "../Errors";
180 
181 
182 /**
183@@ -11,6 +12,7 @@ import {TypeConverter} from "../Utilities/TypeConverter";
184  * @param bins - or classes.
185  * @returns {Array<number>}
186  * @constructor
187+ * TODO: Returns ColumnArray (values stacked in Y-direction)
188  */
189 var FREQUENCY = function (range, bins) : Array<number> {
190   ArgsChecker.checkLength(arguments, 2, "FREQUENCY");
191@@ -55,6 +57,87 @@ var FREQUENCY = function (range, bins) : Array<number> {
192   return r;
193 };
194 
195+
196+/**
197+ * Given partial data with exponential growth, fits and ideal exponential growth trend, and predicts future values. For
198+ * more information see: https://xkcd.com/1102/
199+ * @param knownY - The range or array containing the dependent, y, values that are known, and will be used to fit an
200+ * ideal exponential growth curve.
201+ * @param knownX - OPTIONAL - The range or values of the independent variables that correspond to knownY.
202+ * @param newX - OPTIONAL - The range, values, or data-points to return the y-values on the ideal curve fit.
203+ * @param shouldUseConstant - OPTIONAL - True by default. Given an exponential function y = b*m^x, should this function
204+ * calculate b?
205+ * @returns {Array}
206+ * @constructor
207+ * TODO: Returns RowArray (values stacked in X-direction)
208+ */
209+var GROWTH = function (knownY, knownX?, newX?, shouldUseConstant?) {
210+  // Credits: Ilmari Karonen, FormulaJs (https://github.com/sutoiku/formula.js/)
211+
212+  knownY = Filter.flattenAndThrow(knownY).map(function (value) {
213+    if (typeof value !== "number") {
214+      throw new ValueError("Function GROWTH parameter 1 expects number values. But '" + value + "' is " + (typeof value)
215+          + " and cannot be coerced to a number.");
216+    }
217+    return value;
218+  });
219+
220+  // Default values for optional parameters:
221+  if (arguments.length < 2) {
222+    knownX = [];
223+    for (var i = 1; i <= knownY.length; i++) {
224+      knownX.push(i);
225+    }
226+  }
227+  if (arguments.length < 3) {
228+    newX = [];
229+    for (var i = 1; i <= knownY.length; i++) {
230+      newX.push(i);
231+    }
232+  }
233+  if (arguments.length < 4) {
234+    shouldUseConstant = true || shouldUseConstant;
235+  }
236+
237+  // Calculate sums over the data:
238+  var n = knownY.length;
239+  var avg_x = 0;
240+  var avg_y = 0;
241+  var avg_xy = 0;
242+  var avg_xx = 0;
243+  for (i = 0; i < n; i++) {
244+    var x = knownX[i];
245+    var y = Math.log(knownY[i]);
246+    avg_x += x;
247+    avg_y += y;
248+    avg_xy += x * y;
249+    avg_xx += x * x;
250+  }
251+  avg_x /= n;
252+  avg_y /= n;
253+  avg_xy /= n;
254+  avg_xx /= n;
255+
256+  // Compute linear regression coefficients:
257+  var beta;
258+  var alpha;
259+  if (shouldUseConstant) {
260+    beta = (avg_xy - avg_x * avg_y) / (avg_xx - avg_x * avg_x);
261+    alpha = avg_y - beta * avg_x;
262+  } else {
263+    beta = avg_xy / avg_xx;
264+    alpha = 0;
265+  }
266+
267+  // Compute and return result array:
268+  var new_y = [];
269+  for (i = 0; i < newX.length; i++) {
270+    new_y.push(Math.exp(alpha + beta * newX[i]));
271+  }
272+  return new_y;
273+};
274+
275 export {
276-  FREQUENCY
277+  FREQUENCY,
278+  GROWTH
279 }
280\ No newline at end of file
281diff --git a/tests/Formulas/RangeTest.ts b/tests/Formulas/RangeTest.ts
282index d7604d3..b0007a5 100644
283--- a/tests/Formulas/RangeTest.ts
284+++ b/tests/Formulas/RangeTest.ts
285@@ -1,5 +1,6 @@
286 import {
287-  FREQUENCY
288+  FREQUENCY,
289+  GROWTH
290 } from "../../src/Formulas/Range";
291 import {
292   assertArrayEquals,
293@@ -9,7 +10,6 @@ import {
294 import * as ERRORS from "../../src/Errors";
295 
296 
297-
298 test("FREQUENCY", function(){
299   assertArrayEquals(FREQUENCY([10, 2, 3, 44, 1, 2], 22), [5, 1]);
300   assertArrayEquals(FREQUENCY([10, 2, 3, 44, 1, 2], [22]), [5, 1]);
301@@ -25,4 +25,32 @@ test("FREQUENCY", function(){
302   catchAndAssertEquals(function() {
303     FREQUENCY([10, 2, 3, 44, 1, [], 2], 22);
304   }, ERRORS.REF_ERROR);
305+});
306+
307+
308+test("GROWTH", function(){
309+  assertArrayEquals(GROWTH(
310+    [15.53, 19.99, 20.43, 21.18, 25.93, 30.00, 30.00, 34.01, 36.47],
311+    [1, 2, 3, 4, 5, 6, 7, 8, 9],
312+    [10, 11, 12]
313+  ), [41.740521723275876, 46.22712349335047, 51.19598074591973]);
314+  assertArrayEquals(GROWTH(
315+    [15.53, 19.99, 20.43, 21.18, 25.93, [30.00, 30.00, 34.01], 36.47],
316+    [1, 2, 3, 4, 5, 6, 7, 8, 9],
317+    [10, 11, 12]
318+  ), [41.740521723275876, 46.22712349335047, 51.19598074591973]);
319+  catchAndAssertEquals(function() {
320+    GROWTH(
321+      [15.53, 19.99, 20.43, 21.18, "25.93", 30.00, 30.00, 34.01, 36.47],
322+      [1, 2, 3, 4, 5, 6, 7, 8, 9],
323+      [10, 11, 12]
324+    );
325+  }, ERRORS.VALUE_ERROR);
326+  catchAndAssertEquals(function() {
327+    GROWTH(
328+      [15.53, 19.99, 20.43, 21.18, [25.93, 30.00], [], 30.00, 34.01, 36.47],
329+      [1, 2, 3, 4, 5, 6, 7, 8, 9],
330+      [10, 11, 12]
331+    );
332+  }, ERRORS.REF_ERROR);
333 });
334\ No newline at end of file
335diff --git a/tests/SheetFormulaTest.ts b/tests/SheetFormulaTest.ts
336index fe0f265..81a39d3 100644
337--- a/tests/SheetFormulaTest.ts
338+++ b/tests/SheetFormulaTest.ts
339@@ -679,11 +679,13 @@ test("Sheet FACTDOUBLE", function(){
340   assertFormulaEquals('=FACTDOUBLE(7)', 105);
341 });
342 
343-
344 test("Sheet FREQUENCY", function(){
345   assertFormulaEqualsArray('=FREQUENCY([10, 2, 3, 44, 1, 2], 22)', [5, 1]);
346 });
347 
348+test("Sheet GROWTH", function(){
349+  assertFormulaEqualsArray('=GROWTH([15.53, 19.99, 20.43, 21.18, 25.93, 30.00, 30.00, 34.01, 36.47],[1, 2, 3, 4, 5, 6, 7, 8, 9],[10, 11, 12])', [41.740521723275876, 46.22712349335047, 51.19598074591973]);
350+});
351 
352 test("Sheet *", function(){
353   assertFormulaEquals('= 10 * 10', 100);