UNPKG

8.47 kBJavaScriptView Raw
1import { isBigNumber, isCollection, isNumber } from '../../utils/is'
2import { isInteger } from '../../utils/number'
3import { flatten } from '../../utils/array'
4import { factory } from '../../utils/factory'
5
6const name = 'quantileSeq'
7const dependencies = ['typed', 'add', 'multiply', 'partitionSelect', 'compare']
8
9export const createQuantileSeq = /* #__PURE__ */ factory(name, dependencies, ({ typed, add, multiply, partitionSelect, compare }) => {
10 /**
11 * Compute the prob order quantile of a matrix or a list with values.
12 * The sequence is sorted and the middle value is returned.
13 * Supported types of sequence values are: Number, BigNumber, Unit
14 * Supported types of probability are: Number, BigNumber
15 *
16 * In case of a (multi dimensional) array or matrix, the prob order quantile
17 * of all elements will be calculated.
18 *
19 * Syntax:
20 *
21 * math.quantileSeq(A, prob[, sorted])
22 * math.quantileSeq(A, [prob1, prob2, ...][, sorted])
23 * math.quantileSeq(A, N[, sorted])
24 *
25 * Examples:
26 *
27 * math.quantileSeq([3, -1, 5, 7], 0.5) // returns 4
28 * math.quantileSeq([3, -1, 5, 7], [1/3, 2/3]) // returns [3, 5]
29 * math.quantileSeq([3, -1, 5, 7], 2) // returns [3, 5]
30 * math.quantileSeq([-1, 3, 5, 7], 0.5, true) // returns 4
31 *
32 * See also:
33 *
34 * median, mean, min, max, sum, prod, std, variance
35 *
36 * @param {Array, Matrix} data A single matrix or Array
37 * @param {Number, BigNumber, Array} probOrN prob is the order of the quantile, while N is
38 * the amount of evenly distributed steps of
39 * probabilities; only one of these options can
40 * be provided
41 * @param {Boolean} sorted=false is data sorted in ascending order
42 * @return {Number, BigNumber, Unit, Array} Quantile(s)
43 */
44 function quantileSeq (data, probOrN, sorted) {
45 let probArr, dataArr, one
46
47 if (arguments.length < 2 || arguments.length > 3) {
48 throw new SyntaxError('Function quantileSeq requires two or three parameters')
49 }
50
51 if (isCollection(data)) {
52 sorted = sorted || false
53 if (typeof sorted === 'boolean') {
54 dataArr = data.valueOf()
55 if (isNumber(probOrN)) {
56 if (probOrN < 0) {
57 throw new Error('N/prob must be non-negative')
58 }
59
60 if (probOrN <= 1) {
61 // quantileSeq([a, b, c, d, ...], prob[,sorted])
62 return _quantileSeq(dataArr, probOrN, sorted)
63 }
64
65 if (probOrN > 1) {
66 // quantileSeq([a, b, c, d, ...], N[,sorted])
67 if (!isInteger(probOrN)) {
68 throw new Error('N must be a positive integer')
69 }
70
71 const nPlusOne = probOrN + 1
72 probArr = new Array(probOrN)
73 for (let i = 0; i < probOrN;) {
74 probArr[i] = _quantileSeq(dataArr, (++i) / nPlusOne, sorted)
75 }
76 return probArr
77 }
78 }
79
80 if (isBigNumber(probOrN)) {
81 const BigNumber = probOrN.constructor
82
83 if (probOrN.isNegative()) {
84 throw new Error('N/prob must be non-negative')
85 }
86
87 one = new BigNumber(1)
88
89 if (probOrN.lte(one)) {
90 // quantileSeq([a, b, c, d, ...], prob[,sorted])
91 return new BigNumber(_quantileSeq(dataArr, probOrN, sorted))
92 }
93
94 if (probOrN.gt(one)) {
95 // quantileSeq([a, b, c, d, ...], N[,sorted])
96 if (!probOrN.isInteger()) {
97 throw new Error('N must be a positive integer')
98 }
99
100 // largest possible Array length is 2^32-1
101 // 2^32 < 10^15, thus safe conversion guaranteed
102 const intN = probOrN.toNumber()
103 if (intN > 4294967295) {
104 throw new Error('N must be less than or equal to 2^32-1, as that is the maximum length of an Array')
105 }
106
107 const nPlusOne = new BigNumber(intN + 1)
108 probArr = new Array(intN)
109 for (let i = 0; i < intN;) {
110 probArr[i] = new BigNumber(_quantileSeq(dataArr, new BigNumber(++i).div(nPlusOne), sorted))
111 }
112 return probArr
113 }
114 }
115
116 if (Array.isArray(probOrN)) {
117 // quantileSeq([a, b, c, d, ...], [prob1, prob2, ...][,sorted])
118 probArr = new Array(probOrN.length)
119 for (let i = 0; i < probArr.length; ++i) {
120 const currProb = probOrN[i]
121 if (isNumber(currProb)) {
122 if (currProb < 0 || currProb > 1) {
123 throw new Error('Probability must be between 0 and 1, inclusive')
124 }
125 } else if (isBigNumber(currProb)) {
126 one = new currProb.constructor(1)
127 if (currProb.isNegative() || currProb.gt(one)) {
128 throw new Error('Probability must be between 0 and 1, inclusive')
129 }
130 } else {
131 throw new TypeError('Unexpected type of argument in function quantileSeq') // FIXME: becomes redundant when converted to typed-function
132 }
133
134 probArr[i] = _quantileSeq(dataArr, currProb, sorted)
135 }
136 return probArr
137 }
138
139 throw new TypeError('Unexpected type of argument in function quantileSeq') // FIXME: becomes redundant when converted to typed-function
140 }
141
142 throw new TypeError('Unexpected type of argument in function quantileSeq') // FIXME: becomes redundant when converted to typed-function
143 }
144
145 throw new TypeError('Unexpected type of argument in function quantileSeq') // FIXME: becomes redundant when converted to typed-function
146 }
147
148 /**
149 * Calculate the prob order quantile of an n-dimensional array.
150 *
151 * @param {Array} array
152 * @param {Number, BigNumber} prob
153 * @param {Boolean} sorted
154 * @return {Number, BigNumber, Unit} prob order quantile
155 * @private
156 */
157 function _quantileSeq (array, prob, sorted) {
158 const flat = flatten(array)
159 const len = flat.length
160 if (len === 0) {
161 throw new Error('Cannot calculate quantile of an empty sequence')
162 }
163
164 if (isNumber(prob)) {
165 const index = prob * (len - 1)
166 const fracPart = index % 1
167 if (fracPart === 0) {
168 const value = sorted ? flat[index] : partitionSelect(flat, index)
169
170 validate(value)
171
172 return value
173 }
174
175 const integerPart = Math.floor(index)
176
177 let left
178 let right
179 if (sorted) {
180 left = flat[integerPart]
181 right = flat[integerPart + 1]
182 } else {
183 right = partitionSelect(flat, integerPart + 1)
184
185 // max of partition is kth largest
186 left = flat[integerPart]
187 for (let i = 0; i < integerPart; ++i) {
188 if (compare(flat[i], left) > 0) {
189 left = flat[i]
190 }
191 }
192 }
193
194 validate(left)
195 validate(right)
196
197 // Q(prob) = (1-f)*A[floor(index)] + f*A[floor(index)+1]
198 return add(multiply(left, 1 - fracPart), multiply(right, fracPart))
199 }
200
201 // If prob is a BigNumber
202 let index = prob.times(len - 1)
203 if (index.isInteger()) {
204 index = index.toNumber()
205 const value = sorted ? flat[index] : partitionSelect(flat, index)
206
207 validate(value)
208
209 return value
210 }
211
212 const integerPart = index.floor()
213 const fracPart = index.minus(integerPart)
214 const integerPartNumber = integerPart.toNumber()
215
216 let left
217 let right
218 if (sorted) {
219 left = flat[integerPartNumber]
220 right = flat[integerPartNumber + 1]
221 } else {
222 right = partitionSelect(flat, integerPartNumber + 1)
223
224 // max of partition is kth largest
225 left = flat[integerPartNumber]
226 for (let i = 0; i < integerPartNumber; ++i) {
227 if (compare(flat[i], left) > 0) {
228 left = flat[i]
229 }
230 }
231 }
232
233 validate(left)
234 validate(right)
235
236 // Q(prob) = (1-f)*A[floor(index)] + f*A[floor(index)+1]
237 const one = new fracPart.constructor(1)
238 return add(multiply(left, one.minus(fracPart)), multiply(right, fracPart))
239 }
240
241 /**
242 * Check if array value types are valid, throw error otherwise.
243 * @param {number | BigNumber | Unit} x
244 * @param {number | BigNumber | Unit} x
245 * @private
246 */
247 const validate = typed({
248 'number | BigNumber | Unit': function (x) {
249 return x
250 }
251 })
252
253 return quantileSeq
254})