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);