spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
[LINEST] formula added and tested
author
Ben Vogt <[email protected]>
date
2017-07-02 17:12:57
stats
8 file(s) changed, 151 insertions(+), 4 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 204a770..4ccd508 100644
  3--- a/DOCS.md
  4+++ b/DOCS.md
  5@@ -1380,6 +1380,16 @@
  6 @returns {Array} 
  7 @constructor TODO: Returns RowArray (values stacked in X-direction)
  8 ```
  9+
 10+### LINEST 
 11+
 12+```
 13+  Returns the parameters of a linear trend. 
 14+@param dataY - The range of data representing Y values. 
 15+@param dataX - The range of data representing X values. 
 16+@returns {number[]} 
 17+@constructor
 18+```
 19 ## Statistical
 20 
 21 
 22diff --git a/TODO.md b/TODO.md
 23index fc591c6..4c3340b 100644
 24--- a/TODO.md
 25+++ b/TODO.md
 26@@ -118,7 +118,6 @@ For example 64 tbs to a qt.
 27 * T
 28 * TEXT
 29 * VALUE
 30-* LINEST
 31 * LOGEST
 32 * MDETERM
 33 * MINVERSE
 34diff --git a/dist/Formulas/AllFormulas.js b/dist/Formulas/AllFormulas.js
 35index 5916e90..2b7c47a 100644
 36--- a/dist/Formulas/AllFormulas.js
 37+++ b/dist/Formulas/AllFormulas.js
 38@@ -76,6 +76,7 @@ exports.FACTDOUBLE = Math_1.FACTDOUBLE;
 39 var Range_1 = require("./Range");
 40 exports.FREQUENCY = Range_1.FREQUENCY;
 41 exports.GROWTH = Range_1.GROWTH;
 42+exports.LINEST = Range_1.LINEST;
 43 var Info_1 = require("./Info");
 44 exports.NA = Info_1.NA;
 45 exports.ISTEXT = Info_1.ISTEXT;
 46diff --git a/dist/Formulas/Range.js b/dist/Formulas/Range.js
 47index 559f833..dba186f 100644
 48--- a/dist/Formulas/Range.js
 49+++ b/dist/Formulas/Range.js
 50@@ -4,6 +4,7 @@ var ArgsChecker_1 = require("../Utilities/ArgsChecker");
 51 var Filter_1 = require("../Utilities/Filter");
 52 var TypeConverter_1 = require("../Utilities/TypeConverter");
 53 var Errors_1 = require("../Errors");
 54+var MathHelpers_1 = require("../Utilities/MathHelpers");
 55 /**
 56  * Calculates the frequency distribution of a range into specified classes or "bins".
 57  * @param range - to get frequency for.
 58@@ -70,6 +71,7 @@ exports.FREQUENCY = FREQUENCY;
 59  * TODO: Returns RowArray (values stacked in X-direction)
 60  */
 61 var GROWTH = function (knownY, knownX, newX, shouldUseConstant) {
 62+    ArgsChecker_1.ArgsChecker.checkLengthWithin(arguments, 1, 4, "GROWTH");
 63     // Credits: Ilmari Karonen, FormulaJs (https://github.com/sutoiku/formula.js/)
 64     knownY = Filter_1.Filter.flattenAndThrow(knownY).map(function (value) {
 65         if (typeof value !== "number") {
 66@@ -131,3 +133,38 @@ var GROWTH = function (knownY, knownX, newX, shouldUseConstant) {
 67     return new_y;
 68 };
 69 exports.GROWTH = GROWTH;
 70+/**
 71+ * Returns the parameters of a linear trend.
 72+ * @param dataY - The range of data representing Y values.
 73+ * @param dataX - The range of data representing X values.
 74+ * @returns {number[]}
 75+ * @constructor
 76+ */
 77+var LINEST = function (dataY, dataX) {
 78+    ArgsChecker_1.ArgsChecker.checkLength(arguments, 2, "LINEST");
 79+    var rangeY = Filter_1.Filter.flattenAndThrow(dataY).map(function (value) {
 80+        return TypeConverter_1.TypeConverter.valueToNumber(value);
 81+    });
 82+    var rangeX = Filter_1.Filter.flattenAndThrow(dataX).map(function (value) {
 83+        return TypeConverter_1.TypeConverter.valueToNumber(value);
 84+    });
 85+    if (rangeX.length < 2) {
 86+        throw new Errors_1.NAError("LINEST requires more data points. Expected: 2, found: " + rangeX.length + ".");
 87+    }
 88+    if (rangeY.length < 2) {
 89+        throw new Errors_1.NAError("LINEST requires more data points. Expected: 2, found: " + rangeY.length + ".");
 90+    }
 91+    var xMean = MathHelpers_1.mean(rangeX);
 92+    var yMean = MathHelpers_1.mean(rangeY);
 93+    var n = rangeX.length;
 94+    var num = 0;
 95+    var den = 0;
 96+    for (var i = 0; i < n; i++) {
 97+        num += (rangeX[i] - xMean) * (rangeY[i] - yMean);
 98+        den += Math.pow(rangeX[i] - xMean, 2);
 99+    }
100+    var m = num / den;
101+    var b = yMean - m * xMean;
102+    return [m, b];
103+};
104+exports.LINEST = LINEST;
105diff --git a/src/Formulas/AllFormulas.ts b/src/Formulas/AllFormulas.ts
106index 0bf1855..2aadb28 100644
107--- a/src/Formulas/AllFormulas.ts
108+++ b/src/Formulas/AllFormulas.ts
109@@ -74,7 +74,8 @@ import {
110 } from "./Math";
111 import {
112   FREQUENCY,
113-  GROWTH
114+  GROWTH,
115+  LINEST
116 } from "./Range";
117 import {
118   NA,
119@@ -387,5 +388,6 @@ export {
120   IPMT,
121   FV,
122   ISEMAIL,
123-  ISURL
124+  ISURL,
125+  LINEST
126 }
127\ No newline at end of file
128diff --git a/src/Formulas/Range.ts b/src/Formulas/Range.ts
129index 22f8e28..9d4a21a 100644
130--- a/src/Formulas/Range.ts
131+++ b/src/Formulas/Range.ts
132@@ -1,9 +1,18 @@
133 import {
134   ArgsChecker
135 } from "../Utilities/ArgsChecker";
136-import {Filter} from "../Utilities/Filter";
137-import {TypeConverter} from "../Utilities/TypeConverter";
138-import {ValueError} from "../Errors";
139+import {
140+  Filter
141+} from "../Utilities/Filter";
142+import {
143+  TypeConverter
144+} from "../Utilities/TypeConverter";
145+import {
146+  ValueError, NAError
147+} from "../Errors";
148+import {
149+  mean
150+} from "../Utilities/MathHelpers";
151 
152 
153 /**
154@@ -72,6 +81,7 @@ var FREQUENCY = function (range, bins) : Array<number> {
155  * TODO: Returns RowArray (values stacked in X-direction)
156  */
157 var GROWTH = function (knownY, knownX?, newX?, shouldUseConstant?) {
158+  ArgsChecker.checkLengthWithin(arguments, 1, 4, "GROWTH");
159   // Credits: Ilmari Karonen, FormulaJs (https://github.com/sutoiku/formula.js/)
160 
161   knownY = Filter.flattenAndThrow(knownY).map(function (value) {
162@@ -137,7 +147,46 @@ var GROWTH = function (knownY, knownX?, newX?, shouldUseConstant?) {
163   return new_y;
164 };
165 
166+/**
167+ * Returns the parameters of a linear trend.
168+ * @param dataY - The range of data representing Y values.
169+ * @param dataX - The range of data representing X values.
170+ * @returns {number[]}
171+ * @constructor
172+ */
173+var LINEST = function (dataY, dataX) {
174+  ArgsChecker.checkLength(arguments, 2, "LINEST");
175+  var rangeY = Filter.flattenAndThrow(dataY).map(function (value) {
176+    return TypeConverter.valueToNumber(value);
177+  });
178+  var rangeX = Filter.flattenAndThrow(dataX).map(function (value) {
179+    return TypeConverter.valueToNumber(value);
180+  });
181+
182+  if (rangeX.length < 2) {
183+    throw new NAError("LINEST requires more data points. Expected: 2, found: " + rangeX.length + ".");
184+  }
185+  if (rangeY.length < 2) {
186+    throw new NAError("LINEST requires more data points. Expected: 2, found: " + rangeY.length + ".");
187+  }
188+
189+  var xMean = mean(rangeX);
190+  var yMean = mean(rangeY);
191+  var n = rangeX.length;
192+  var num = 0;
193+  var den = 0;
194+  for (var i = 0; i < n; i++) {
195+    num += (rangeX[i] - xMean) * (rangeY[i] - yMean);
196+    den += Math.pow(rangeX[i] - xMean, 2);
197+  }
198+  var m = num / den;
199+  var b = yMean - m * xMean;
200+  return [m, b];
201+};
202+
203+
204 export {
205   FREQUENCY,
206-  GROWTH
207+  GROWTH,
208+  LINEST
209 }
210\ No newline at end of file
211diff --git a/tests/Formulas/RangeTest.ts b/tests/Formulas/RangeTest.ts
212index b0007a5..e0efeca 100644
213--- a/tests/Formulas/RangeTest.ts
214+++ b/tests/Formulas/RangeTest.ts
215@@ -1,6 +1,7 @@
216 import {
217   FREQUENCY,
218-  GROWTH
219+  GROWTH,
220+  LINEST
221 } from "../../src/Formulas/Range";
222 import {
223   assertArrayEquals,
224@@ -53,4 +54,42 @@ test("GROWTH", function(){
225       [10, 11, 12]
226     );
227   }, ERRORS.REF_ERROR);
228+});
229+
230+
231+test("GROWTH", function(){
232+  assertArrayEquals(LINEST(
233+    [15.53, 19.99, 20.43, 21.18, 25.93, 30.00, 30.00, 34.01, 36.47],
234+    [1, 2, 3, 4, 5, 6, 7, 8, 9]
235+  ), [2.563, 13.13388888888889]);
236+  assertArrayEquals(LINEST(
237+    [15.53, 19.99, 20.43, 21.18, 25.93, 30],
238+    [1, 2, 3, 4, 5, 6]
239+  ), [2.5977142857142863,	13.08466666666666]);
240+  catchAndAssertEquals(function() {
241+    LINEST(
242+      [15.53, 19.99, 20.43, 21.18, 25.93, "string", 30],
243+      [1, 2, 3, 4, 5, 6]
244+    );
245+  }, ERRORS.VALUE_ERROR);
246+  catchAndAssertEquals(function() {
247+    LINEST(
248+      [15.53],
249+      [1]
250+    );
251+  }, ERRORS.NA_ERROR);
252+  catchAndAssertEquals(function() {
253+    LINEST.apply(
254+      this,
255+      [[15.53, 19.99, 20.43, 21.18, 25.93, 30],
256+        [1, 2, 3, 4, 5, 6],
257+        "another"]
258+    );
259+  }, ERRORS.NA_ERROR);
260+  catchAndAssertEquals(function() {
261+    LINEST.apply(
262+      this,
263+      [[15.53, 19.99, 20.43, 21.18, 25.93, 30]]
264+    );
265+  }, ERRORS.NA_ERROR);
266 });
267\ No newline at end of file
268diff --git a/tests/SheetFormulaTest.ts b/tests/SheetFormulaTest.ts
269index 2336543..bf28619 100644
270--- a/tests/SheetFormulaTest.ts
271+++ b/tests/SheetFormulaTest.ts
272@@ -763,6 +763,10 @@ test("Sheet ISURL", function(){
273   assertFormulaEquals('=ISURL("example.com")', true);
274 });
275 
276+test("Sheet LINEST", function(){
277+  assertFormulaEqualsArray('=LINEST([15.53, 19.99, 20.43, 21.18, 25.93, 30], [1, 2, 3, 4, 5, 6])', [2.5977142857142863,	13.08466666666666]);
278+});
279+
280 test("Sheet *", function(){
281   assertFormulaEquals('= 10 * 10', 100);
282   assertFormulaEquals('= 10 * 0', 0);