├── .gitignore
├── README.md
├── package.json
├── src
├── arrays
│ └── index.js
├── decomp
│ └── index.js
├── distributions
│ └── index.js
├── misc
│ └── index.js
├── models
│ └── index.js
├── prep
│ └── index.js
├── stat
│ └── index.js
└── tests
│ └── index.js
└── test
├── tests-arrays-heavy.js
├── tests-arrays.js
├── tests-decomp.js
├── tests-distributions.js
├── tests-misc.js
├── tests-models.js
├── tests-prep.js
├── tests-speed.js
├── tests-stat.js
├── tests-temp.js
└── tests-tests.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 | .#package.json
4 | package-lock.json
5 | .DS_Store
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Javascript library for statistics and multivariate data analysis
2 |
3 | A simple library with implementation of most common methods for descriptive and inferential statistics as well as matrix operations, decompositions and projection based methods for multivariate data analysis. The library is currently under development, breaking changes may occur in the coming versions.
4 |
5 | ## What is new
6 |
7 | Version **1.x.x** introduces many breaking changes as the library was almost fully re-written from the scratch. If you used a pre-release version (0.6.1), do not upgrade.
8 |
9 | The documentation below has been also re-written to match the new version.
10 |
11 | ### v. 1.4.0
12 | released 24/09/2024
13 |
14 | * added methods for incomplete beta function, CDF anf quantile function for F-distribution, and corresponding helper methods.
15 |
16 | ### v. 1.3.x
17 |
18 | * contain small bug fixes.
19 |
20 | ### v. 1.3.0
21 | released 22/09/2023
22 |
23 | * improvements to chi-square distribution functions (`qchisq` and `pchisq`), to make better approximation, especially when DoF is large (above 30).
24 |
25 | ### v. 1.2.0
26 | released 15/09/2023
27 |
28 | * improvements to singular value decomposition methods (`svd` and `rsvd`).
29 | * refactoring and small improvements for PCA methods.
30 |
31 |
32 | ## Matrices, vectors and indices
33 |
34 | The values are represented using instances of the following classes:
35 |
36 | * `Vector` is a class for representing sequence of values. The values are stored inside the class instances as `Float64Array`.
37 | * `Matrix` is a class for representing numerical matrices (2D Arrays). The values are stored inside the class instances as `Float64Array`.
38 | * `Index` is a class for representing vectors of indices — integer numbers specifying positions of values in vectors and matrices. The values are stored inside the class instances as `Int32Array`.
39 | * `Factor` is a class for representing categorical variable, which is turned to array of labels for the categories (as strings) and array of indices (as `Uint8Array`).
40 |
41 | These classes and their methods can be imported from `'mdatools/arrays'` module.
42 |
43 | ### Vectors of indices
44 |
45 | The simplest way to create an instance of `Index` class is to use method `index(x)` where `x` is a conventional JavaScript array, e.g. `index([1, 2, 3])`. Alternatively index values can be generated as a sequence using static method `seq(start, end, by)`:
46 |
47 | ```javascript
48 | import { Index } from 'mdatools/arrays';
49 |
50 | const ind1 = Index.seq(1, 5); // returns index([1, 2, 3, 4, 5])
51 | const ind2 = Index.seq(2, 10, 2); // returns index([2, 4, 6, 8, 10])
52 | ```
53 |
54 | There are also two additional static methods for creating index vectors:
55 |
56 | * `Index.fill(v, n)` — creates an index vector of size `n` filled with integer value `v`.
57 | * `Index.ones(n)` — creates an index vector of size `n` filled with ones.
58 |
59 | Values of any index vector can be replicated using class methods (here `ind` is an instance of class `Index`):
60 |
61 | * `ind.rep(n)` — replicates the index vector `n` times.
62 | * `ind.repeach(n)` — replicates each value from the index vector `n` times.
63 | * `ind.sort([decreasing=false])` — sorts indices in `ind`.
64 | * `ind.shuffle()` — shuffles indices in `ind` using Fisher–Yates algorithm.
65 |
66 | All methods above return instance of `Index` as a result. It must be noted that when indices are used for subsetting values in vectors and matrices, it is expected that they start from 1, not 0.
67 |
68 | Two (or more) vectors with indices, e.g. `i1` and `i2` can be concatenated into one by using method `i = c(i1, i2)`.
69 |
70 | ### Class Vector
71 |
72 | The simplest way to create an instance of `Vector` class is to use method `vector(x)` where `x` is a conventional JavaScript array, e.g. `vector([1.1, 2.2, 3.3])`. The values can also be generated using the following static methods:
73 |
74 | * `Vector.zeros(n)` — creates a vector of size `n` filled with zeros.
75 | * `Vector.fill(v, n)` — creates a vector of size `n` filled with value `v`.
76 | * `Vector.ones(n)` — creates a vector of size `n` filled with ones.
77 | * `Vector.rand(n, [a=0], [b=1])` — creates a vector of size `n` filled with random values uniformly distributed between `a` and `b`.
78 | * `Vector.randn(n, [mu=0], [sigma=1])` — creates a vector of size `n` filled with normally distributed random values.
79 | * `Vector.seq(start, end, by)` — creates a sequence of values, similar to the `seq` method for `Index` class.
80 | * `Vector.c(a, b, ...)` — concatenates any amount of numbers, arrays or/and vectors into a vector.
81 |
82 | The `Vector` object object also has class methods `rep(n)` and `repeach(n)` which work similar to the methods for `Index` objects.
83 |
84 | Similar to indices, several vectors can be concatenated into a single vector by using method `c`:
85 |
86 | ```javascript
87 | import {vector, c} from 'mdatools/arrays';
88 |
89 | const v1 = vector([1, 2, 3]);
90 | const v2 = vector([10, 11, 12]);
91 | const v3 = vector([20, 21, 22]);
92 |
93 | const x = c(v1, v2, v3);
94 | ```
95 |
96 | Other class methods are listed below.
97 |
98 | #### Method for subsetting of vectors
99 |
100 | The indices used for subsetting of vectors and matrices, must start with one, not zero. All subsetting methods create a copy of values (not a reference to original values).
101 |
102 | * `x.subset(ind)` — creates a subset of vector `x` by taking values, whose position is specified by vector with indices `ind`.
103 | * `x.slice(start, end)` — creates a subset of vector `x` by taking a sequence of values between the two positions (both are included to the subset).
104 | * `x.copy()` — creates a deep copy of the vector.
105 |
106 | #### Methods for manipulation with vector values
107 |
108 | Following methods do something with vector values, e.g. arithmetic operations, transformations, etc.
109 |
110 | * `x.apply(fun)` — applies function `fun` to every value of `x` and creates a new vector with values returned by the function.
111 | * `x.add(y)` — adds values from another vector or a number.
112 | * `x.subtract(y)` — subtracts values from another vector or a number.
113 | * `x.mult(y)` — multiplies to values from another vector or to a number.
114 | * `x.divide(y)` — divides by values from another vector or by a number.
115 | * `x.dot(y)` — takes a dot (inner) product of two vectors (results in a number).
116 | * `x.sort([decreasing=false])` — sorts values in `x`.
117 | * `x.shuffle()` — shuffles values in `x` using Fisher–Yates algorithm.
118 |
119 | ### Class Factor
120 |
121 | Factors are vectors representing categorical variables. To create an instance of `Factor` use method `factor()` and provide it an array of categories as strings:
122 |
123 | ```javascript
124 | import {factor} from 'mdatools/arrays';
125 |
126 | const f = factor(["red", "green", "red", "green", "red", "green", "blue]);
127 | ```
128 |
129 | Factor has following class methods:
130 |
131 | * `f.which(label)` — returns indices (as instance of `Index` class) of all entries of given label inside the factor.
132 |
133 |
134 |
135 | ### Methods for class Matrix
136 |
137 | The simplest way to create an instance of `Matrix` class is to use method `matrix(x, nrows, ncols)` where `x` is a conventional JavaScript array, e.g. `matrix([1, 2, 3, 4], 2, 2)`. The values can also be generated using the following static methods:
138 |
139 | * `Matrix.zeros(nrows, ncols)` — creates a matrix filled with zeros.
140 | * `Matrix.fill(v, nrows, ncols)` — creates a matrix filled with value `v`.
141 | * `Matrix.ones(nrows, ncols)` — creates a matrix filled with ones.
142 | * `Matrix.rand(nrows, ncols, [a=0], [b=1])` — creates a matrix filled with random values uniformly distributed between `a` and `b`.
143 | * `Matrix.randn(nrows, ncols, [mu=0], [sigma=1])` — creates a matrix filled with normally distributed random values.
144 | * `Matrix.eye(nrows, ncols)` — creates an identity matrix (can be rectangular).
145 | * `Matrix.diagm(x)` — creates a squared matrix with main diagonal filled with values from vector `x`.
146 | * `Matrix.outer(x, y, fun)` — creates a matrix by applying function `fun` to all possible pairs of values from vectors `x` and `y`.
147 |
148 | One can also create a matrix by reshaping a vector as shown below.
149 |
150 |
151 | ```javascript
152 | import { vector, reshape } from 'mdatools/arrays';
153 |
154 | const x = vector([1, 3, 5, 7, 9, 11, 13, 15, 17]);
155 | const Y = reshape(x, 3, 3);
156 | ```
157 | Matrix can be created by merging/binding several vectors as rows (method `rbind()`) or as columns (method `cbind()`) of the matrix:
158 |
159 | ```javascript
160 | import {vector, cbind, rbind} from 'mdatools/arrays';
161 |
162 | const v1 = vector([1, 2, 3]);
163 | const v2 = vector([10, 11, 12]);
164 | const v3 = vector([20, 21, 22]);
165 |
166 | const X1 = cbind(v1, v2, v3);
167 | const X2 = rbind(v1, v2, v3);
168 | ```
169 | Both methods can also bind matrices as well as vectors and matrices.
170 |
171 | #### Method for subsetting of matrices
172 |
173 | The indices in the methods below can be single numbers, conventional Javascript arrays or instances of Index class. They must start with one, not zero. All subsetting methods create a copy of values (not reference to original values).
174 |
175 | * `X.subset(rind, cind)` — creates a subset of matrix `X` by taking values, whose position is specified by vector with row and column indices. If all rows or all columns must be taken just provide an empty array, `[]` as a value for index argument. Indices must start from 1.
176 | * `X.copy()` — creates a deep copy of the matrix object `X`.
177 | * `X.getcolumn(ic)` — returns values of `ic`-th column of `X` as a vector (index must start from 1).
178 | * `X.getrow(ir)` — returns values of `ir`-th row of `X` as a vector (index must start from 1).
179 | * `X.diag()` — returns values from main diagonal of the matrix as a vector.
180 |
181 | One can also replace some of the values in a matrix by providing another matrix with new values as well as row and column indices for the values to replace. In the example below we replace values for all odd rows and columns.
182 |
183 | ```javascript
184 | import { matrix } from 'mdatools/arrays';
185 |
186 | const X = matrix([1, 3, 5, 7, 9, 11, 13, 15, 17], 3, 3);
187 | const Y = matrix([2, 4, 6, 8], 2, 2);
188 |
189 | X.replace(Y, [1, 3], [1, 3]);
190 | ```
191 |
192 | #### Methods for manipulation with matrix values
193 |
194 | Following methods do something with matrix values, e.g. arithmetic operations, transformations, etc.
195 |
196 | * `X.apply(fun, [dims=2])` — applies function `fun` to every row (if `dims=1`), every column (if `dims=2`) or every single value (if `dims=0`) of matrix `X` and returns either a vector or a new matrix with values returned by the function.
197 | * `X.t()` — transposes matrix `X` (swaps columns and rows).
198 | * `X.inv()` — computes inverse of squared matrix `X` (if exists).
199 |
200 | The following methods implement element-wise arithmetic operations with matrices, a matrix and a vector and a matrix and a number.
201 |
202 | * `X.add(Y, [dims=2])` — adds values from another matrix, a vector or a number.
203 | * `X.subtract(Y, [dims=2])` — subtracts values from another matrix, a vector or a number.
204 | * `X.mult(Y, [dims=2])` — multiplies to values from another matrix, a vector or to a number.
205 | * `X.divide(Y, [dims=2])` — divides by values from another matrix, a vector or from by a number.
206 | * `X.dot(Y, [dims=2])` — takes a dot product of two matrices or a matrix and a vector.
207 |
208 | In case if the argument is a vector its length must match one of the matrix dimension (number of rows or columns) and you need to specify this dimension explicitly by providing second argument, `dims`. In the example below we subtract mean from every column of matrix `X` (the statistical functions are introduced later in this document).
209 |
210 | ```javascript
211 | import { matrix } from 'mdatools/arrays';
212 | import { mean } from 'mdatools/stat';
213 |
214 | // create matrix X
215 | const X = matrix([1, 3, 5, 7, 9, 11, 13, 15, 17], 3, 3);
216 |
217 | // compute mean of every column of X
218 | const m = X.apply(mean, 2);
219 |
220 | // mean center columns of X
221 | const Y = X.subtract(m, 2);
222 | ```
223 |
224 | Following methods are implemented as a standalone methods (not class methods):
225 |
226 | * `crossprod(X, Y)` — computes a cross-product of two matrices as $\mathbf{X}^\textrm{T}\mathbf{Y}$.
227 | * `tcrossprod(X, Y)` — computes a cross-product of two matrices as $\mathbf{XY}^\textrm{T}$.
228 |
229 | #### Additional methods for matrices
230 |
231 | There is also a set of methods which check property of a matrix:
232 |
233 | * `X.islowertriangular()` — returns `true` if `X` is lower triangular matrix.
234 | * `X.isuppertriangular()` — returns `true` if `X` is upper triangular matrix.
235 |
236 | You can also convert a matrix to a string which looks nice if you want to show/check the
237 | matrix values by e.g. `console.log()`:
238 |
239 | * `X.toString(ndec)` — here `ndec` is number of decimals to show in the output.
240 |
241 | Finally there are two methods which can be helpful when it is necessary to read a matrix values from a CSV file or save the values as CSV.
242 |
243 | Class method `X.toCSV([sep=","], [dec="."], [header=[]], [labels=[]])` — generates a string which can be saved as CSV file. The parameters are: `sep`: symbol to use for separating values, `dec`: symbol to use for separating decimals, `header`: a conventional Javascript array with column names (variable names), `labels`: a conventional Javascript array with row names (observation labels).
244 |
245 | Static method `Matrix.parseCSV(str, sep, hasHeader, hasLabels)` — parses a string, which is a result of reading a CSV file, into a JSON. The parameters are: `str`: string with CSV file content with rows delimited by `\n` or `\r\n`, `sep`: symbol to use for separating values, `hasHeader`: logical, does the data have header or not, `hasLabels`: logical, does the data have labels or not. The method returns a JSON with matrix values, symbols for values and decimal separators, arrays with header and labels.
246 |
247 | ## Descriptive statistics
248 |
249 | The following methods can be used to compute most common statistics (or a vector with statistics). The methods can work with both conventional Javascript arrays and instances of class `Vector` although it is recommended to provide the latter in any case.
250 |
251 | Methods can be imported from `'mdatools/stat'` module.
252 |
253 | ### Methods for computing single statistics
254 |
255 | * `min(x)` — returns the smallest value in vector `x`.
256 | * `minind(x)` — returns index (position) of the smallest value in vector `x`.
257 | * `max(x)` — returns the largest value in vector `x`.
258 | * `maxind(x)` — returns index (position) of the largest value in vector `x`.
259 | * `prod(x)` — returns a product of all values from vector `x`.
260 | * `sum(x)` — returns a sum of all values from vector `x`.
261 | * `mean(x)` — returns mean (average) of values from vector `x`.
262 | * `variance(x, [biased=false])` — returns variance of values from vector `x`.
263 | * `sd(x, [biased=false])` — returns standard deviation of values from vector `x`.
264 | * `ssq(x)` — returns a sum of squared values from vector `x`.
265 | * `norm2(x)` — returns a Eucledian norm (length) of vector `x`.
266 | * `skewness(x)` — returns skewness of values from vector `x`.
267 | * `kurtosis(x)` — returns kurtosis of values from vector `x`.
268 | * `cor(x, y, method='pearson')` — returns a correlation ("pearson" or "spearman") of values from the two vectors.
269 | * `cov(x, y)` — returns a covariance ("pearson" or "spearman") of values from the two vectors.
270 |
271 |
272 | ### Methods for computing vectors of statistics
273 |
274 | * `range(x)` — returns an array with two values, smallest and largest.
275 | * `mrange(x, m)` — computes range with margin, `m` is how much range must be stretched (e.g. `0.1` for 10%).
276 | * `rank(x)` — returns rank (position) for every value from vector `x`.
277 | * `cumsum(x)` — returns a cumulative sum of the values from vector `x`.
278 | * `ppoints(n)` — computes `n` probabilities
279 | * `diff(x)` — returns difference between the adjacent values from vector `x`.
280 | * `mids(x)` — returns middle points for each pair of values from vector `x`.
281 | * `split(x, n)` — splits the range of values in `x` into equal intervals (bins).
282 | * `count(x, s)` — counts how many values from vector `x`, falls into each interval from `s`.
283 | * `quantile(x, p)` — returns `p`-th quantiles computed based on values from vector `x`.
284 |
285 | ## Distributions
286 |
287 | The following methods implement Probability Density Function (PDF), Cumulative Distribution Function (CDF) and Inverse Cumulative Distribution Function (ICDF) a.k.a. quantile function for several known distributions. For uniform and normal distributions there are also functions which generate random numbers.
288 |
289 | Methods can be imported from `'mdatools/distributions'` module.
290 |
291 |
292 | * `dnorm(x, mu, sigma)`, `pnorm(x, mu, sigma)`, `qnorm(p, mu, sigma)`, `rnorm(x, mu, sigma)` — PDF, CDF, ICDF, and random numbers generator for normal distribution. Default values for the paramaters are `mu=0`, `sigma=1`.
293 | * `dunif(x, a, b)`, `punif(x, a, b)`, `runif(x, a, b)` — PDF, CDF, and random numbers generator for uniform distribution. Default values for the paramaters are `a=0`, `b=1`.
294 | * `dt(t, dof)`, `pt(x, mu, sigma)`, `qt(p, mu, sigma)` — PDF, CDF, abd ICDF for Student's t-distribution.
295 | * `df(F, dof1, dof2)`, `pf(F, dof1, dof2)` — PDF and CDF for F-distribution.
296 | * `pchisq(x, dof)`, `qchisq(p, dof)` — CDF and ICDF function for chi-square distribution.
297 |
298 | There are also a set of helper functions used to compute the values for the distributions, which can be useful:
299 |
300 | * `erf(x)` — error function for normal distribution.
301 | * `beta(x, y)` — Beta function (approximation via numerical integration).
302 | * `gamma(z)` — Gamma function (approximation).
303 | * `ibeta(x, a, b)` — incomplete Beta function (approximation via numerical integration).
304 |
305 | Because the distribution values are computed using approximated functions they are not very accurate. The accuracy achieved in tests is about 10-5 comparing to implementation of the corresponding methods in e.g. R.
306 |
307 | ## Additional methods useful in statistics
308 |
309 | Methods can be imported from `'mdatools/misc'` module.
310 |
311 | * `closestind(x, a)` — finds index of a value from vector `x` which is the closest to value `a`.
312 | * `integrate(f, a, b)` — computes an integral of function `f` with limits `a`and `b`.
313 | * `getoutliers(x)` — returns values from `x`, lying beyond the 1.5IQR distance from the first and the third quartiles.
314 | * `expandgrid(...args)` — generates all possible combinations of values from two or more vectors provided as arguments to the function. Values must be unique.
315 |
316 | ## Hypothesis testing
317 |
318 | Methods can be imported from `'mdatools/tests'` module.
319 |
320 | ### Student's t-test for means
321 |
322 | The tests compute the observed effect, standard error, t- and p-values as well
323 | as confidence interval and return everything as JSON. The two-sample t-test assumes
324 | that population variances are equal. Confidence intervals always computed around
325 | the observed effect and for both tails.
326 |
327 | * `ttest(x, mu=0, alpha=0.05, tail='both')` — one sample t-test
328 | * `ttest2(x, y, alpha=0.05, tail='both')` — two sample t-test
329 |
330 | The `x` and `y` arguments must be instances of `Vector` class.
331 |
332 |
333 | ## Decompositions
334 |
335 | Functions can be imported from `'mdatools/decomp'` module.
336 |
337 | Methods for computing decomposition of matrices and related methods (e.g. inverse).
338 |
339 | * `qr(X)` — computes QR decomposition of `X` using Householder reflections.
340 | * `lu(X)` — computes LU decomposition of `X` using Givens rotations.
341 | * `svd(X)` — computes SVD decomposition of `X` using Golub-Reinsch bidiagonalization.
342 | * `rsvd(X)` — randomized version of SVD which is must faster than the original method.
343 |
344 | ## Preprocessing
345 |
346 | Functions can be imported from `'mdatools/prep'`.
347 |
348 | Methods for preprocessing of data values. Every method takes a matrix as a main argument and returns a matrix as a result (plus potentially other outcomes).
349 |
350 | * `scale(X, [center=true], [scale=false])` — center and scale each column of matrix `X`. If values for `center` and `scale` arguments are logical, the method will do standardization (using mean values for centring and standard deviation for scaling). Alternatively the values for these two arguments can be specified by user as a vector of values. Number of values must be equal to the number of columns in `X`.
351 | * `unscale(X, centerValues, scaleValues)` — does the inverse to `scale()` operation using vectors with values used to center and scale the data.
352 |
353 | More methods are coming in future versions.
354 |
355 | ## Modelling
356 |
357 | Functions can be imported from `'mdatools/models'`.
358 |
359 | Methods for fitting various models. Every method returns a JSON with fitted model parameters, their inference (where it can be done). The prediction methods contain the predicted values as well as performance statistics (when it is possible to compute).
360 |
361 | ### Simple and multiple linear regression
362 | * `lmfit(X, y)` — fits linear regression model (simple or multiple).
363 | * `lmpredict(m, X)` — computes predictions using model from `lmfit()` and vector or matrix with predictors.
364 |
365 |
366 | ### Polynomial regression
367 | * `polyfit(x, y, d)` — polynomial regression model (univariate).
368 | * `polypredict(m, x)` — computes predictions using polynomial model from `polyfit()`.
369 |
370 | ### Principal component anaylsis
371 |
372 | * `pcafit(X, ncomp)` — fits PCA model with given number of components.
373 | * `pcapredict(m, X)` — projects data from `X` to the PCA model and computes main outcomes (scores, distances, variance, etc.).
374 |
375 | ### DD-SIMCA classification
376 |
377 | DD-SIMCA classification is done based on PCA model with additional parameters.
378 |
379 | * `getsimcaparams(className, alpha, limType)` — creates an object with SIMCA parameters, necessary for classification.
380 | * `simcapredict(m, params, X, [cRef])` — classifies rows of data matrix `X` using PCA model `m` and DD-SIMCA parameters `params`. If factor with reference classes, `cRef` is provided, the method also computes classification statistics and adds it to he outcome.
381 |
382 | ### Principal components regression
383 |
384 | * `pcrfit(X, Y, ncomp)` — fits PCR model with given number of components.
385 | * `pcrpredict(m, X, [Y])` — projects data from `X` to the PCR model and computes main outcomes (predicted y-values for each component, scores, distances, variances and other performance statistics).
386 | *
387 | ### Partial least squares regression
388 |
389 | * `plsfit(X, Y, ncomp)` — fits PLS1 model with given number of components.
390 | * `plspredict(m, X, [Y])` — projects data from `X` to the PLS model and computes main outcomes (predicted y-values for each component, scores, distances, variances and other performance statistics).
391 |
392 |
393 |
394 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mdatools",
3 | "version": "1.4.0",
4 | "private": false,
5 | "type": "module",
6 | "description": "Tools and methods for statistics and multivariate data analysis.",
7 | "scripts": {
8 | "test": "mocha",
9 | "speedtest": "node ./test/tests-speed.js"
10 | },
11 | "exports": {
12 | "./arrays": "./src/arrays/index.js",
13 | "./decomp": "./src/decomp/index.js",
14 | "./distributions": "./src/distributions/index.js",
15 | "./misc": "./src/misc/index.js",
16 | "./models": "./src/models/index.js",
17 | "./prep": "./src/prep/index.js",
18 | "./stat": "./src/stat/index.js",
19 | "./tests": "./src/tests/index.js"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "https://github.com/svkucheryavski/mdatools-js/"
24 | },
25 | "keywords": [
26 | "statistics"
27 | ],
28 | "author": "Sergey Kucheryavskiy (https://mda.tools)",
29 | "license": "MIT",
30 | "bugs": {
31 | "url": "https://github.com/svkucheryavski/mdatools-js/issues"
32 | },
33 | "homepage": "https://github.com/svkucheryavski/mdatools-js/",
34 | "devDependencies": {
35 | "chai": "^4.x",
36 | "chai-almost": "^1.x",
37 | "mocha": "^10.x"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/decomp/index.js:
--------------------------------------------------------------------------------
1 | /************************************************************/
2 | /* Methods for computing matrix decompositions */
3 | /************************************************************/
4 |
5 | import { ssq } from '../stat/index.js';
6 | import { crossprod, Vector, Matrix, Index } from '../arrays/index.js';
7 |
8 |
9 | /**
10 | * Randomized SVD decomposition.
11 | *
12 | * @param {Matrix} X - matrix to decompose.
13 | * @param {number} [ncomp] - number of components.
14 | * @param {number} [pa=1] - oversampling factor (ncomp * pa + pb).
15 | * @param {number} [pb=10] - oversampling increment (ncomp * pa + pb).
16 | * @param {number} [its=3] - number of iterations
17 | *
18 | * @returns {JSON} JSON with three fields, 's' - vector with singular values,
19 | * 'U', 'V' - matrices with left and right singular vectors.
20 | *
21 | */
22 | export function rsvd(X, ncomp, pa, pb, its) {
23 |
24 | const m = X.nrows;
25 | const n = X.ncols;
26 |
27 | if (its === undefined) {
28 | its = 3;
29 | }
30 |
31 | if (pb === undefined) {
32 | pb = 5;
33 | }
34 |
35 | if (pa === undefined) {
36 | pa = 1;
37 | }
38 |
39 | if (its < 1 || its > 10) {
40 | throw new Error('rsvd: wrong value for parameter "its" (must be between 1 and 10).');
41 | }
42 |
43 | if (pa < 1 || pa > 5) {
44 | throw new Error('rsvd: wrong value for parameter "pa" (must be between 1 and 5).');
45 | }
46 |
47 | if (pb < 1 || pb > 100) {
48 | throw new Error('rsvd: wrong value for parameter "pb" (must be between 1 and 100).');
49 | }
50 |
51 |
52 | if (ncomp === undefined) {
53 | ncomp = Math.round(Math.min(m - 1, n) / 1.5);
54 | }
55 |
56 | if (m < n) {
57 | const res = rsvd(X.t(), ncomp, pa, pb, its);
58 | return {s: res.s, V: res.U, U: res.V}
59 | }
60 |
61 | // the more the better but slower
62 | const l = Math.round(pa * ncomp + pb);
63 | let Q = X.dot(Matrix.rand(n, l, -1, 1));
64 |
65 | for (let it = 1; it <= its; it++) {
66 | Q = lu(crossprod(X, Q)).L;
67 | Q = it < its ? lu(X.dot(Q)).L : qr(X.dot(Q)).Q;
68 | }
69 |
70 | const B = crossprod(Q, X);
71 | const res = svd(B, ncomp);
72 | return {s: res.s, V: res.V, U: Q.dot(res.U)};
73 | }
74 |
75 | /**
76 | * QR decomposition
77 | *
78 | * @param {Matrix} X - matrix to decompose.
79 | *
80 | * @returns {JSON} JSON with two matrices, Q and R, so X = QR.
81 | *
82 | */
83 | export function qr(X) {
84 |
85 | const m = X.nrows;
86 | const n = X.ncols;
87 |
88 | if (m < n) {
89 | const res = qr(X.subset([], Index.seq(1, m)));
90 | return {Q:res.Q, R:crossprod(res.Q, X)}
91 | }
92 |
93 | let Q = Matrix.eye(m);
94 | let Rt = X.t();
95 |
96 | for (let j = 1; j <= n; j++) {
97 | for (let i = m; i >= (j + 1); i--) {
98 |
99 | const r = Rt.v.subarray((i - 2) * n, i * n);
100 | const q = Q.v.subarray((i - 2) * m, i * m);
101 | const rc = r.slice();
102 | const qc = q.slice();
103 |
104 | const [c, s, temp] = rot(rc[j - 1], rc[j - 1 + n]);
105 |
106 | // apply transformation to columns i and i - 1 of matrix Rt
107 | for (let k = 0; k < n; k++) {
108 |
109 | r[k] = rc[k] * c + rc[k + n] * s;
110 | r[k + n] = rc[k] * -s + rc[k + n] * c;
111 |
112 | q[k] = qc[k] * c + qc[k + m] * s;
113 | q[k + m] = qc[k] * -s + qc[k + m] * c;
114 | }
115 |
116 | for (let k = n; k < m; k++) {
117 | q[k] = qc[k] * c + qc[k + m] * s;
118 | q[k + m] = qc[k] * -s + qc[k + m] * c;
119 | }
120 | }
121 | }
122 |
123 | const ind = Index.seq(1, n);
124 | return {
125 | Q: m === n ? Q : Q.subset([], ind),
126 | R: m === n ? Rt.t() : Rt.subset([], ind).t()
127 | }
128 | }
129 |
130 | /**
131 | * LU decomposition.
132 | *
133 | * @param {Matrix} X - matrix to decompose.
134 | *
135 | * @returns {JSON} JSON with two matrices, L and U, so X = LU.
136 | *
137 | */
138 | export function lu(X) {
139 |
140 | const nr = X.nrows;
141 | const nc = X.ncols
142 | const n = Math.min(nr, nc)
143 |
144 | const Lv = new Float64Array(nr * nc);
145 | const Uv = new Float64Array(nc * nc);
146 | const Xv = X.v.slice();
147 |
148 | const s = Math.round(Math.pow(n, 0.5285425));
149 | let z = 1;
150 |
151 | for (let c = 1; c <= n; c++) {
152 |
153 | if (c == z + s) {
154 | const nro = nr - c + 1;
155 | const nco = nc - c + 1;
156 | const ni = c - z;
157 | const Sv = new Float64Array(nro * nco);
158 |
159 | for (let i = 0; i < nro; i++) {
160 | for (let j = 0; j < nco; j++) {
161 | let s = 0;
162 | for (let k = 0; k < ni; k++) {
163 | s += Lv[(z - 1 + k) * nr + (c - 1) + i] * Uv[(c - 1 + j) * nc + (z - 1 + k)];
164 | }
165 | Sv[j * nro + i] = s;
166 | }
167 | }
168 |
169 | for (let i = c; i <= nr; i++) {
170 | for (let j = c; j <= nc; j++) {
171 | Xv[(j - 1) * nr + (i - 1)] = Xv[(j - 1) * nr + (i - 1)] - Sv[(j - c) * nro + (i - c)];
172 | }
173 | }
174 |
175 | z = c;
176 | }
177 |
178 | for (let i = c; i <= nr; i++) {
179 | let acc = Xv[(c - 1) * nr + (i - 1)];
180 | for (let k = z; k <= c - 1; k++) {
181 | acc = acc - Lv[(k - 1) * nr + (i - 1)] * Uv[(c - 1) * nc + (k - 1)];
182 | }
183 | Lv[(c - 1) * nr + (i - 1)] = acc;
184 | }
185 |
186 | for (let i = c; i <= nc; i++) {
187 | let acc = Xv[(i - 1) * nr + (c - 1)];
188 | for (let k = z; k <= c - 1; k++) {
189 | acc = acc - Lv[(k - 1) * nr + (c - 1)] * Uv[(i - 1) * nc + (k - 1)];
190 | }
191 | const l = Lv[(c - 1) * nr + (c - 1)];
192 | Uv[(i - 1) * nc + (c - 1)] = (l == 0 ? acc : acc / l);
193 | }
194 | }
195 |
196 | return {L: new Matrix(Lv, nr, nc), U: new Matrix(Uv, nc, nc)}
197 | }
198 |
199 |
200 | /**
201 | * Singular value decomposition
202 | *
203 | * @param {Matrix} X - matrix to decompose.
204 | * @param {number} [ncomp] - number of components.
205 | *
206 | * @returns {JSON} JSON with three fields, 's' - vector with singular values,
207 | * 'U', 'V' - matrices with left and right singular vectors.
208 | *
209 | */
210 | export function svd(X, ncomp) {
211 |
212 | let d, e, bV, bU, m, n, transposed;
213 |
214 | if (X.nrows < X.ncols) {
215 | [d, e, bV, bU] = bidiag(X.t());
216 | m = X.ncols;
217 | n = X.nrows;
218 | transposed = true;
219 | } else {
220 | [d, e, bV, bU] = bidiag(X);
221 | m = X.nrows;
222 | n = X.ncols;
223 | transposed = false;
224 | }
225 |
226 | if (!ncomp) {
227 | ncomp = Math.min(m, n);
228 | }
229 |
230 | const maxit = 500 * n * n;
231 | const thresh = Math.pow(10, -64);
232 | let Gt = Matrix.eye(n);
233 | let P = Matrix.eye(n);
234 |
235 | for (let it = 0; it < maxit; it++) {
236 |
237 | // find first nonzero element in e from bottom
238 | let iU = 0;
239 | for (let i = n - 1; i >= 1; i--) {
240 | if (Math.abs(e.v[i - 1]) > thresh) {
241 | iU = i
242 | break;
243 | }
244 | }
245 |
246 | // find first nonzero element in e from top
247 | let iL = iU + 1;
248 | for (let i = 1; i <= n - 1; i++) {
249 | if (Math.abs(e.v[i - 1]) > thresh) {
250 | iL = i;
251 | break;
252 | }
253 | }
254 |
255 | // check the convergence and return result
256 | if ((iU == iL && Math.abs(e[iU - 1]) <= thresh) || (iU < iL)) {
257 |
258 | // compute final results:
259 | // V = bU * P
260 | // U = bV * Gt
261 | // and correct sign of singular values and columns of V
262 |
263 | const s = Vector.zeros(ncomp);
264 | const U = Matrix.zeros(X.nrows, ncomp);
265 | const V = Matrix.zeros(X.ncols, ncomp);
266 |
267 | for (let k = 1; k <= ncomp; k++) {
268 | const sign = Math.sign(d.v[k - 1]);
269 | s.v[k - 1] = Math.abs(d.v[k - 1]);
270 |
271 | let uk, vk;
272 | if (transposed) {
273 | uk = V.getcolref(k);
274 | vk = U.getcolref(k);
275 | } else {
276 | uk = U.getcolref(k);
277 | vk = V.getcolref(k);
278 | }
279 |
280 | const pk = P.getcolref(k);
281 | const gtk = Gt.getcolref(k);
282 |
283 | for (let i = 0; i < n; i++) {
284 | const pki = pk[i];
285 | const gtki = gtk[i] * sign;
286 |
287 | const bur = bU.getcolref(i + 1);
288 | const vur = bV.getcolref(i + 1);
289 |
290 | for (let r = 0; r < n; r++) {
291 | uk[r] += bur[r] * pki;
292 | vk[r] += vur[r] * gtki;
293 | }
294 | for (let r = n; r < m; r++) {
295 | uk[r] += bur[r] * pki;
296 | }
297 | }
298 | }
299 |
300 | return {s: s, U: U, V: V};
301 | }
302 |
303 | // re-sweep
304 | const cstart = iL - 1;
305 | const cend = iU + 1;
306 | const clen = iU - iL + 2;
307 | const [rd, re, rG, rPt] = vsweep(d.v.subarray(cstart, cend), e.v.subarray(cstart, cend - 1));
308 |
309 | // replace elements of d and e
310 | d.v.set(rd, cstart);
311 | e.v.set(re, cstart);
312 |
313 | // now we need to compute:
314 | // G = rG' * G = (G' * rG)' -> this should be done only fo selected columns in G
315 | // P = P * rP' -> only for selected columns in P
316 |
317 | // get local copy of the "clen" columns from G' and P starting from column "cstart"
318 | const lgt = Gt.v.slice(cstart * n, cstart * n + clen * n);
319 | const lp = P.v.slice(cstart * n, cstart * n + clen * n);
320 |
321 | // compute dot(lG', rG) and save back to G'
322 | // compute dot(P, rP') and save back to P
323 | for (let c = 0; c < clen; c++) {
324 |
325 | // get pointers to column "c" (c from "cstart" to "cend") of Gt and P - to save the results
326 | const gtc = Gt.getcolref(c + cstart + 1); // nc x 1
327 | const pc = P.getcolref(c + cstart + 1); // nc x 1
328 |
329 | // get pointers to the columns of rG and rPt
330 | const rgc = rG.getcolref(c + 1); // clen x 1
331 | const rptc = rPt.getcolref(c + 1); // clen x 1
332 |
333 | // copmpute and save the dot products
334 | for (let r = 0; r < n; r++) {
335 | // row "r" of lGt (nc x clen) times column "c" of rG (clen x 1)
336 | // row of lP times column of rPt
337 | let gtcr = 0;
338 | let pcr = 0;
339 | for (let i = 0; i < clen; i++) {
340 | gtcr += lgt[i * n + r] * rgc[i];
341 | pcr += lp[i * n + r] * rptc[i];
342 | }
343 | gtc[r] = gtcr;
344 | pc[r] = pcr;
345 | }
346 | }
347 |
348 |
349 | // get local copy of selected columns from G' and P
350 | /*
351 | const lGt = Matrix.zeros(n, l);
352 | const lP = Matrix.zeros(n, l)
353 | for (let c = iL; c <= iU + 1; c++) {
354 | lGt.v.set(Gt.v.subarray((c - 1) * n, c * n), (c - iL) * n);
355 | lP.v.set(P.v.subarray((c - 1) * n, c * n), (c - iL) * n);
356 | }
357 |
358 | // compute dot(lG', rG) and save back to G
359 | // compute dot(P, rP') and save back to P
360 | for (let c = 1; c <= l; c++) {
361 | const newgc = lGt.dot(new Vector(rG.getcolref(c)));
362 | const newpc = lP.dot(new Vector(rPt.getcolref(c)));
363 | Gt.v.set(newgc.v, (c - 1 + iL - 1) * n);
364 | P.v.set(newpc.v, (c - 1 + iL - 1) * n);
365 | }
366 | */
367 | }
368 |
369 | throw Error("svd: can not converge.")
370 | }
371 |
372 |
373 | /**
374 | * Applies givens rotations to diagonal elements of bidiagonalized matrix.
375 | *
376 | * @param {Float64Array} d - vector with main diagonal elements.
377 | * @param {Float64Array} e - vector with diagonal elements on top of main diagonal.
378 | *
379 | * @returns {Array} array with vectors 'd' and 'e' after rotation as well as right
380 | * transformation matrices G and P'.
381 | *
382 | */
383 | export function vsweep(d, e) {
384 |
385 | const n = d.length;
386 |
387 | let cold = 1;
388 | let sold = 1;
389 |
390 | let c = 1;
391 | let s = 0;
392 | let r = 0;
393 |
394 | let G = Matrix.eye(n);
395 | let Pt = Matrix.eye(n);
396 |
397 | for (let k = 1; k <= (n - 1); k++) {
398 |
399 | [c, s, r] = rot(c * d[k - 1], e[k - 1]);
400 |
401 | // get two columns from G and rotate their values
402 | const gk = G.v.subarray((k - 1) * n, (k + 1) * n);
403 | const gkc = gk.slice();
404 |
405 | for (let i = 0; i < n; i++) {
406 | gk[i] = gkc[i] * c + gkc[i + n] * s;
407 | gk[i + n] = -gkc[i] * s + gkc[i + n] * c;
408 | }
409 |
410 | if (k != 1) {
411 | e[k - 2] = r * sold;
412 | }
413 |
414 | [cold, sold, r] = rot(cold * r, d[k] * s);
415 | d[k - 1] = r;
416 |
417 | // get two columns from Pt and rotate their values
418 | const ptk = Pt.v.subarray((k - 1) * n, (k + 1) * n);
419 | const ptkc = ptk.slice();
420 |
421 | for (let i = 0; i < n; i++) {
422 | ptk[i] = ptkc[i] * cold + ptkc[i + n] * sold;
423 | ptk[i + n] = -ptkc[i] * sold + ptkc[i + n] * cold;
424 | }
425 |
426 | }
427 |
428 | const h = c * d[n - 1];
429 | e[n - 2] = h * sold;
430 | d[n - 1] = h * cold;
431 |
432 | return [d, e, G, Pt];
433 | }
434 |
435 |
436 | /**
437 | * Generate c, s, r values for Givens rotations, so [c s; -s c][f; g] = [r; 0].
438 | *
439 | * @param {number} f - first element of vector.
440 | * @param {number} g - second element of vector.
441 | *
442 | * @return array with values [c, s, r].
443 | *
444 | */
445 | export function rot(f, g) {
446 |
447 | if (f == 0) {
448 | return [0, 1, g];
449 | }
450 |
451 | if (Math.abs(f) > Math.abs(g)) {
452 | const t = g / f;
453 | const t1 = Math.sqrt(1 + t * t);
454 | const c = 1 / t1
455 | return [c, t * c, f * t1];
456 | }
457 |
458 | const t = f / g;
459 | const t1 = Math.sqrt(1 + t * t)
460 | const s = 1 / t1
461 | return [t * s, s, g * t1];
462 | }
463 |
464 |
465 | /**
466 | * Golub-Reinsch bidiagonalization of matrix A
467 | *
468 | * @param {Matrix} A - matrix with values.
469 | *
470 | * @returns {Array} array with three matrices [B, V, U].
471 | *
472 | */
473 | export function bidiag(A) {
474 |
475 | const m = A.nrows;
476 | const n = A.ncols;
477 |
478 | if (m < n) {
479 | throw Error("bidiag: number of rows in A must not be smaller number of columns.");
480 | }
481 |
482 | let Ut = Matrix.eye(m);
483 | let V = Matrix.eye(n);
484 | let B = A.copy();
485 |
486 | for (let k = 1; k <= (m > n ? n : n - 1); k++) {
487 | // for (let k = 1; k <= 1; k++) {
488 | const mk = m - k + 1;
489 |
490 | // compute:
491 | // B = H * B
492 | // U' = H' * U';
493 | const H1 = householder(B.getcolref(k), k);
494 |
495 | for (let c = 1; c <= n; c++) {
496 |
497 | // get c-th column of B (starting from (k-1) row) and make a copy
498 | const bc = B.v.subarray((c - 1) * m + k - 1, c * m)
499 | const bcc = bc.slice();
500 |
501 | // get c-th column of U' (starting from (k-1) row) and make a copy
502 | const utc = Ut.v.subarray((c - 1) * m + k - 1, c * m)
503 | const utcc = utc.slice();
504 |
505 | for (let r = k; r <= m; r++) {
506 | // because H is symmetric we take column instead of row
507 | const hr = H1.getcolref(r - k + 1);
508 | let sb = 0;
509 | let sut = 0;
510 | for (let i = 0; i < mk; i++) {
511 | sb += hr[i] * bcc[i];
512 | sut += hr[i] * utcc[i]
513 | }
514 | bc[r - k] = sb;
515 | utc[r - k] = sut;
516 | }
517 | }
518 |
519 |
520 | for (let c = n + 1; c <= m; c++) {
521 |
522 | // get c-th column of U' and make a copy
523 | const utc = Ut.v.subarray((c - 1) * m + k - 1, c * m)
524 | const utcc = utc.slice();
525 |
526 | for (let r = k; r <= m; r++) {
527 | const hr = H1.getcolref(r - k + 1);
528 | let sut = 0;
529 | for (let i = 0; i < mk; i++) {
530 | sut += hr[i] * utcc[i];
531 | }
532 | utc[r - k] = sut;
533 | }
534 | }
535 |
536 | if (k < n - 1) {
537 |
538 | // compute:
539 | // B = B * H'
540 | // V = V * H
541 | const H2 = householder(B.getrow(k).v, k + 1);
542 |
543 | // we need to process columns from k + 1, so make a subset from the start
544 | const Bvk = B.v.subarray(k * m);
545 | const Vvk = V.v.subarray(k * n);
546 |
547 | let br = new Float64Array(n - k)
548 | let vr = new Float64Array(n - k)
549 |
550 | // process first n rows for both B and V
551 | for (let r = 1; r <= n; r++) {
552 |
553 | // get elements from r-th row
554 | for (let i = 0; i < n - k; i++) {
555 | br[i] = Bvk[i * m + r - 1];
556 | vr[i] = Vvk[i * n + r - 1];
557 | }
558 |
559 | for (let c = k + 1; c <= n; c++) {
560 | const hc = H2.getcolref(c - k);
561 | let sb = 0;
562 | let sv = 0;
563 | for (let i = 0; i < H2.nrows; i++) {
564 | sb += br[i] * hc[i];
565 | sv += vr[i] * hc[i];
566 | }
567 | Bvk[(c - k - 1) * m + (r - 1)] = sb;
568 | Vvk[(c - k - 1) * n + (r - 1)] = sv
569 | }
570 | }
571 |
572 | // process rows from (n+1) to m for B only
573 | for (let r = n + 1; r <= m; r++) {
574 |
575 | // get elements from r-th row
576 | for (let i = 0; i < n - k; i++) {
577 | br[i] = Bvk[i * m + r - 1];
578 | }
579 |
580 | for (let c = k + 1; c <= n; c++) {
581 | const hc = H2.getcolref(c - k);
582 | let sb = 0;
583 | for (let i = 0; i < H2.nrows; i++) {
584 | sb += br[i] * hc[i];
585 | }
586 | Bvk[(c - k - 1) * m + (r - 1)] = sb;
587 | }
588 | }
589 | }
590 | }
591 |
592 | const d = Vector.zeros(n);
593 | const e = Vector.zeros(n - 1);
594 | for (let i = 0; i < n - 1; i++) {
595 | d.v[i] = B.v[i * m + i];
596 | e.v[i] = B.v[(i + 1) * m + i];
597 | }
598 | d.v[n - 1] = B.v[(n - 1) * m + (n - 1)];
599 |
600 | return [d, e, V, Ut.t()];
601 | }
602 |
603 |
604 | /**
605 | * Compute elements of Householder transformation as a matrix.
606 | *
607 | * @param {Float64Array} b - array with diagonal values.
608 | * @param {number} k - position to start with.
609 | *
610 | * @returns {Matrix} matrix with transformation values.
611 | *
612 | */
613 | export function householder(b, k) {
614 |
615 | // get vector with values
616 | const h = householderv(b, k);
617 | const n = h.length;
618 | // compute matrix as outer product of the vector
619 | const H = Matrix.zeros(n);
620 | for (let c = 0; c < n; c++) {
621 | const hc = H.getcolref(c + 1);
622 | for (let r = 0; r < n; r++) {
623 | hc[r] = 1 * (r == c) - 2 * h[r] * h[c];
624 | }
625 | }
626 |
627 | return H;
628 | }
629 |
630 |
631 | /**
632 | * Compute elements of Householder transformation as a vector.
633 | *
634 | * @param {Float64Array} b - array with diagonal values.
635 | * @param {number} k - position to start with.
636 | *
637 | * @returns {Float64Array} vector with transformation values.
638 | *
639 | */
640 | export function householderv(b, k) {
641 |
642 | const n = b.length;
643 |
644 | if (k >= n) {
645 | throw Error("householder: parameter 'k' must be smaller than length of vector 'b'.")
646 | }
647 |
648 | const hlen = n - k + 1;
649 | const h = b.slice(k - 1);
650 | // to avoid computing norm2 twice we will
651 | // compute it based on the first norm
652 | const hssq = ssq(h);
653 | const hn2 = Math.sqrt(hssq);
654 | const s = Math.sign(h[0]);
655 | const a = h[0];
656 |
657 | // change first value in the vector
658 | h[0] = a - s * hn2;
659 |
660 | // recompute the norm
661 | const hn2a = Math.sqrt(2 * hssq - 2 * s * a * hn2);
662 |
663 | // if norm is zero return vector as is
664 | if (hn2a < Number.EPSILON) return h
665 |
666 | // normalize vector and return
667 | for (let i = 0; i < hlen; i++) {
668 | h[i] /= hn2a;
669 | }
670 | return h;
671 | }
672 |
673 |
674 |
675 |
676 |
677 |
--------------------------------------------------------------------------------
/src/misc/index.js:
--------------------------------------------------------------------------------
1 | /*************************************************/
2 | /* Misc extra methods for statistics */
3 | /*************************************************/
4 |
5 | import { isvector, vector, Index, Vector } from "../arrays/index.js";
6 | import { prod, quantile } from "../stat/index.js";
7 |
8 |
9 | /**
10 | * Finds index of value in x which is closest to the value a.
11 | *
12 | * @param {Array|Vector} x - a vector with values.
13 | * @param {number} a - a value
14 | *
15 | * @returns {number} the index value (starts from 1).
16 | *
17 | */
18 | export function closestind(x, a) {
19 |
20 | if (isvector(x)) {
21 | return closestind(x.v, a);
22 | }
23 |
24 | const c = x.reduce((prev, curr) => Math.abs(curr - a) < Math.abs(prev - a) ? curr : prev);
25 | return x.indexOf(c) + 1;
26 | }
27 |
28 | /**
29 | * Finds index of value in x which is closest to the value a from the left.
30 | *
31 | * @param {Array|Vector} x - a vector with values.
32 | * @param {number} a - a value
33 | *
34 | * @returns {number} the index value (starts from 1).
35 | *
36 | */
37 | export function closestindleft(x, a) {
38 |
39 | if (isvector(x)) {
40 | return closestindleft(x.v, a);
41 | }
42 |
43 | if (x[0] > a) return 1;
44 | for (let i = 1; i < x.length; i++) {
45 | if (x[i] > a) return i;
46 | }
47 | return x.length;
48 | }
49 |
50 | /**
51 | * Finds index of value in x which is closest to the value a from the right.
52 | *
53 | * @param {Array|Vector} x - a vector with values.
54 | * @param {number} a - a value
55 | *
56 | * @returns {number} the index value (starts from 1).
57 | *
58 | */
59 | export function closestindright(x, a) {
60 |
61 | if (isvector(x)) {
62 | return closestindright(x.v, a);
63 | }
64 |
65 | if (x[x.length - 1] < a) return x.length;
66 | for (let i = (x.length - 1); i >= 0; i--) {
67 | if (x[i] < a) return i + 2;
68 | }
69 | return 1;
70 | }
71 |
72 |
73 | /**
74 | * Computes numeric integral for function "f" with limits (a, b).
75 | *
76 | * @param {function} f - a reference to a function.
77 | * @param {number} a - lower limit for integration.
78 | * @param {number} b - upper limit for integration.
79 | * @param {number} [acc=0.000001] - absolute accuracy.
80 | * @param {number} [eps=0.00001] - relative accuracy.
81 | * @param {number[]} oldfs - vector of values needed for recursion.
82 | *
83 | * @returns {number} result of integration.
84 | *
85 | */
86 | export function integrate(f, a, b, acc, eps, oldfs) {
87 |
88 | if (acc === undefined) {
89 | acc = 0.000001;
90 | }
91 |
92 | if (eps === undefined) {
93 | eps = 0.00001
94 | }
95 |
96 | if (typeof(a) !== "number" || typeof(b) !== "number") {
97 | throw Error("Parameters 'a' and 'b' must be numbers.");
98 | }
99 |
100 | if (b < a) {
101 | throw Error("Parameter 'b' must be larger 'a'.");
102 | }
103 |
104 | // special case when left limit is minus infinity
105 | if (a === -Infinity && b !== Infinity) {
106 | return integrate((t) => f(b - (1 - t) / t) / (t ** 2), 0, 1);
107 | }
108 |
109 | // special case when right limit is plus infinity
110 | if (a !== -Infinity && b === Infinity) {
111 | return integrate((t) => f(a + (1 - t) / t) / (t ** 2), 0, 1);
112 | }
113 |
114 | // special case when both limits are infinite
115 | if (a === -Infinity && b === Infinity) {
116 | return integrate((t) => (f((1 - t) / t) + f((t - 1) / t)) / t ** 2, 0, 1);
117 | }
118 |
119 | // constants for splitting the integration interval
120 | const x = [1/6, 2/6, 4/6, 5/6];
121 | const w = [2/6, 1/6, 1/6, 2/6];
122 | const v = [1/4, 1/4, 1/4, 1/4];
123 | const p = [1, 0, 0, 1];
124 |
125 | let n = x.length, h = b - a;
126 | let fs;
127 |
128 | if (oldfs === undefined) {
129 | fs = x.map(v => f(a + v * h));
130 | } else {
131 | fs = new Array(n);
132 | for (let k = 0, i = 0; i < n; i++) {
133 | fs[i] = p[i] === 1 ? f(a + x[i] * h) : oldfs[k++];
134 | }
135 | }
136 |
137 | let q4 = 0, q2 = 0;
138 | for (let i = 0; i < n; i++) {
139 | q4 += w[i] * fs[i] * h;
140 | q2 += v[i] * fs[i] * h;
141 | }
142 |
143 | if (isNaN(q2) || isNaN(q4)) {
144 | throw Error("Numerical integration ended up with NaN number.")
145 | }
146 |
147 | let tol = acc + eps * Math.abs(q4);
148 | let err = Math.abs((q4 - q2)/3);
149 |
150 | if (err < tol) return q4;
151 |
152 | acc = acc / Math.sqrt(2.);
153 | const mid = (a + b) / 2;
154 | const left = fs.filter((v, i) => i < n/2);
155 | const right = fs.filter((v, i) => i >= n/2);
156 |
157 | const ql = integrate(f, a, mid, eps, acc, left);
158 | const qr = integrate(f, mid, b, eps, acc, right);
159 |
160 | return ql + qr;
161 | }
162 |
163 |
164 | /**
165 | * Finds outliers in a vector based on inter-quartile range distance.
166 | *
167 | * @param {Array|Vector} x - vector with values.
168 | * @param {number} q1 - first quartile (optional parameter).
169 | * @param {number} q3 - third quartile (optional parameter).
170 | *
171 | * @returns {Vector} vector with outliers or empty vector if none were found.
172 | *
173 | */
174 | export function getoutliers(x, q1 = undefined, q3 = undefined) {
175 |
176 | if (q1 === undefined) q1 = quantile(x, 0.25);
177 | if (q3 === undefined) q3 = quantile(x, 0.75);
178 |
179 | const iqr15 = (q3 - q1) * 1.5;
180 | const bl = q1 - iqr15
181 | const bu = q3 + iqr15
182 |
183 | return new Vector(x.v.filter(v => v < bl || v > bu));
184 | }
185 |
186 |
187 | /**
188 | * Rounds number (or vector of numbers) to given amount of decimals.
189 | *
190 | * @param {numbr|Array|Vector} x - a number or a vector with values.
191 | *
192 | * @return {number|Array|Vector} a number or a vector with rounded values.
193 | *
194 | */
195 | export function round(x, n = 0) {
196 |
197 | if (isvector(x)) {
198 | return vector(x.v.map(v => round(v, n)));
199 | }
200 |
201 | if (Array.isArray(x)) {
202 | return x.map(v => round(v, n));
203 | }
204 |
205 | return Number.parseFloat(x.toFixed(n));
206 | }
207 |
208 |
209 | /**
210 | * Generate combination of all levels of vectors.
211 | *
212 | * @param {...} args - a sequence of vectors.
213 | *
214 | * @returns {Array} array of arrays with values for each vector.
215 | *
216 | */
217 | export function expandgrid(...args) {
218 |
219 | const nargs = args.length;
220 | const d = args.map(v => v.length);
221 | let orep = prod(d);
222 |
223 | let grid = Array(nargs);
224 | let repFac = 1;
225 |
226 | for (let i = 0; i < nargs; i++) {
227 | const x = isvector(args[i]) ? args[i] : vector(args[i]);
228 | const nx = x.length;
229 | orep = orep / nx;
230 | grid[i] = x.subset(Index.seq(1, nx).repeach(repFac).rep(orep));
231 | repFac = repFac * nx;
232 | }
233 |
234 | return grid;
235 | }
236 |
--------------------------------------------------------------------------------
/src/models/index.js:
--------------------------------------------------------------------------------
1 | import { rsvd } from '../decomp/index.js';
2 | import { pf, pt, qt, qchisq } from '../distributions/index.js';
3 | import { norm2, variance, median, iqr, mean, sd, ssq } from '../stat/index.js';
4 | import { scale as prep_scale, unscale as prep_unscale } from '../prep/index.js';
5 | import { _dot, isfactor, factor, cbind, tcrossprod, crossprod, reshape, ismatrix, Index,
6 | Matrix, vector, isvector, Vector } from '../arrays/index.js';
7 |
8 | /**
9 | * Check if an object has a proper class.
10 | *
11 | * @param {JSON} obj - JSON wiht object (e.g. model or results).
12 | * @param {string} className - expected name of the class.
13 | *
14 | * @return {boolean} true or false.
15 | *
16 | */
17 | export function isa(obj, className) {
18 | return obj && obj.class && Array.isArray(obj.class) && obj.class.includes(className);
19 | }
20 |
21 |
22 |
23 | export function getclassres(cPred, className, cRef) {
24 |
25 | // check inputs
26 | if (!cPred || !Array.isArray(cPred) || cPred.length < 1 || !cPred.every(isfactor)) {
27 | throw new Error('getclassres: parameter "cPred" must be array of factors.');
28 | }
29 |
30 | if (!className || typeof(className) !== 'string' || className == '' || className == 'none') {
31 | throw new Error('getclassres: wrong value for "className" parameter (must me a string).');
32 | }
33 |
34 | if (cRef && !isfactor(cRef)) {
35 | throw new Error('getclassres: parameter "cRef" must be a factor (or undefined).');
36 | }
37 |
38 | // if reference values are not provided return results without statistics
39 | if (!cRef) {
40 | return {
41 | class: ['classres'],
42 | className: className,
43 | cPred: cPred,
44 | cRef: cRef
45 | }
46 | }
47 |
48 | // find index of class in factor with reference class names
49 | const refInd = cRef.labels.findIndex(v => v === className);
50 | const ncomp = cPred.length;
51 |
52 | // prepare variables for statistics
53 | const TP = Vector.zeros(ncomp);
54 | const FP = Vector.zeros(ncomp);
55 | const TN = Vector.zeros(ncomp);
56 | const FN = Vector.zeros(ncomp);
57 | const sensitivity = Vector.zeros(ncomp);
58 | const specificity = Vector.zeros(ncomp);
59 | const accuracy = Vector.zeros(ncomp);
60 |
61 | // loop over components
62 | for (let a = 1; a <= ncomp; a++) {
63 |
64 | const predInd = cPred[a - 1].labels.findIndex(v => v === className);
65 | let TPa = 0, TNa = 0, FPa = 0, FNa = 0;
66 | for (let i = 0; i < cRef.length; i++) {
67 | if (cPred[a - 1].v[i] === predInd && cRef.v[i] === refInd) TPa += 1;
68 | if (cPred[a - 1].v[i] !== predInd && cRef.v[i] !== refInd) TNa += 1;
69 | if (cPred[a - 1].v[i] === predInd && cRef.v[i] !== refInd) FPa += 1;
70 | if (cPred[a - 1].v[i] !== predInd && cRef.v[i] === refInd) FNa += 1;
71 | }
72 |
73 | sensitivity.v[a - 1] = TPa / (TPa + FNa);
74 | specificity.v[a - 1] = TNa / (TNa + FPa);
75 | accuracy.v[a - 1] = (TPa + TNa) / (TNa + TPa + FNa + FPa);
76 | TP.v[a - 1] = TPa;
77 | TN.v[a - 1] = TNa;
78 | FP.v[a - 1] = FPa;
79 | FN.v[a - 1] = FNa;
80 |
81 | }
82 |
83 | return {
84 | class: ['classres'],
85 | className: className,
86 | cPred: cPred,
87 | cRef: cRef,
88 | TP: TP,
89 | FN: FN,
90 | TN: TN,
91 | FP: FP,
92 | sensitivity: sensitivity,
93 | specificity: specificity,
94 | accuracy: accuracy
95 | }
96 | }
97 |
98 |
99 | export function getsimcaparams(className, alpha, limType) {
100 |
101 | const validLimTypes = ['classic', 'robust'];
102 |
103 | if (!alpha) alpha = 0.05;
104 | if (!limType) limType = 'classic';
105 |
106 | if (!className || typeof(className) !== 'string' || className == '' || className == 'none') {
107 | throw new Error('simcamodel: wrong value for "className" parameter (must me a string).');
108 | }
109 |
110 | if (isNaN(alpha) || alpha < 0 || alpha > 1) {
111 | throw new Error('simcamodel: wrong value for "alpha" parameter.');
112 | }
113 |
114 | if (typeof(limType) !== 'string' || !validLimTypes.includes(limType)) {
115 | throw new Error('simcamodel: wrong value for "limType" parameter (must be either "classic" or "robust").');
116 | }
117 |
118 | return {
119 | class: ['simcaparams'],
120 | className: className,
121 | alpha: alpha,
122 | limType: limType
123 | }
124 | }
125 |
126 | export function simcapredict(m, params, X, cRef) {
127 |
128 | // check inputs
129 | if (!isa(m, 'pcamodel')) {
130 | throw new Error('simcapredict: parameter "m" must be an object with PCA model.');
131 | }
132 |
133 | if (!isa(params, 'simcaparams')) {
134 | throw new Error('simcapredict: parameter "params" must be an object from "simcaparams" method.');
135 | }
136 |
137 | if (!ismatrix(X)) {
138 | throw new Error('simcapredict: parameter "X" must be instance of Matrix class.');
139 | }
140 |
141 | if (cRef && !factor(cRef)) {
142 | throw new Error('simcapredict: parameter "cRef" must be instance of Factor class.');
143 | }
144 |
145 | if (cRef && cRef.length !== X.nrows) {
146 | throw new Error('simcapredict: parameter "cRef" must have the same number of values as rows in "X".');
147 | }
148 |
149 |
150 | // project data to PCA model
151 | const pcares = pcapredict(m, X);
152 |
153 | // prepare array for predictions
154 | const cPred = new Array(m.ncomp);
155 |
156 | // get parameters for the distances
157 | const Nq = m.qParams[params.limType][1].v;
158 | const q0 = m.qParams[params.limType][0].v;
159 | const h0 = m.hParams[params.limType][0].v;
160 | const Nh = m.hParams[params.limType][1].v;
161 |
162 | // loop over components
163 | for (let a = 1; a <= m.ncomp; a++) {
164 |
165 | const h0a = h0[a - 1];
166 | const q0a = q0[a - 1];
167 | const Nha = Nh[a - 1];
168 | const Nqa = Nq[a - 1];
169 |
170 | const ha = pcares.H.getcolref(a);
171 | const qa = pcares.Q.getcolref(a);
172 | const cpa = new Array(X.nrows);
173 |
174 | // compute critical value for FD
175 | const fCrit = qchisq(1 - params.alpha, Nqa + Nha);
176 |
177 | // loop over rows to make predictions
178 | for (let i = 0; i < X.nrows; i++) {
179 | const fa = ha[i] / h0a * Nha + qa[i] / q0a * Nqa;
180 | cpa[i] = fa <= fCrit ? params.className : "none";
181 | }
182 |
183 | cPred[a - 1] = factor(cpa);
184 | }
185 |
186 | return {
187 | class: ['simcares'],
188 | pcares: pcares,
189 | classres: getclassres(cPred, params.className, cRef)
190 | }
191 | }
192 |
193 | /**
194 | * Fit Partial Least Squares Regression model.
195 | *
196 | * @param {Matrix} X - matrix with predictors.
197 | * @param {Matrix} Y - matirx with responses (must have one column).
198 | * @param {number} ncomp - number of components.
199 | * @param {boolean} [center=true] - logical, mean center X and Y or not.
200 | * @param {boolean} [scale=false] - logical, standardize X and Y or not.
201 | *
202 | * @returns {JSON} object with model parameters.
203 | *
204 | */
205 | export function plsfit(X, Y, ncomp, center, scale) {
206 |
207 | if (!ismatrix(X) || X.ncols < 2 || X.nrows < 2) {
208 | throw Error('plsfit: parameter "X" must be a matrix with at least two rows and two columns.');
209 | }
210 |
211 | if (!ismatrix(Y) || Y.ncols !== 1) {
212 | throw Error('plsfit: parameter "Y" must be a matrix with one column.');
213 | }
214 |
215 | if (Y.nrows !== X.nrows) {
216 | throw Error('plsfit: number of rows in "X" and "Y" do not match.');
217 | }
218 |
219 | if (center === undefined) {
220 | center = true;
221 | }
222 |
223 | if (scale === undefined) {
224 | scale = false;
225 | }
226 |
227 | if (!ncomp) {
228 | ncomp = Math.min(ncols, nrows - 1);
229 | }
230 |
231 | // center and scale the training set
232 | const [Xp, mX, sX] = prep_scale(X, center, scale, true);
233 | const [Yp, mY, sY] = prep_scale(Y, center, scale, true);
234 |
235 | // compute loadings and eigenvalues for X using randomized SVD
236 | const m = simpls(Xp, Yp, ncomp);
237 |
238 | // compute distances and variance
239 | const pcares = pcagetmainres(Xp, m.T, m.P, m.xeigenvals);
240 |
241 | // compute parameters for distances
242 | const hParams = getDistParams(pcares.H);
243 | const qParams = getDistParams(pcares.Q);
244 |
245 | // compute Y-loadings
246 |
247 | // return the model object
248 | return {
249 | class: ['plsmodel', 'regmodel', 'pcamodel'],
250 | ncomp: ncomp,
251 | center: center,
252 | scale: scale,
253 |
254 | // X part
255 | mX: mX,
256 | sX: sX,
257 | P: m.P,
258 | R: m.R,
259 | xeigenvals: m.xeigenvals,
260 |
261 | // Y part
262 | mY: mY,
263 | sY: sY,
264 | C: m.C,
265 | yeigenvals: m.yeigenvals,
266 |
267 | // distances
268 | qParams: qParams,
269 | hParams: hParams,
270 | };
271 | }
272 |
273 |
274 | /**
275 | * Make predictions for PCR model and new dataset.
276 | *
277 | * @param {JSON} m - PCR model created by 'pcrfit()'.
278 | * @param {Matrix} X - matrix with predictors.
279 | * @param {Matrix} Y - matirx with responses (must have one column, use empty array [], if no response available).
280 | * @param {string} name - text label for the results with objects.
281 | *
282 | * @returns {JSON} object with main results.
283 | *
284 | */
285 | export function plspredict(m, X, Y, name) {
286 |
287 | const Xp = prep_scale(X, m.mX, m.sX);
288 |
289 | // compute scores
290 | const T = Xp.dot(m.R);
291 |
292 | // compute distances and variance
293 | const pcares = pcagetmainres(Xp, T, m.P, m.xeigenvals);
294 |
295 | // compute Y-scores if reference values provided
296 | let U = null;
297 | if (Y !== undefined && Y !== null) {
298 |
299 | if (!ismatrix(Y)) {
300 | throw Error('plcapredict: parameter "Y" must be a matrix with one column or null/undefined.');
301 | }
302 |
303 | const Yp = prep_scale(Y, m.mY, m.sY);
304 | U = Yp.dot(m.C);
305 |
306 | // oprthogonalize y-scores
307 | for (let a = 2; a <= m.ncomp; a++) {
308 | const ua = U.subset([], a);
309 | const Ta = T.subset([], Index.seq(1, a - 1));
310 | const uao = ua.subtract(Ta.dot(crossprod(Ta, ua)));
311 | U.v.set(uao.v, (a - 1) * U.nrows);
312 | }
313 | }
314 |
315 | const regres = reggetmainres(T, m.C, m.mY, m.sY, Y);
316 |
317 | return {
318 | class: ['pcares', 'regres', 'plsres'],
319 | name: name,
320 | T: T,
321 | U: U,
322 | ...pcares,
323 | ...regres
324 | };
325 | }
326 |
327 |
328 | /**
329 | * Fit Principal Component Regression model.
330 | *
331 | * @param {Matrix} X - matrix with predictors.
332 | * @param {Matrix} Y - matirx with responses (must have one column).
333 | * @param {number} ncomp - number of components.
334 | * @param {boolean} [center=true] - logical, mean center X and Y or not.
335 | * @param {boolean} [scale=false] - logical, standardize X and Y or not.
336 | *
337 | * @returns {JSON} object with model parameters.
338 | */
339 | export function pcrfit(X, Y, ncomp, center, scale) {
340 |
341 | if (!ismatrix(X) || X.ncols < 2 || X.nrows < 2) {
342 | throw Error('pcrfit: parameter X must be a matrix with at least two rows and two columns.');
343 | }
344 |
345 | if (!ismatrix(Y) || Y.ncols !== 1) {
346 | throw Error('pcrfit: parameter Y must be a matrix with one column.');
347 | }
348 |
349 | if (Y.nrows !== X.nrows) {
350 | throw Error('pcrfit: number of rows in X and Y do not match.');
351 | }
352 |
353 |
354 | if (center === undefined) {
355 | center = true;
356 | }
357 |
358 | if (scale === undefined) {
359 | scale = false;
360 | }
361 |
362 | if (!ncomp) {
363 | ncomp = Math.min(ncols, nrows - 1);
364 | }
365 |
366 | // center and scale the training set
367 | const [Xp, mX, sX] = prep_scale(X, center, scale, true);
368 | const [Yp, mY, sY] = prep_scale(Y, center, scale, true);
369 |
370 | // compute loadings and eigenvalues for X using randomized SVD
371 | const m = rsvd(Xp, ncomp);
372 | const eigenvals = m.s.apply(x => x * x / (X.nrows - 1));
373 |
374 | // compute main PCA results for calibration set
375 | const calres = pcagetmainres(Xp, m.U.dot(Matrix.diagm(m.s)), m.V, eigenvals);
376 |
377 | // compute parameters for distances
378 | const hParams = getDistParams(calres.H);
379 | const qParams = getDistParams(calres.Q);
380 |
381 | // compute Y-loadings
382 | const C = Matrix.zeros(1, ncomp);
383 | for (let a = 1; a <= ncomp; a++) {
384 | const sigmaa = m.s.v[a - 1];
385 | const ta = calres.T.getcolref(a);
386 | C.v[a - 1] = _dot(Yp.v, ta, 1, X.nrows, X.nrows, 1) / (sigmaa * sigmaa);
387 | }
388 |
389 | // return the model object
390 | return {
391 | class: ['pcrmodel', 'regmodel', 'pcamodel'],
392 |
393 | // PCA part
394 | P: m.V,
395 | eigenvals: eigenvals,
396 | center: center,
397 | scale: scale,
398 | mX: mX,
399 | sX: sX,
400 | qParams: qParams,
401 | hParams: hParams,
402 |
403 | // PCR part
404 | ncomp: ncomp,
405 | C: C,
406 | mY: mY,
407 | sY: sY,
408 | }
409 | }
410 |
411 |
412 | /**
413 | * Make predictions for PCR model and new dataset.
414 | *
415 | * @param {JSON} m - PCR model created by 'pcrfit()'.
416 | * @param {Matrix} X - matrix with predictors.
417 | * @param {Matrix} Y - matirx with responses (must have one column, use empty array [], if no response available).
418 | * @param {string} name - text label for the results with objects.
419 | *
420 | * @returns {JSON} object with main results.
421 | */
422 | export function pcrpredict(m, X, Y, name) {
423 |
424 |
425 | const Xp = prep_scale(X, m.mX, m.sX);
426 |
427 | // compute PCA results
428 | const T = Xp.dot(m.P);
429 | const pcares = pcagetmainres(Xp, T, m.P, m.eigenvals);
430 |
431 | // compute regression results
432 | const regres = reggetmainres(T, m.C, m.mY, m.sY, Y);
433 |
434 | return {
435 | class: ['pcares', 'regres', 'pcrres'],
436 | name: name,
437 | ...pcares,
438 | ...regres
439 | };
440 | }
441 |
442 |
443 | /**
444 | * Fit a Principal Component Analysis model.
445 | *
446 | * @param {Matrix} X - matrix or dataset with data values.
447 | * @param {number} ncomp - number of components to compute.
448 | * @param {boolean|Vector} [center=true] - logical (mean center or not) or vector with values for centering.
449 | * @param {boolean|Vector} [scale=false] - logical (standardize or not) or vector with values for scaling.
450 | *
451 | * @returns {Object} JSON with model parameters.
452 | *
453 | */
454 | export function pcafit(X, ncomp, center, scale) {
455 |
456 | if (center === undefined) {
457 | center = true;
458 | }
459 |
460 | if (scale === undefined) {
461 | scale = false;
462 | }
463 |
464 | if (!ismatrix(X) || X.ncols < 2 || X.nrows < 2) {
465 | throw Error('plsfit: parameter "X" must be a matrix with at least two rows and two columns.');
466 | }
467 |
468 | if (!ncomp) {
469 | ncomp = Math.min(X.ncols, X.nrows - 1);
470 | }
471 |
472 | if (ncomp < 0 || ncomp > X.ncols || ncomp > X.nrows - 1) {
473 | throw new Error('pcafit: wrong value for "ncomp" parameter.');
474 | }
475 |
476 | // center and scale the training set
477 | const [Xp, mX, sX] = prep_scale(X, center, scale, true);
478 |
479 | // compute loadings and eigenvalues
480 | const m = rsvd(Xp, ncomp);
481 | const eigenvals = m.s.apply(v => v * v / (X.nrows - 1));
482 |
483 | // compute main PCA results for calibration set
484 | const calres = pcagetmainres(Xp, m.U.dot(Matrix.diagm(m.s)), m.V, eigenvals);
485 |
486 | // compute mean values for distances
487 | const hParams = getDistParams(calres.H);
488 | const qParams = getDistParams(calres.Q);
489 |
490 | // return the model object
491 | return {
492 | class: ['pcamodel'],
493 | P: m.V,
494 | eigenvals: eigenvals,
495 | center: center,
496 | scale: scale,
497 | mX: mX,
498 | sX: sX,
499 | qParams: qParams,
500 | hParams: hParams,
501 | ncomp: ncomp,
502 | nCalObj: Xp.nrows,
503 | results: {'cal': calres}
504 | }
505 | }
506 |
507 |
508 | /**
509 | * Compute parameters of distance distributions from distance matrix U.
510 | *
511 | * @param {Matrix} U - matrix with distances.
512 | *
513 | * @returns {Array} array with two vectors, u0 (scalars) and Nu (number of degrees of freedom).
514 | *
515 | */
516 | export function getDistParams(U) {
517 |
518 | const ncomp = U.ncols;
519 | const u0m = Vector.zeros(ncomp)
520 | const u0r = Vector.zeros(ncomp)
521 | const Num = Vector.zeros(ncomp)
522 | const Nur = Vector.zeros(ncomp)
523 |
524 | const Nu = Vector.zeros(ncomp);
525 | for (let i = 0; i < ncomp; i++) {
526 | const u = U.getcolref(i + 1);
527 |
528 | // moments
529 | u0m.v[i] = mean(u);
530 | const fm = u0m.v[i] / sd(u);
531 | const Nm = Math.round(2 * fm * fm);
532 | Num.v[i] = Nm < 1 ? 1 : Nm > 250 ? 250 : Nm;
533 |
534 | // robust
535 | const M = median(u);
536 | const S = iqr(u);
537 | const RM = S / M;
538 | const Nr = RM > 2.685592117 ? 1 :
539 | (RM < 0.194565995 ? 100 :
540 | Math.round(Math.exp(Math.pow(1.380948 * Math.log(2.68631 / RM), 1.185785))));
541 | u0r.v[i] = 0.5 * Nr * (M / qchisq(0.5, Nr) + S / (qchisq(0.75, Nr) - qchisq(0.25, Nr)))
542 | Nur.v[i] = Nr;
543 | }
544 |
545 | return {'classic': [u0m, Num], 'robust': [u0r, Nur]};
546 | }
547 |
548 |
549 | /**
550 | * Compute main PCA results for already preprocessed dataset.
551 | *
552 | * @param {Matrix} Xp - matrix with preprocessed (e.g. centered) data values.
553 | * @param {Matrix} T - matrix with scores.
554 | * @param {Matrix} P - matrix with loadings.
555 | * @param {Vector} eigenvals - vector with eigenvalues.
556 | *
557 | * @returns {Object} JSON with main outcomes (scores, distances, variances).
558 | *
559 | */
560 | export function pcagetmainres(Xp, T, P, eigenvals) {
561 |
562 | const ncomp = P.ncols;
563 | const nrows = Xp.nrows;
564 | const nvars = Xp.ncols;
565 |
566 | // prepare objects for results
567 | const H = Matrix.zeros(nrows, ncomp);
568 | const Q = Matrix.zeros(nrows, ncomp);
569 | const expvar = Vector.zeros(ncomp);
570 | const cumexpvar = Vector.zeros(ncomp);
571 |
572 | // compute total sum of squares
573 | const ha = new Vector.valuesConstructor(nrows);
574 | const E = Xp.copy();
575 | const totssq = ssq(Xp.v);
576 |
577 | // loop for computing variances and distances
578 | for (let a = 1; a <= ncomp; a++) {
579 | const ta = T.getcolref(a);
580 | const pa = P.getcolref(a);
581 | const ts = 1 / eigenvals.v[a - 1];
582 |
583 | const qa = new Vector.valuesConstructor(nrows);
584 | let qs = 0;
585 | for (let c = 0; c < nvars; c++) {
586 | const e = E.getcolref(c + 1);
587 | for (let r = 0; r < nrows; r++) {
588 | e[r] = e[r] - ta[r] * pa[c]
589 | const essq = e[r] * e[r];
590 | qa[r] += essq;
591 | qs += essq;
592 | }
593 | }
594 |
595 | for (let r = 0; r < nrows; r++) {
596 | ha[r] += ta[r] * ta[r] * ts;
597 | }
598 |
599 | H.v.set(ha, (a - 1) * nrows);
600 | Q.v.set(qa, (a - 1) * nrows);
601 |
602 | cumexpvar.v[a - 1] = 100 * (1 - qs / totssq);
603 | expvar.v[a - 1] = a === 1 ? cumexpvar.v[a - 1] : cumexpvar.v[a - 1] - cumexpvar.v[a - 2];
604 | }
605 |
606 | return {
607 | class: ['pcares', 'ldecomp'],
608 | T: T,
609 | H: H,
610 | Q: Q,
611 | expvar: expvar,
612 | cumexpvar: cumexpvar
613 | }
614 | }
615 |
616 |
617 | export function getfulldistance(h, q, h0, q0, Nh, Nq) {
618 | const fh = Nh / h0;
619 | const fq = Nq / q0;
620 |
621 | const f = Vector.zeros(h.length);
622 | for (let r = 0; r <= f.length; r++) {
623 | f.v[r] = h.v[r] * fh + q.v[r] * fq;
624 | }
625 |
626 | return f;
627 | }
628 |
629 |
630 | /**
631 | * Project new data to a PCA model and create object with main outcomes.
632 | *
633 | * @param {Object} m - JSON with PCA model created by 'pcafit()'.
634 | * @param {Matrix} X - matrix with data values.
635 | * @param {string} name - name/label for the result object (e.g. "cal", "val", etc.).
636 | *
637 | * @returns {Object} - JSON wiht main outcomes (scores, distances, variance, etc.).
638 | *
639 | */
640 | export function pcapredict(m, X, name) {
641 |
642 | if (!ismatrix(X) || X.nrows < 1) {
643 | throw Error('pcapredict: parameter "X" must be a matrix.');
644 | }
645 |
646 | if (X.ncols != m.P.nrows) {
647 | throw Error('pcapredict: parameter "X" has wrong number of columns.');
648 | }
649 |
650 | const Xp = prep_scale(X, m.mX, m.sX);
651 | const res = pcagetmainres(Xp, Xp.dot(m.P), m.P, m.eigenvals);
652 | res.name = name;
653 |
654 | return res;
655 | }
656 |
657 |
658 | /**
659 | * For given vector 'x' and number 'd' returns matrix with power of x values from 1 to d as columns.
660 | *
661 | * @param {Vector} x - vector with values.
662 | * @param {number} d - polynomial degree.
663 | *
664 | * @returns a matrix with 'd' columns.
665 | *
666 | */
667 | export function polymat(x, d) {
668 |
669 | if (!isvector(x)) {
670 | throw Error('polymat: argument "x" must be a vector.');
671 | }
672 |
673 | const X = Matrix.zeros(x.length, d);
674 | for (let i = 1; i <= d; i++) {
675 | X.v.set(x.apply(v => v ** i).v, x.length * (i - 1));
676 | }
677 |
678 | return X;
679 | }
680 |
681 |
682 | /**
683 | * Fitting a univariate polynomial model.
684 | *
685 | * @param {Vector} x - vector with predictors.
686 | * @param {Vector} y - vector with responses.
687 | * @param {number} d - polynomial degree.
688 | *
689 | * @return JSON with model parameters and performance statistics.
690 | *
691 | */
692 | export function polyfit(x, y, d) {
693 |
694 | if (d < 1 || d >= x.length) {
695 | throw Error('polyfit: polynomial degree "d" must a positive value smaller than number of measurements.');
696 | }
697 |
698 | let model = lmfit(polymat(x, d), y);
699 | model.pdegree = d;
700 | model.class = "pm";
701 |
702 | return model;
703 | }
704 |
705 |
706 | /**
707 | * Predicts response values based on the fitted model and predictors.
708 | *
709 | * @param {JSON} m - regression model (object returned by 'polyfit()').
710 | * @param {vector} x - vector with predictors.
711 | *
712 | * @return vector with predicted response values.
713 | *
714 | */
715 | export function polypredict(m, x) {
716 |
717 | if (!isvector(x)) {
718 | throw Error('polypredict: Argument "x" must be a vector.');
719 | }
720 |
721 | if (m.class !== "pm") {
722 | throw Error('polypredict: argument "m" must be object with "pm" model.');
723 | }
724 |
725 | return lmpredict(m, polymat(x, m.pdegree));
726 | }
727 |
728 |
729 | /**
730 | * Fit a linear model (SLR or MLR).
731 | *
732 | * @param {Vector|Matrix} X - vector or matrix with predictors.
733 | * @param {Vector} y - vector with responses.
734 | *
735 | * @return JSON with model parameters and performance statistics.
736 | *
737 | */
738 | export function lmfit(X, y) {
739 |
740 | if (isvector(X)) {
741 | X = reshape(X, X.length, 1);
742 | }
743 |
744 | if (!ismatrix(X)) {
745 | throw Error('lmfit: argument "X" must be a matrix or a vector.');
746 | }
747 |
748 | if (!isvector(y)) {
749 | throw Error('lmfit: argument "y" must be a vector.');
750 | }
751 |
752 | const n = X.nrows;
753 | if (y.length !== n) {
754 | throw Error('lmfit: arguments "X" and "y" must have the same number of objects.');
755 | }
756 |
757 | if (n <= X.ncols) {
758 | throw Error('lmfit: number of objects must be larger than number of predictors.');
759 | }
760 |
761 | // add column of ones for estimation of intercept
762 | const Xr = cbind(Vector.ones(n), X);
763 |
764 | // compute inverse of variance-covariance matrix
765 | const R = crossprod(Xr).inv();
766 |
767 | // estimate regression coefficients
768 | const estimate = reshape(R.dot(Xr.t()).dot(y), X.ncols + 1);
769 |
770 | // compute predicted y-values and performance statistics
771 | const fitted = reshape(Xr.dot(estimate), n);
772 | const stat = regstat(y, fitted, X.ncols);
773 |
774 | // compute standard error and t-values for regression coefficients, H0: beta = 0
775 | const coeffse = R.mult(stat.se * stat.se).diag().apply(Math.sqrt);
776 | const tstat = estimate.divide(coeffse);
777 |
778 | // compute critical t-value for confidence intervals
779 | const tCrit = qt(0.975, stat.DoF);
780 |
781 | // return JSON with all results
782 | const errMargin = coeffse.mult(tCrit);
783 | return {
784 | class: "lm",
785 | data: {X: X, y: y},
786 | coeffs: {
787 | estimate: estimate,
788 | se: coeffse,
789 | tstat: tstat,
790 | p: tstat.apply(t => t > 0 ? 2 * pt(-t, stat.DoF) : 2 * pt(t, stat.DoF)),
791 | lower: estimate.subtract(errMargin),
792 | upper: estimate.add(errMargin)
793 | },
794 | fitted: fitted,
795 | stat: stat
796 | }
797 | }
798 |
799 |
800 | /**
801 | * Predicts response values based on the fitted model and predictors
802 | * @param {JSON} m - regression model (object returned by 'limfit()')
803 | * @param {number[]} X - vector or matrix with predictors
804 | * @return vector with predicted response values
805 | */
806 | export function lmpredict(m, X) {
807 |
808 | if (isvector(X)) {
809 | X = reshape(X, X.length, 1);
810 | }
811 |
812 | if (!ismatrix(X)) {
813 | throw Error('lmpredict: argument "X" must be a matrix or a vector.');
814 | }
815 |
816 | if (!(m.class === 'lm' || m.class === 'pm') || !m.coeffs.estimate || m.coeffs.estimate.length < 1) {
817 | throw Error('lmpredict: argument "m" must be object with "lm" model.');
818 | }
819 |
820 | if (X.ncols !== (m.coeffs.estimate.length - 1)) {
821 | throw Error('lmpredict: number of columns in "X" does not match number of coefficients in model.');
822 | }
823 |
824 |
825 | // add column of ones for estimation of intercept
826 | const n = X.nrows;
827 | const Xr = cbind(Vector.ones(n), X);
828 | return reshape(Xr.dot(m.coeffs.estimate), n);
829 | }
830 |
831 |
832 | /**
833 | * Computes performance statistics for predicted and reference response values
834 | * @param {number[]} y — vector with reference response values
835 | * @param {number[]} yp — vector with predicted response values
836 | * @return JSON with statistics (adjusted R2, R2, s(e), F-value, p-value)
837 | */
838 | export function regstat(y, yp, p) {
839 |
840 | const n = y.length;
841 | if (!p) p = 1;
842 | const e = y.subtract(yp);
843 | const SSe = ssq(e);
844 | const SSy = variance(y) * (n - 1);
845 | const R2 = (1 - SSe / SSy);
846 | const DoF = n - p - 1
847 | const F = ((SSy - SSe)/p) / (SSe/DoF);
848 |
849 | return {
850 | R2: R2,
851 | R2adj: 1 - (1 - R2) * (n - 1) / DoF,
852 | Fstat: F,
853 | p: 1 - pf(F, p, DoF),
854 | DoF: DoF,
855 | se: Math.sqrt(SSe/DoF)
856 | };
857 | }
858 |
859 |
860 | /**
861 | * Split matrix with data values into matrix of predictors (X) and and matrix with responses (Y).
862 | *
863 | * @param {Matrix} data - matrix with data values.
864 | *
865 | * @returns {Array} array with X and Y.
866 | */
867 | export function splitregdata(data) {
868 |
869 | return [
870 | new Matrix(data.v.subarray(data.nrows), data.nrows, data.ncols - 1),
871 | new Matrix(data.getcolref(1), data.nrows, 1)
872 | ];
873 | }
874 |
875 |
876 | /**
877 | * Make predictions based on x-scores and y-loadings (for PCR or PLS1) and compute
878 | * performance statistics if reference y-values are provided.
879 | *
880 | * @param {Matrix} T - matrix with X-scores.
881 | * @param {Matrix} C - matrix with y-loadings.
882 | * @param {number} mY - number to use for centering y-values (from the model).
883 | * @param {number} sY - number to use for scaling y-values (from the model).
884 | * @param {Vector} [y] - vector with reference response values.
885 | */
886 | export function reggetmainres(T, C, mY, sY, Yref) {
887 |
888 | const nrows = T.nrows;
889 | const ncomp = T.ncols;
890 |
891 | // prepare variables for predictions and statistics
892 | const Ypred = Matrix.zeros(nrows, ncomp);
893 |
894 | // if no reference values, compute predictions and return
895 | if (Yref === undefined || Yref === null || Yref.length === 0) {
896 | for (let a = 1; a <= ncomp; a++) {
897 | // compute predictions
898 | const cind = Index.seq(1, a);
899 | const Ypreda = prep_unscale(tcrossprod(T.subset([], cind), C.subset([], cind)), mY, sY);
900 | Ypred.v.set(Ypreda.v, (a - 1) * nrows);
901 | }
902 |
903 | return {'Ypred': Ypred};
904 | }
905 |
906 | // otherwise compute statistics as well
907 | const rmse = Vector.zeros(ncomp);
908 | const r2 = Vector.zeros(ncomp);
909 | const bias = Vector.zeros(ncomp);
910 |
911 | // total y-variance
912 | const ssy = variance(Yref.v) * (Yref.nrows - 1);
913 | for (let a = 1; a <= ncomp; a++) {
914 |
915 | // compute predictions
916 | const cind = Index.seq(1, a);
917 | const Ypreda = prep_unscale(tcrossprod(T.subset([], cind), C.subset([], cind)), mY, sY);
918 | Ypred.v.set(Ypreda.v, (a - 1) * nrows);
919 |
920 | // compute performance statistics
921 | const e = Yref.subtract(Ypreda);
922 | const sse = ssq(e.v);
923 | bias.v[a - 1] = mean(e.v);
924 | rmse.v[a - 1] = Math.sqrt(sse / nrows);
925 | r2.v[a - 1] = 1 - sse / ssy;
926 | }
927 |
928 | return {
929 | "Yref": Yref,
930 | "Ypred": Ypred,
931 | "rmse": rmse,
932 | "r2": r2,
933 | "bias": bias
934 | };
935 | }
936 |
937 |
938 | /**
939 | * Implementation of SIMPLS algorithm.
940 | *
941 | * @param {Matrix} X - matrix with predictors.
942 | * @param {Matrix} Y - matrix with responses.
943 | * @param {number} ncomp - number of components.
944 | *
945 | * @return {JSON} JSON with decomposition results.
946 | *
947 | */
948 | export function simpls(X, Y, ncomp) {
949 |
950 | const nobj = X.nrows;
951 | const npred = X.ncols;
952 | const nresp = Y.ncols;
953 |
954 | // initial estimation
955 | let S = crossprod(X, Y);
956 | let M = crossprod(X);
957 |
958 | // prepare space for results
959 | const C = Matrix.zeros(nresp, ncomp)
960 | const R = Matrix.zeros(npred, ncomp)
961 | const V = Matrix.zeros(npred, ncomp)
962 | const P = Matrix.zeros(npred, ncomp)
963 | const T = Matrix.zeros(nobj, ncomp)
964 | const U = Matrix.zeros(nobj, ncomp)
965 |
966 | const xeigenvals = Vector.zeros(ncomp);
967 | const yeigenvals = Vector.zeros(ncomp);
968 |
969 | // loop for each components
970 | for (let a = 1; a <= ncomp; a++) {
971 |
972 | let r = rsvd(S, 1).U
973 | let t = X.dot(r);
974 |
975 | const tnorm = norm2(t.v);
976 | t = t.apply(v => v / tnorm, 0);
977 | r = r.apply(v => v / tnorm, 0);
978 |
979 | const p = crossprod(X, t);
980 | const c = crossprod(Y, t);
981 | let u = Y.dot(c);
982 | let v = p.copy()
983 |
984 | if (a > 1) {
985 | v = v.subtract(V.dot(crossprod(V, p)));
986 | u = u.subtract(T.dot(crossprod(T, u)));
987 | }
988 |
989 | const vnorm = norm2(v.v);
990 | v = v.apply(x => x / vnorm, 0);
991 |
992 | R.v.set(r.v, (a - 1) * npred);
993 | V.v.set(v.v, (a - 1) * npred);
994 | P.v.set(p.v, (a - 1) * npred);
995 | T.v.set(t.v, (a - 1) * nobj);
996 | U.v.set(u.v, (a - 1) * nobj);
997 | C.v.set(c.v, (a - 1) * nresp);
998 |
999 |
1000 | xeigenvals.v[a - 1] = ssq(t.v) / (nobj - 1);
1001 | yeigenvals.v[a - 1] = ssq(u.v) / (nobj - 1);
1002 |
1003 | M = M.subtract(tcrossprod(p))
1004 | S = S.subtract(v.dot(crossprod(v, S)));
1005 | }
1006 |
1007 | return {
1008 | R: R, P: P, T: T, C: C, U: U,
1009 | xeigenvals: xeigenvals,
1010 | yeigenvals: yeigenvals
1011 | };
1012 | }
1013 |
--------------------------------------------------------------------------------
/src/prep/index.js:
--------------------------------------------------------------------------------
1 | /********************************************************************/
2 | /* Methods for Preprocessing (work with vectors and matrices) */
3 | /********************************************************************/
4 |
5 | import { isvector, Vector, Matrix, vector } from '../arrays/index.js';
6 | import { mean, sd } from '../stat/index.js';
7 |
8 | /**
9 | * Uncenter and unscale values in every column of a matrix.
10 | *
11 | * @param {Matrix} X - matrix with values.
12 | * @param {Vector} center - vector with numbers to use for uncentering.
13 | * @param {Vector} scale - vector with numbers to use for unscaling.
14 | *
15 | * @returns {Matrix} a matrix with uncentered and unscaled column values.
16 | *
17 | */
18 | export function unscale(X, centerValues, scaleValues) {
19 |
20 | const Xp = Matrix.zeros(X.nrows, X.ncols);
21 | for (let c = 1; c <= X.ncols; c++) {
22 | const xc = X.getcolref(c);
23 | const xpc = Xp.getcolref(c);
24 |
25 | const cv = centerValues.v[c - 1];
26 | const sv = scaleValues.v[c - 1];
27 |
28 | for (let r = 0; r < X.nrows; r++) {
29 | xpc[r] = xc[r] * sv + cv;
30 | }
31 | }
32 |
33 | return Xp;
34 | }
35 |
36 |
37 | /**
38 | * Center and scale values in every column of a matrix.
39 | *
40 | * @param {Matrix} X - matrix with values.
41 | * @param {boolean|Vector} [center=true] - logical value or a vector with numbers to use for centering.
42 | * @param {boolean|Vector} [scale=true] - logical value or a vector with numbers to use for scaling.
43 | * @param {boolean} [full=true] - logical, influences the return values.
44 | *
45 | * @returns {Array|Matrix} either a matrix with centered and scaled column values (if 'full = false')
46 | * or an array with three elements: the matrix, vector with values for centring and vector with values
47 | * for scaling.
48 | *
49 | */
50 | export function scale(X, center, scale, full) {
51 |
52 | if (center === undefined) {
53 | center = true;
54 | }
55 |
56 | if (scale === undefined) {
57 | scale = false;
58 | }
59 |
60 | if (full === undefined) {
61 | full = false;
62 | }
63 |
64 | function getStat(X, param, fun, alt) {
65 |
66 | if (typeof(param) === "boolean") {
67 | return param ? X.apply(fun, 2) : vector([alt]).rep(X.ncols);
68 | }
69 |
70 | if (!isvector(param)) {
71 | throw Error("scale: parameters 'center' and 'scale' must be boolean or vectors with numeric values.");
72 | }
73 |
74 | if (param.length !== X.ncols ) {
75 | throw Error("scale: number of values for centring and scaling must be the same as number of columns in the matrix.");
76 | }
77 |
78 | return param;
79 | }
80 |
81 | // prepare values for centering and scaling
82 | const centerValues = getStat(X, center, mean, 0);
83 | const scaleValues = getStat(X, scale, sd, 1);
84 |
85 | const Xp = Matrix.zeros(X.nrows, X.ncols);
86 | for (let c = 1; c <= X.ncols; c++) {
87 | const xc = X.getcolref(c);
88 | const xpc = Xp.getcolref(c);
89 |
90 | const cv = centerValues.v[c - 1];
91 | const sv = 1 / scaleValues.v[c - 1];
92 |
93 | for (let r = 0; r < X.nrows; r++) {
94 | xpc[r] = (xc[r] - cv) * sv;
95 | }
96 | }
97 |
98 | return full ? [Xp, centerValues, scaleValues] : Xp;
99 | }
100 |
101 |
--------------------------------------------------------------------------------
/src/stat/index.js:
--------------------------------------------------------------------------------
1 | /*************************************************/
2 | /* Methods for computing statistics */
3 | /*************************************************/
4 |
5 | import { isnumber, isindex, isvector, vector, Vector, _sort } from '../arrays/index.js';
6 |
7 |
8 | /**
9 | * Compute median of vector values.
10 | *
11 | * @param {Array|Vector} x - vector with values.
12 | *
13 | * @returns {number} median of x.
14 | *
15 | */
16 | export function median(x) {
17 | return quantile(x, 0.5)
18 | }
19 |
20 | /**
21 | * Compute inter-quartile range for vector of values.
22 | *
23 | * @param {Array|Vector} x - vector with values.
24 | *
25 | * @returns {number} IQR of x (Q3 - Q1).
26 | *
27 | */
28 | export function iqr(x) {
29 | return quantile(x, 0.75) - quantile(x, 0.25);
30 | }
31 |
32 |
33 | /**
34 | * Computes a p-th quantile/quantiles for a numeric vector.
35 | *
36 | * @param {Vector} x - vector with values.
37 | * @param {number|Array|Vector} p - probability (one value or a vector).
38 | *
39 | * @returns {number|Vector} quantile value or a vector with quantiles.
40 | */
41 | export function quantile(x, p) {
42 |
43 | if (isvector(x)) {
44 | return quantile(x.v, p);
45 | }
46 |
47 | if (isvector(p)) {
48 | return quantile(x, p.v);
49 | }
50 |
51 | const n = x.length;
52 |
53 | if (!Array.isArray(p)) p = [p];
54 |
55 | if (!isnumber(p[0]) || min(p) < 0 || max(p) > 1) {
56 | throw new Error("Parameter 'p' must be between 0 and 1 (both included).");
57 | }
58 |
59 | function q(x, p) {
60 | const h = (n - 1) * p + 1;
61 | const n1 = Math.floor(h);
62 | const n2 = Math.ceil(h);
63 | return x[n1 - 1] + (x[n2 - 1] - x[n1 - 1]) * (h - Math.floor(h));
64 | }
65 |
66 | const xs = _sort(x);
67 | const out = p.map(v => q(xs, v));
68 | return p.length == 1 ? out[0] : vector(out);
69 | }
70 |
71 |
72 | /**
73 | * Counts how many values from a vector falls into provided intervals (bins).
74 | *
75 | * @param {Array|Vector} x - vector with values.
76 | * @param {Array|Vector} bins - vector with bins boundaries.
77 | *
78 | * @returns {Vector} vector with counts for each bean.
79 | *
80 | */
81 | export function count(x, bins) {
82 |
83 | if (isvector(x)) {
84 | return count(x.v, isvector(bins) ? bins.v : bins);
85 | }
86 |
87 | const n = bins.length;
88 |
89 | // add a bit extra to right side of the last bin
90 | bins[n - 1] = bins[n - 1] * 1.0001
91 |
92 | // count
93 | let counts = new Vector.valuesConstructor(n - 1);
94 | for (let i = 0; i < x.length; i++) {
95 | for (let j = 0; j < n - 1; j++) {
96 | if (x[i] >= bins[j] && x[i] < bins[j + 1]) counts[j] += 1;
97 | }
98 | }
99 |
100 | return new Vector(counts);
101 | }
102 |
103 |
104 | /**
105 | * Computes middle points between values of a vector.
106 | *
107 | * @param {Array|Vector} x - vector with values.
108 | *
109 | * @returns {Vector} vector with middle points.
110 | *
111 | */
112 | export function mids(x) {
113 |
114 | if (isvector(x)) {
115 | return mids(x.v);
116 | }
117 |
118 | const out = new Vector.valuesConstructor(x.length - 1);
119 | for (let i = 0; i < out.length; i++) {
120 | out[i] = 0.5 * (x[i] + x[i + 1]);
121 | }
122 |
123 | return new Vector(out);
124 | }
125 |
126 |
127 | /**
128 | * Splits range of vector values into equal intervals.
129 | *
130 | * @param {Array|Vector} x - vector with values.
131 | * @param {number} n - number of intervals.
132 | *
133 | * @returns {Vector} vector with boundaries of the intervals.
134 | *
135 | */
136 | export function split(x, n) {
137 |
138 | if (isvector(x)) {
139 | split(x.v, n);
140 | }
141 |
142 | const rn = range(x);
143 |
144 | if (rn[0] === rn[1]) {
145 | throw new Error('split: values in a vector "x" should vary.');
146 | }
147 |
148 | const by = (rn[1] - rn[0]) / n;
149 | return Vector.seq(rn[0], rn[1], by);
150 | }
151 |
152 |
153 | /**
154 | * Computes difference between all adjacent values in a vector.
155 | *
156 | * @param {Array|Vector} x - vector with values.
157 | *
158 | * @returns {Vector} vector with the differences.
159 | *
160 | */
161 | export function diff(x) {
162 |
163 | if (isvector(x)) {
164 | return diff(x.v);
165 | }
166 |
167 | const out = new Vector.valuesConstructor(x.length - 1);
168 | for (let i = 0; i < x.length - 1; i++) {
169 | out[i] = x[i + 1] - x[i];
170 | }
171 |
172 | return new Vector(out);
173 | }
174 |
175 |
176 | /**
177 | * Generate probability points for QQ plot.
178 | *
179 | * @param {number} n - number of points.
180 | *
181 | * @returns {Vector} a sequence of probabilities between 0 and 1.
182 | *
183 | */
184 | export function ppoints(n) {
185 |
186 | const a = n < 10 ? 3.0/8.0 : 0.5;
187 | const out = new Vector.valuesConstructor(n);
188 |
189 | for (let i = 0; i < n; i++) {
190 | out[i] = (i + 1 - a) / (n + (1 - a) - a);
191 | }
192 |
193 | return new Vector(out);
194 | }
195 |
196 |
197 | /**
198 | * Computes cumulative sums for the vector values.
199 | *
200 | * @param {Array|Vector} x - vector with values.
201 | *
202 | * @returns {Vectors} vector with cumulative sums.
203 | *
204 | */
205 | export function cumsum(x) {
206 |
207 | if (isvector(x)) {
208 | return cumsum(x.v);
209 | }
210 |
211 | let s = 0;
212 | let out = new Vector.valuesConstructor(x.length);
213 | for (let i = 0; i < x.length; i++) {
214 | s += x[i];
215 | out[i] = s
216 | }
217 |
218 | return new Vector(out);
219 | }
220 |
221 |
222 | /**
223 | * Computes kurtosis of values.
224 | *
225 | * @param {Array|Vector} x - vector with values.
226 | *
227 | * @returns {number} kurtosis of x.
228 | *
229 | */
230 | export function kurtosis(x) {
231 |
232 | if (isvector(x)) {
233 | return kurtosis(x.v);
234 | }
235 |
236 | let n = x.length;
237 | let m = mean(x);
238 |
239 | let m4 = 0.0;
240 | let m2 = 0.0;
241 | for (let i = 0; i < n; i++) {
242 | m2 = m2 + Math.pow((x[i] - m), 2);
243 | m4 = m4 + Math.pow((x[i] - m), 4);
244 | }
245 |
246 | return (m4/n) / Math.pow((m2/n), 2);
247 | }
248 |
249 |
250 | /**
251 | * Computes skewness of values.
252 | *
253 | * @param {Array|Vector} x - vector with values.
254 | *
255 | * @returns {number} skewness of x.
256 | *
257 | */
258 | export function skewness(x) {
259 |
260 | if (isvector(x)) {
261 | return skewness(x.v);
262 | }
263 |
264 | let n = x.length;
265 | let m = mean(x);
266 |
267 | let m3 = 0.0;
268 | let m2 = 0.0;
269 | for (let i = 0; i < n; i++) {
270 | m2 = m2 + Math.pow((x[i] - m), 2);
271 | m3 = m3 + Math.pow((x[i] - m), 3);
272 | }
273 |
274 | return (m3/n) / Math.pow((m2/n), 1.5);
275 | }
276 |
277 |
278 | /**
279 | * Computes correlation between two vectors.
280 | *
281 | * @param {Array|Vector} x - vector with values.
282 | * @param {Array|Vector} y - vector with values.
283 | * @param {string} method - which method to use ("pearson" or "spearman").
284 | *
285 | * @returns {number} correlation between x and y.
286 | *
287 | */
288 | export function cor(x, y, method = "pearson") {
289 |
290 | if (isvector(x)) {
291 | return cor(x.v, y.v, method);
292 | }
293 |
294 | if (method === "spearman") {
295 | return cor(rank(x), rank(y));
296 | }
297 |
298 | return cov(x, y) / (sd(x) * sd(y));
299 | }
300 |
301 |
302 | /**
303 | * Computes covariance between two vectors.
304 | *
305 | * @param {Array|Vector} x - vector with values.
306 | * @param {Array|Vector} y - vector with values.
307 | * @param {boolean} [biased=false] - compute a biased version with n degrees of freedom or not (with n - 1).
308 | * @param {number} [mx=undefined] - mean of x values (if already known).
309 | * @param {number} [my=undefined] - mean of y values (if already known).
310 | *
311 | * @returns {number} covariance between x and y.
312 | *
313 | */
314 | export function cov(x, y, biased = false, mx = undefined, my = undefined) {
315 |
316 | if (isvector(x)) {
317 | return cov(x.v, y.v, biased, mx, my);
318 | }
319 |
320 | const n = x.length;
321 |
322 | if (y.length !== n) {
323 | throw Error("Vectors 'x' and 'y' must have the same length.");
324 | }
325 |
326 | if (n < 2) {
327 | throw Error("Vectors 'x' and 'y' must have at least two values.");
328 | }
329 |
330 | if (mx === undefined) mx = mean(x);
331 | if (my === undefined) my = mean(y);
332 |
333 | let s = 0;
334 | for (let i = 0; i < n; i++) {
335 | s = s + (x[i] - mx) * (y[i] - my);
336 | }
337 |
338 | return s / (biased ? n : n - 1);
339 | }
340 |
341 |
342 | /**
343 | * Returns ranks of values in a vector (ranks start from 1, not 0).
344 | *
345 | * @param {Array|Vector} x - vector with values.
346 | *
347 | * @returns {Vector} vector with ranks.
348 | *
349 | */
350 | export function rank(x) {
351 |
352 | if (isvector(x)) {
353 | return rank(x.v);
354 | }
355 |
356 | const y = [...x].sort((a, b) => a - b);
357 |
358 | return new Vector(x.map(v => y.indexOf(v) + 1));
359 | }
360 |
361 |
362 | /**
363 | * Compute marginal range of values as [min, max]
364 | *
365 | * @param {Array|Vector} x - vector with values.
366 | * @param {number} m - margin as per cent of range (value between 0 and 1).
367 | *
368 | * @return {Array} array with two values, min and max.
369 | *
370 | */
371 | export function mrange(x, m) {
372 |
373 | if (isvector(x)) {
374 | return mrange(x.v, m);
375 | }
376 |
377 | if (m === undefined) m = 0.10;
378 | const r = range(x);
379 | const d = (r[1] - r[0]) * m;
380 |
381 | return [r[0] - d, r[1] + d];
382 | }
383 |
384 |
385 | /**
386 | * Compute range of values as [min, max].
387 | *
388 | * @param {Array|Vector} x - vector with values.
389 | *
390 | * @return {Array} array with two values, min and max.
391 | *
392 | */
393 | export function range(x) {
394 |
395 | if (isvector(x)) {
396 | return range(x.v);
397 | }
398 |
399 | let min = x[0];
400 | let max = x[0];
401 |
402 | for (let i = 1; i < x.length; i++) {
403 | if (x[i] < min) min = x[i];
404 | if (x[i] > max) max = x[i];
405 | }
406 |
407 | return [min, max];
408 | }
409 |
410 |
411 | /**
412 | * Compute norm2 of a vector (Euclidean distance).
413 | *
414 | * @param {Array|Vector} x - vector with values.
415 | *
416 | * @returns {number} norm value.
417 | *
418 | */
419 | export function norm2(x) {
420 |
421 | if (isvector(x)) {
422 | return norm2(x.v);
423 | }
424 |
425 | return Math.sqrt(ssq(x));
426 | }
427 |
428 |
429 | /**
430 | * Compute sum of squared vector values
431 | *
432 | * @param {Array|Vector} x - vector with values.
433 | *
434 | * @returns {number} sum of squared values of x.
435 | *
436 | */
437 | export function ssq(x) {
438 |
439 | if (isvector(x)) {
440 | return ssq(x.v);
441 | }
442 |
443 | let ssqv = 0;
444 | const n = x.length;
445 | for (let i = 0; i < n; i++) {
446 | const v = x[i]
447 | ssqv += v * v;
448 | }
449 |
450 | return ssqv;
451 | }
452 |
453 |
454 | /**
455 | * Compute standard deviation of vector values.
456 | *
457 | * @param {Array|Vector} x - vector with values.
458 | * @param {boolean} [biased=false] - compute a biased value (n degrees of freedom) or unbiased (n - 1 degrees of freedom)
459 | *
460 | * @returns {number} standard deviation of x.
461 | *
462 | */
463 | export function sd(x, biased) {
464 |
465 | if (isvector(x)) {
466 | return sd(x.v, biased);
467 | }
468 |
469 | return Math.sqrt(variance(x, biased));
470 | }
471 |
472 |
473 | /**
474 | * Compute variance of vector values.
475 | *
476 | * @param {Array|Vector} x - vector with values.
477 | * @param {boolean} [biased=false] - compute a biased variance (n degrees of freedom) or unbiased (n - 1 degrees of freedom)
478 | *
479 | * @returns {number} variance of x.
480 | *
481 | */
482 | export function variance(x, biased) {
483 |
484 | if (biased === undefined) {
485 | biased = false;
486 | }
487 |
488 | if (isvector(x)) {
489 | return variance(x.v);
490 | }
491 |
492 | const m = mean(x);
493 | let s = 0;
494 | for (let i = 0; i < x.length; i++) {
495 | const d = (x[i] - m);
496 | s = s + d * d ;
497 | }
498 |
499 | return s / (biased ? x.length : (x.length - 1));
500 | }
501 |
502 |
503 | /**
504 | * Compute average of vector values.
505 | *
506 | * @param {Array|Vector} x - vector with values.
507 | *
508 | * @returns {number} mean of x.
509 | *
510 | */
511 | export function mean(x) {
512 |
513 | if (isvector(x)) {
514 | return mean(x.v);
515 | }
516 |
517 | return sum(x) / x.length;
518 | }
519 |
520 |
521 | /**
522 | * Compute sum of all values in a vector.
523 | *
524 | * @param {Array|Vector} x - vector with values.
525 | *
526 | * @returns {number} sum of x.
527 | *
528 | */
529 | export function sum(x) {
530 |
531 | if (isvector(x)) {
532 | return sum(x.v);
533 | }
534 |
535 | let s = 0;
536 | for (let i = 0; i < x.length; i++) {
537 | s = s + x[i];
538 | }
539 |
540 | return s;
541 | }
542 |
543 |
544 | /**
545 | * Compute product of all values in a vector.
546 | *
547 | * @param {Array|Vector} x - vector with values.
548 | *
549 | * @returns {number} product of x.
550 | *
551 | */
552 | export function prod(x) {
553 |
554 | if (isvector(x)) {
555 | return prod(x.v);
556 | }
557 |
558 | let p = 1;
559 | for (let i = 0; i < x.length; i++) {
560 | p = p * x[i];
561 | }
562 |
563 | return p;
564 | }
565 |
566 |
567 | /**
568 | * Find the smallest element in a vector.
569 | *
570 | * @param {Array|Vector|Index} x - vector or index with values.
571 | *
572 | * @returns {number} the smallest value.
573 | *
574 | */
575 | export function min(x) {
576 |
577 | if (isvector(x) || isindex(x)) {
578 | return x.v[minind(x.v) - 1];
579 | }
580 |
581 | return x[minind(x) - 1];
582 | }
583 |
584 |
585 | /**
586 | * Find index of the smallest element in a vector.
587 | *
588 | * @param {Array|Vector|Index} x - vector or index with values.
589 | *
590 | * @returns {number} index of the smallest value (starting from 1).
591 | *
592 | */
593 | export function minind(x) {
594 |
595 | if (isvector(x) || isindex(x)) {
596 | return minind(x.v);
597 | }
598 |
599 | let outind = 1;
600 | let out = x[0]
601 | for (let i = 2; i <= x.length; i++) {
602 | if (x[i - 1] < out) {
603 | outind = i;
604 | out = x[i - 1];
605 | }
606 | }
607 |
608 | return outind;
609 | }
610 |
611 |
612 | /**
613 | * Find the largest element in a vector.
614 | *
615 | * @param {Array|Vector|Index} x - vector or index with values.
616 | *
617 | * @returns {number} the largest value.
618 | *
619 | */
620 | export function max(x) {
621 |
622 | if (isvector(x) || isindex(x)) {
623 | return x.v[maxind(x.v) - 1];
624 | }
625 |
626 | return x[maxind(x) - 1];
627 | }
628 |
629 |
630 | /**
631 | * Find index of the largest element in a vector.
632 | *
633 | * @param {Array|Vector|Index} x - vector or index with values.
634 | *
635 | * @returns {number} index of the largest value (starting from 1).
636 | *
637 | */
638 | export function maxind(x) {
639 |
640 | if (isvector(x) || isindex(x)) {
641 | return maxind(x.v);
642 | }
643 |
644 | let outind = 1;
645 | let out = x[0]
646 | for (let i = 2; i <= x.length; i++) {
647 | if (x[i - 1] > out) {
648 | outind = i;
649 | out = x[i - 1];
650 | }
651 | }
652 |
653 | return outind;
654 | }
655 |
656 |
657 |
--------------------------------------------------------------------------------
/src/tests/index.js:
--------------------------------------------------------------------------------
1 | /*************************************************/
2 | /* Methods for hypothesis testing */
3 | /*************************************************/
4 |
5 | import { min, mean, variance, sd } from '../stat/index.js';
6 | import { pt, qt } from '../distributions/index.js';
7 |
8 | /**
9 | * Returns a p-value for any test.
10 | *
11 | * @param {function} pfun - reference to a CDF function (e.g. pnorm).
12 | * @param {number} crit - critical value for the test (e.g. z-score or t-score).
13 | * @param {string} tail - which tail to use ("left", "right", or "both").
14 | * @param {Array} params - additional parameters to CDF function.
15 | *
16 | * @returns {number} a p-value for the test.
17 | *
18 | */
19 | export function getpvalue(pfun, crit, tail, params = []) {
20 |
21 | if (tail === "left") {
22 | return(pfun(crit, ...params));
23 | }
24 |
25 | if (tail === "right") {
26 | return(1 - pfun(crit, ...params));
27 | }
28 |
29 | if (tail === "both") {
30 | let p = pfun(crit, ...params);
31 | return min([p, 1 - p]) * 2;
32 | }
33 | }
34 |
35 |
36 | /**
37 | * Makes two-sample t-test for a difference of means assuming population variances equal.
38 | *
39 | * @param {Vector} x - vector with sample 1 values.
40 | * @param {Vector} y - vector with sample 2 values.
41 | * @param {number} alpha - significance level (used to compute confidence interval).
42 | * @param {string} tail - which tail to use ("left", "right", or "both").
43 | *
44 | * @returns {Object} a JSON with test results.
45 | *
46 | */
47 | export function ttest2(x, y, alpha = 0.05, tail = "both") {
48 |
49 | const nx = x.length;
50 | const mx = mean(x);
51 | const my = mean(y);
52 | const ny = y.length;
53 |
54 | const effectExpected = 0;
55 | const effectObserved = mx - my;
56 | const se = Math.sqrt( (variance(x) / nx) + (variance(y) / ny));
57 | const tValue = (effectObserved - effectExpected) / se;
58 | const DoF = (nx - 1) + (ny - 1);
59 | const errMargin = qt(1 - alpha/2, DoF) * se;
60 |
61 | return {
62 | test: "Two sample t-test",
63 | effectExpected: effectExpected,
64 | effectObserved: effectObserved,
65 | se: se,
66 | tValue: tValue,
67 | alpha: alpha,
68 | tail: tail,
69 | DoF: DoF,
70 | pValue: getpvalue(pt, tValue, tail, [DoF]),
71 | ci: [effectObserved - errMargin, effectObserved + errMargin]
72 | };
73 | }
74 |
75 |
76 | /**
77 | * Makes one-sample t-test for a mean.
78 | *
79 | * @param {Vector} x - vector with sample values.
80 | * @param {number} mu - assumed mean value for population (H0).
81 | * @param {number} alpha - significance level (used to compute confidence interval).
82 | * @param {string} tail - which tail to use ("left", "right", or "both").
83 | *
84 | * @returns {Object} a JSON with test results.
85 | *
86 | */
87 | export function ttest(x, mu = 0, alpha = 0.05, tail = "both") {
88 |
89 | if (typeof(mu) !== "number") {
90 | throw Error("Parameter 'mu' should be a number.");
91 | }
92 |
93 | const nx = x.length;
94 |
95 | const effectExpected = mu;
96 | const effectObserved = mean(x);
97 | const se = sd(x) / Math.sqrt(nx);
98 | const tValue = (effectObserved - effectExpected) / se;
99 | const DoF = nx - 1
100 | const errMargin = qt(1 - alpha/2, DoF) * se;
101 |
102 | return {
103 | test: "One sample t-test",
104 | effectExpected: mu,
105 | effectObserved: effectObserved,
106 | se: se,
107 | tValue: tValue,
108 | alpha: alpha,
109 | tail: tail,
110 | DoF: DoF,
111 | pValue: getpvalue(pt, tValue, tail, [DoF]),
112 | ci: [effectObserved - errMargin, effectObserved + errMargin]
113 | };
114 | }
115 |
116 |
--------------------------------------------------------------------------------
/test/tests-arrays-heavy.js:
--------------------------------------------------------------------------------
1 | /****************************************************************/
2 | /* Tests for array methods (Index/Vector/Matrix classes) */
3 | /****************************************************************/
4 |
5 | import {default as chai} from 'chai';
6 | import {default as chaiAlmost} from 'chai-almost';
7 |
8 | // import classes and related methods
9 | import { isindex, Index, ismatrix, Matrix, isvector, Vector } from '../src/arrays/index.js';
10 |
11 | // import non-class methods
12 | import { tcrossprod, crossprod, rbind, cbind } from '../src/arrays/index.js';
13 |
14 | // set up test settings
15 | const expect = chai.expect;
16 | chai.use(chaiAlmost(0.00001));
17 |
18 |
19 | // function to test a matrix structure
20 | function testMatrixStructure(X, nr, nc, values) {
21 | expect(X.constructor).equal(Matrix);
22 | expect(X.v.constructor).equal(Float64Array);
23 | expect(X.v.length).equal(nr * nc);
24 | expect(X.nrows).equal(nr);
25 | expect(X.ncols).equal(nc);
26 | expect(ismatrix(X)).to.be.true;
27 | expect(isvector(X)).to.be.false;
28 |
29 | if (values) {
30 | expect(X.v).to.deep.equal(new Float64Array(values));
31 | }
32 | }
33 |
34 | // function to test a vector structure
35 | function testVectorStructure(x, length, values) {
36 | expect(x.constructor).equal(Vector);
37 | expect(x.v.constructor).equal(Vector.valuesConstructor);
38 | expect(x.v.length).equal(length);
39 | expect(ismatrix(x)).to.be.false;
40 | expect(isvector(x)).to.be.true;
41 |
42 | if (values) {
43 | expect(x.v).to.deep.almost.equal(new Vector.valuesConstructor(values));
44 | }
45 | }
46 |
47 | // function to test an index structure
48 | function testIndexStructure(x, length, values) {
49 | expect(x.constructor).equal(Index);
50 | expect(x.v.constructor).equal(Index.valuesConstructor);
51 | expect(x.v.length).equal(length);
52 | expect(ismatrix(x)).to.be.false;
53 | expect(isvector(x)).to.be.false;
54 | expect(isindex(x)).to.be.true;
55 |
56 | if (values) {
57 | expect(x.v).to.deep.equal(new Index.valuesConstructor(values));
58 | }
59 | }
60 |
61 | describe('Tests of methods for matrices and vectors which are heavy in computation.', function() {
62 |
63 | it('tests for method "tcrossprod"', function() {
64 |
65 | // large matrices - nrows = ncols
66 | const X4 = Matrix.rand(1000, 1000);
67 | const Y4 = Matrix.rand(1000, 1000);
68 | const Z4 = tcrossprod(X4, Y4);
69 | testMatrixStructure(Z4, 1000, 1000);
70 |
71 | // large matrices - nrows > ncols
72 | const X5 = Matrix.rand(2000, 500);
73 | const Y5 = Matrix.rand(2000, 500);
74 | const Z5 = tcrossprod(X5, Y5);
75 | testMatrixStructure(Z5, 2000, 2000);
76 |
77 | // large matrices - ncols < nrows
78 | const X6 = Matrix.rand(500, 2000);
79 | const Y6 = Matrix.rand(500, 2000);
80 | const Z6 = tcrossprod(X6, Y6);
81 | testMatrixStructure(Z6, 500, 500);
82 |
83 | // large matrices - same matrix
84 | const Z6a = tcrossprod(X6);
85 | testMatrixStructure(Z6a, 500, 500);
86 |
87 | }).timeout(10000);
88 |
89 | it('tests for method "crossprod"', function() {
90 |
91 | // large matrices - nrows = ncols
92 | const X4 = Matrix.rand(1000, 1000);
93 | const Y4 = Matrix.rand(1000, 1000);
94 | const Z4 = crossprod(X4, Y4);
95 | testMatrixStructure(Z4, 1000, 1000);
96 |
97 | // large matrices - nrows > ncols
98 | const X5 = Matrix.rand(2000, 500);
99 | const Y5 = Matrix.rand(2000, 500);
100 | const Z5 = crossprod(X5, Y5);
101 | testMatrixStructure(Z5, 500, 500);
102 |
103 | // large matrices - ncols < nrows
104 | const X6 = Matrix.rand(500, 2000);
105 | const Y6 = Matrix.rand(500, 2000);
106 | const Z6 = crossprod(X6, Y6);
107 | testMatrixStructure(Z6, 2000, 2000);
108 |
109 | // large matrices - same matrix
110 | const Z6a = crossprod(X6);
111 | testMatrixStructure(Z6a, 2000, 2000);
112 |
113 | }).timeout(10000);
114 |
115 | it ('tests for method "rbind"', function() {
116 |
117 | const X4 = Matrix.rand(5000, 5000);
118 | const X5 = Matrix.rand(5000, 5000);
119 | const X6 = Matrix.rand(5000, 5000);
120 |
121 | const R3 = rbind(X4, X5, X6);
122 | testMatrixStructure(R3, 15000, 5000);
123 |
124 | }).timeout(10000);
125 |
126 | it ('tests for method "cbind"', function() {
127 |
128 | const X4 = Matrix.rand(5000, 5000);
129 | const X5 = Matrix.rand(5000, 5000);
130 | const X6 = Matrix.rand(5000, 5000);
131 |
132 | const R3 = cbind(X4, X5, X6);
133 | testMatrixStructure(R3, 5000, 15000);
134 |
135 | }).timeout(10000);
136 |
137 | it ('tests for method "dot"', function() {
138 |
139 | // large matrices - squared
140 | const X3 = Matrix.rand(2000, 2000);
141 | const Y3 = Matrix.rand(2000, 2000);
142 | const Z3 = X3.dot(Y3);
143 | testMatrixStructure(Z3, 2000, 2000);
144 |
145 | // large matrices - nrows > ncols
146 | const X4 = Matrix.rand(2000, 500);
147 | const Y4 = Matrix.rand(500, 2000);
148 | const Z4 = X4.dot(Y4);
149 | testMatrixStructure(Z4, 2000, 2000);
150 |
151 | // large matrices - ncols < nrows
152 | const X5 = Matrix.rand(500, 2000);
153 | const Y5 = Matrix.rand(2000, 500);
154 | const Z5 = X5.dot(Y5);
155 | testMatrixStructure(Z5, 500, 500);
156 |
157 | }).timeout(20000);
158 |
159 | it ('tests for method "mult"', function() {
160 |
161 | // difference two large matrices
162 | const L1 = Matrix.rand(10000, 10000);
163 | const L2 = Matrix.rand(10000, 10000);
164 | const L3 = L1.mult(L2);
165 | expect(L3.v[100]).to.be.almost.equal(L1.v[100] * L2.v[100]);
166 | }).timeout(10000);
167 |
168 | it ('tests for method "divide"', function() {
169 |
170 | // difference two large matrices
171 | const L1 = Matrix.rand(10000, 10000);
172 | const L2 = Matrix.rand(10000, 10000);
173 | const L3 = L1.divide(L2);
174 | expect(L3.v[100]).to.be.almost.equal(L1.v[100] / L2.v[100]);
175 | }).timeout(10000);
176 |
177 | it ('tests for method "subtract"', function() {
178 |
179 | // difference two large matrices
180 | const L1 = Matrix.rand(10000, 10000);
181 | const L2 = Matrix.rand(10000, 10000);
182 | const L3 = L1.subtract(L2);
183 | expect(L3.v[100]).to.be.almost.equal(L1.v[100] - L2.v[100]);
184 |
185 | }).timeout(10000);
186 |
187 | it ('tests for method "add"', function() {
188 |
189 | // sum of two large matrices
190 | const L1 = Matrix.rand(10000, 10000);
191 | const L2 = Matrix.rand(10000, 10000);
192 | const L3 = L1.add(L2);
193 | expect(L3.v[100]).to.be.almost.equal(L1.v[100] + L2.v[100]);
194 | }).timeout(10000);
195 |
196 | it ('tests for method "t"', function() {
197 |
198 | // large matrix - squared
199 | const X1 = Matrix.rand(10000, 10000);
200 |
201 | const X1t1 = X1.t();
202 | expect(X1t1.v).to.not.deep.equal(X1.v);
203 | expect(X1t1.nrows).to.be.equal(10000);
204 | expect(X1t1.ncols).to.be.equal(10000);
205 |
206 | const X1t2 = X1t1.t();
207 | expect(X1t2.v).to.deep.equal(X1.v);
208 | expect(X1t2.nrows).to.be.equal(X1.nrows);
209 | expect(X1t2.ncols).to.be.equal(X1.ncols);
210 |
211 | // large matrix - nrows > ncols
212 | const X2 = Matrix.rand(10000, 5000);
213 |
214 | const X2t1 = X2.t();
215 | expect(X2t1.v).to.not.deep.equal(X2.v);
216 | expect(X2t1.nrows).to.be.equal(5000)
217 | expect(X2t1.ncols).to.be.equal(10000);
218 |
219 | const X2t2 = X2t1.t();
220 | expect(X2t2.v).to.deep.equal(X2.v);
221 | expect(X2t2.nrows).to.be.equal(X2.nrows);
222 | expect(X2t2.ncols).to.be.equal(X2.ncols);
223 |
224 | // large matrix - ncols > nrows
225 | const X3 = Matrix.rand(5000, 10000);
226 |
227 | const X3t1 = X3.t();
228 | expect(X3t1.v).to.not.deep.equal(X3.v);
229 | expect(X3t1.nrows).to.be.equal(10000);
230 | expect(X3t1.ncols).to.be.equal(5000);
231 |
232 | const X3t2 = X3t1.t();
233 | expect(X3t2.v).to.deep.equal(X3.v);
234 | expect(X3t2.nrows).to.be.equal(X3.nrows);
235 | expect(X3t2.ncols).to.be.equal(X3.ncols);
236 |
237 | }).timeout(10000);
238 |
239 | });
240 |
241 |
--------------------------------------------------------------------------------
/test/tests-decomp.js:
--------------------------------------------------------------------------------
1 | /*****************************************************************
2 | * Tests for methods for decompositions of a matrix *
3 | *****************************************************************/
4 |
5 | // Tests of randomized SVD decomposition.
6 | // ✓ rsvd() works correctly for m > n. (7789ms)
7 |
8 | // Tests of LU and QR decompositions.
9 | // ✓ qr() works correctly. (4100ms)
10 |
11 | // Tests of SVD decomposition and related methods.
12 | // ✓ svd() works correctly. (16686ms)
13 | // ✓ rot() works correctly.
14 | // ✓ tests for method "bidiag" (1645ms)
15 | // ✓ tests for method "householder" (49ms)
16 |
17 |
18 | // import dependencies
19 | import {default as chai} from 'chai';
20 | import {default as chaiAlmost} from 'chai-almost';
21 | import { crossprod, tcrossprod, reshape, isvector, vector, Vector,
22 | ismatrix, matrix, Matrix, Index } from '../src/arrays/index.js';
23 |
24 | // import methods to test
25 | import { rsvd, qr, lu, svd, rot, bidiag, householder } from '../src/decomp/index.js';
26 |
27 | // set up test settings
28 | const expect = chai.expect;
29 | chai.use(chaiAlmost(0.0001));
30 |
31 | const ZERO = Math.pow(10.0, -6);
32 |
33 | // function to test a matrix structure
34 | function testMatrixStructure(X, nr, nc, values) {
35 | expect(X.constructor).equal(Matrix);
36 | expect(X.v.constructor).equal(Float64Array);
37 | expect(X.v.length).equal(nr * nc);
38 | expect(X.nrows).equal(nr);
39 | expect(X.ncols).equal(nc);
40 | expect(ismatrix(X)).to.be.true;
41 | expect(isvector(X)).to.be.false;
42 |
43 | if (values) {
44 | expect(X.v).to.deep.almost.equal(new Float64Array(values));
45 | }
46 | }
47 |
48 | // computes sum of squared differences between two matrices
49 | function ssqdiff(A, B) {
50 | return A.subtract(B).v.map(v => v ** 2).reduce((p, s) => p + s);
51 | }
52 |
53 | // test SVD results
54 | function svdtests(X, r, ncomp) {
55 | // checks that:
56 | // - V and Y and s have proper dimensions
57 | // - V and U has orthogonal columns
58 | // - (T = XV) == (US)
59 | // - X ≈ TV'
60 |
61 | const m = X.nrows;
62 | const n = X.ncols;
63 |
64 | if (ncomp === undefined) {
65 | ncomp = Math.min(m, n);
66 | }
67 |
68 | // check size of U and V
69 | testMatrixStructure(r.U, m, ncomp);
70 | testMatrixStructure(r.V, n, ncomp);
71 |
72 | // check orthogonality of V and U
73 | const A = r.s.v.filter(v => v > Math.pow(10,-12)).length;
74 | const ind = Index.seq(1, A);
75 | expect(crossprod(r.V.subset([], ind)).apply(Math.abs, 0)).to.be.deep.almost.equal(Matrix.eye(A));
76 | expect(crossprod(r.U.subset([], ind)).apply(Math.abs, 0)).to.be.deep.almost.equal(Matrix.eye(A));
77 |
78 | // check that T = US
79 | const T = X.dot(r.V);
80 | const US = r.U.dot(Matrix.diagm(r.s));
81 | expect(T).to.be.deep.almost.equal(US);
82 |
83 | // check that X ≈ USV
84 | const R = tcrossprod(r.U.dot(Matrix.diagm(r.s)), r.V);
85 | expect(X.v).to.be.deep.almost.equal(R.v);
86 | }
87 |
88 | // create simulated dataset as a linear combination of 6 sinus curves
89 | function simdata(m, n) {
90 | let x = new Float64Array(n);
91 | x = x.map((v, i) => i + 1.);
92 | const s1 = x.map(v => 9. * Math.sin(v));
93 | const s2 = x.map(v => 8. * Math.sin(v / 2.));
94 | const s3 = x.map(v => 7. * Math.sin(v / 3.));
95 | const s4 = x.map(v => 6. * Math.sin(v / 4.));
96 | const s5 = x.map(v => 5. * Math.sin(v / 5.));
97 | const s6 = x.map(v => 4. * Math.sin(v / 6.));
98 | const S = new Matrix(new Float64Array([...s1, ...s2, ...s3, ...s4, ...s5, ...s6]), n, 6);
99 |
100 | const C = Matrix.rand(m, 6);
101 | return tcrossprod(C, S);
102 | }
103 |
104 |
105 | describe('Tests for matrix decompositions.', function () {
106 |
107 | it('tests for method "rsvd".', function() {
108 |
109 | // simple example to compare with R results
110 | const A0a = matrix([1, 3, 17, 19, 10, 14, 7, 13, 9, 11, 2, 15, 8, 6, 4, 12, 22, 18], 6, 3);
111 | const r0a = rsvd(A0a, 3);
112 | svdtests(A0a, r0a)
113 |
114 | expect(r0a.s).to.be.deep.almost.equal(vector([
115 | 47.82344, 15.49189, 12.07974
116 | ]));
117 |
118 | expect(r0a.V.t().apply(x => Math.abs(x), 0).v).to.be.deep.almost.equal(new Float64Array([
119 | 0.6074766, 0.4602873, 0.64738,
120 | 0.4757956, 0.4417727, 0.76056,
121 | 0.6360745, 0.7700471, 0.04936
122 | ]));
123 |
124 | expect(r0a.U.t().apply(x => Math.abs(x), 0).v).to.be.deep.almost.equal(new Float64Array([
125 | 0.1887493, 0.16832558, 0.4198321,
126 | 0.2472474, 0.16160874, 0.6822448,
127 | 0.3586852, 0.56291730, 0.3280726,
128 | 0.5103920, 0.28172128, 0.2766423,
129 | 0.4395333, 0.73939460, 0.3200999,
130 | 0.5664784, 0.05100958, 0.2676873
131 | ]));
132 |
133 | // simple example to compare with R results
134 | const A0b = matrix([1, 3, 17, 19, 10, 14, 7, 13, 9, 11, 2, 15, 8, 6, 4, 12, 22, 18, 11, 12], 5, 4);
135 | const r0b = rsvd(A0b, 4);
136 | svdtests(A0b, r0b)
137 |
138 | expect(r0b.s).to.be.deep.almost.equal(vector([
139 | 50.4985905, 16.4651258, 10.7821742, 0.7326014
140 | ]));
141 |
142 | // simple example to compare with R results
143 | const A0c = reshape(Vector.seq(1, 20), 5, 4);
144 | const r0c = rsvd(A0c, 4);
145 | svdtests(A0c, r0c)
146 |
147 | expect(r0c.s).to.be.deep.almost.equal(vector([
148 | 53.49077, 2.955910, 0, 0
149 | ]));
150 |
151 | // simulated data m > n
152 | const A1 = simdata(1000, 100)
153 | const r1a = rsvd(A1, 70)
154 | svdtests(A1, r1a, 70)
155 | const r1b = rsvd(A1, 10)
156 | svdtests(A1, r1b, 10)
157 |
158 | // simulated data m < n
159 | const A2 = simdata(100, 1000)
160 | const r2a = rsvd(A2, 70)
161 | svdtests(A2, r2a, 70)
162 | const r2b = rsvd(A2, 10)
163 | svdtests(A2, r2b, 10)
164 |
165 | // simulated data m = n
166 | const A3 = simdata(100, 100)
167 | const r3a = rsvd(A3, 70)
168 | svdtests(A3, r3a, 70)
169 | const r3b = rsvd(A3, 10)
170 | svdtests(A3, r3b, 10)
171 |
172 | }).timeout(200000);
173 |
174 | it('tests for method "qr.', function() {
175 |
176 | // test function, checks that:
177 | // - Q and R has proper dimensions
178 | // - Q has orthogonal columns
179 | // - R is upper triangular
180 | // - X ≈ QR
181 | function qrtests(X, r) {
182 |
183 | const m = X.nrows;
184 | const n = X.ncols;
185 |
186 | if (m > n) {
187 | testMatrixStructure(r.Q, m, n)
188 | testMatrixStructure(r.R, n, n)
189 | expect(crossprod(r.Q).v).to.be.deep.almost.equal(Matrix.eye(n).v);
190 | expect(r.R.isuppertriangular()).to.be.true;
191 | } else {
192 | testMatrixStructure(r.Q, m, m)
193 | testMatrixStructure(r.R, m, n)
194 | expect(crossprod(r.Q).v).to.be.deep.almost.equal(Matrix.eye(m).v);
195 | }
196 | expect(ssqdiff(X, r.Q.dot(r.R)) < ZERO).to.be.true;
197 | }
198 |
199 | // small squared matrix
200 | const X1 = matrix([4, 6, 3, 3], 2, 2);
201 | const r1 = qr(X1);
202 | qrtests(X1, r1);
203 |
204 | // small matrix m > n
205 | const X2a = matrix([4, 5, 6, 3, 3, 3], 3, 2);
206 | const r2a = qr(X2a);
207 | qrtests(X2a, r2a);
208 |
209 | // small matrix n > m
210 | const X2b = matrix([4, 5, 6, 3, 3, 3], 2, 3);
211 | const r2b = qr(X2b);
212 | qrtests(X2b, r2b);
213 |
214 | // small matrix from rsvd example
215 | const X2c = matrix([
216 | 20.248701174781164, 22.01952684298161,
217 | 23.790352511182057, 25.5611781793825,
218 | 27.332003847582946, -1.9975236505272767,
219 | -1.0679127357664608, -0.13830182100564326,
220 | 0.7913090937551726, 1.7209200085159875,
221 | 18.675243127797636, 20.372992503051968,
222 | 22.0707418783063, 23.768491253560633,
223 | 25.466240628814965, 7.761140001162658,
224 | 7.8419852095081035, 7.922830417853547,
225 | 8.00367562619899, 8.084520834544435
226 | ], 5, 4);
227 |
228 | const r2c = qr(X2c);
229 | qrtests(X2c, r2c);
230 |
231 |
232 | // small matrix from paper describing algoritm
233 | // https://www.math.usm.edu/lambers/mat610/sum10/lecture9.pdf
234 | const X2d = matrix([
235 | 0.8147, 0.9058, 0.1270, 0.9134, 0.6324,
236 | 0.0975, 0.2785, 0.5469, 0.9575, 0.9649,
237 | 0.1576, 0.9706, 0.9572, 0.4854, 0.8003
238 | ], 5, 3)
239 |
240 | const r2d = qr(X2d);
241 | qrtests(X2a, r2a);
242 |
243 | expect(r2d.Q.v).to.be.deep.almost.eql(new Float64Array([
244 | 0.4927, 0.5478, 0.0768, 0.5524, 0.3824,
245 | -0.4807, -0.3583, 0.4754, 0.3391, 0.5473,
246 | 0.1780, -0.5777, -0.6343, 0.4808, 0.0311
247 | // -0.7033, 0.4825, -0.4317, 0.2769, -0.0983,
248 | // 0.0000, 0.0706, -0.4235, -0.5216, 0.7373
249 | ]));
250 |
251 | expect(r2d.R.v).to.be.deep.almost(new Float64Array([
252 | 1.6536, 0.0000, 0.0000,
253 | 1.1405, 0.9661, 0.0000,
254 | 1.2569, 0.6341, -0.8816
255 | ]));
256 |
257 |
258 | // middle size random matrix m = n
259 | const n3 = 150
260 | const m3 = 150
261 | const X3 = Matrix.rand(m3, n3);
262 | const r3 = qr(X3);
263 | qrtests(X3, r3)
264 |
265 | // large size random matrix m > n
266 | const n4 = 500
267 | const m4 = 1000
268 | const X4 = Matrix.rand(m4, n4);
269 | const r4 = qr(X4);
270 | qrtests(X4, r4)
271 |
272 | // large size random matrix m < n
273 | const n5 = 1000
274 | const m5 = 500
275 | const X5 = Matrix.rand(m5, n5);
276 | const r5 = qr(X5);
277 | qrtests(X5, r5)
278 |
279 | }).timeout(20000);
280 |
281 | it('tests for method "lu".', function() {
282 | const X1 = matrix([4, 6, 3, 3], 2, 2);
283 | const r1 = lu(X1);
284 | // check that U is upper triangular
285 | expect(r1.U.isuppertriangular()).to.be.true;
286 | // check that L is lower triangular
287 | expect(r1.L.islowertriangular()).to.be.true;
288 | // check that X ≈ LU
289 | expect(ssqdiff(X1, r1.L.dot(r1.U)) < ZERO).to.be.true;
290 |
291 | const X2 = matrix([8, 4, 6, 2, 9, 7, 9, 4, 9], 3, 3);
292 | const r2 = lu(X2);
293 | // check that U is upper triangular
294 | expect(r2.U.isuppertriangular()).to.be.true;
295 | // check that L is lower triangular
296 | expect(r2.L.islowertriangular()).to.be.true;
297 | // check that X ≈ LU
298 | expect(ssqdiff(X2, r2.L.dot(r2.U)) < ZERO).to.be.true;
299 |
300 | const X3 = Matrix.rand(1000, 1000);
301 | const r3 = lu(X3);
302 | // check that U is upper triangular
303 | expect(r3.U.isuppertriangular()).to.be.true;
304 | // check that L is lower triangular
305 | expect(r3.L.islowertriangular()).to.be.true;
306 | // check that X ≈ LU
307 | expect(ssqdiff(X3, r3.L.dot(r3.U)) < ZERO).to.be.true;
308 |
309 | const X4 = Matrix.rand(1000, 500);
310 | const r4 = lu(X4);
311 | // check that X ≈ LU
312 | expect(ssqdiff(X4, r4.L.dot(r4.U)) < ZERO).to.be.true;
313 |
314 | const X5 = Matrix.rand(500, 1000);
315 | const r5 = lu(X5);
316 | // check that X ≈ LU
317 | expect(ssqdiff(X5, r5.L.dot(r5.U)) < ZERO).to.be.true;
318 |
319 | }).timeout(20000);
320 |
321 | it('tests for method "svd".', function() {
322 |
323 | // simple example to compare with R results
324 | const A0a = matrix([1, 3, 17, 19, 10, 14, 7, 13, 9, 11, 2, 15, 8, 6, 4, 12, 22, 18], 6, 3);
325 | const r0a = svd(A0a);
326 | svdtests(A0a, r0a)
327 |
328 | expect(r0a.s).to.be.deep.almost.equal(vector([
329 | 47.82344, 15.49189, 12.07974
330 | ]));
331 |
332 | expect(r0a.V.t().apply(x => Math.abs(x), 0).v).to.be.deep.almost.equal(new Float64Array([
333 | 0.6074766, 0.4602873, 0.64738,
334 | 0.4757956, 0.4417727, 0.76056,
335 | 0.6360745, 0.7700471, 0.04936
336 | ]));
337 |
338 | expect(r0a.U.t().apply(x => Math.abs(x), 0).v).to.be.deep.almost.equal(new Float64Array([
339 | 0.1887493, 0.16832558, 0.4198321,
340 | 0.2472474, 0.16160874, 0.6822448,
341 | 0.3586852, 0.56291730, 0.3280726,
342 | 0.5103920, 0.28172128, 0.2766423,
343 | 0.4395333, 0.73939460, 0.3200999,
344 | 0.5664784, 0.05100958, 0.2676873
345 | ]));
346 |
347 |
348 | // simple example to compare with R results
349 | const A0b = matrix([1, 3, 17, 19, 10, 14, 7, 13, 9, 11, 2, 15, 8, 6, 4, 12, 22, 18, 11, 12], 5, 4);
350 | const r0b = svd(A0b);
351 | svdtests(A0b, r0b)
352 |
353 | expect(r0b.s).to.be.deep.almost.equal(vector([
354 | 50.4985905, 16.4651258, 10.7821742, 0.7326014
355 | ]));
356 |
357 | // simple example to compare with R results
358 | const A0c = reshape(Vector.seq(1, 20), 5, 4);
359 | const r0c = svd(A0c);
360 | svdtests(A0c, r0c)
361 |
362 | expect(r0c.s).to.be.deep.almost.equal(vector([
363 | 53.49077, 2.955910, 0, 0
364 | ]));
365 |
366 | // simulated data m > n
367 | const A1 = simdata(20, 10)
368 | const r1a = svd(A1)
369 | svdtests(A1, r1a)
370 | const r1b = svd(A1, 10)
371 | svdtests(A1, r1b, 10)
372 |
373 | // simulated data m < n
374 | const A2 = simdata(10, 20)
375 | const r2a = svd(A2)
376 | svdtests(A2, r2a)
377 | const r2b = svd(A2, 10)
378 | svdtests(A2, r2b, 10)
379 |
380 | // simulated data m = n
381 | const A3 = simdata(20, 20)
382 | const r3a = svd(A3)
383 | svdtests(A3, r3a)
384 | const r3b = svd(A3, 20)
385 | svdtests(A3, r3b, 20)
386 |
387 | }).timeout(10000000);
388 |
389 | it('tests for method "rot".', function () {
390 | expect(rot(1, 0)).to.be.deep.almost([1, 0, 1]);
391 |
392 | const r1 = rot(1, 2);
393 | const r12 = matrix([r1[0], -r1[1], r1[1], r1[0]], 2, 2).dot(matrix([1, 2], 2, 1));
394 | expect(r12.v).to.be.deep.almost(new Float64Array([r1[2], 0]));
395 |
396 | const r2 = rot(0, 1);
397 | const r22 = matrix([r2[0], -r2[1], r2[1], r2[0]], 2, 2).dot(matrix([0, 1], 2, 1));
398 | expect(r22.v).to.be.deep.almost(new Float64Array([r2[2], 0]));
399 | });
400 |
401 | it ('tests for method "bidiag"', function () {
402 | function getB(d, e, m, n) {
403 | const B = Matrix.zeros(m, n);
404 | for (let i = 0; i < n - 1; i++) {
405 | B.v[i * m + i] = d.v[i];
406 | B.v[(i + 1) * m + i] = e.v[i];
407 | }
408 | B.v[(n - 1) * m + (n - 1)] = d.v[(n - 1)];
409 | return B;
410 | }
411 |
412 | const A1 = reshape(Vector.seq(1, 12), 4, 3);
413 | const [d1, e1, V1, U1] = bidiag(A1);
414 | const B1 = getB(d1, e1, 4, 3);
415 | const R1 = U1.dot(tcrossprod(B1, V1));
416 | expect(R1.v).to.be.deep.almost.equal(A1.v);
417 |
418 | // best time so far 1.42 for 201 x 200
419 | const A2 = Matrix.rand(201, 200);
420 | const [d2, e2, V2, U2] = bidiag(A2);
421 | const B2 = getB(d2, e2, 201, 200);
422 | const R2 = U2.dot(tcrossprod(B2, V2));
423 | expect(R2.v).to.be.deep.almost.equal(A2.v);
424 |
425 | }).timeout(20000);
426 |
427 | it ('tests for method "householder"', function() {
428 | const H1 = householder([1, 2, 3], 2);
429 | testMatrixStructure(H1, 2, 2, [
430 | 0.5547002, 0.8320503,
431 | 0.8320503, -0.5547002
432 | ]);
433 |
434 | const H2 = householder([1, 2, 3], 1);
435 | testMatrixStructure(H2, 3, 3, [
436 | 0.2672612, 0.5345225, 0.8017837,
437 | 0.5345225, 0.6100735, -0.5848898,
438 | 0.8017837, -0.5848898, 0.1226653
439 | ]);
440 |
441 | const H3 = householder(Vector.seq(1, 10000).v, 5000);
442 | testMatrixStructure(H3, 5001, 5001);
443 | });
444 |
445 | });
--------------------------------------------------------------------------------
/test/tests-distributions.js:
--------------------------------------------------------------------------------
1 | // import dependencies
2 | import {default as chai} from 'chai';
3 | import {sum, sd, mean, min, max} from '../src/stat/index.js';
4 | import { isvector, vector, Vector } from '../src/arrays/index.js';
5 |
6 | // import of functions to test
7 | import {igamma, gamma, beta, runif, dunif, punif, rnorm, dnorm, pnorm, qnorm,
8 | dt, pt, qt, df, pf, qf, pchisq, qchisq} from '../src/distributions/index.js';
9 |
10 | const should = chai.should();
11 | const expect = chai.expect;
12 |
13 | describe('Tests for helper functions.', function () {
14 |
15 | it ('tests for "igamma" function.', function () {
16 | igamma(1, 1).should.be.closeTo(0.632120, 0.00001)
17 | igamma(1, 2).should.be.closeTo(0.264241, 0.00001)
18 | igamma(2, 1).should.be.closeTo(0.864665, 0.00001)
19 | igamma(2, 2).should.be.closeTo(0.593994, 0.00001)
20 |
21 | igamma( 5, 5).should.be.closeTo(13.42816, 0.00001)
22 | igamma( 5, 10).should.be.closeTo(11549.765, 0.001)
23 | igamma(10, 5).should.be.closeTo(23.29793, 0.00001)
24 | igamma(10, 10).should.be.closeTo(196706.465212, 0.00001)
25 | });
26 |
27 | it ('tests for "gamma" function.', function () {
28 | expect(() => gamma(-1)).to.throw(Error, "gamma: the function only works with arguments > 0.");
29 | expect(() => gamma(0)).to.throw(Error, "gamma: the function only works with arguments > 0.");
30 | gamma(1).should.be.closeTo(1, 0.000001);
31 | gamma(4).should.be.closeTo(6, 0.000001);
32 | gamma(0.5).should.be.closeTo(1.772454, 0.000001);
33 | gamma(1.5).should.be.closeTo(0.8862269, 0.000001);
34 |
35 | expect(() => gamma(vector([1, 2, -1, 3]))).to.throw(Error, "gamma: the function only works with arguments > 0.");
36 | expect(() => gamma(vector([1, 2, 0, 3]))).to.throw(Error, "gamma: the function only works with arguments > 0.");
37 | const g = gamma(vector([1, 4, 0.5, 1.5]));
38 | g.v[0].should.be.closeTo(1, 0.000001);
39 | g.v[1].should.be.closeTo(6, 0.000001);
40 | g.v[2].should.be.closeTo(1.772454, 0.000001);
41 | g.v[3].should.be.closeTo(0.8862269, 0.000001);
42 | });
43 |
44 | it ('tests for "beta".', function () {
45 | beta(1, 1).should.be.closeTo(1, 0.000001);
46 | beta(0.5, 0.5).should.be.closeTo(3.141593, 0.000001);
47 | beta(0.5, 1.5).should.be.closeTo(1.570796, 0.000001);
48 | });
49 |
50 | });
51 |
52 | describe('Tests for theoretical distribution functions.', function () {
53 |
54 | it ('tests for method "dunif".', function () {
55 |
56 | const n = 1000000;
57 |
58 | // standardized distribution for a = 0, b = 1
59 | const x1 = Vector.seq(0, 1, 1/(n - 1));
60 | const d1 = dunif(x1);
61 |
62 | expect(d1.v).to.have.lengthOf(n);
63 | d1.v[0].should.be.closeTo(1, 0.0000000001);
64 | d1.v[n-1].should.be.closeTo(1, 0.0000000001);
65 | d1.v[n/2].should.be.closeTo(1, 0.0000000001);
66 |
67 | // distribution with mu = 10 and sigma = 10, for ± 3 sigma
68 | const a = 10;
69 | const b = 100;
70 | const x2 = Vector.seq(a, b, (b - a) / (n - 1));
71 | const d2 = dunif(x2, a, b);
72 |
73 | expect(d2.v).to.have.lengthOf(n);
74 | d2.v[0].should.be.closeTo(1 / (b - a), 0.00000001);
75 | d2.v[n-1].should.be.closeTo(1 / (b - a), 0.00000001);
76 | d2.v[n/2].should.be.closeTo(1 / (b - a), 0.00000001);
77 | (sum(d2) * (b - a)/n).should.be.closeTo(1.0, 0.000001);
78 |
79 | dunif(a - 0.0000001, a, b).should.be.closeTo(0.0, 0.0000001);
80 | dunif(b + 0.0000001, a, b).should.be.closeTo(0.0, 0.0000001);
81 | });
82 |
83 | it('tests for method "punif".', function () {
84 |
85 | const n = 1000000;
86 |
87 | // standardized distribution for a = 0, b = 1
88 | const x1 = Vector.seq(0, 1, 1 / (n - 1));
89 | const p1 = punif(x1);
90 |
91 | expect(p1.v).to.have.lengthOf(n);
92 | p1.v[0].should.be.closeTo(0, 0.00001);
93 | p1.v[n-1].should.be.closeTo(1, 0.00001);
94 | p1.v[n/2].should.be.closeTo(0.5, 0.00001);
95 |
96 | // outside the range
97 | punif(-1).should.be.closeTo(0, 0.00001);
98 | punif( 1).should.be.closeTo(1, 0.00001);
99 |
100 | // distribution with mu = 10 and sigma = 10, for ± 3 sigma
101 | const a = 10;
102 | const b = 100;
103 | const x2 = Vector.seq(a, b, (b - a) / (n - 1));
104 | const p2 = punif(x2, a, b);
105 |
106 | expect(p2.v).to.have.lengthOf(n);
107 | punif(a - 1, a, b).should.be.closeTo(0.0, 0.00001);
108 | punif(b + 1, a, b).should.be.closeTo(1.0, 0.00001);
109 | });
110 |
111 | it('tests for method "runif".', function () {
112 |
113 | const n = 1000000;
114 | const r1 = runif(n);
115 |
116 | expect(r1.v).to.have.lengthOf(n)
117 | expect(min(r1)).to.be.above(0);
118 | expect(max(r1)).to.be.below(1);
119 |
120 | const r2 = runif(n, 10, 20);
121 | expect(r2.v).to.have.lengthOf(n)
122 | expect(min(r2)).to.be.above(10);
123 | expect(max(r2)).to.be.below(20);
124 | });
125 |
126 | it ('tests for method "dnorm".', function () {
127 |
128 | const n = 1000000;
129 |
130 | // standardized distribution for ± 3 sigma
131 | const x1 = Vector.seq(-3, 3, 6 / (n - 1));
132 | const d1 = dnorm(x1);
133 | expect(d1.v).to.have.lengthOf(n);
134 | d1.v[0].should.be.closeTo(0.004431848, 0.00000001);
135 | d1.v[n-1].should.be.closeTo(0.004431848, 0.00000001);
136 | d1.v[n/2].should.be.closeTo(0.3989423, 0.0000001);
137 |
138 | // distribution with mu = 10 and sigma = 10, for ± 3 sigma
139 | const mu = 10;
140 | const sigma = 10
141 | const x2 = Vector.seq(mu - 3 * sigma, mu + 3 * sigma, 6 * sigma / (n - 1));
142 | const d2 = dnorm(x2, mu, sigma);
143 | expect(d2.v).to.have.lengthOf(n);
144 | d2.v[0].should.be.closeTo(0.0004431848, 0.00000001);
145 | d2.v[n-1].should.be.closeTo(0.0004431848, 0.00000001);
146 | d2.v[n/2].should.be.closeTo(0.03989423, 0.0000001);
147 |
148 | // distribution with mu = 10 and sigma = 10, for ± 6 sigma should have area of one
149 | const x3 = Vector.seq(mu - 6 * sigma, mu + 6 * sigma, 12 * sigma / (n - 1));
150 | const d3 = dnorm(x3, mu, sigma);
151 | expect(d3.v).to.have.lengthOf(n);
152 | (sum(d3) * 12 * sigma / n).should.be.closeTo(1.0, 0.00001);
153 |
154 | // if values are far from mean density is 0
155 | dnorm(mu - 6 * sigma, mu, sigma).should.be.closeTo(0.0, 0.0000001);
156 | dnorm(mu + 6 * sigma, mu, sigma).should.be.closeTo(0.0, 0.0000001);
157 | });
158 |
159 | it ('tests for method "pnorm".', function () {
160 |
161 | const n = 1000000;
162 |
163 | // standardized distribution for ± 3 sigma
164 | const x1 = Vector.seq(-3, 3, 6 / (n - 1));
165 | const p1 = pnorm(x1);
166 |
167 | expect(p1.v).to.have.lengthOf(n);
168 | p1.v[ 0].should.be.closeTo(0.00134996, 0.00001);
169 | p1.v[n-1].should.be.closeTo(0.998650, 0.00001);
170 | p1.v[n/2].should.be.closeTo(0.5, 0.00001);
171 |
172 | // distribution with mu = 10 and sigma = 10, for ± 3 sigma
173 | const mu = 10;
174 | const sigma = 10
175 | const x2 = Vector.seq(mu - 3 * sigma, mu + 3 * sigma, 6 * sigma / (n - 1));
176 | const p2 = pnorm(x2, mu, sigma);
177 | expect(p2.v).to.have.lengthOf(n);
178 | p2.v[ 0].should.be.closeTo(0.001350, 0.000001);
179 | p2.v[n-1].should.be.closeTo(0.998650, 0.000001);
180 | p2.v[n/2].should.be.closeTo(0.5, 0.00001);
181 |
182 | });
183 |
184 | it ('tests for method "qnorm".', function () {
185 |
186 | const n = 1000000;
187 |
188 | // border cases
189 | qnorm(0).should.be.equal(-Infinity);
190 | qnorm(1).should.be.equal(Infinity);
191 | qnorm(vector([0, 0, 1, 1])).should.be.eql(vector([-Infinity, -Infinity, Infinity, Infinity]));
192 |
193 | // middle point and border cases
194 | qnorm(0.5).should.be.equal(0);
195 | qnorm(vector([0.5, 0.5])).should.be.eql(vector([0, 0]));
196 | qnorm(vector([0, 0.5, 1])).should.be.eql(vector([-Infinity, 0, Infinity]));
197 |
198 | // other cases
199 | qnorm(0.9999).should.be.closeTo( 3.719016, 0.00001);
200 | qnorm(0.0001).should.be.closeTo(-3.719016, 0.00001);
201 |
202 | qnorm(0.975).should.be.closeTo( 1.959964, 0.00001);
203 | qnorm(0.025).should.be.closeTo(-1.959964, 0.00001);
204 |
205 | qnorm(0.840).should.be.closeTo( 0.9944579, 0.00001);
206 | qnorm(0.160).should.be.closeTo(-0.9944579, 0.00001);
207 |
208 | qnorm(0.750).should.be.closeTo( 0.6744898, 0.00001);
209 | qnorm(0.250).should.be.closeTo(-0.6744898, 0.00001);
210 |
211 | // cases with non standard distribution
212 | qnorm(0.975, 10, 2).should.be.closeTo(13.91993, 0.00001);
213 | qnorm(0.025, 10, 2).should.be.closeTo( 6.080072, 0.00001);
214 |
215 | // errors
216 | expect(() => qnorm(-0.0001)).to.throw(Error, 'Parameter "p" must be between 0 and 1.');
217 | expect(() => qnorm( 1.0001)).to.throw(Error, 'Parameter "p" must be between 0 and 1.');
218 |
219 | // long vectors
220 | const p = Vector.seq(0.0001, 0.9999, 0.9998 / (n - 1));
221 | const q = qnorm(p);
222 | expect(q.v).to.have.lengthOf(n);
223 | q.v[0].should.be.equal(qnorm(0.0001));
224 | q.v[n-1].should.be.closeTo(qnorm(0.9999), 0.000000001);
225 | });
226 |
227 | it('tests for method "rnorm".', function () {
228 | const n = 10000000;
229 |
230 | const r1 = rnorm(n);
231 | expect(r1.v).to.have.lengthOf(n)
232 | sd(r1).should.be.closeTo(1, 0.01);
233 | mean(r1).should.be.closeTo(0, 0.01);
234 | min(r1).should.be.above(-6);
235 | max(r1).should.be.below(6);
236 |
237 | const r2 = rnorm(n, 10, 5);
238 | expect(r2.v).to.have.lengthOf(n)
239 | sd(r2).should.be.closeTo(5, 0.01);
240 | mean(r2).should.be.closeTo(10, 0.01);
241 | min(r2).should.be.above(-20);
242 | max(r2).should.be.below(40);
243 | });
244 |
245 | it ('tests for method "dt".', function () {
246 |
247 | const n = 100000;
248 |
249 | // distribution for DoF = 1
250 | const x1 = Vector.seq(-5, 5, 10/(n - 1));
251 | const d1 = dt(x1, 1);
252 | expect(d1.v).to.have.lengthOf(n);
253 | d1.v[0].should.be.closeTo(0.01224269, 0.00000001);
254 | d1.v[n-1].should.be.closeTo(0.01224269, 0.00000001);
255 | d1.v[n/2].should.be.closeTo(0.31830989, 0.0000001);
256 |
257 | // distribution for DoF = 3
258 | const x2 = Vector.seq(-5, 5, 10/(n - 1));
259 | const d2 = dt(x2, 3);
260 | expect(d2.v).to.have.lengthOf(n);
261 | d2.v[0].should.be.closeTo(0.004219354, 0.00000001);
262 | d2.v[n-1].should.be.closeTo(0.004219354, 0.00000001);
263 | d2.v[n/2].should.be.closeTo(0.3675526, 0.0000001);
264 |
265 | // distribution for DoF = 30
266 | const x3 = Vector.seq(-3, 3, 6/(n - 1));
267 | const d3 = dt(x3, 30);
268 | expect(d3.v).to.have.lengthOf(n);
269 | d3.v[0].should.be.closeTo(0.006779063, 0.00000001);
270 | d3.v[n-1].should.be.closeTo(0.006779063, 0.00000001);
271 | d3.v[n/2].should.be.closeTo(0.3956322, 0.0000001);
272 |
273 | });
274 |
275 | it ('tests for mehtod "pt".', function () {
276 |
277 | const n = 10000;
278 |
279 | // distribution for DoF = 1
280 | const t1 = Vector.seq(-5, 5, 10 / (n - 1));
281 | const p1 = pt(t1, 1);
282 | expect(p1.v).to.have.lengthOf(n);
283 | p1.v[0].should.be.closeTo(0.06283296, 0.001);
284 | p1.v[n-1].should.be.closeTo(0.937167, 0.001);
285 | p1.v[n/2].should.be.closeTo(0.5, 0.001);
286 |
287 | // distribution for DoF = 3
288 | const t2 = Vector.seq(-5, 5, 10 / (n - 1));
289 | const p2 = pt(t2, 3);
290 | expect(p2.v).to.have.lengthOf(n);
291 | p2.v[0].should.be.closeTo(0.007696219, 0.001);
292 | p2.v[n-1].should.be.closeTo(0.9923038, 0.001);
293 | p2.v[n/2].should.be.closeTo(0.5, 0.001);
294 |
295 | // distribution for DoF = 30
296 | const t3 = Vector.seq(-5, 5, 10 / (n - 1));
297 | const p3 = pt(t3, 30);
298 | expect(p3.v).to.have.lengthOf(n);
299 | p3.v[0].should.be.closeTo(0.00001164834, 0.001);
300 | p3.v[n-1].should.be.closeTo(0.9999884, 0.001);
301 | p3.v[n/2].should.be.closeTo(0.5, 0.001);
302 |
303 | // special cases
304 | const p4 = pt(2.9703, 200)
305 | p4.should.be.closeTo(0.9983, 0.0001);
306 |
307 | const p5 = pt(1.97, 200)
308 | p5.should.be.closeTo(0.9748907, 0.0001);
309 |
310 | const p6 = pt(3, 500)
311 | p6.should.be.closeTo(0.9985828, 0.0001);
312 |
313 | });
314 |
315 | it ('tests for method "qt".', function () {
316 |
317 | const n = 1000000;
318 |
319 | // border cases
320 | qt(0, 1).should.be.equal(-Infinity);
321 | qt(1, 1).should.be.equal(Infinity);
322 | qt(vector([0, 0, 1, 1]), 1).should.be.eql(vector([-Infinity, -Infinity, Infinity, Infinity]));
323 |
324 | // middle point and border cases
325 | qt(0.5, 1).should.be.equal(0);
326 | qt(vector([0.5, 0.5]), 1).should.be.eql(vector([0, 0]));
327 | qt(vector([0, 0.5, 1]), 1).should.be.eql(vector([-Infinity, 0, Infinity]));
328 |
329 | // other fixed cases
330 | const dof = [1, 2, 3, 4, 10, 30, 100];
331 | let p, expected;
332 |
333 | p = 0.9999
334 | expected = [3183.098757, 70.700071, 22.203742, 13.033672, 5.693820, 4.233986, 3.861600];
335 | for (let i = 0; i < dof.length; i++) {
336 | qt(p, dof[i]).should.be.closeTo(expected[i], 0.00001);
337 | qt(1 - p, dof[i]).should.be.closeTo(-expected[i], 0.00001);
338 | }
339 |
340 | p = 0.99
341 | expected = [31.820516, 6.964557, 4.540703, 3.746947, 2.763769, 2.457262, 2.364217];
342 | for (let i = 0; i < dof.length; i++) {
343 | qt(p, dof[i]).should.be.closeTo(expected[i], 0.0001);
344 | qt(1 - p, dof[i]).should.be.closeTo(-expected[i], 0.0001);
345 | }
346 |
347 | p = 0.95
348 | expected = [6.313752, 2.919986, 2.353363, 2.131847, 1.812461, 1.697261, 1.660234];
349 | for (let i = 0; i < dof.length; i++) {
350 | qt(p, dof[i]).should.be.closeTo(expected[i], 0.0001);
351 | qt(1 - p, dof[i]).should.be.closeTo(-expected[i], 0.0001);
352 | }
353 |
354 | p = 0.85
355 | expected = [1.962611, 1.386207, 1.249778, 1.189567, 1.093058, 1.054662, 1.041836];
356 | for (let i = 0; i < dof.length; i++) {
357 | qt(p, dof[i]).should.be.closeTo(expected[i], 0.0001);
358 | qt(1 - p, dof[i]).should.be.closeTo(-expected[i], 0.0001);
359 | }
360 |
361 | p = 0.75
362 | expected = [1.0000000, 0.8164966, 0.7648923, 0.7406971, 0.6998121, 0.6827557, 0.6769510];
363 | for (let i = 0; i < dof.length; i++) {
364 | qt(p, dof[i]).should.be.closeTo(expected[i], 0.0001);
365 | qt(1 - p, dof[i]).should.be.closeTo(-expected[i], 0.0001);
366 | }
367 |
368 | // errors
369 | expect(() => qt(-0.0001, 1)).to.throw(Error, 'Parameter "p" must be between 0 and 1.');
370 | expect(() => qt( 1.0001, 1)).to.throw(Error, 'Parameter "p" must be between 0 and 1.');
371 | expect(() => qt(0.2)).to.throw(Error, 'Parameter "df" (degrees of freedom) must be an integer number >= 1.');
372 | expect(() => qt(0.2, -1)).to.throw(Error, 'Parameter "df" (degrees of freedom) must be an integer number >= 1.');
373 | expect(() => qt(0.2, 0.5)).to.throw(Error, 'Parameter "df" (degrees of freedom) must be an integer number >= 1.');
374 |
375 | // long vectors
376 | p = Vector.seq(0.0001, 0.9999, 0.9998/(n - 1));
377 | const q = qt(p, 10);
378 | expect(q.v).to.have.lengthOf(n);
379 | q.v[0].should.be.equal(qt(0.0001, 10));
380 | q.v[n - 1].should.be.equal(qt(0.9998999999999999, 10));
381 |
382 | // special case
383 | const t4 = qt(0.9983, 200);
384 | t4.should.be.closeTo(2.97, 0.01);
385 | });
386 |
387 | it ('tests for method "df".', function () {
388 |
389 | const n = 10000;
390 |
391 | // distribution for DoF = 1, 2
392 | const F1 = Vector.seq(0.001, 10, 10/n);
393 | const d1 = df(F1, 1, 2);
394 | expect(d1.v).to.have.lengthOf(n);
395 | d1.v[0].should.be.closeTo(11.17196, 0.001);
396 | d1.v[n-1].should.be.closeTo(0.007607258, 0.001);
397 | d1.v[n/2].should.be.closeTo(0.02414726, 0.001);
398 |
399 | // distribution for DoF = 3, 10
400 | const F2 = Vector.seq(0.001, 10, 10/n);
401 | const d2 = df(F2, 3, 10);
402 | expect(d2.v).to.have.lengthOf(n);
403 | d2.v[0].should.be.closeTo(0.07019374, 0.001);
404 | d2.v[n-1].should.be.closeTo(0.0008585295, 0.001);
405 | d2.v[n/2].should.be.closeTo(0.01288309, 0.001);
406 |
407 | });
408 |
409 | it ('tests for method "pf".', function () {
410 |
411 | const n = 10000;
412 |
413 | // distribution for DoF = 1, 2
414 | const F1 = Vector.seq(0, 10, 10/n);
415 | const p1 = pf(F1, 1, 2);
416 | expect(p1.v).to.have.lengthOf(n + 1);
417 | p1.v[0].should.be.closeTo(0, 0.001);
418 | p1.v[n-1].should.be.closeTo(0.9128709, 0.001);
419 | p1.v[n/2].should.be.closeTo(0.8451543, 0.001);
420 |
421 | // distribution for DoF = 3, 10
422 | const F2 = Vector.seq(0, 10, 10/n);
423 | const p2 = pf(F2, 3, 10);
424 | expect(p2.v).to.have.lengthOf(n + 1);
425 | p2.v[0].should.be.closeTo(0, 0.001);
426 | p2.v[n].should.be.closeTo(0.9976484, 0.001);
427 | p2.v[n/2].should.be.closeTo(0.9773861, 0.001);
428 |
429 | // special cases
430 | const p31 = pf(2.97, 1, 200);
431 | p31.should.be.closeTo(0.9136327, 0.0001);
432 |
433 | });
434 |
435 | it ('tests for method "pchisq".', function () {
436 | // all outcomes are compared with similar outcomes from R
437 |
438 | // single value
439 | let x = 0.5
440 | pchisq(x, 0).should.be.equal(1);
441 | pchisq(x, 1).should.be.closeTo(0.5205000, 0.0001);
442 | pchisq(x, 2).should.be.closeTo(0.2211990, 0.0001);
443 | pchisq(x, 5).should.be.closeTo(0.0078767, 0.0001);
444 | pchisq(x, 10).should.be.closeTo(0.0000000, 0.0001);
445 | pchisq(x, 20).should.be.closeTo(0.0000000, 0.0001);
446 | pchisq(x, 100).should.be.closeTo(0.0000000, 0.0001);
447 | pchisq(x, 250).should.be.closeTo(0.0000000, 0.0001);
448 |
449 | x = 5
450 | pchisq(x, 0).should.be.equal(1);
451 | pchisq(x, 1).should.be.closeTo(0.9746527, 0.0001);
452 | pchisq(x, 2).should.be.closeTo(0.9179150, 0.0001);
453 | pchisq(x, 5).should.be.closeTo(0.5841198, 0.0001);
454 | pchisq(x, 10).should.be.closeTo(0.1088220, 0.0001);
455 | pchisq(x, 20).should.be.closeTo(0.0002774, 0.0001);
456 | pchisq(x, 100).should.be.closeTo(0.0000000, 0.0001);
457 | pchisq(x, 250).should.be.closeTo(0.0000000, 0.0001);
458 |
459 | x = 10
460 | pchisq(x, 0).should.be.equal(1);
461 | pchisq(x, 1).should.be.closeTo(0.9984346, 0.0001);
462 | pchisq(x, 2).should.be.closeTo(0.9932621, 0.0001);
463 | pchisq(x, 5).should.be.closeTo(0.9247648, 0.0001);
464 | pchisq(x, 10).should.be.closeTo(0.5595067, 0.0001);
465 | pchisq(x, 20).should.be.closeTo(0.0318281, 0.0001);
466 | pchisq(x, 100).should.be.closeTo(0.0000000, 0.0001);
467 | pchisq(x, 250).should.be.closeTo(0.0000000, 0.0001);
468 |
469 | x = 50
470 | pchisq(x, 0).should.be.equal(1);
471 | pchisq(x, 1).should.be.closeTo(1.0000000, 0.0001);
472 | pchisq(x, 2).should.be.closeTo(1.0000000, 0.0001);
473 | pchisq(x, 5).should.be.closeTo(1.0000000, 0.0001);
474 | pchisq(x, 10).should.be.closeTo(0.9999997, 0.0001);
475 | pchisq(x, 20).should.be.closeTo(0.9997785, 0.0001);
476 | pchisq(x, 100).should.be.closeTo(0.0000000, 0.0001);
477 | pchisq(x, 250).should.be.closeTo(0.0000000, 0.0001);
478 |
479 | x = 150
480 | pchisq(x, 0).should.be.equal(1);
481 | pchisq(x, 1).should.be.closeTo(1.0000000, 0.0001);
482 | pchisq(x, 2).should.be.closeTo(1.0000000, 0.0001);
483 | pchisq(x, 5).should.be.closeTo(1.0000000, 0.0001);
484 | pchisq(x, 10).should.be.closeTo(1.0000000, 0.0001);
485 | pchisq(x, 20).should.be.closeTo(1.0000000, 0.0001);
486 | pchisq(x, 100).should.be.closeTo(0.9990961, 0.0001);
487 | pchisq(x, 150).should.be.closeTo(0.5153564, 0.0001);
488 | pchisq(x, 250).should.be.closeTo(0.0000000, 0.0001);
489 |
490 | // vector with values
491 | const x4 = vector([10, 0, 3.16]);
492 | const p4 = pchisq(x4, 5)
493 |
494 | expect(isvector(p4)).to.be.true;
495 | p4.length.should.be.equal(3)
496 | p4.v[0].should.be.closeTo(0.9247648, 0.0001);
497 | p4.v[1].should.be.closeTo(0.0000000, 0.0001);
498 | p4.v[2].should.be.closeTo(0.3246659, 0.0001);
499 | });
500 |
501 | it ('tests for method "qchisq".', function () {
502 | // all outcomes are compared with similar outcomes from R
503 |
504 | // single value
505 | let p = 0.001
506 | qchisq(p, 0).should.be.equal(0);
507 | qchisq(p, 1).should.be.closeTo(0.000000, 0.0001);
508 | qchisq(p, 2).should.be.closeTo(0.002001, 0.001);
509 | qchisq(p, 5).should.be.closeTo(0.210213, 0.001);
510 | qchisq(p, 10).should.be.closeTo(1.478740, 0.001);
511 | qchisq(p, 20).should.be.closeTo(5.921040, 0.001);
512 | qchisq(p, 100).should.be.closeTo(61.9179, 0.01);
513 | qchisq(p, 250).should.be.closeTo(186.554, 0.01);
514 |
515 | p = 0.01
516 | qchisq(p, 0).should.be.equal(0);
517 | qchisq(p, 1).should.be.closeTo(0.000157, 0.0001);
518 | qchisq(p, 2).should.be.closeTo(0.020101, 0.001);
519 | qchisq(p, 5).should.be.closeTo(0.554298, 0.001);
520 | qchisq(p, 10).should.be.closeTo(2.558210, 0.001);
521 | qchisq(p, 20).should.be.closeTo(8.260400, 0.001);
522 | qchisq(p, 100).should.be.closeTo(70.0649, 0.01);
523 | qchisq(p, 250).should.be.closeTo(200.939, 0.01);
524 |
525 | p = 0.05
526 | qchisq(p, 0).should.be.equal(0);
527 | qchisq(p, 1).should.be.closeTo(0.003932, 0.0001);
528 | qchisq(p, 2).should.be.closeTo(0.102587, 0.001);
529 | qchisq(p, 5).should.be.closeTo(1.145476, 0.001);
530 | qchisq(p, 10).should.be.closeTo(3.940299, 0.001);
531 | qchisq(p, 20).should.be.closeTo(10.85081, 0.001);
532 | qchisq(p, 100).should.be.closeTo(77.92947, 0.01);
533 | qchisq(p, 250).should.be.closeTo(214.3916, 0.01);
534 |
535 | p = 0.10
536 | qchisq(p, 0).should.be.equal(0);
537 | qchisq(p, 1).should.be.closeTo(0.0157908, 0.0001);
538 | qchisq(p, 2).should.be.closeTo(0.210721, 0.001);
539 | qchisq(p, 5).should.be.closeTo(1.61031, 0.001);
540 | qchisq(p, 10).should.be.closeTo(4.86518, 0.001);
541 | qchisq(p, 20).should.be.closeTo(12.4426, 0.001);
542 | qchisq(p, 100).should.be.closeTo(82.3581, 0.01);
543 | qchisq(p, 250).should.be.closeTo(221.806, 0.01);
544 |
545 | p = 0.90
546 | qchisq(p, 0).should.be.equal(0);
547 | qchisq(p, 1).should.be.closeTo(2.70554, 0.0001);
548 | qchisq(p, 2).should.be.closeTo(4.60517, 0.001);
549 | qchisq(p, 5).should.be.closeTo(9.23636, 0.001);
550 | qchisq(p, 10).should.be.closeTo(15.9872, 0.001);
551 | qchisq(p, 20).should.be.closeTo(28.4120, 0.001);
552 | qchisq(p, 100).should.be.closeTo(118.498, 0.01);
553 | qchisq(p, 250).should.be.closeTo(279.050, 0.01);
554 |
555 | p = 0.95
556 | qchisq(p, 0).should.be.equal(0);
557 | qchisq(p, 1).should.be.closeTo(3.84146, 0.001);
558 | qchisq(p, 2).should.be.closeTo(5.99146, 0.001);
559 | qchisq(p, 5).should.be.closeTo(11.0705, 0.001);
560 | qchisq(p, 10).should.be.closeTo(18.3070, 0.001);
561 | qchisq(p, 20).should.be.closeTo(31.4104, 0.001);
562 | qchisq(p, 100).should.be.closeTo(124.342, 0.01);
563 | qchisq(p, 250).should.be.closeTo(287.882, 0.01);
564 |
565 | p = 0.99
566 | qchisq(p, 0).should.be.equal(0);
567 | qchisq(p, 1).should.be.closeTo(6.63490, 0.001);
568 | qchisq(p, 2).should.be.closeTo(9.21034, 0.001);
569 | qchisq(p, 5).should.be.closeTo(15.0863, 0.001);
570 | qchisq(p, 10).should.be.closeTo(23.2093, 0.001);
571 | qchisq(p, 20).should.be.closeTo(37.5662, 0.001);
572 | qchisq(p, 100).should.be.closeTo(135.807, 0.01);
573 | qchisq(p, 250).should.be.closeTo(304.94, 0.01);
574 |
575 | p = 0.999
576 | qchisq(p, 0).should.be.equal(0);
577 | qchisq(p, 1).should.be.closeTo(10.8276, 0.001);
578 | qchisq(p, 2).should.be.closeTo(13.8155, 0.001);
579 | qchisq(p, 5).should.be.closeTo(20.5150, 0.001);
580 | qchisq(p, 10).should.be.closeTo(29.5883, 0.001);
581 | qchisq(p, 20).should.be.closeTo(45.3147, 0.001);
582 | qchisq(p, 100).should.be.closeTo(149.449, 0.01);
583 | qchisq(p, 250).should.be.closeTo(324.832, 0.01);
584 |
585 | // vector with values
586 | const p3 = vector([0.01, 0.05]);
587 | const q3 = qchisq(p3, 5)
588 | q3.v[0].should.be.closeTo(0.55, 0.02);
589 | q3.v[1].should.be.closeTo(1.15, 0.02);
590 |
591 | // array with values
592 | const p4 = [0.01, 0.05];
593 | const q4 = qchisq(p3, 5)
594 | q4.v[0].should.be.closeTo(0.55, 0.02);
595 | q4.v[1].should.be.closeTo(1.15, 0.02);
596 | });
597 |
598 | it ('tests for method "qf".', function () {
599 | // all outcomes are compared with similar outcomes from R
600 |
601 | // single value
602 | let p ;
603 |
604 | p = 0.001
605 | qf(p, 0, 1).should.be.NaN;
606 | qf(p, 1, 0).should.be.NaN;
607 | qf(p, 1, 1).should.be.closeTo(0.000003, 0.000001);
608 | qf(p, 2, 3).should.be.closeTo(0.001, 0.0001);
609 | qf(p, 5, 10).should.be.closeTo(0.0371, 0.0001);
610 | qf(p, 10, 20).should.be.closeTo(0.128, 0.001);
611 | qf(p, 20, 30).should.be.closeTo(0.250, 0.01);
612 | qf(p, 100, 200).should.be.closeTo(0.572, 0.01);
613 | qf(p, 1, 200).should.be.closeTo(0.0000016, 0.0000001);
614 | qf(p, 250, 500).should.be.closeTo(0.7065, 0.0001);
615 |
616 | p = 0.01
617 | qf(p, 0, 1).should.be.NaN;
618 | qf(p, 1, 0).should.be.NaN;
619 | qf(p, 1, 1).should.be.closeTo(0.000247, 0.000001);
620 | qf(p, 2, 3).should.be.closeTo(0.010084, 0.000001);
621 | qf(p, 5, 10).should.be.closeTo(0.099492, 0.000001);
622 | qf(p, 10, 20).should.be.closeTo(0.2269944, 0.000001);
623 | qf(p, 20, 30).should.be.closeTo(0.3599084, 0.000001);
624 | qf(p, 100, 200).should.be.closeTo(0.6585758, 0.000001);
625 | qf(p, 1, 200).should.be.closeTo(0.000157, 0.000001);
626 | qf(p, 250, 500).should.be.closeTo(0.7707644, 0.000001);
627 |
628 | p = 0.05
629 | qf(p, 1, 1).should.be.closeTo(0.006194, 0.000001);
630 | qf(p, 2, 3).should.be.closeTo(0.052180, 0.000001);
631 | qf(p, 5, 10).should.be.closeTo(0.211190, 0.000001);
632 | qf(p, 10, 20).should.be.closeTo(0.3604881, 0.000001);
633 | qf(p, 20, 30).should.be.closeTo(0.4904158, 0.000001);
634 | qf(p, 100, 200).should.be.closeTo(0.745352, 0.000001);
635 | qf(p, 1, 200).should.be.closeTo(0.003942, 0.000001);
636 | qf(p, 250, 500).should.be.closeTo(0.8322605, 0.000001);
637 |
638 | p = 0.10
639 | qf(p, 1, 1).should.be.closeTo(0.02508563, 0.000001);
640 | qf(p, 2, 3).should.be.closeTo(0.109149, 0.000001);
641 | qf(p, 5, 10).should.be.closeTo(0.3032691, 0.000001);
642 | qf(p, 10, 20).should.be.closeTo(0.4543918, 0.000001);
643 | qf(p, 20, 30).should.be.closeTo(0.5753002, 0.000001);
644 | qf(p, 100, 200).should.be.closeTo(0.7954999, 0.000001);
645 | qf(p, 1, 200).should.be.closeTo(0.01583093, 0.000001);
646 | qf(p, 250, 500).should.be.closeTo(0.8667542, 0.000001);
647 |
648 | p = 0.90
649 | qf(p, 1, 1).should.be.closeTo(39.86346, 0.0001);
650 | qf(p, 2, 3).should.be.closeTo(5.462383, 0.00001);
651 | qf(p, 5, 10).should.be.closeTo(2.521641, 0.00001);
652 | qf(p, 10, 20).should.be.closeTo(1.936738, 0.00001);
653 | qf(p, 20, 30).should.be.closeTo(1.667309, 0.00001);
654 | qf(p, 100, 200).should.be.closeTo(1.241822, 0.00001);
655 | qf(p, 1, 200).should.be.closeTo(2.730783, 0.00001);
656 | qf(p, 250, 500).should.be.closeTo(1.148128, 0.00001);
657 |
658 |
659 | p = 0.95
660 | qf(p, 1, 1).should.be.closeTo(161.4476, 0.0001);
661 | qf(p, 2, 3).should.be.closeTo(9.552094, 0.00001);
662 | qf(p, 5, 10).should.be.closeTo(3.325835, 0.00001);
663 | qf(p, 10, 20).should.be.closeTo(2.347878, 0.00001);
664 | qf(p, 20, 30).should.be.closeTo(1.931653, 0.00001);
665 | qf(p, 100, 200).should.be.closeTo(1.320637, 0.00001);
666 | qf(p, 1, 200).should.be.closeTo(3.888375, 0.00001);
667 | qf(p, 250, 500).should.be.closeTo(1.194013, 0.00001);
668 |
669 | p = 0.99
670 | qf(p, 1, 1).should.be.closeTo(4052.181, 0.01);
671 | qf(p, 2, 3).should.be.closeTo(30.81652, 0.0001);
672 | qf(p, 5, 10).should.be.closeTo(5.636326, 0.00001);
673 | qf(p, 10, 20).should.be.closeTo(3.368186, 0.00001);
674 | qf(p, 20, 30).should.be.closeTo(2.548659, 0.00001);
675 | qf(p, 100, 200).should.be.closeTo(1.481056, 0.00001);
676 | qf(p, 1, 200).should.be.closeTo(6.763299, 0.00001);
677 | qf(p, 250, 500).should.be.closeTo(1.284611, 0.00001);
678 |
679 | p = 0.999
680 | qf(p, 1, 1).should.be.closeTo(405284.1, 20);
681 | qf(p, 2, 3).should.be.closeTo(148.5, 0.1);
682 | qf(p, 5, 10).should.be.closeTo(10.48072, 0.01);
683 | qf(p, 10, 20).should.be.closeTo(5.075246, 0.00001);
684 | qf(p, 20, 30).should.be.closeTo(3.492784, 0.00001);
685 | qf(p, 100, 200).should.be.closeTo(1.68243, 0.00001);
686 | qf(p, 1, 200).should.be.closeTo(11.1545, 0.00001);
687 | qf(p, 250, 500).should.be.closeTo(1.393628, 0.00001);
688 |
689 | // vector with values
690 | const p3 = vector([0.01, 0.99]);
691 | const q3 = qf(p3, 5, 10)
692 | q3.v[0].should.be.closeTo(0.09949242, 0.000001);
693 | q3.v[1].should.be.closeTo(5.636326, 0.00001);
694 |
695 | // array with values
696 | const p4 = [0.01, 0.99];
697 | const q4 = qf(p4, 5, 10)
698 | q4.v[0].should.be.closeTo(0.09949242, 0.000001);
699 | q4.v[1].should.be.closeTo(5.636326, 0.00001);
700 | });
701 |
702 |
703 | });
704 |
705 |
--------------------------------------------------------------------------------
/test/tests-misc.js:
--------------------------------------------------------------------------------
1 | /****************************************************************/
2 | /* Tests for methods computing various statistics */
3 | /****************************************************************/
4 |
5 | // import dependencies
6 | import {default as chai} from 'chai';
7 | import {default as chaiAlmost} from 'chai-almost';
8 | import { vector } from '../src/arrays/index.js';
9 | import { quantile } from '../src/stat/index.js';
10 |
11 | // import methods to test
12 | import { closestind, closestindleft, closestindright, getoutliers, expandgrid, integrate, round } from '../src/misc/index.js';
13 |
14 | // set up test settings
15 | const should = chai.should();
16 | const expect = chai.expect;
17 | chai.use(chaiAlmost(0.00001));
18 |
19 | describe('Tests for misc extra functions.', function () {
20 |
21 | it('tests for method "getoutliers"".', function () {
22 |
23 | const x4 = vector([-100, -2, -1, 0, 1, 2, 3, 50, 100]);
24 | const o1 = getoutliers(x4);
25 | expect(o1).to.eql(vector([-100, 50, 100]));
26 |
27 | const o2 = getoutliers(x4, quantile(x4, 0.25));
28 | expect(o2).to.eql(vector([-100, 50, 100]));
29 |
30 | const o3 = getoutliers(x4, quantile(x4, 0.25), quantile(x4, 0.75));
31 | expect(o3).to.eql(vector([-100, 50, 100]));
32 |
33 | });
34 |
35 | it('tests for method "integrate".', function() {
36 |
37 | integrate(x => x**2, -1, 1).should.be.closeTo(0.6666667, 0.00001);
38 | integrate(x => x**2, -1, 0).should.be.closeTo(0.3333333, 0.00001);
39 | integrate(x => x**2, 0, 1).should.be.closeTo(0.3333333, 0.00001);
40 |
41 | integrate(Math.sin, 0, Math.PI).should.be.closeTo(2, 0.00001);
42 | integrate(Math.sin, 0, 2 * Math.PI).should.be.closeTo(0, 0.00001);
43 | integrate((x) => Math.exp(-x), 0, Infinity).should.be.closeTo(1, 0.00001);
44 |
45 | [0.1, 0.5, 1, 5, 10].map(a =>
46 | integrate((x) => 1/Math.sqrt(a**2 - x**2), 0, a).should.be.closeTo(Math.PI/2, 0.00001)
47 | );
48 |
49 | integrate((x) => Math.exp(-(x**2)), -Infinity, Infinity).should.be.closeTo(Math.sqrt(Math.PI), 0.0001);
50 | });
51 |
52 | it('tests for method "expandgrid".', function () {
53 |
54 | const z1 = expandgrid(vector([100, 200, 300]), vector([10, 20]));
55 | z1.should.be.a('Array');
56 | expect(z1).to.have.lengthOf(2);
57 | expect(z1[0]).to.eql(vector([100, 200, 300, 100, 200, 300]));
58 | expect(z1[1]).to.eql(vector([10, 10, 10, 20, 20, 20]));
59 |
60 | const z2 = expandgrid(vector([100, 200, 300]), vector([10, 20]), vector([-20, +20]));
61 | z2.should.be.a('Array');
62 | expect(z2).to.have.lengthOf(3);
63 | expect(z2[0]).to.eql(vector([100, 200, 300, 100, 200, 300, 100, 200, 300, 100, 200, 300]));
64 | expect(z2[1]).to.eql(vector([10, 10, 10, 20, 20, 20, 10, 10, 10, 20, 20, 20]));
65 | expect(z2[2]).to.eql(vector([-20, -20, -20, -20, -20, -20, 20, 20, 20, 20, 20, 20]));
66 |
67 | });
68 |
69 | it ('tests for method "closestindleft".', function() {
70 | const x1 = vector([-200, -100, -50, -0.5, 0.1, 1.5, 20, 500, 1000]);
71 |
72 | closestindleft(x1, -120).should.equal(1);
73 | closestindleft(x1, -101).should.equal(1);
74 | closestindleft(x1, -99).should.equal(2);
75 | closestindleft(x1, -51).should.equal(2);
76 | closestindleft(x1, -50).should.equal(3);
77 | closestindleft(x1, -49).should.equal(3);
78 | closestindleft(x1, 0).should.equal(4);
79 | closestindleft(x1, 0.099999).should.equal(4);
80 | closestindleft(x1, 0.1).should.equal(5);
81 | closestindleft(x1, 0.11).should.equal(5);
82 | closestindleft(x1, 499).should.equal(7);
83 | closestindleft(x1, 501).should.equal(8);
84 | closestindleft(x1, 2000).should.equal(9);
85 | });
86 |
87 | it ('tests for method "closestindright".', function() {
88 | const x1 = vector([-200, -100, -50, -0.5, 0.1, 1.5, 20, 500, 1000]);
89 |
90 | closestindright(x1, -1200).should.equal(1);
91 | closestindright(x1, -120).should.equal(2);
92 | closestindright(x1, -101).should.equal(2);
93 | closestindright(x1, -99).should.equal(3);
94 | closestindright(x1, -51).should.equal(3);
95 | closestindright(x1, -50).should.equal(3);
96 | closestindright(x1, -49).should.equal(4);
97 | closestindright(x1, 0).should.equal(5);
98 | closestindright(x1, 0.099999).should.equal(5);
99 | closestindright(x1, 0.1).should.equal(5);
100 | closestindright(x1, 0.11).should.equal(6);
101 | closestindright(x1, 499).should.equal(8);
102 | closestindright(x1, 501).should.equal(9);
103 | closestindright(x1, 2000).should.equal(9);
104 | });
105 |
106 |
107 | it ('tests for method "closestind".', function() {
108 | const x1 = vector([-200, -0.5, -0.45, 1.2, -5.1, 4.2, -10.0]);
109 |
110 | closestind(x1, -120).should.equal(1);
111 | closestind(x1, -20).should.equal(7);
112 | closestind(x1, -7).should.equal(5);
113 | closestind(x1, 0).should.equal(3);
114 | closestind(x1, 3).should.equal(6);
115 | closestind(x1, 100000000).should.equal(6);
116 | closestind(x1, -100000000).should.equal(1);
117 | });
118 |
119 | it('tests for method "round".', function () {
120 | expect(round([1.23456789, 2.3456789])).be.eql([1, 2])
121 | expect(round([1.23456789, 2.3456789], 2)).be.eql([1.23, 2.35])
122 | });
123 |
124 | });
--------------------------------------------------------------------------------
/test/tests-models.js:
--------------------------------------------------------------------------------
1 | /****************************************************************/
2 | /* Tests for modelling methods */
3 | /****************************************************************/
4 |
5 | // import dependencies
6 | import {default as chai} from 'chai';
7 | import {default as chaiAlmost} from 'chai-almost';
8 | import { factor, cbind, vector, matrix, tcrossprod, Vector, Matrix } from '../src/arrays/index.js';
9 | import { variance, median, mean, sd, sum } from '../src/stat/index.js';
10 | import { svd } from '../src/decomp/index.js';
11 | import { scale as prep_scale } from '../src/prep/index.js';
12 |
13 | // import of functions to test
14 | import {simcapredict, getclassres, simpls, plsfit, plspredict, splitregdata, pcrfit, pcrpredict, pcafit, pcapredict, lmfit,
15 | lmpredict, polyfit, polypredict, getsimcaparams} from '../src/models/index.js';
16 |
17 | // set up test settings
18 | const expect = chai.expect;
19 | const should = chai.should();
20 | chai.use(chaiAlmost(0.001));
21 |
22 | function testClassResObject(x, cPred, cRef, className, stat) {
23 |
24 | expect(x.class.includes('classres'));
25 | expect(x.cPred).to.be.deep.equal(cPred);
26 | expect(x.className).to.be.equal(className);
27 |
28 | if (cRef) {
29 | expect(x.cRef).to.be.deep.equal(cRef);
30 | expect(x.TP).to.be.deep.equal(vector(stat[0]));
31 | expect(x.FP).to.be.deep.equal(vector(stat[1]));
32 | expect(x.TN).to.be.deep.equal(vector(stat[2]));
33 | expect(x.FN).to.be.deep.equal(vector(stat[3]));
34 | expect(x.sensitivity).to.be.deep.equal(vector(stat[4]));
35 | expect(x.specificity).to.be.deep.equal(vector(stat[5]));
36 | expect(x.accuracy).to.be.deep.equal(vector(stat[6]));
37 | } else {
38 | expect(x.cRef === undefined).to.be.true;
39 | expect(x.TP === undefined).to.be.true;
40 | expect(x.FP === undefined).to.be.true;
41 | expect(x.TN === undefined).to.be.true;
42 | expect(x.FP === undefined).to.be.true;
43 | expect(x.sensitivity === undefined).to.be.true;
44 | expect(x.specificity === undefined).to.be.true;
45 | expect(x.accuracy === undefined).to.be.true;
46 | }
47 | }
48 |
49 | describe('Tests for classification methods.', function () {
50 |
51 | it ('tests for method "getclassres" - one predicted item.', function () {
52 |
53 | // perfect case
54 | const cp1 = [factor(['none', 'red', 'none', 'red', 'none', 'red', 'none', 'red'])];
55 | const cr1 = factor(['blue', 'red', 'blue', 'red', 'blue', 'red', 'green', 'red']);
56 | const r1a = getclassres(cp1, 'red', cr1);
57 | const r1b = getclassres(cp1, 'red');
58 | testClassResObject(r1a, cp1, cr1, 'red', [[4], [0], [4], [0], [1], [1], [1]])
59 | testClassResObject(r1b, cp1, null, 'red')
60 |
61 | // one false negative
62 | const cp2 =[factor(['none', 'red', 'none', 'none', 'none', 'red', 'none', 'red'])];
63 | const cr2 = factor(['blue', 'red', 'green', 'red', 'blue', 'red', 'green', 'red']);
64 | const r2a = getclassres(cp2, 'red', cr2);
65 | const r2b = getclassres(cp2, 'red');
66 | testClassResObject(r2a, cp2, cr2, 'red', [[3], [0], [4], [1], [0.75], [1], [0.875]])
67 | testClassResObject(r2b, cp2, null, 'red')
68 |
69 | // one false positive
70 | const cp3 = [factor(['red', 'red', 'none', 'red', 'none', 'red', 'none', 'red'])];
71 | const cr3 = factor(['blue', 'red', 'blue', 'red', 'blue', 'red', 'green', 'red']);
72 | const r3a = getclassres(cp3, 'red', cr3);
73 | const r3b = getclassres(cp3, 'red');
74 | testClassResObject(r3a, cp3, cr3, 'red', [[4], [1], [3], [0], [1], [0.75], [0.875]])
75 | testClassResObject(r3b, cp3, null, 'red')
76 |
77 | // everything is wrong
78 | const cp4 = [factor(['red', 'none', 'red', 'none', 'red', 'none', 'red', 'none'])];
79 | const cr4 = factor(['blue', 'red', 'blue', 'red', 'blue', 'red', 'green', 'red']);
80 | const r4a = getclassres(cp4, 'red', cr4);
81 | const r4b = getclassres(cp4, 'red');
82 | testClassResObject(r4a, cp4, cr4, 'red', [[0], [4], [0], [4], [0], [0], [0]])
83 | testClassResObject(r4b, cp4, null, 'red')
84 |
85 | // no class items in predictions (all none)
86 | const cp5 = [factor(['none', 'none', 'none', 'none', 'none', 'none', 'none', 'none'])];
87 | const cr5 = factor(['blue', 'red', 'blue', 'red', 'blue', 'red', 'green', 'red']);
88 | const r5a = getclassres(cp5, 'red', cr5);
89 | const r5b = getclassres(cp5, 'red');
90 | testClassResObject(r5a, cp5, cr5, 'red', [[0], [0], [4], [4], [0], [1], [0.5]])
91 | testClassResObject(r5b, cp5, null, 'red')
92 |
93 | // no class items in references
94 | const cp6 = [factor(['red', 'none', 'red', 'none', 'red', 'none', 'red', 'none'])];
95 | const cr6 = factor(['blue', 'green', 'blue', 'green', 'blue', 'green', 'green', 'blue']);
96 | const r6a = getclassres(cp6, 'red', cr6);
97 | const r6b = getclassres(cp6, 'red');
98 | testClassResObject(r6a, cp6, cr6, 'red', [[0], [4], [4], [0], [NaN], [0.5], [0.5]])
99 | testClassResObject(r6b, cp6, null, 'red')
100 |
101 | // only class items in references
102 | const cp7 = [factor(['red', 'none', 'red', 'none', 'red', 'none', 'red', 'none'])];
103 | const cr7 = factor(['red', 'red', 'red', 'red', 'red', 'red', 'red', 'red']);
104 | const r7a = getclassres(cp7, 'red', cr7);
105 | const r7b = getclassres(cp7, 'red');
106 | testClassResObject(r7a, cp7, cr7, 'red', [[4], [0], [0], [4], [0.5], [NaN], [0.5]])
107 | testClassResObject(r7b, cp7, null, 'red')
108 |
109 | });
110 |
111 | it ('tests for method "getclassres" - several predicted items.', function () {
112 |
113 | // perfect case
114 | const cp1 = [
115 | factor(['none', 'red', 'none', 'red', 'none', 'red', 'none', 'red']),
116 | factor(['none', 'red', 'none', 'none', 'none', 'red', 'none', 'red']),
117 | factor(['red', 'none', 'red', 'none', 'red', 'none', 'red', 'none']),
118 | factor(['none', 'none', 'none', 'none', 'none', 'none', 'none', 'none'])
119 | ];
120 | const cr1 = factor(['blue', 'red', 'blue', 'red', 'blue', 'red', 'green', 'red']);
121 | const r1a = getclassres(cp1, 'red', cr1);
122 | const r1b = getclassres(cp1, 'red');
123 | testClassResObject(r1a, cp1, cr1, 'red', [
124 | [4, 3, 0, 0],
125 | [0, 0, 4, 0],
126 | [4, 4, 0, 4],
127 | [0, 1, 4, 4],
128 | [1, 0.75, 0, 0],
129 | [1, 1, 0, 1],
130 | [1, 0.875, 0, 0.5]
131 | ])
132 | testClassResObject(r1b, cp1, null, 'red')
133 | });
134 | });
135 |
136 | describe('Tests for SIMCA methods.', function () {
137 |
138 | it ('tests for method "simcapredict".', function () {
139 | const X1 = Matrix.rand(50, 10);
140 | const X2 = Matrix.rand(20, 10);
141 | const mpca = pcafit(X1, 10);
142 | const params = getsimcaparams('red', 0.05, 'classic');
143 | const c = simcapredict(mpca, params, X2)
144 | });
145 |
146 | });
147 |
148 | describe('Tests for PLS methods.', function () {
149 |
150 | it ('tests for method "pldpredict"', function () {
151 | // common dataset for all tests
152 | const data = matrix([
153 | 32, 150, 41, 28000, 119,
154 | 35, 160, 48, 31000, 129,
155 | 36, 166, 47, 28000, 112,
156 | 37, 166, 49, 14000, 123,
157 | 42, 175, 67, 38000, 105,
158 | 43, 180, 80, 30000, 129,
159 | 43, 181, 75, 31000, 105,
160 | 44, 180, 81, 42000, 113
161 | ], 5, 8).t();
162 |
163 | const [Xc, Yc] = splitregdata(data);
164 |
165 | // make PCA and PCR for centered and scaled data and check all results in full
166 | const m1 = plsfit(Xc, Yc, 3, true, true);
167 | const r1 = plspredict(m1, Xc, Yc);
168 | const r2 = plspredict(m1, Xc);
169 |
170 | const T1 = matrix([
171 | 0.4945617, 0.36754034, 0.1250141,
172 | 0.3220733, 0.02656872, 0.5115363,
173 | 0.1747736, 0.20483128, 0.2758381,
174 | 0.3630906, 0.53394896, 0.4310806,
175 | 0.2781758, 0.39131972, 0.1470429,
176 | 0.2420359, 0.59936775, 0.3803569,
177 | 0.3655882, 0.01375190, 0.4564504,
178 | 0.4686992, 0.15680855, 0.2935047,
179 | ], 3, 8).t();
180 |
181 | const H1 = matrix([
182 | 1.7121391, 2.6577404, 2.767140,
183 | 0.7261183, 0.7310596, 2.562745,
184 | 0.2138206, 0.5075116, 1.040118,
185 | 0.9228437, 2.9185541, 4.219368,
186 | 0.5416725, 1.6135903, 1.764942,
187 | 0.4100696, 2.9247616, 3.937461,
188 | 0.9355833, 0.9369071, 2.395336,
189 | 1.5377529, 1.7098753, 2.312890,
190 | ], 3, 8).t();
191 |
192 | const Q1 = matrix([
193 | 0.8783125, 0.15842635, 9.720942e-02,
194 | 1.1479065, 1.16544180, 3.649739e-02,
195 | 0.7186410, 0.38216058, 6.778351e-02,
196 | 2.0996044, 0.79275237, 8.243227e-05,
197 | 1.0857301, 0.09480393, 3.913798e-03,
198 | 3.0896422, 0.61662640, 2.667597e-03,
199 | 0.8971517, 0.90605443, 1.124068e-02,
200 | 0.4379797, 0.36701856, 1.254689e-03,
201 | ], 3, 8).t();
202 |
203 | const U1 = matrix([
204 | 3.950369, 0.16641101, 0.0064092242,
205 | 2.257354, 0.03450792, 0.0046740695,
206 | 1.693015, 0.12839530, 0.0102507432,
207 | 1.128677, 0.29291384, 0.0188942539,
208 | 1.693015, 0.03033479, 0.0206833688,
209 | 2.257354, 0.15737118, 0.0109039174,
210 | 2.257354, 0.03229076, 0.0069754088,
211 | 2.821692, 0.05834525, 0.0003642595,
212 | ], 3, 8).t();
213 |
214 | const Yp1 = matrix([
215 | 33.25851, 32.25875, 32.19505,
216 | 35.26097, 35.18870, 34.92805,
217 | 36.97101, 36.41384, 36.55440,
218 | 34.78479, 36.23720, 36.45686,
219 | 42.22941, 41.16497, 41.23990,
220 | 41.80985, 43.44021, 43.24640,
221 | 43.24420, 43.28161, 43.51419,
222 | 44.44125, 44.01471, 43.86515,
223 | ], 3, 8).t();
224 |
225 | // check main outcomes
226 | expect(r1.T.apply(Math.abs, 0)).to.be.deep.almost.equal(T1);
227 | expect(r1.H).to.be.deep.almost.equal(H1);
228 | expect(r1.Q).to.be.deep.almost.equal(Q1);
229 | expect(r1.U.apply(Math.abs, 0)).to.be.deep.almost.equal(U1);
230 | expect(r1.Ypred).to.be.deep.almost.equal(Yp1);
231 |
232 | expect(r2.T.apply(Math.abs, 0)).to.be.deep.almost.equal(T1);
233 | expect(r2.H).to.be.deep.almost.equal(H1);
234 | expect(r2.Q).to.be.deep.almost.equal(Q1);
235 | expect(r2.Ypred).to.be.deep.almost.equal(Yp1);
236 |
237 | });
238 |
239 | it ('tests for method "plsfit"', function () {
240 |
241 | // common dataset for all tests
242 | const data = matrix([
243 | 32, 150, 41, 28000, 119,
244 | 35, 160, 48, 31000, 129,
245 | 36, 166, 47, 28000, 112,
246 | 37, 166, 49, 14000, 123,
247 | 42, 175, 67, 38000, 105,
248 | 43, 180, 80, 30000, 129,
249 | 43, 181, 75, 31000, 105,
250 | 44, 180, 81, 42000, 113
251 | ], 5, 8).t();
252 |
253 | const [Xc, Yc] = splitregdata(data);
254 |
255 | // make PCA and PCR for centered and scaled data and check all results in full
256 | const ncomp = 3;
257 | const m1 = plsfit(Xc, Yc, ncomp, true, true);
258 |
259 | const R1 = matrix([
260 | 0.15492668, 0.1881235, 0.12558132,
261 | 0.15317320, 0.1316393, 0.06823508,
262 | 0.08218702, 0.2824183, 0.32337181,
263 | 0.05900391, 0.2131759, 0.34074718
264 | ], 3, 4).t();
265 |
266 | const P1 = matrix([
267 | 2.481456, 0.7967174, 0.3398910,
268 | 2.526248, 0.6219986, 0.3463264,
269 | 1.828851, 1.5105447, 1.1672083,
270 | 1.326946, 1.6025908, 1.6324190
271 | ], 3, 4).t();
272 |
273 | const C1 = matrix([
274 | 2.559598, 0.5997323, 0.1123446
275 | ], 3, 1).t();
276 |
277 | const yeigenvals1 = vector([
278 | 6.5515441996, 0.0230428629, 0.0001600688
279 | ]);
280 |
281 | const xeigenvals1 = vector([
282 | 0.1428571, 0.1428571, 0.1428571
283 | ]);
284 |
285 | const Nq1 = vector([
286 | 4, 4, 1
287 | ]);
288 |
289 | const Nh1 = vector([
290 | 6, 6, 13
291 | ]);
292 |
293 | const q01 = vector([
294 | 1.29437100, 0.56041055, 0.02758119
295 | ]);
296 |
297 | const h01 = vector([
298 | 0.875, 1.750, 2.625
299 | ]);
300 |
301 | // check that the model outcomes are correct
302 | expect(m1.P.apply(Math.abs, 0)).to.be.deep.almost.equal(P1);
303 | expect(m1.R.apply(Math.abs, 0)).to.be.deep.almost.equal(R1);
304 | expect(m1.C.apply(Math.abs, 0)).to.be.deep.almost.equal(C1);
305 | expect(m1.yeigenvals).to.be.deep.almost.equal(yeigenvals1);
306 | expect(m1.xeigenvals).to.be.deep.almost.equal(xeigenvals1);
307 | expect(m1.qParams['classic'][1]).to.be.deep.almost.equal(Nq1);
308 | expect(m1.hParams['classic'][1]).to.be.deep.almost.equal(Nh1);
309 | expect(m1.qParams['classic'][0]).to.be.deep.almost.equal(q01);
310 | expect(m1.hParams['classic'][0]).to.be.deep.almost.equal(h01);
311 | });
312 |
313 | it ('tests for method "simpls"', function () {
314 |
315 | // common dataset for all tests
316 | const data = matrix([
317 | 32, 150, 41, 28000, 119,
318 | 35, 160, 48, 31000, 129,
319 | 36, 166, 47, 28000, 112,
320 | 37, 166, 49, 14000, 123,
321 | 42, 175, 67, 38000, 105,
322 | 43, 180, 80, 30000, 129,
323 | 43, 181, 75, 31000, 105,
324 | 44, 180, 81, 42000, 113
325 | ], 5, 8).t();
326 |
327 | const [Xc, Yc] = splitregdata(data);
328 |
329 | const Xp = prep_scale(Xc, true, true);
330 | const Yp = prep_scale(Yc, true, true);
331 |
332 | const R1 = matrix([
333 | 0.15492668, 0.1881235, 0.12558132,
334 | 0.15317320, 0.1316393, 0.06823508,
335 | 0.08218702, 0.2824183, 0.32337181,
336 | 0.05900391, 0.2131759, 0.34074718
337 | ], 3, 4).t();
338 |
339 | const P1 = matrix([
340 | 2.481456, 0.7967174, 0.3398910,
341 | 2.526248, 0.6219986, 0.3463264,
342 | 1.828851, 1.5105447, 1.1672083,
343 | 1.326946, 1.6025908, 1.6324190
344 | ], 3, 4).t();
345 |
346 | const T1 = matrix([
347 | 0.4945617, 0.36754034, 0.1250141,
348 | 0.3220733, 0.02656872, 0.5115363,
349 | 0.1747736, 0.20483128, 0.2758381,
350 | 0.3630906, 0.53394896, 0.4310806,
351 | 0.2781758, 0.39131972, 0.1470429,
352 | 0.2420359, 0.59936775, 0.3803569,
353 | 0.3655882, 0.01375190, 0.4564504,
354 | 0.4686992, 0.15680855, 0.2935047
355 | ], 3, 8).t();
356 |
357 | const C1 = matrix([
358 | 2.559598, 0.5997323, 0.1123446
359 | ], 3, 1).t();
360 |
361 | const U1 = matrix([
362 | 3.950369, 0.16641101, 0.0064092242,
363 | 2.257354, 0.03450792, 0.0046740695,
364 | 1.693015, 0.12839530, 0.0102507432,
365 | 1.128677, 0.29291384, 0.0188942539,
366 | 1.693015, 0.03033479, 0.0206833688,
367 | 2.257354, 0.15737118, 0.0109039174,
368 | 2.257354, 0.03229076, 0.0069754088,
369 | 2.821692, 0.05834525, 0.0003642595
370 | ], 3, 8).t();
371 |
372 | // partial decomposition - compare with results from R
373 | const m1 = simpls(Xp, Yp, 3);
374 |
375 | // decomposition of X
376 | expect(m1.R.apply(Math.abs, 0)).to.be.deep.almost.equal(R1);
377 | expect(m1.P.apply(Math.abs, 0)).to.be.deep.almost.equal(P1);
378 | expect(m1.T.apply(Math.abs, 0)).to.be.deep.almost.equal(T1);
379 |
380 | // decomposition of Y
381 | expect(m1.C.apply(Math.abs, 0)).to.be.deep.almost.equal(C1);
382 | expect(m1.U.apply(Math.abs, 0)).to.be.deep.almost.equal(U1);
383 |
384 | // full decomposition - check that X = TP' and Y = UC'
385 | const m2 = simpls(Xp, Yp, 4);
386 | // residual variance for X is almost 0
387 | expect(tcrossprod(m2.T, m2.P)).to.be.deep.almost.equal(Xp);
388 | // residual variance for Y is below 5%
389 | expect(sum(Yp.subtract(tcrossprod(m2.T, m2.C)).apply(v => v * v, 0).v) / sum(Yp.apply(v => v * v, 0).v) < 0.05).to.been.true;
390 |
391 | });
392 | });
393 |
394 | describe('Tests for PCR methods.', function () {
395 |
396 | it ('tests for method "pcrfit"', function () {
397 |
398 | // common dataset for all PCR tests
399 | const data = matrix([
400 | 36, 166, 47, 28000, 112,
401 | 43, 180, 80, 30000, 129,
402 | 44, 180, 81, 42000, 113,
403 | 36, 166, 49, 14000, 123,
404 | 43, 181, 75, 31000, 105,
405 | 42, 175, 67, 38000, 105,
406 | 35, 160, 48, 31000, 129
407 | ], 5, 7).t();
408 |
409 | const [Xc, Yc] = splitregdata(data);
410 |
411 | // make PCA and PCR for centered and scaled data and check all results in full
412 | const ncomp = 3;
413 | const m1 = pcrfit(Xc, Yc, ncomp, true, true);
414 | const m2 = pcafit(Xc, ncomp, true, true);
415 | const C1 = matrix([0.5953372, -0.2370964, 0.1571044], 1, ncomp);
416 |
417 | // check that PCA part is correct
418 | expect(m1.P.apply(Math.abs, 0)).to.be.deep.almost.equal(m2.P.apply(Math.abs, 0));
419 | expect(m1.eigenvals).to.be.deep.almost.equal(m2.eigenvals);
420 | expect(m1.mX).to.be.deep.almost.equal(m2.mX);
421 | expect(m1.sX).to.be.deep.almost.equal(m2.sX);
422 | expect(m1.center).to.be.deep.almost.equal(m2.center);
423 | expect(m1.scale).to.be.deep.almost.equal(m2.scale);
424 | expect(m1.h0).to.be.deep.almost.equal(m2.h0);
425 | expect(m1.q0).to.be.deep.almost.equal(m2.q0);
426 | expect(m1.Nq).to.be.deep.almost.equal(m2.Nq);
427 | expect(m1.Nh).to.be.deep.almost.equal(m2.Nh);
428 |
429 | // check that PCR part is correct
430 | expect(m1.C.v.map(v => Math.abs(v))).to.be.deep.almost.equal(C1.v.map(v => Math.abs(v)));
431 | expect(m1.mY).to.be.deep.equal(Yc.apply(mean, 2));
432 | expect(m1.sY).to.be.deep.equal(Yc.apply(sd, 2));
433 |
434 |
435 | // make PCR for centered and non-scaled data and check only results PCR results
436 | const m3 = pcrfit(Xc, Yc, ncomp, true, false);
437 | const C3 = matrix([0.0002877219, -0.2121022329, 0.0296248313], 1, ncomp);
438 | expect(m3.C.v.map(v => Math.abs(v))).to.be.deep.almost.equal(C3.v.map(v => Math.abs(v)));
439 | expect(m3.mY).to.be.deep.equal(Yc.apply(mean));
440 | expect(m3.sY).to.be.deep.equal(vector([1]));
441 | });
442 |
443 | it ('tests for method "pcrpredict"', function () {
444 |
445 | // common dataset for all PCR tests
446 | const data = matrix([
447 | 36, 166, 47, 28000, 112,
448 | 43, 180, 80, 30000, 129,
449 | 44, 180, 81, 42000, 113,
450 | 36, 166, 49, 14000, 123,
451 | 43, 181, 75, 31000, 105,
452 | 42, 175, 67, 38000, 105,
453 | 35, 160, 48, 31000, 129
454 | ], 5, 7).t();
455 |
456 | const [Xc, Yc] = splitregdata(data);
457 |
458 | // make PCA and PCR for centered and scaled data and check all results in full
459 | const m1 = pcrfit(Xc, Yc, 3, true, true);
460 | const m2 = pcafit(Xc, 3, true, true);
461 |
462 | const r1 = pcrpredict(m1, Xc, Yc);
463 | const r2 = pcapredict(m2, Xc);
464 |
465 | // check that PCA part is correct
466 | expect(r2.T.apply(Math.abs, 0)).to.be.deep.almost.equal(r1.T.apply(Math.abs, 0));
467 | expect(r2.H).to.be.deep.almost.equal(r1.H);
468 | expect(r2.Q).to.be.deep.almost.equal(r1.Q);
469 | expect(r2.expvar).to.be.deep.almost.equal(r1.expvar);
470 | expect(r2.cumexpvar).to.be.deep.almost.equal(r1.cumexpvar);
471 |
472 | });
473 |
474 | });
475 |
476 | describe('Tests for PCA methods.', function () {
477 |
478 | it ('tests for method "pcafit"', function () {
479 |
480 | const X1 = Matrix.rand(50, 10);
481 | const mX1 = X1.apply(mean, 2);
482 | const m11 = pcafit(X1);
483 | const m12 = svd(X1.subtract(mX1));
484 | const T1 = m12.U.dot(Matrix.diagm(m12.s));
485 |
486 | expect(m11.P.apply(Math.abs, 0)).to.be.deep.almost.equal(m12.V.apply(Math.abs, 0));
487 | expect(m11.mX).to.be.deep.almost.equal(mX1);
488 | expect(m11.sX).to.be.deep.almost.equal(Vector.ones(X1.ncols));
489 | expect(m11.eigenvals).to.be.deep.almost.equal(T1.apply(variance, 2));
490 | expect(m11.center).to.be.true;
491 | expect(m11.scale).to.be.false;
492 | expect(m11.ncomp).to.be.equal(10);
493 |
494 | const X2 = Matrix.rand(10, 50);
495 | const mX2 = X2.apply(mean, 2);
496 | const sX2 = X2.apply(sd, 2);
497 | const m21 = pcafit(X2, 5, true, true);
498 | const m22 = svd(X2.subtract(mX2).divide(sX2), 5);
499 | const T2 = m22.U.dot(Matrix.diagm(m22.s));
500 |
501 | expect(m21.P.apply(Math.abs, 0)).to.be.deep.almost.equal(m22.V.apply(Math.abs, 0));
502 | expect(m21.mX).to.be.deep.almost.equal(mX2);
503 | expect(m21.sX).to.be.deep.almost.equal(sX2);
504 | expect(m21.eigenvals).to.be.deep.almost.equal(T2.apply(variance, 2));
505 | expect(m21.center).to.be.true;
506 | expect(m21.scale).to.be.true;
507 | expect(m21.ncomp).to.be.equal(5);
508 |
509 | // compare with results from R
510 | const X3 = matrix([
511 | 166, 180, 157, 180, 166, 181, 175, 160,
512 | 47, 80, 47, 81, 49, 75, 67, 48,
513 | 28000, 30000, 32000, 42000, 14000, 31000, 38000, 31000,
514 | 112, 129, 127, 113, 123, 105, 105, 129
515 | ], 8, 4);
516 |
517 | const P3 = matrix([
518 | 0.5732978, -0.3559463, -0.2788432, 0.6832850,
519 | 0.5707159, -0.4525553, 0.0850746, -0.6798818,
520 | 0.4182815, 0.4024368, 0.7935761, 0.1825437,
521 | -0.4131024, -0.7117166, 0.5340840, 0.1938042
522 | ], 4, 4).t();
523 |
524 | const eigenvals3 = vector([2.594793132, 0.714955083, 0.682758553, 0.007493231]);
525 |
526 | // moments based
527 | const h03m = vector([0.875000, 1.750000, 2.625000, 3.500000]);
528 | const q03m = vector([1.229556, 0.6039703, 0.006556578, 0.0]);
529 | const Nh3m = vector([6.0, 6.0, 12.0, 16.0]);
530 | const Nq3m = vector([4.0, 3.0, 1.0, 4.0]);
531 |
532 | // robust based
533 | const h03r = vector([1.041937, 1.484588, 2.282291, 3.934952]);
534 | const q03r = vector([0.977954, 0.671042, 0.005573, 0.0]);
535 | const Nh3r = vector([13.0, 19.0, 20.0, 11.0]);
536 | const Nq3r = vector([11.0, 5.0, 2.0, 7.0]);
537 |
538 | const m3 = pcafit(X3, 4, true, true);
539 |
540 | expect(m3.P.apply(Math.abs, 0)).to.be.deep.almost.equal(P3.apply(Math.abs, 0));
541 | expect(m3.eigenvals).to.be.deep.almost.equal(eigenvals3);
542 |
543 | expect(m3.qParams['classic'][1].slice(1, 3)).to.be.deep.almost.equal(Nq3m.slice(1, 3));
544 | expect(m3.hParams['classic'][1]).to.be.deep.almost.equal(Nh3m);
545 | expect(m3.hParams['classic'][0]).to.be.deep.almost.equal(h03m);
546 | expect(m3.qParams['classic'][0]).to.be.deep.almost.equal(q03m);
547 |
548 | chai.use(chaiAlmost(0.01));
549 | expect(m3.qParams['robust'][1].slice(1, 3)).to.be.deep.almost.equal(Nq3r.slice(1, 3));
550 | expect(m3.hParams['robust'][1]).to.be.deep.almost.equal(Nh3r);
551 | expect(m3.hParams['robust'][0]).to.be.deep.almost.equal(h03r);
552 | expect(m3.qParams['robust'][0]).to.be.deep.almost.equal(q03r);
553 | chai.use(chaiAlmost(0.001));
554 |
555 | });
556 |
557 | it ('tests for method "pcapredict"', function () {
558 |
559 | const X1 = Matrix.rand(50, 10);
560 | const m11 = pcafit(X1);
561 | const mX1 = X1.apply(mean, 2);
562 | const m12 = svd(X1.subtract(mX1));
563 | const T1 = m12.U.dot(Matrix.diagm(m12.s));
564 |
565 | const r1 = pcapredict(m11, X1);
566 | const q12 = X1.subtract(mX1)
567 | .subtract(tcrossprod(r1.T.subset([], [1, 2]), m11.P.subset([], [1, 2])))
568 | .apply(v => v * v, 0)
569 | .apply(sum, 1);
570 | const h12 = m12.U.subset([], [1, 2])
571 | .apply(v => v * v, 0)
572 | .apply(sum, 1)
573 | .apply(v => v * (X1.nrows -1));
574 |
575 | expect(r1.T.apply(Math.abs, 0)).to.be.deep.almost.equal(T1.apply(Math.abs, 0));
576 | expect(r1.Q.getcolumn(2)).to.be.deep.almost.equal(q12);
577 | expect(r1.H.getcolumn(2)).to.be.deep.almost.equal(h12);
578 |
579 | const X2 = Matrix.rand(10, 50);
580 | const mX2 = X2.apply(mean, 2);
581 | const sX2 = X2.apply(sd, 2);
582 | const m21 = pcafit(X2, 5, true, true);
583 | const m22 = svd(X2.subtract(mX2).divide(sX2), 5);
584 | const T2 = m22.U.dot(Matrix.diagm(m22.s));
585 |
586 | const r2 = pcapredict(m21, X2);
587 | const q22 = X2.subtract(mX2).divide(sX2).
588 | subtract(tcrossprod(r2.T.subset([], [1, 2]), m21.P.subset([], [1, 2]))).apply(v => v * v, 0).apply(sum, 1);
589 | const h22 = m22.U.subset([], [1, 2]).apply(v => v * v, 0).apply(sum, 1).apply(v => v * (X2.nrows -1));
590 |
591 | expect(r2.T.apply(Math.abs, 0)).to.be.deep.almost.equal(T2.apply(Math.abs, 0));
592 | expect(r2.Q.getcolumn(2)).to.be.deep.almost.equal(q22);
593 | expect(r2.H.getcolumn(2)).to.be.deep.almost.equal(h22);
594 |
595 |
596 | // compare with results from R
597 | const X3 = matrix([
598 | 166, 180, 157, 180, 166, 181, 175, 160,
599 | 47, 80, 47, 81, 49, 75, 67, 48,
600 | 28000, 30000, 32000, 42000, 14000, 31000, 38000, 31000,
601 | 112, 129, 127, 113, 123, 105, 105, 129
602 | ], 8, 4);
603 |
604 | const T3 = matrix([
605 | -0.7231736, 0.87047760, -0.51644930, 0.143706174,
606 | 0.7468125, -1.68232514, 0.32979598, 0.062205787,
607 | -1.6567570, 0.36752606, 0.90839404, -0.126899054,
608 | 2.0362791, -0.01931637, 0.67077094, -0.014230821,
609 | -1.8047465, -0.63358644, -1.29333991, -0.049380702,
610 | 1.6340299, 0.12882816, -0.87065320, -0.076003459,
611 | 1.3393512, 0.92824983, -0.06167086, 0.002243734,
612 | -1.5717957, 0.04014630, 0.83315232, 0.058358340
613 | ], 4, 8).t();
614 |
615 | const H3 = matrix([
616 | 0.2015498, 1.2613804, 1.652031, 4.408047,
617 | 0.2149415, 4.1735369, 4.332840, 4.849247,
618 | 1.0578276, 1.2467561, 2.455353, 4.604408,
619 | 1.5979819, 1.5985038, 2.257498, 2.284524,
620 | 1.2552483, 1.8167267, 4.266682, 4.592103,
621 | 1.0290045, 1.0522181, 2.162474, 2.933374,
622 | 0.6913312, 1.8965087, 1.902079, 1.902751,
623 | 0.9521151, 0.9543694, 1.971043, 2.425546
624 | ], 4, 8).t();
625 |
626 | const Q3 = matrix([
627 | 1.0451026, 0.28737135, 2.065146e-02, 0.0,
628 | 2.9428528, 0.11263495, 3.869560e-03, 0.0,
629 | 0.9763585, 0.84128309, 1.610337e-02, 0.0,
630 | 0.4505093, 0.45013617, 2.025163e-04, 0.0,
631 | 2.0765984, 1.67516659, 2.438454e-03, 0.0,
632 | 0.7804102, 0.76381352, 5.776526e-03, 0.0,
633 | 0.8654561, 0.00380833, 5.034343e-06, 0.0,
634 | 0.6991602, 0.69754849, 3.405696e-03, 0.0
635 | ], 4, 8).t();
636 |
637 | const cumexpvar3 = vector([64.86983, 82.74371, 99.81267, 100.00000]);
638 | const expvar3 = vector([64.8698283, 17.8738771, 17.0689638, 0.1873308]);
639 | const m3 = pcafit(X3, 4, true, true);
640 | const r3 = pcapredict(m3, X3)
641 |
642 | expect(r3.T.apply(Math.abs, 0)).to.be.deep.almost.equal(T3.apply(Math.abs, 0));
643 | expect(r3.Q).to.be.deep.almost.equal(Q3);
644 | expect(r3.H).to.be.deep.almost.equal(H3);
645 | expect(r3.expvar).to.be.deep.almost.equal(expvar3);
646 | expect(r3.cumexpvar).to.be.deep.almost.equal(cumexpvar3)
647 |
648 | // same model new prediction set
649 | const X4 = matrix([
650 | 168, 52, 23500, 100,
651 | 166, 49, 14000, 123
652 | ], 4, 2).t();
653 |
654 | const T4 = matrix([
655 | -0.1700773, 1.2564700, -1.603759, -0.2578108,
656 | -1.8047465, -0.6335864, -1.293340, -0.0493807
657 | ], 4, 2).t();
658 |
659 | const H4 = matrix([
660 | 0.01114782, 2.219282, 5.986415, 14.856606,
661 | 1.25524835, 1.816727, 4.266682, 4.592103
662 | ], 4, 2).t();
663 |
664 | const Q4 = matrix([
665 | 4.217225, 2.638508, 0.066466391, 1.195617e-30,
666 | 2.076598, 1.675167, 0.002438454, 1.103173e-30
667 | ], 4, 2).t();
668 |
669 | const cumexpvar4 = vector([34.30150, 54.97142, 99.28073, 100.00000]);
670 | const expvar4 = vector([34.3015044, 20.6699134, 44.3093144, 0.7192678]);
671 | const r4 = pcapredict(m3, X4);
672 |
673 | expect(r4.T.apply(Math.abs, 0)).to.be.deep.almost.equal(T4.apply(Math.abs, 0));
674 | expect(r4.Q).to.be.deep.almost.equal(Q4);
675 | expect(r4.H).to.be.deep.almost.equal(H4);
676 | expect(r4.expvar).to.be.deep.almost.equal(expvar4);
677 | expect(r4.cumexpvar).to.be.deep.almost.equal(cumexpvar4);
678 |
679 | });
680 |
681 | });
682 |
683 | describe('Tests for polynomial regression methods.', function () {
684 |
685 | it ('tests for method "polyfit".', function () {
686 |
687 | const x = vector([1, 2, 3, 4, 5]);
688 | const y = vector([11, 14, 19, 26, 35]);
689 |
690 | expect(() => polyfit(x, y, 5)).to.throw(Error, 'polyfit: polynomial degree "d" must a positive value smaller than number of measurements.');
691 | expect(() => polyfit(cbind(x, x), y, 1)).to.throw(Error, 'polymat: argument "x" must be a vector.');
692 |
693 | const m1 = polyfit(x, y, 3);
694 |
695 | // check class and polynomial degree
696 | m1.class.should.be.equal("pm");
697 | m1.pdegree.should.be.equal(3);
698 |
699 | // check coefficients and related statistics
700 | m1.coeffs.estimate.length.should.be.equal(4);
701 | expect(m1.coeffs.estimate).to.be.deep.almost(vector([10, 0, 1, 0]));
702 | expect(m1.coeffs.p.apply(v => v < 0.10)).to.be.deep.almost(vector([1, 0, 1, 0]));
703 | });
704 |
705 | it ('tests for method "polypredict".', function () {
706 | const x = vector([1, 2, 3, 4, 5]);
707 | const y = vector([11, 14, 19, 26, 35]);
708 | const m = polyfit(x, y, 3);
709 |
710 | const yp = polypredict(m, x);
711 | expect(yp).to.be.deep.almost(y);
712 | });
713 |
714 | });
715 |
716 | describe('Tests for lm (MLR) methods.', function () {
717 |
718 | it ('tests for method "lmfit".', function () {
719 |
720 | // errors
721 | expect(() => lmfit(1, 2)).to.throw(Error,
722 | 'lmfit: argument "X" must be a matrix or a vector.');
723 | expect(() => lmfit(vector([1]), 2)).to.throw(Error,
724 | 'lmfit: argument "y" must be a vector.');
725 | expect(() => lmfit(vector([1]), vector([2]))).to.throw(Error,
726 | 'lmfit: number of objects must be larger than number of predictors.');
727 | expect(() => lmfit(vector([1, 2]), vector([2, 3, 4]))).to.throw(Error,
728 | 'lmfit: arguments "X" and "y" must have the same number of objects.');
729 |
730 | // vector - vector
731 | const X = vector([1, 2, 3, 4, 5]);
732 | const y = vector([10, 19, 31, 39, 55]);
733 | const m = lmfit(X, y);
734 |
735 | m.class.should.be.equal('lm');
736 |
737 | // check coefficients and related statistics
738 | m.coeffs.estimate.length.should.be.equal(2);
739 | expect(m.coeffs.estimate).to.be.deep.almost(vector([-2.2, 11.0]));
740 | expect(m.coeffs.se).to.be.deep.almost(vector([2.3295, 0.7024]));
741 | expect(m.coeffs.tstat).to.be.deep.almost(vector([-0.944, 15.661]));
742 | expect(m.coeffs.p).to.be.deep.almost(vector([0.4146, 0.000566]));
743 |
744 | // check predictions
745 | m.fitted.length.should.be.equal(y.length);
746 | expect(m.fitted).to.be.deep.almost(vector([8.8, 19.8, 30.8, 41.8, 52.8]));
747 |
748 | // check performance statistics
749 | m.stat.DoF.should.be.equal(3);
750 | expect(m.stat.se).to.be.almost(2.221);
751 | expect(m.stat.R2).to.be.almost(0.9879);
752 | expect(m.stat.R2adj).to.be.almost(0.9839);
753 | expect(m.stat.Fstat).to.be.almost(245.270);
754 | expect(m.stat.p).to.be.almost(0.0005658);
755 |
756 | // matrix - vector
757 | const X2 = matrix([1, 2, 3, 4, 5, 10, 20, 10, 20, 10], 5, 2);
758 | const y2 = vector([10, 19, 31, 39, 55]);
759 | const m2 = lmfit(X2, y2);
760 |
761 | m.class.should.be.equal("lm");
762 |
763 | // check coefficients and related statistics
764 | m2.coeffs.estimate.length.should.be.equal(3);
765 | expect(m2.coeffs.estimate).to.be.deep.almost(vector([2.0, 11.0, -0.3]));
766 | expect(m2.coeffs.se).to.be.deep.almost(vector([2.3381, 0.4472, 0.1291]));
767 | expect(m2.coeffs.tstat).to.be.deep.almost(vector([0.855, 24.597, -2.324]));
768 | expect(m2.coeffs.p).to.be.deep.almost(vector([0.48245, 0.00165, 0.14576]));
769 |
770 | // check predictions
771 | m2.fitted.length.should.be.equal(y.length);
772 | expect(m2.fitted).to.be.deep.almost(vector([10, 18, 32, 40, 54]));
773 |
774 | // check performance statistics
775 | m2.stat.DoF.should.be.equal(2);
776 | expect(m2.stat.se).to.be.almost(1.414);
777 | expect(m2.stat.R2).to.be.almost(0.9967);
778 | expect(m2.stat.R2adj).to.be.almost(0.9935);
779 | expect(m2.stat.Fstat).to.be.almost(305.2);
780 | expect(m2.stat.p).to.be.almost(0.003266);
781 |
782 | });
783 |
784 | it('tests for method "lmpredict".', function () {
785 |
786 | // vector - vector
787 | const X1 = vector([1, 2, 3, 4, 5]);
788 | const y1 = vector([10, 19, 31, 39, 55]);
789 | const m1 = lmfit(X1, y1);
790 |
791 | // errors
792 | expect(() => lmpredict({}, 2)).to.throw(Error, 'lmpredict: argument "X" must be a matrix or a vector.');
793 | expect(() => lmpredict({}, vector([2, 3]))).to.throw(Error, 'lmpredict: argument "m" must be object with "lm" model.');
794 | expect(() => lmpredict(m1, matrix([2, 3], 1, 2))).to.throw(Error, 'lmpredict: number of columns in "X" does not match number of coefficients in model.');
795 |
796 | const yp1 = lmpredict(m1, X1)
797 | yp1.should.be.eql(m1.fitted);
798 |
799 | // matrix - vector
800 | const X2 = matrix([1, 2, 3, 4, 5, 10, 20, 10, 20, 10], 5, 2);
801 | const y2 = vector([10, 19, 31, 39, 55]);
802 | const m2 = lmfit(X2, y2);
803 |
804 | const yp2 = lmpredict(m2, X2)
805 | yp2.should.be.eql(m2.fitted);
806 | });
807 |
808 | });
809 |
--------------------------------------------------------------------------------
/test/tests-prep.js:
--------------------------------------------------------------------------------
1 | /*****************************************************************
2 | * Tests for preprocessing methods *
3 | *****************************************************************/
4 |
5 | // import dependencies
6 | import { default as chai} from 'chai';
7 | import { default as chaiAlmost} from 'chai-almost';
8 | import { vector, ismatrix, Vector, Matrix } from '../src/arrays/index.js';
9 | import { sd, mean } from '../src/stat/index.js';
10 |
11 | // import methods to test
12 | import { scale, unscale } from '../src/prep/index.js';
13 |
14 |
15 | // set up test settings
16 | const expect = chai.expect;
17 | chai.use(chaiAlmost(0.0001));
18 |
19 | describe('Tests of preprocessing methods.', function () {
20 |
21 | it ('tests for method "scale" for matrices', function () {
22 |
23 | const X1 = Matrix.rand(500, 10, 0, 2);
24 | const mX1 = X1.apply(mean, 2);
25 | const sX1 = X1.apply(sd, 2);
26 |
27 | // short version
28 | const X1p11 = scale(X1);
29 | const X1p12 = scale(X1, true, false);
30 | const X1p13 = scale(X1, true, true);
31 | const X1p14 = scale(X1, false, true);
32 |
33 | expect(X1p11.v).to.be.deep.almost.equal(X1p12.v);
34 | expect(X1p12.apply(mean, 2)).to.be.deep.almost.equal(Vector.zeros(10));
35 | expect(X1p12.apply(sd, 2)).to.be.deep.almost.equal(sX1);
36 | expect(X1p13.apply(mean, 2)).to.be.deep.almost.equal(Vector.zeros(10));
37 | expect(X1p13.apply(sd, 2)).to.be.deep.almost.equal(Vector.ones(10));
38 | expect(X1p14.apply(mean, 2)).to.be.deep.almost.equal(mX1.divide(sX1));
39 | expect(X1p14.apply(sd, 2)).to.be.deep.almost.equal(Vector.ones(10));
40 |
41 | // full version
42 | const [X1p22, mp22, sp22] = scale(X1, true, false, true);
43 | const [X1p23, mp23, sp23] = scale(X1, true, true, true);
44 | const [X1p24, mp24, sp24] = scale(X1, false, true, true);
45 |
46 | expect(ismatrix(X1p22))
47 | expect(mp22).to.be.deep.almost.equal(mX1);
48 | expect(sp22).to.be.deep.almost.equal(Vector.ones(10));
49 | expect(mp23).to.be.deep.almost.equal(mX1);
50 | expect(sp23).to.be.deep.almost.equal(sX1);
51 | expect(mp24).to.be.deep.almost.equal(Vector.zeros(10));
52 | expect(sp24).to.be.deep.almost.equal(sX1);
53 | });
54 |
55 | it ('tests for method "unscale" for matrices', function () {
56 |
57 | const X1 = Matrix.rand(500, 5, 0, 2);
58 | const [X1s, mX1, sX1] = scale(X1, true, true, true);
59 | const X1u = unscale(X1s, mX1, sX1);
60 | expect(X1u.v).to.be.deep.almost.equal(X1.v);
61 | expect(X1s.v).to.be.deep.almost.not.equal(X1.v);
62 |
63 | const mX2 = vector([1, 2, 3, 4, 5]);
64 | const sX2 = vector([0.5, 0.4, 0.3, 0.2, 0.1]);
65 | const X2 = Matrix.rand(500, 5, 0, 2);
66 | const X2s = scale(X2, mX2, sX2);
67 | const X2u = unscale(X2s, mX2, sX2);
68 | expect(X2u.v).to.be.deep.almost.equal(X2.v);
69 | expect(X2s.v).to.be.deep.almost.not.equal(X2.v);
70 |
71 | });
72 | });
73 |
74 |
--------------------------------------------------------------------------------
/test/tests-speed.js:
--------------------------------------------------------------------------------
1 | /****************************************************************/
2 | /* Tests for array methods (Index/Vector/Matrix classes) */
3 | /****************************************************************/
4 |
5 |
6 | // Speed tests for operations with two 10.000 x 10.000 matrices:
7 | // ---------------------------------------------------------------
8 | // sum - done in: 0.152 s.
9 | // difference - done in: 0.964 s.
10 | // division - done in: 0.981 s.
11 | // multiplication - done in: 0.951 s.
12 | // transposition - done in: 0.331 s.
13 |
14 | // Speed tests for operations with two 2.000 x 2.000 matrices:
15 | // -------------------------------------------------------------
16 | // tcrossprod - done in: 7.808 s.
17 | // crossprod - done in: 7.336 s.
18 | // dot - done in: 6.76 s.
19 |
20 | // Speed tests for decomposition of 2000 x 2000 matrix:
21 | // ----------------------------------------------------
22 | // qr - done in: 30.673 s.
23 | // lu - done in: 4.524 s.
24 |
25 | // Speed tests for decomposition of 200 x 200 matrix:
26 | // ----------------------------------------------------
27 | // bidiag - done in: 1.535 s.
28 | // svd - done in: 290.863 s.
29 | // rsvd - done in: 0.979 s.
30 |
31 | // Speed tests for modelling with 500 x 1000 matrix:
32 | // ---------------------------------------------------
33 | // pcafit - done in: 4.111 s.
34 | // pcapredict - done in: 0.03 s.
35 | // pcrfit - done in: 4.219 s.
36 | // pcrpredict - done in: 0.029 s.
37 | // plsfit - done in: 1.373 s.
38 | // plspredict - done in: 0.026 s.
39 |
40 |
41 | // import methods
42 | import { Matrix, tcrossprod, crossprod } from '../src/arrays/index.js';
43 | import { bidiag, rsvd, svd, qr, lu } from '../src/decomp/index.js';
44 | import { pcafit, pcapredict, plsfit, plspredict, pcrfit, pcrpredict } from '../src/models/index.js';
45 |
46 | function measure(f, msg = '') {
47 | const start = Date.now();
48 | const out = f()
49 | console.log(msg + ' - done in: ' + (Date.now() - start) / 1000 + ' s.')
50 | return out;
51 | }
52 |
53 | const X1 = Matrix.rand(10000, 10000);
54 | const Y1 = Matrix.rand(10000, 10000);
55 | const X2 = Matrix.rand(2000, 2000);
56 | const Y2 = Matrix.rand(2000, 2000);
57 | const X3 = Matrix.rand(200, 200);
58 | const X4 = Matrix.rand(500, 1000);
59 | const Y4 = Matrix.rand(500, 1);
60 |
61 | console.log('\nSpeed tests for operations with two 10.000 x 10.000 matrices:');
62 | console.log('---------------------------------------------------------------');
63 | measure( () => X1.add(Y1), 'sum');
64 | measure( () => X1.subtract(Y1), 'difference');
65 | measure( () => X1.divide(Y1), 'division');
66 | measure( () => X1.mult(Y1), 'multiplication');
67 | measure( () => X1.t(), 'transposition');
68 |
69 |
70 | console.log('\nSpeed tests for operations with two 2.000 x 2.000 matrices:');
71 | console.log('-------------------------------------------------------------');
72 | measure( () => tcrossprod(X2, Y2), 'tcrossprod');
73 | measure( () => crossprod(X2, Y2), 'crossprod');
74 | measure( () => X2.dot(Y2), 'dot');
75 |
76 | console.log('\nSpeed tests for decomposition of 2000 x 2000 matrix:');
77 | console.log('----------------------------------------------------');
78 | measure( () => qr(X2), 'qr');
79 | measure( () => lu(X2), 'lu');
80 |
81 | console.log('\nSpeed tests for decomposition of 200 x 200 matrix:');
82 | console.log('----------------------------------------------------');
83 | measure( () => bidiag(X3), 'bidiag');
84 | measure( () => svd(X3, 20), 'svd');
85 | measure( () => rsvd(X3, 20), 'rsvd');
86 |
87 |
88 | console.log('\nSpeed tests for modelling with 500 x 1000 matrix:');
89 | console.log('---------------------------------------------------');
90 |
91 | const mpca = measure( () => pcafit(X4, 20), 'pcafit');
92 | measure( () => pcapredict(mpca, X4), 'pcapredict');
93 | const mpcr = measure( () => pcrfit(X4, Y4, 20), 'pcrfit');
94 | measure( () => pcrpredict(mpcr, X4), 'pcrpredict');
95 | const mpls = measure( () => plsfit(X4, Y4, 20), 'plsfit');
96 | measure( () => plspredict(mpls, X4, Y4), 'plspredict');
97 |
98 | console.log('');
99 |
--------------------------------------------------------------------------------
/test/tests-stat.js:
--------------------------------------------------------------------------------
1 | /****************************************************************/
2 | /* Tests for methods computing various statistics */
3 | /****************************************************************/
4 |
5 | // import dependencies
6 | import {default as chai} from 'chai';
7 | import {default as chaiAlmost} from 'chai-almost';
8 | import { isvector, vector, Vector } from '../src/arrays/index.js';
9 |
10 | // import methods to test
11 | import {
12 | norm2, quantile, count, mids, split, diff, ppoints, cumsum,
13 | cor, cov, skewness, kurtosis, rank, mean, sd, sum, prod, min, minind, max, maxind,
14 | median, iqr
15 | } from '../src/stat/index.js';
16 |
17 | // set up test settings
18 | const should = chai.should();
19 | const expect = chai.expect;
20 | chai.use(chaiAlmost(0.00001));
21 |
22 |
23 |
24 | describe('Tests of stat methods.', function () {
25 |
26 | // mix of negative and positive numbers - main dataset for tests
27 | const mn1 = -2.008; // min
28 | const mx1 = 2.001; // max
29 | const sm1 = -0.007; // sum
30 | const av1 = -0.001; // average/mean
31 | const sd1 = 1.733785; // std (/n-1)
32 | const sd1b = 1.605173; // std biased (/n)
33 |
34 |
35 | const x1 = vector([mx1, 2, 1, 0, -1, -2, mn1]);
36 |
37 | // only negative numbers
38 | const mx2 = -0.001;
39 | const mn2 = -2.003;
40 | const sm2 = -5.004;
41 | const av2 = -1.251;
42 | const x2 = vector([mx2, -1, -2, mn2]);
43 |
44 | // only positive numbers
45 | const mn3 = 0.001;
46 | const mx3 = 2.003;
47 | const sm3 = 5.004;
48 | const av3 = 1.251;
49 | const x3 = vector([mn3, 1, 2, mx3]);
50 |
51 | // vectors for percentiles, etc.
52 | const xp = vector([2.001, 2, 1, 0, -1, -2, -2.008]);
53 | const Q1 = -1.5; // 25% percentile for x1
54 | const Q2 = 0; // 50% percentile for x1
55 | const Q3 = 1.5; // 75% percentile for x1
56 | const P10 = -2.0032; // 10% percentile for x1
57 | const P90 = 2.0004; // 90% percentile for x1
58 |
59 | const xp1 = vector([-10, -2, 0, 2, 10, 20, 50, 100, 150]);
60 | const xp2 = vector([150, -2, 100, 2, 50, 0, 10, 20, -10]);
61 | const xp3 = vector([150, 100, 50, 20, 10, 2, 0, -2, -10]);
62 |
63 | // large random vector
64 | const yp = Vector.rand(1000000, 10, 20);
65 |
66 | it('tests for method "iqr".', function () {
67 | iqr(xp).should.be.a('number');
68 | iqr(xp).should.equal(quantile(xp, 0.75) - quantile(xp, 0.25));
69 | });
70 |
71 | it('tests for method "median".', function () {
72 | median(xp).should.be.a('number');
73 | median(xp).should.equal(quantile(xp, 0.5));
74 | median(yp).should.equal(quantile(yp, 0.5));
75 | });
76 |
77 | it('tests for method "norm2".', function () {
78 | const x1 = vector([1, 2, 3, 4]);
79 | const x2 = vector([1, -2, -3, 4]);
80 | const x3 = vector([0, 0, 0, 0]);
81 | const x4 = vector([1, 1]);
82 |
83 | expect(norm2(x1)).to.equal(Math.sqrt(30));
84 | expect(norm2(x2)).to.equal(Math.sqrt(30));
85 | expect(norm2(x3)).to.equal(0);
86 | expect(norm2(x4)).to.equal(Math.sqrt(2));
87 | });
88 |
89 | it('tests for method "quantile".', function () {
90 |
91 | expect(() => quantile(xp, -0.01)).to.throw(Error, "Parameter 'p' must be between 0 and 1 (both included).");
92 | expect(() => quantile(xp, 1.00001)).to.throw(Error, "Parameter 'p' must be between 0 and 1 (both included).");
93 |
94 | quantile(xp, 0.01).should.be.a('number');
95 | quantile(xp, 0).should.equal(min(xp));
96 | quantile(xp, 1).should.equal(max(xp));
97 |
98 | quantile(xp, 0.25).should.be.closeTo(Q1, 0.000001);
99 | quantile(xp, 0.50).should.be.closeTo(Q2, 0.000001);
100 | quantile(xp, 0.75).should.be.closeTo(Q3, 0.000001);
101 | quantile(xp, 0.10).should.be.closeTo(P10, 0.000001);
102 | quantile(xp, 0.90).should.be.closeTo(P90, 0.000001);
103 |
104 | expect(() => quantile(xp, [0.2, 0.4, -0.01])).to.throw(Error, "Parameter 'p' must be between 0 and 1 (both included).");
105 | expect(() => quantile(xp, [0.2, 1.00001, 0.4])).to.throw(Error, "Parameter 'p' must be between 0 and 1 (both included).");
106 |
107 | const p1 = quantile(xp, [0.25, 0.50, 0.75, 0.10, 0.90]);
108 | expect(p1).to.deep.equal(vector([Q1, Q2, Q3, P10, P90]));
109 |
110 | const p2 = quantile(yp, [0.25, 0.50, 0.75]);
111 | p2.v[0].should.be.closeTo(12.5, 0.1);
112 | p2.v[1].should.be.closeTo(15.0, 0.1);
113 | p2.v[2].should.be.closeTo(17.5, 0.1);
114 |
115 | });
116 |
117 | it('tests for method "count".', function () {
118 |
119 | const bins1 = [-20, 0, 200];
120 | const c1 = count(xp1, bins1);
121 | isvector(c1).should.be.true;
122 | c1.v.should.have.lengthOf(2);
123 | c1.v[0].should.equal(2);
124 | c1.v[1].should.equal(7);
125 |
126 | const bins2 = [-20, 0, 50, 200];
127 | const c2 = count(xp1, bins2);
128 | isvector(c2).should.be.true;
129 | c2.v.should.have.lengthOf(3);
130 | c2.v[0].should.equal(2);
131 | c2.v[1].should.equal(4);
132 | c2.v[2].should.equal(3);
133 |
134 | const bins = [10, 12, 14, 16, 18, 20];
135 | const c3 = count(yp, bins);
136 | isvector(c3).should.be.true;
137 | c3.v.should.have.lengthOf(5);
138 | c3.v[0].should.be.closeTo(200000, 2000);
139 | c3.v[1].should.be.closeTo(200000, 2000);
140 | c3.v[2].should.be.closeTo(200000, 2000);
141 | c3.v[3].should.be.closeTo(200000, 2000);
142 | c3.v[4].should.be.closeTo(200000, 2000);
143 |
144 | });
145 |
146 | it ('tests for method "mids".', function () {
147 | const m1 = mids(xp1);
148 | isvector(m1).should.be.true;
149 | m1.should.have.lengthOf(xp1.length - 1);
150 | m1.v[0].should.equal(0.5 * xp1.v[0] + 0.5 * xp1.v[1]);
151 | m1.v[1].should.equal(0.5 * xp1.v[1] + 0.5 * xp1.v[2]);
152 | m1.v[7].should.equal(0.5 * xp1.v[7] + 0.5 * xp1.v[8]);
153 | });
154 |
155 | it ('tests for method "split".', function () {
156 | const s1 = split(xp1, 2);
157 | isvector(s1).should.be.true;
158 | s1.v.should.have.lengthOf(3);
159 | s1.v[0].should.equal(min(xp1));
160 | s1.v[1].should.equal(min(xp1) + (max(xp1) - min(xp1)) / 2);
161 | s1.v[2].should.equal(max(xp1));
162 |
163 | const s2 = split(xp1, 4);
164 | isvector(s2).should.be.true;
165 | s2.v.should.have.lengthOf(5);
166 | s2.v[0].should.equal(min(xp1));
167 | s2.v[1].should.equal(min(xp1) + (max(xp1) - min(xp1)) * 0.25);
168 | s2.v[2].should.equal(min(xp1) + (max(xp1) - min(xp1)) * 0.50);
169 | s2.v[3].should.equal(min(xp1) + (max(xp1) - min(xp1)) * 0.75);
170 | s2.v[4].should.equal(max(xp1));
171 |
172 | const s3 = split(yp, 2);
173 | isvector(s1).should.be.true;
174 | s3.v.should.have.lengthOf(3);
175 | s3.v[0].should.equal(min(yp));
176 | s3.v[1].should.equal(min(yp) + (max(yp) - min(yp)) / 2);
177 | s3.v[2].should.equal(max(yp));
178 |
179 | const s4 = split(yp, 100);
180 | isvector(s4).should.be.true;
181 | s4.v.should.have.lengthOf(101);
182 |
183 | });
184 |
185 | it ('tests for method "diff".', function () {
186 | const d1 = diff(x1);
187 |
188 | isvector(d1).should.be.true;
189 | d1.v.should.have.lengthOf(x1.length - 1);
190 | d1.v[0].should.equal(x1.v[1] - x1.v[0]);
191 | d1.v[1].should.equal(x1.v[2] - x1.v[1]);
192 | d1.v[5].should.equal(x1.v[6] - x1.v[5]);
193 | });
194 |
195 | it ('tests for method "ppoints".', function () {
196 |
197 | const p1 = ppoints(1);
198 |
199 | expect(p1.v).to.have.lengthOf(1);
200 | p1.v[0].should.be.closeTo(0.5, 0.0000001);
201 |
202 | const p2 = ppoints(3);
203 | expect(p2.v).to.have.lengthOf(3);
204 | p2.v[0].should.be.closeTo(0.1923077, 0.0000001);
205 | p2.v[1].should.be.closeTo(0.5000000, 0.0000001);
206 | p2.v[2].should.be.closeTo(0.8076923, 0.0000001);
207 |
208 | const p3 = ppoints(5);
209 | expect(p3.v).to.have.lengthOf(5);
210 | p3.v[0].should.be.closeTo(0.1190476, 0.0000001);
211 | p3.v[1].should.be.closeTo(0.3095238, 0.0000001);
212 | p3.v[2].should.be.closeTo(0.5000000, 0.0000001);
213 | p3.v[3].should.be.closeTo(0.6904762, 0.0000001);
214 | p3.v[4].should.be.closeTo(0.8809524, 0.0000001);
215 |
216 | const p4 = ppoints(11);
217 | expect(p4.v).to.have.lengthOf(11);
218 | p4.v[ 0].should.be.closeTo(0.04545455, 0.0000001);
219 | p4.v[ 3].should.be.closeTo(0.31818182, 0.0000001);
220 | p4.v[ 5].should.be.closeTo(0.50000000, 0.0000001);
221 | p4.v[ 7].should.be.closeTo(0.68181818, 0.0000001);
222 | p4.v[10].should.be.closeTo(0.95454545, 0.0000001);
223 | });
224 |
225 | it ('tests for method "cumsum".', function () {
226 | const x1 = vector([1, 10, 20, 100, 300]);
227 | const x2 = vector([1, -10, 20, -100, 300]);
228 |
229 | expect(cumsum(x1)).to.be.deep.equal(vector([1, 11, 31, 131, 431]));
230 | expect(cumsum(x2)).to.be.deep.equal(vector([1, -9, 11, -89, 211]));
231 | });
232 |
233 | it ('tests for method "rank".', function () {
234 | // i: 1 2 3 4 5 6 7 8
235 | // sorted: -3 0 1 2 3 4 8 9
236 | // rank: 5 1 2 6 3 4 8 7
237 | const x1 = vector([3, -3, 0, 4, 1, 2, 9, 8]);
238 | expect(rank(x1)).to.eql(vector([5, 1, 2, 6, 3, 4, 8, 7]));
239 |
240 | // i: 1 2 3 4 5 6 7
241 | // sorted: -2 -2 -2 1 3 10 11
242 | // rank: 1 5 1 4 6 7 1
243 | const x2 = vector([-2, 3, -2, 1, 10, 11, -2]);
244 | expect(rank(x2)).to.eql(vector([1, 5, 1, 4, 6, 7, 1]));
245 | });
246 |
247 | it ('tests for methods "minind" and "min"', function () {
248 |
249 | // normal cases
250 | const x1l = vector([10, 1, 2, 3, 100, 0, 5, 500, 200]);
251 | const x2l = vector([-30, -10, -2, -1000, -100, -200]);
252 | const x3l = vector([0, 1, 2, 3, 100, 10, 5, 500, 200]);
253 | const x4l = vector([-1000, -10, -20, -100, -200]);
254 | const x5l = vector([10, 11, 2, 3, 100, 10, 5, 500, 1]);
255 | const x6l = vector([-10, -15, -20, -100, -200, -1000]);
256 |
257 | expect(minind(x1l)).equal(6)
258 | expect(minind(x2l)).equal(4)
259 | expect(minind(x3l)).equal(1)
260 | expect(minind(x4l)).equal(1)
261 | expect(minind(x5l)).equal(9)
262 | expect(minind(x6l)).equal(6)
263 |
264 | expect(min(x1l)).equal(0)
265 | expect(min(x2l)).equal(-1000)
266 | expect(min(x3l)).equal(0)
267 | expect(min(x4l)).equal(-1000)
268 | expect(min(x5l)).equal(1)
269 | expect(min(x6l)).equal(-1000)
270 |
271 | // large vector case
272 | const x7l = Vector.rand(1000000);
273 | x7l.v[99999] = -0.01;
274 | expect(minind(x7l)).equal(100000);
275 | expect(min(x7l)).almost.equal(-0.01);
276 |
277 | // repeated min values - return first index
278 | const x8l = [-1, 3, -5, 10, -11, 3, -2, -11, 3, 2, -11, -10, 1];
279 | expect(minind(x8l)).equal(5);
280 | expect(min(x8l)).almost.equal(-11);
281 |
282 | });
283 |
284 | it ('tests for methods "maxind" and "max"', function () {
285 |
286 | // normal cases
287 | const x1 = vector([10, 1, 2, 3, 100, 0, 5, 500, 200]);
288 | const x2 = vector([-30, -10, -2, -1, -100, -200]);
289 | const x3 = vector([1000, 1, 2, 3, 100, 10, 5, 500, 200]);
290 | const x4 = vector([-1, -10, -20, -100, -200]);
291 | const x5 = vector([0, 1, 2, 3, 100, 10, -5, 500, 2000]);
292 | const x6 = vector([-10, -15, -20, -100, -200, -0.00001]);
293 |
294 | expect(maxind(x1)).equal(8)
295 | expect(maxind(x2)).equal(4)
296 | expect(maxind(x3)).equal(1)
297 | expect(maxind(x4)).equal(1)
298 | expect(maxind(x5)).equal(9)
299 | expect(maxind(x6)).equal(6)
300 |
301 | expect(max(x1)).equal(500)
302 | expect(max(x2)).equal(-1)
303 | expect(max(x3)).equal(1000)
304 | expect(max(x4)).equal(-1)
305 | expect(max(x5)).equal(2000)
306 | expect(max(x6)).equal(-0.00001)
307 |
308 |
309 | // large vector case
310 | const x7 = Vector.rand(1000000);
311 | x7.v[99999] = 1.0;
312 | expect(maxind(x7.v)).equal(100000);
313 | expect(max(x7.v)).equal(1.0);
314 |
315 | // repeated min values - return first index
316 | const x8 = [-1, 3, -5, 10, 11, 3, -2, 11, 3, 2, 11, -10, 1];
317 | expect(maxind(x8)).equal(5);
318 | expect(max(x8)).almost.equal(11);
319 |
320 | });
321 |
322 | it ('tests for methods "min" and "max" (additional).', function () {
323 | min(x1).should.be.a('number');
324 | min(x1).should.equal(mn1);
325 | min(x2).should.equal(mn2);
326 | min(x3).should.equal(mn3);
327 |
328 | max(x1).should.be.a('number');
329 | max(x1).should.equal(mx1);
330 | max(x2).should.equal(mx2);
331 | max(x3).should.equal(mx3);
332 | });
333 |
334 | it ('tests for methods "sum" and "prod".', function () {
335 | sum(x1).should.be.a('number');
336 | sum(x1).should.be.closeTo(sm1, 0.0000001);
337 | sum(x2).should.be.closeTo(sm2, 0.0000001);
338 | sum(x3).should.be.closeTo(sm3, 0.0000001);
339 |
340 | prod(vector([1, 2, 3, 4, 5])).should.be.a('number');
341 | prod(vector([1, 2, 3, 4, 5])).should.be.closeTo(120, 0.0000000001);
342 | prod(vector([10])).should.be.a('number');
343 | prod(vector([10])).should.be.closeTo(10, 0.0000000001);
344 | });
345 |
346 | it ('tests for method "mean".', function () {
347 | mean(x1).should.be.a('number');
348 | mean(x1).should.be.closeTo(av1, 0.0000001);
349 | mean(x2).should.be.closeTo(av2, 0.0000001);
350 | mean(x3).should.be.closeTo(av3, 0.0000001);
351 | });
352 |
353 | it ('tests for method "sd".', function () {
354 | sd(x1).should.be.a('number');
355 | sd(x1).should.be.closeTo(sd1, 0.000001);
356 | sd(x1, true).should.be.closeTo(sd1b, 0.000001);
357 | sd(x1, false, mean(x1)).should.be.closeTo(sd1, 0.000001);
358 | });
359 |
360 | it ('tests for method "skewness".', function() {
361 | skewness([0, 1, 2]).should.be.closeTo(0, 0.0000001);
362 | skewness([-10, 1, 2]).should.be.closeTo(-0.6892055, 0.0000001);
363 | skewness([10, 1, 2]).should.be.closeTo(0.6745555, 0.0000001);
364 | });
365 |
366 | it ('tests for method "kurtosis".', function() {
367 | kurtosis([1, 2, 3]).should.be.closeTo(1.5, 0.0000001);
368 | });
369 |
370 | it ('tests for method "cov".', function() {
371 | expect(() => cov(vector([1, 2]), vector([1, 2, 3])).to.throw(Error, "Vectors 'x' and 'y' must have the same length."));
372 | expect(() => cov(vector([1]), vector([1])).to.throw(Error, "Vectors 'x' and 'y' must have at least two values."));
373 |
374 | cov(vector([1, 2, 3]), vector([1, 2, 3])).should.equal(1);
375 | cov(vector([1, 2, 3]), vector([3, 2, 1])).should.equal(-1);
376 | cov(vector([1, 2, 3]), vector([10, 20, 30])).should.equal(10);
377 | cov(vector([1, 2, 3]), vector([30, 20, 10])).should.equal(-10);
378 | cov(vector([1, 2, 1, 2]), vector([10, 10, 20, 20])).should.equal(0);
379 | });
380 |
381 | it ('tests for method "cor".', function() {
382 | cor(vector([1, 2, 3]), vector([1, 2, 3])).should.equal(1);
383 | cor(vector([1, 2, 3]), vector([3, 2, 1])).should.equal(-1);
384 | cor(vector([1, 2, 3]), vector([10, 20, 30])).should.equal(1);
385 | cor(vector([1, 2, 3]), vector([30, 20, 10])).should.equal(-1);
386 | cor(vector([1, 2, 1, 2]), vector([10, 10, 20, 20])).should.equal(0);
387 | });
388 |
389 | });
390 |
391 |
--------------------------------------------------------------------------------
/test/tests-temp.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | // import dependencies
4 | import {default as chai} from 'chai';
5 | import {default as chaiAlmost} from 'chai-almost';
6 | import { quantile } from 'mdatools/stat';
7 | import { crossprod, tcrossprod, reshape, isvector, vector, Vector,
8 | ismatrix, matrix, Matrix, Index } from '../src/arrays/index.js';
9 |
10 | // import methods to test
11 | import { rsvd, qr, lu, svd, rot, bidiag, householder } from '../src/decomp/index.js';
12 | import { pchisq, qchisq, gamma, igamma } from '../src/distributions/index.js';
13 |
14 | // set up test settings
15 | const expect = chai.expect;
16 | chai.use(chaiAlmost(0.0001));
17 |
18 | const ZERO = Math.pow(10.0, -6);
19 |
20 | describe('Temporary tests.', function () {
21 |
22 | it ('tests for method "bidiag"', function () {
23 | }).timeout(20000);
24 |
25 |
26 |
27 | });
--------------------------------------------------------------------------------
/test/tests-tests.js:
--------------------------------------------------------------------------------
1 | // /******************************************************************
2 | // * Tests for functions for hypothesis testing *
3 | // ******************************************************************/
4 |
5 | // import dependencies
6 | import {default as chai} from 'chai';
7 | import {pnorm} from '../src/distributions/index.js';
8 | import {vector} from '../src/arrays/index.js';
9 |
10 | // import of functions to test
11 | import {getpvalue, ttest, ttest2} from '../src/tests/index.js';
12 |
13 | const should = chai.should();
14 | const expect = chai.expect;
15 |
16 | describe('Tests of methods for hypothesis testing.', function () {
17 |
18 | it('tests for method "getpvalue".', function () {
19 |
20 | // left tail
21 | getpvalue(pnorm, 0, "left").should.be.closeTo(0.5, 0.00001);
22 | getpvalue(pnorm, 10, "left", [10, 1]).should.be.closeTo(0.5, 0.00001);
23 | getpvalue(pnorm, 8.04, "left", [10, 1]).should.be.closeTo(0.025, 0.00001);
24 | getpvalue(pnorm, 11.96, "left", [10, 1]).should.be.closeTo(0.975, 0.00001);
25 |
26 | // right tail
27 | getpvalue(pnorm, 0, "right").should.be.closeTo(0.5, 0.00001);
28 | getpvalue(pnorm, 10, "right", [10, 1]).should.be.closeTo(0.5, 0.00001);
29 | getpvalue(pnorm, 8.04, "right", [10, 1]).should.be.closeTo(0.975, 0.00001);
30 | getpvalue(pnorm, 11.96, "right", [10, 1]).should.be.closeTo(0.025, 0.00001);
31 |
32 | // both tails
33 | getpvalue(pnorm, 0, "both").should.be.closeTo(1.0, 0.00001);
34 | getpvalue(pnorm, 10, "both", [10, 1]).should.be.closeTo(1.0, 0.00001);
35 | getpvalue(pnorm, 8.04, "both", [10, 1]).should.be.closeTo(0.05, 0.00001);
36 | getpvalue(pnorm, 11.96, "both", [10, 1]).should.be.closeTo(0.05, 0.00001);
37 | });
38 |
39 | it('tests for method "ttest".', function () {
40 |
41 | let res = ttest(vector([-2, -1, 0, 1, 2]));
42 |
43 | // exact numbers
44 | res.effectObserved.should.be.equal(0);
45 | res.alpha.should.be.equal(0.05);
46 | res.tail.should.be.equal("both");
47 | res.DoF.should.be.equal(4);
48 |
49 | // estimated numbers
50 | res.se.should.be.closeTo(0.7071068, 0.00001);
51 | res.tValue.should.be.closeTo(0, 0.00001);
52 | res.pValue.should.be.closeTo(1, 0.00001);
53 | res.ci[0].should.be.closeTo(-1.963243, 0.00001);
54 | res.ci[1].should.be.closeTo(1.963243, 0.00001);
55 |
56 | // same but for left tail
57 | res = ttest(vector([-2, -1, 0, 1, 2]), 0, 0.05, "left");
58 |
59 | // exact numbers
60 | res.effectObserved.should.be.equal(0);
61 | res.alpha.should.be.equal(0.05);
62 | res.tail.should.be.equal("left");
63 | res.DoF.should.be.equal(4);
64 |
65 | // estimated numbers
66 | res.se.should.be.closeTo(0.7071068, 0.00001);
67 | res.tValue.should.be.closeTo(0, 0.00001);
68 | res.pValue.should.be.closeTo(0.5, 0.00001);
69 | res.ci[0].should.be.closeTo(-1.963243, 0.00001);
70 | res.ci[1].should.be.closeTo(1.963243, 0.00001);
71 |
72 | // same but for right tail
73 | res = ttest(vector([-2, -1, 0, 1, 2]), 0, 0.05, "right");
74 |
75 | // exact numbers
76 | res.effectObserved.should.be.equal(0);
77 | res.alpha.should.be.equal(0.05);
78 | res.tail.should.be.equal("right");
79 | res.DoF.should.be.equal(4);
80 |
81 | // estimated numbers
82 | res.se.should.be.closeTo(0.7071068, 0.00001);
83 | res.tValue.should.be.closeTo(0, 0.00001);
84 | res.pValue.should.be.closeTo(0.5, 0.00001);
85 | res.ci[0].should.be.closeTo(-1.963243, 0.00001);
86 | res.ci[1].should.be.closeTo(1.963243, 0.00001);
87 |
88 | // changed mu and alpha - both tails
89 | res = ttest(vector([-3, -2, -1, 0, 1, 2]), 2, 0.01, "both");
90 |
91 | // exact numbers
92 | res.effectExpected.should.be.equal(2.0);
93 | res.effectObserved.should.be.equal(-0.5);
94 | res.alpha.should.be.equal(0.01);
95 | res.tail.should.be.equal("both");
96 | res.DoF.should.be.equal(5);
97 |
98 | // estimated numbers
99 | res.se.should.be.closeTo(0.7637626, 0.0001);
100 | res.tValue.should.be.closeTo(-3.2733, 0.0001);
101 | res.pValue.should.be.closeTo( 0.02212, 0.0001);
102 | res.ci[0].should.be.closeTo(-3.5796, 0.0001);
103 | res.ci[1].should.be.closeTo( 2.5796, 0.0001);
104 |
105 |
106 | // changed mu and alpha - left tail
107 | res = ttest(vector([-3, -2, -1, 0, 1, 2]), 2, 0.01, "left");
108 |
109 | // exact numbers
110 | res.effectExpected.should.be.equal(2.0);
111 | res.effectObserved.should.be.equal(-0.5);
112 | res.alpha.should.be.equal(0.01);
113 | res.tail.should.be.equal("left");
114 | res.DoF.should.be.equal(5);
115 |
116 | // estimated numbers
117 | res.se.should.be.closeTo(0.7637626, 0.0001);
118 | res.tValue.should.be.closeTo(-3.2733, 0.0001);
119 | res.pValue.should.be.closeTo( 0.01106, 0.0001);
120 | res.ci[0].should.be.closeTo(-3.5796, 0.0001);
121 | res.ci[1].should.be.closeTo( 2.5796, 0.0001);
122 |
123 | // changed mu and alpha - right tail
124 | // changed mu and alpha - left tail
125 | res = ttest(vector([-3, -2, -1, 0, 1, 2]), 2, 0.01, "right");
126 |
127 | // exact numbers
128 | res.effectExpected.should.be.equal(2.0);
129 | res.effectObserved.should.be.equal(-0.5);
130 | res.alpha.should.be.equal(0.01);
131 | res.tail.should.be.equal("right");
132 | res.DoF.should.be.equal(5);
133 |
134 | // estimated numbers
135 | res.se.should.be.closeTo(0.7637626, 0.0001);
136 | res.tValue.should.be.closeTo(-3.2733, 0.0001);
137 | res.pValue.should.be.closeTo( 0.9889, 0.0001);
138 | res.ci[0].should.be.closeTo(-3.5796, 0.0001);
139 | res.ci[1].should.be.closeTo( 2.5796, 0.0001);
140 | });
141 |
142 | it('tests for method "ttest2".', function () {
143 | let res = ttest2(vector([-2, -1, 0, 1, 2]), vector([-3, -2, -1, 0, 1]));
144 |
145 | // exact numbers
146 | res.effectObserved.should.be.equal(1);
147 | res.alpha.should.be.equal(0.05);
148 | res.tail.should.be.equal("both");
149 | res.DoF.should.be.equal(8);
150 |
151 | // estimated numbers
152 | res.se.should.be.closeTo(1, 0.00001);
153 | res.tValue.should.be.closeTo(1, 0.00001);
154 | res.pValue.should.be.closeTo(0.3466, 0.0001);
155 | res.ci[0].should.be.closeTo(-1.306004, 0.00001);
156 | res.ci[1].should.be.closeTo(3.306004, 0.00001);
157 |
158 | res = ttest2(vector([-2, -1, 0, 1, 2]), vector([-3, -2, -1, 0, 1]), 0.05, "left");
159 |
160 | // exact numbers
161 | res.effectObserved.should.be.equal(1);
162 | res.alpha.should.be.equal(0.05);
163 | res.tail.should.be.equal("left");
164 | res.DoF.should.be.equal(8);
165 |
166 | // estimated numbers
167 | res.se.should.be.closeTo(1, 0.00001);
168 | res.tValue.should.be.closeTo(1, 0.00001);
169 | res.pValue.should.be.closeTo(0.8267, 0.0001);
170 | res.ci[0].should.be.closeTo(-1.306004, 0.00001);
171 | res.ci[1].should.be.closeTo(3.306004, 0.00001);
172 |
173 | res = ttest2(vector([-2, -1, 0, 1, 2]), vector([-3, -2, -1, 0, 1]), 0.05, "right");
174 |
175 | // exact numbers
176 | res.effectObserved.should.be.equal(1);
177 | res.alpha.should.be.equal(0.05);
178 | res.tail.should.be.equal("right");
179 | res.DoF.should.be.equal(8);
180 |
181 | // estimated numbers
182 | res.se.should.be.closeTo(1, 0.00001);
183 | res.tValue.should.be.closeTo(1, 0.00001);
184 | res.pValue.should.be.closeTo(0.1733, 0.0001);
185 | res.ci[0].should.be.closeTo(-1.306004, 0.00001);
186 | res.ci[1].should.be.closeTo(3.306004, 0.00001);
187 |
188 | res = ttest2(vector([-2, -1, 0, 1, 2]), vector([-3, -2, -1, 0, 1]), 0.01);
189 |
190 | // exact numbers
191 | res.effectObserved.should.be.equal(1);
192 | res.alpha.should.be.equal(0.01);
193 | res.tail.should.be.equal("both");
194 | res.DoF.should.be.equal(8);
195 |
196 | // estimated numbers
197 | res.se.should.be.closeTo(1, 0.00001);
198 | res.tValue.should.be.closeTo(1, 0.00001);
199 | res.pValue.should.be.closeTo(0.3466, 0.0001);
200 | res.ci[0].should.be.closeTo(-2.355387, 0.00001);
201 | res.ci[1].should.be.closeTo(4.355387, 0.00001);
202 | });
203 |
204 | });
205 |
206 |
--------------------------------------------------------------------------------