spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
[PERCENTRANK, PERCENTRANK.INC] formulas added and tested
author
Ben Vogt <[email protected]>
date
2017-07-03 15:47:25
stats
10 file(s) changed, 169 insertions(+), 11 deletions(-)
files
DOCS.md
TODO.md
dist/Formulas/AllFormulas.js
dist/Formulas/Statistical.js
dist/Utilities/Filter.js
src/Formulas/AllFormulas.ts
src/Formulas/Statistical.ts
src/Utilities/Filter.ts
tests/Formulas/StatisticalTest.ts
tests/SheetFormulaTest.ts
  1diff --git a/DOCS.md b/DOCS.md
  2index 86d963c..ad58531 100644
  3--- a/DOCS.md
  4+++ b/DOCS.md
  5@@ -1711,13 +1711,24 @@
  6 ### POISSON 
  7 
  8 ```
  9-  Returns the Poisson distribution for the given number. 
 10+  Returns the Poisson distribution for the given number. Functions the same as POISSON.DIST. 
 11 @param x - Number to use. 
 12 @param meanValue - The middle value for the Poisson distribution. 
 13 @param cumulative - [OPTIONAL] - 0 calculates the density function, 1 calculates the distribution. Defaults to 0. 
 14 @returns {number} 
 15 @constructor
 16 ```
 17+
 18+### RCENTRANK 
 19+
 20+```
 21+  Returns the percentage rank (percentile) of the given value in a sample. Functions the same as PERCENTRANK.INC. 
 22+@param data - The array or range of data in the sample. 
 23+@param x - The value. 
 24+@param significance - [OPTIONAL] - The number of significant digits to use in the calculation. 
 25+@returns {number} 
 26+@constructor
 27+```
 28 ## Text
 29 
 30 
 31diff --git a/TODO.md b/TODO.md
 32index 0f59ee1..88112eb 100644
 33--- a/TODO.md
 34+++ b/TODO.md
 35@@ -73,9 +73,7 @@ For example 64 tbs to a qt.
 36 * NORMINV
 37 * NORMSDIST
 38 * NORMSINV
 39-* PERCENTRANK
 40 * PERCENTRANK.EXC
 41-* PERCENTRANK.INC
 42 * PERMUT
 43 * PROB
 44 * RANK
 45diff --git a/dist/Formulas/AllFormulas.js b/dist/Formulas/AllFormulas.js
 46index 1caa9d5..adde802 100644
 47--- a/dist/Formulas/AllFormulas.js
 48+++ b/dist/Formulas/AllFormulas.js
 49@@ -156,6 +156,7 @@ exports.KURT = Statistical_1.KURT;
 50 exports.INTERCEPT = Statistical_1.INTERCEPT;
 51 exports.FORECAST = Statistical_1.FORECAST;
 52 exports.POISSON = Statistical_1.POISSON;
 53+exports.PERCENTRANK = Statistical_1.PERCENTRANK;
 54 var Text_1 = require("./Text");
 55 exports.ARABIC = Text_1.ARABIC;
 56 exports.CHAR = Text_1.CHAR;
 57@@ -196,6 +197,7 @@ var __COMPLEX = {
 58     "F.DIST": Statistical_1.FDIST$LEFTTAILED,
 59     "NETWORKDAYS.INTL": Date_1.NETWORKDAYS$INTL,
 60     "WORKDAY.INTL": Date_1.WORKDAY$INTL,
 61-    "POISSON.DIST": Statistical_1.POISSON
 62+    "POISSON.DIST": Statistical_1.POISSON,
 63+    "PERCENTRANK.INC": Statistical_1.PERCENTRANK
 64 };
 65 exports.__COMPLEX = __COMPLEX;
 66diff --git a/dist/Formulas/Statistical.js b/dist/Formulas/Statistical.js
 67index 3c2d373..5e24370 100644
 68--- a/dist/Formulas/Statistical.js
 69+++ b/dist/Formulas/Statistical.js
 70@@ -933,7 +933,7 @@ var FORECAST = function (x, rangeY, rangeX) {
 71 };
 72 exports.FORECAST = FORECAST;
 73 /**
 74- * Returns the Poisson distribution for the given number.
 75+ * Returns the Poisson distribution for the given number. Functions the same as POISSON.DIST.
 76  * @param x - Number to use.
 77  * @param meanValue - The middle value for the Poisson distribution.
 78  * @param cumulative - [OPTIONAL] - 0 calculates the density function, 1 calculates the distribution. Defaults to 0.
 79@@ -970,3 +970,49 @@ var POISSON = function (x, meanValue, cumulative) {
 80     return (cumulative) ? poissonCDF(x, meanValue) : poissonPDF(x, meanValue);
 81 };
 82 exports.POISSON = POISSON;
 83+/**
 84+ * Returns the percentage rank (percentile) of the given value in a sample. Functions the same as PERCENTRANK.INC.
 85+ * @param data - The array or range of data in the sample.
 86+ * @param x - The value.
 87+ * @param significance - [OPTIONAL] - The number of significant digits to use in the calculation.
 88+ * @returns {number}
 89+ * @constructor
 90+ */
 91+var PERCENTRANK = function (data, x, significance) {
 92+    ArgsChecker_1.ArgsChecker.checkLengthWithin(arguments, 2, 3, "PERCENTRANK");
 93+    data = Filter_1.Filter.flattenAndThrow(data).map(TypeConverter_1.TypeConverter.valueToNumber).sort(function (a, b) {
 94+        return a - b;
 95+    });
 96+    x = TypeConverter_1.TypeConverter.firstValueAsNumber(x);
 97+    var uniques = Filter_1.Filter.unique(data);
 98+    var n = data.length;
 99+    var m = uniques.length;
100+    if (x < uniques[0] || x > uniques[m - 1]) {
101+        throw new Errors_1.NAError("PERCENTRANK does not have valid input data.");
102+    }
103+    if (m === 1 && uniques[0] === x) {
104+        return 1;
105+    }
106+    significance = (typeof significance === 'undefined') ? 3 : TypeConverter_1.TypeConverter.firstValueAsNumber(significance);
107+    var power = Math.pow(10, significance);
108+    var result = 0;
109+    var match = false;
110+    var i = 0;
111+    while (!match && i < m) {
112+        if (x === uniques[i]) {
113+            result = data.indexOf(uniques[i]) / (n - 1);
114+            match = true;
115+        }
116+        else if (x >= uniques[i] && (x < uniques[i + 1] || i === m - 1)) {
117+            result = (data.indexOf(uniques[i]) + (x - uniques[i]) / (uniques[i + 1] - uniques[i])) / (n - 1);
118+            match = true;
119+        }
120+        i++;
121+    }
122+    var v = Math.floor(result * power) / power;
123+    if (isNaN(v)) {
124+        throw new Errors_1.NAError("PERCENTRANK does not have valid input data.");
125+    }
126+    return v;
127+};
128+exports.PERCENTRANK = PERCENTRANK;
129diff --git a/dist/Utilities/Filter.js b/dist/Utilities/Filter.js
130index e565e62..48ce227 100644
131--- a/dist/Utilities/Filter.js
132+++ b/dist/Utilities/Filter.js
133@@ -78,6 +78,20 @@ var Filter = (function () {
134         }
135         return toReturn;
136     };
137+    /**
138+     * Returns an array as unique values.
139+     * @param arr - to filter down to uniques.
140+     * @returns {Array}
141+     */
142+    Filter.unique = function (arr) {
143+        var a = [];
144+        for (var i = 0, l = arr.length; i < l; i++) {
145+            if (a.indexOf(arr[i]) === -1 && arr[i] !== '') {
146+                a.push(arr[i]);
147+            }
148+        }
149+        return a;
150+    };
151     return Filter;
152 }());
153 exports.Filter = Filter;
154diff --git a/src/Formulas/AllFormulas.ts b/src/Formulas/AllFormulas.ts
155index f50780a..14416fa 100644
156--- a/src/Formulas/AllFormulas.ts
157+++ b/src/Formulas/AllFormulas.ts
158@@ -161,7 +161,8 @@ import {
159   KURT,
160   INTERCEPT,
161   FORECAST,
162-  POISSON
163+  POISSON,
164+  PERCENTRANK
165 } from "./Statistical";
166 import {
167   ARABIC,
168@@ -206,7 +207,8 @@ var __COMPLEX = {
169   "F.DIST": FDIST$LEFTTAILED,
170   "NETWORKDAYS.INTL": NETWORKDAYS$INTL,
171   "WORKDAY.INTL": WORKDAY$INTL,
172-  "POISSON.DIST": POISSON
173+  "POISSON.DIST": POISSON,
174+  "PERCENTRANK.INC": PERCENTRANK
175 };
176 
177 export {
178@@ -392,5 +394,6 @@ export {
179   ISEMAIL,
180   ISURL,
181   LINEST,
182-  POISSON
183+  POISSON,
184+  PERCENTRANK
185 }
186\ No newline at end of file
187diff --git a/src/Formulas/Statistical.ts b/src/Formulas/Statistical.ts
188index c813c6a..b170058 100644
189--- a/src/Formulas/Statistical.ts
190+++ b/src/Formulas/Statistical.ts
191@@ -922,7 +922,7 @@ var FORECAST = function (x, rangeY, rangeX) {
192 
193 
194 /**
195- * Returns the Poisson distribution for the given number.
196+ * Returns the Poisson distribution for the given number. Functions the same as POISSON.DIST.
197  * @param x - Number to use.
198  * @param meanValue - The middle value for the Poisson distribution.
199  * @param cumulative - [OPTIONAL] - 0 calculates the density function, 1 calculates the distribution. Defaults to 0.
200@@ -962,6 +962,52 @@ var POISSON = function (x, meanValue, cumulative?) {
201 };
202 
203 
204+/**
205+ * Returns the percentage rank (percentile) of the given value in a sample. Functions the same as PERCENTRANK.INC.
206+ * @param data - The array or range of data in the sample.
207+ * @param x - The value.
208+ * @param significance - [OPTIONAL] - The number of significant digits to use in the calculation.
209+ * @returns {number}
210+ * @constructor
211+ */
212+var PERCENTRANK = function (data, x, significance?) {
213+  ArgsChecker.checkLengthWithin(arguments, 2, 3, "PERCENTRANK");
214+  data = Filter.flattenAndThrow(data).map(TypeConverter.valueToNumber).sort(function (a, b) {
215+    return a - b;
216+  });
217+  x = TypeConverter.firstValueAsNumber(x);
218+  var uniques = Filter.unique(data);
219+  var n = data.length;
220+  var m = uniques.length;
221+  if (x < uniques[0] || x > uniques[m - 1]) {
222+    throw new NAError("PERCENTRANK does not have valid input data.");
223+  }
224+  if (m === 1 && uniques[0] === x) {
225+    return 1;
226+  }
227+  significance = (typeof significance === 'undefined') ? 3 : TypeConverter.firstValueAsNumber(significance);
228+  var power = Math.pow(10, significance);
229+  var result = 0;
230+  var match = false;
231+  var i = 0;
232+  while (!match && i < m) {
233+    if (x === uniques[i]) {
234+      result = data.indexOf(uniques[i]) / (n - 1);
235+      match = true;
236+    } else if (x >= uniques[i] && (x < uniques[i + 1] || i === m - 1)) {
237+      result = (data.indexOf(uniques[i]) + (x - uniques[i]) / (uniques[i + 1] - uniques[i])) / (n - 1);
238+      match = true;
239+    }
240+    i++;
241+  }
242+  var v = Math.floor(result * power) / power;
243+  if (isNaN(v)) {
244+    throw new NAError("PERCENTRANK does not have valid input data.");
245+  }
246+  return v;
247+};
248+
249+
250 export {
251   AVERAGE,
252   AVERAGEA,
253@@ -996,5 +1042,6 @@ export {
254   KURT,
255   INTERCEPT,
256   FORECAST,
257-  POISSON
258+  POISSON,
259+  PERCENTRANK
260 }
261\ No newline at end of file
262diff --git a/src/Utilities/Filter.ts b/src/Utilities/Filter.ts
263index 676ea81..4ee6015 100644
264--- a/src/Utilities/Filter.ts
265+++ b/src/Utilities/Filter.ts
266@@ -80,6 +80,21 @@ class Filter {
267     }
268     return toReturn;
269   }
270+
271+  /**
272+   * Returns an array as unique values.
273+   * @param arr - to filter down to uniques.
274+   * @returns {Array}
275+   */
276+  static unique(arr: Array<any>) : Array<any> {
277+    var a = [];
278+    for (var i = 0, l = arr.length; i < l; i++) {
279+      if (a.indexOf(arr[i]) === -1 && arr[i] !== '') {
280+        a.push(arr[i]);
281+      }
282+    }
283+    return a;
284+  }
285 }
286 
287 export {
288diff --git a/tests/Formulas/StatisticalTest.ts b/tests/Formulas/StatisticalTest.ts
289index 19beb83..84dae7f 100644
290--- a/tests/Formulas/StatisticalTest.ts
291+++ b/tests/Formulas/StatisticalTest.ts
292@@ -32,7 +32,8 @@ import {
293   KURT,
294   INTERCEPT,
295   FORECAST,
296-  POISSON
297+  POISSON,
298+  PERCENTRANK
299 } from "../../src/Formulas/Statistical";
300 import * as ERRORS from "../../src/Errors";
301 import {
302@@ -716,3 +717,19 @@ test("POISSON", function() {
303     POISSON.apply(this, [1]);
304   }, ERRORS.NA_ERROR);
305 });
306+
307+
308+test("PERCENTRANK", function() {
309+  assertEquals(PERCENTRANK([1, 5, 3, 7, 3, 2, 6, 8, 4, 9, 0, 3, 1], 4), 0.583);
310+  assertEquals(PERCENTRANK([1, 5, 3, 7, 3, 2, 6, 8, 4, 9, 0, 3, 1], 5), 0.666);
311+  assertEquals(PERCENTRANK([1, 5, 3, 7, 3, 2, 6, 8, 4, 9, 0, 3, 1], 0), 0);
312+  assertEquals(PERCENTRANK([1, 5, 3, 7, 3, 2, 6, 8, 4, 9, 0, 3, 1, -4], -1), 0.057);
313+  assertEquals(PERCENTRANK([1], 1), 1);
314+  assertEquals(PERCENTRANK([44], 44), 1);
315+  catchAndAssertEquals(function() {
316+    PERCENTRANK([1, 5, 3, 7, 3, 2, 6, 8, 4, 9, 0, 3, 1], 10);
317+  }, ERRORS.NA_ERROR);
318+  catchAndAssertEquals(function() {
319+    PERCENTRANK([1, 5, 3, 7, 3, 2, 6, 8, 4, 9, 0, 3, 1], -1);
320+  }, ERRORS.NA_ERROR);
321+});
322\ No newline at end of file
323diff --git a/tests/SheetFormulaTest.ts b/tests/SheetFormulaTest.ts
324index cfa25d8..8a8a1ed 100644
325--- a/tests/SheetFormulaTest.ts
326+++ b/tests/SheetFormulaTest.ts
327@@ -772,6 +772,11 @@ test("Sheet POISSON, POISSON.DIST", function(){
328   assertFormulaEquals('=POISSON.DIST(3, 5, true)', 0.26502591529736175);
329 });
330 
331+test("Sheet PERCENTRANK, PERCENTRANK.INC", function(){
332+  assertFormulaEquals('=PERCENTRANK([1], 1)', 1);
333+  assertFormulaEquals('=PERCENTRANK.INC([1], 1)', 1);
334+});
335+
336 test("Sheet *", function(){
337   assertFormulaEquals('= 10 * 10', 100);
338   assertFormulaEquals('= 10 * 0', 0);