PsychLib is a TypeScript library for math, statistics, and data analysis. Featured in psychological and educational research.
- PsychLib can be used in all modern JavaScript/TypeScript environments, including browsers, Node.js, Deno, and Bun.
- For use cases, please refer to my another project PsychPen.
- All functions have been tested for consistency with R's psych package and other JavaScript/R statistical libraries.
For full documentation, see https://jsr.io/@psych/lib/doc.
npx jsr add @psych/lib # if using npm
bunx jsr add @psych/lib # if using bun
deno add jsr:@psych/lib # if using deno
pnpm dlx jsr add @psych/lib # if using pnpm
yarn dlx jsr add @psych/lib # if using yarnimport { randomNormal, WelchTTest } from '@psych/lib'
const groupA = new Array(500).fill(0).map(() => randomNormal(10, 2))
const groupB = new Array(500).fill(0).map(() => randomNormal(11, 2))
const tTest = new WelchTTest(groupA, groupB)
console.log(tTest.t, tTest.df, tTest.p) // Maybe -6.5..., 997.4..., 0.0...For full documentation, see https://jsr.io/@psych/lib/doc.
If you haven't installed deno yet, please install it referring to the https://deno.com. Then, clone this repository.
git clone https://github.com/LeafYeeXYZ/PsychLib.gitNow you can write TypeScript code in /lib/**/*.ts and export functions in /lib/index.ts. Note that you should not import base functions from /lib/index.ts to avoid circular dependencies. Instead, you can import them directly from /lib/base.ts or /lib/xxx/index.ts.
After writing the code, remember to add test cases in /tests/*.test.ts. You can run the test cases using the following command.
deno task r:install # only once
deno task r:server
deno task testYou can also add benchmark cases in /benchs/*.bench.ts and run the benchmark using the following command.
deno task benchThis project publishes to https://jsr.io, so you don't need to compile the code to JavaScript. And you also don't need to publish the package manually. Just modify deno.json and push the code to the repository. The GitHub Action will do the rest for you.
| PsychLib Function | Testing Method | Passed | Precision | Note |
|---|---|---|---|---|
| Basic Functions | JS:simple-statistics |
🟩 | 1e-6 |
|
Matrix |
JS:ml-matrix |
🟩 | 1e-6 |
|
sort |
JS:Array.prototype.sort |
🟩 | 0 |
|
OneSampleTTest |
R:psych |
🟩 | 1e-4 |
|
TwoSampleTTest |
R:psych |
🟩 | 1e-4 |
|
PeerSampleTTest |
R:psych |
🟩 | 1e-4 |
|
WelchTTest |
R:psych |
🟩 | 1e-4 |
|
AlphaRealiability |
R:psych |
🟩 | 1e-4 |
|
LeveneTest |
R:car |
🟩 | 1e-4 |
|
OneSampleKSTest |
R:stats |
🟨 | 1e-4 |
the precision for p when n < 100 is worse than 1e-4 |
z2p & p2z |
R:stats |
🟩 | 1e-4 |
|
t2p & p2t |
R:stats |
🟩 | 1e-4 |
|
f2p & p2f |
R:stats |
🟩 | 1e-4 |
|
c2p & p2c |
R:stats |
🟩 | 1e-4 |
|
PearsonCorrTest |
R:stats |
🟩 | 1e-4 |
|
OneWayAnova |
R:stats |
🟩 | 1e-4 |
|
PeerAnova |
R:stats |
🟩 | 1e-4 |
|
| ANOVA Post Hoc | see tests/anova.test.ts |
🟥 WIP | ||
| Regression | See tests/regression.test.ts |
🟥 WIP | ||
| Mediation | R:lavaan |
🟩 | 1e-4 |
p value is not tested |
I'm working on migrating all tests to
R, you can see the progress above.
CPU | Apple M3
Runtime | Deno 2.4.1 (aarch64-apple-darwin)
File | /benchs/base.bench.ts
| benchmark | time/iter (avg) | iter/s | (min … max) | p75 | p99 | p995 |
| ------------------------------------- | --------------- | ------------- | --------------------- | -------- | -------- | -------- |
| sum() - n=1000 | 1.0 µs | 979,400 | (583.0 ns … 53.5 µs) | 1.3 µs | 1.6 µs | 1.6 µs |
| mean() - n=1000 | 1.3 µs | 748,500 | (625.0 ns … 34.0 µs) | 1.4 µs | 1.6 µs | 1.6 µs |
| max() - n=1000 | 442.0 ns | 2,262,000 | (333.0 ns … 39.5 µs) | 458.0 ns | 708.0 ns | 709.0 ns |
| min() - n=1000 | 1.1 µs | 917,400 | (375.0 ns … 39.4 µs) | 1.2 µs | 1.5 µs | 1.5 µs |
| median() - n=1000 | 41.9 µs | 23,880 | ( 36.8 µs … 92.1 µs) | 42.4 µs | 49.5 µs | 51.6 µs |
| mode() - n=1000 | 82.2 µs | 12,160 | ( 72.2 µs … 169.5 µs) | 82.2 µs | 123.6 µs | 135.0 µs |
| quantile() - n=1000 | 41.9 µs | 23,890 | ( 36.9 µs … 77.1 µs) | 42.5 µs | 49.5 µs | 51.2 µs |
| range() - n=1000 | 1.4 µs | 697,400 | (834.0 ns … 31.0 µs) | 1.5 µs | 1.8 µs | 1.8 µs |
| vari() - n=1000 | 3.0 µs | 332,600 | ( 1.4 µs … 62.5 µs) | 3.0 µs | 3.6 µs | 3.8 µs |
| std() - n=1000 | 1.4 µs | 709,200 | ( 1.2 µs … 30.0 µs) | 1.4 µs | 1.8 µs | 3.0 µs |
| cov() - n=1000 | 2.1 µs | 473,900 | ( 1.9 µs … 16.1 µs) | 2.1 µs | 2.6 µs | 2.7 µs |
| corr() - n=1000 | 6.8 µs | 147,000 | ( 3.4 µs … 97.5 µs) | 7.3 µs | 8.2 µs | 8.9 µs |
| kurtosis() - n=1000 | 13.3 µs | 74,960 | ( 11.7 µs … 4.7 ms) | 12.6 µs | 30.0 µs | 30.1 µs |
| skewness() - n=1000 | 12.5 µs | 79,780 | ( 11.7 µs … 76.9 µs) | 12.5 µs | 15.4 µs | 18.4 µs |
| ss() - n=1000 | 2.0 µs | 506,100 | ( 1.3 µs … 22.4 µs) | 2.9 µs | 3.0 µs | 3.2 µs |
| ssDiff() - n=1000 | 1.9 µs | 525,200 | (708.0 ns … 49.3 µs) | 2.1 µs | 2.3 µs | 2.5 µs |
| sem() - n=1000 | 1.4 µs | 702,700 | ( 1.3 µs … 9.9 µs) | 1.5 µs | 1.8 µs | 1.8 µs |
| array.sort() - n=1000 | 260.5 µs | 3,839 | (243.0 µs … 1.0 ms) | 262.5 µs | 314.6 µs | 334.9 µs |
| array.toSorted() - n=1000 | 127.6 µs | 7,839 | (118.6 µs … 551.9 µs) | 126.6 µs | 164.4 µs | 217.9 µs |
| sort() - quickSort (iter) - n=1000 | 45.6 µs | 21,910 | ( 40.5 µs … 261.1 µs) | 45.9 µs | 54.0 µs | 56.0 µs |
| sort() - quickSort (recur) - n=1000 | 50.3 µs | 19,870 | ( 44.5 µs … 160.5 µs) | 50.8 µs | 55.7 µs | 57.7 µs |
| sort() - mergeSort - n=1000 | 73.0 µs | 13,690 | ( 70.8 µs … 477.7 µs) | 72.9 µs | 80.8 µs | 86.9 µs |
| sort() - heapSort - n=1000 | 91.6 µs | 10,910 | ( 88.2 µs … 287.5 µs) | 91.3 µs | 106.0 µs | 112.3 µs |
| z2p() | 4.2 ns | 239,700,000 | ( 3.7 ns … 1.1 µs) | 3.9 ns | 9.0 ns | 9.9 ns |
| p2z() | 14.6 ns | 68,410,000 | ( 13.8 ns … 31.4 ns) | 14.6 ns | 19.5 ns | 22.0 ns |
| t2p() - df=30 | 116.0 ns | 8,623,000 | (112.8 ns … 139.1 ns) | 116.3 ns | 127.3 ns | 128.8 ns |
| p2t() - df=30 | 556.9 ns | 1,796,000 | (550.7 ns … 584.7 ns) | 558.3 ns | 575.0 ns | 584.7 ns |
| f2p() - df=5,30 | 93.7 ns | 10,680,000 | ( 92.6 ns … 106.8 ns) | 93.7 ns | 100.3 ns | 101.7 ns |
| p2f() - df=5,30 | 565.3 ns | 1,769,000 | (550.6 ns … 593.1 ns) | 568.1 ns | 593.1 ns | 593.1 ns |
| c2p() - df=5 | 46.4 ns | 21,560,000 | ( 45.7 ns … 59.1 ns) | 46.5 ns | 48.3 ns | 50.0 ns |
| p2c() - df=5 | 345.1 ns | 2,898,000 | (325.0 ns … 361.2 ns) | 347.4 ns | 357.4 ns | 361.2 ns |
| randomNormal() | 15.6 ns | 64,020,000 | ( 14.5 ns … 29.1 ns) | 15.7 ns | 18.4 ns | 20.0 ns |
| randomT() - df=30 | 73.1 ns | 13,690,000 | ( 70.5 ns … 87.6 ns) | 73.6 ns | 80.2 ns | 82.8 ns |
| randomF() - df=5,30 | 113.3 ns | 8,829,000 | (108.8 ns … 128.1 ns) | 114.4 ns | 124.8 ns | 127.2 ns |
| randomChi2() - df=5 | 54.9 ns | 18,220,000 | ( 52.6 ns … 66.4 ns) | 55.2 ns | 59.5 ns | 61.5 ns |
| matrix.add() - 50x50 | 12.1 µs | 82,710 | ( 11.3 µs … 176.0 µs) | 12.0 µs | 16.5 µs | 34.4 µs |
| matrix.transpose() - 50x50 | 11.0 µs | 91,160 | ( 10.4 µs … 165.0 µs) | 11.0 µs | 12.5 µs | 13.9 µs |
| matrix.multiply() - 50x50 | 378.5 µs | 2,642 | (339.8 µs … 2.9 ms) | 348.0 µs | 498.0 µs | 1.6 ms |
| matrix.inverse() - 50x50 | 472.6 µs | 2,116 | (449.1 µs … 5.6 ms) | 457.1 µs | 779.0 µs | 994.7 µs |