LCOV - code coverage report
Current view: top level - mentat/stats - distributions.ts (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 35 296 11.8 %
Date: 2021-03-12 10:43:40 Functions: 6 37 16.2 %

          Line data    Source code
       1             : /**
       2             :  * INFO: a collection of log probability density functions (PDF)
       3             :  */
       4             : 
       5             : // A number of log probability density functions (PDF). Naming and parameterization
       6             : // should match R's, except for that all functions reside in an ld object (
       7             : // as in "log density"), so to get a normal log density you would write
       8             : // ld.norm(...).
       9             : // Most of the code below is directly taken from the great Jstat project
      10             : // (https://github.com/jstat/) which includes PDF for many common probability
      11             : // distributions. What I have done is only to convert these to log PDFs.
      12             : 
      13             : /*
      14             : Original work Copyright (c) 2013 jStat
      15             : Modified work Copyright (c) 2015 Rasmus Bååth
      16             : Modified work Copyright (c) 2020 Rui Vieira
      17             : 
      18             : Permission is hereby granted, free of charge, to any person obtaining a copy
      19             : of this software and associated documentation files (the "Software"), to deal
      20             : in the Software without restriction, including without limitation the rights
      21             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      22             : copies of the Software, and to permit persons to whom the Software is
      23             : furnished to do so, subject to the following conditions:
      24             : 
      25             : The above copyright notice and this permission notice shall be included in
      26             : all copies or substantial portions of the Software.
      27             : 
      28             : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      29             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      30             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      31             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      32             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      33             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
      34             : THE SOFTWARE.
      35             : 
      36             : */
      37           1 : import { range } from "../utils.ts";
      38             : 
      39             : interface UnivariateContinuousDistribution {
      40             :   sample(): number;
      41             :   sampleN(n: number): Array<number>;
      42             : }
      43             : 
      44             : /** Returns a random real number from a normal distribbution defined
      45             :    *  by mean and sd. 
      46             :    *  Adapted from https://github.com/jstat/jstat/blob/master/src/special.js */
      47             : 
      48           1 : export function rnorm(mean: number, sd: number) {
      49       23001 :   var u, v, x, y, q;
      50       23001 :   do {
      51       54345 :     u = Math.random();
      52       54345 :     v = 1.7156 * (Math.random() - 0.5);
      53       54345 :     x = u - 0.449871;
      54       54345 :     y = Math.abs(v) + 0.386595;
      55       54345 :     q = x * x + y * (0.19600 * y - 0.25472 * x);
      56       23001 :   } while (q > 0.27597 && (q > 0.27846 || v * v > -4 * Math.log(u) * u * u));
      57             : 
      58       23001 :   return (v / u) * sd + mean;
      59           1 : }
      60             : 
      61             : // Returns the error function erf(x)
      62           0 : function erf(x: number): number {
      63           0 :   const cof = [
      64           0 :     -1.3026537197817094,
      65           0 :     6.4196979235649026e-1,
      66           0 :     1.9476473204185836e-2,
      67           0 :     -9.561514786808631e-3,
      68           0 :     -9.46595344482036e-4,
      69           0 :     3.66839497852761e-4,
      70           0 :     4.2523324806907e-5,
      71           0 :     -2.0278578112534e-5,
      72           0 :     -1.624290004647e-6,
      73           0 :     1.303655835580e-6,
      74           0 :     1.5626441722e-8,
      75           0 :     -8.5238095915e-8,
      76           0 :     6.529054439e-9,
      77           0 :     5.059343495e-9,
      78           0 :     -9.91364156e-10,
      79           0 :     -2.27365122e-10,
      80           0 :     9.6467911e-11,
      81           0 :     2.394038e-12,
      82           0 :     -6.886027e-12,
      83           0 :     8.94487e-13,
      84           0 :     3.13092e-13,
      85           0 :     -1.12708e-13,
      86           0 :     3.81e-16,
      87           0 :     7.106e-15,
      88           0 :     -1.523e-15,
      89           0 :     -9.4e-17,
      90           0 :     1.21e-16,
      91           0 :     -2.8e-17,
      92           0 :   ];
      93           0 :   var j = cof.length - 1;
      94           0 :   var isneg = false;
      95           0 :   var d = 0;
      96           0 :   var dd = 0;
      97           0 :   var t, ty, tmp, res;
      98             : 
      99           0 :   if (x < 0) {
     100           0 :     x = -x;
     101           0 :     isneg = true;
     102           0 :   }
     103             : 
     104           0 :   t = 2 / (2 + x);
     105           0 :   ty = 4 * t - 2;
     106             : 
     107           0 :   for (; j > 0; j--) {
     108           0 :     tmp = d;
     109           0 :     d = ty * d - dd + cof[j];
     110           0 :     dd = tmp;
     111           0 :   }
     112             : 
     113           0 :   res = t * Math.exp(-x * x + 0.5 * (cof[0] + ty * d) - dd);
     114           0 :   return isneg ? res - 1 : 1 - res;
     115           0 : }
     116             : 
     117             : // Returns the complmentary error function erfc(x)
     118           0 : function erfc(x: number): number {
     119           0 :   return 1 - erf(x);
     120           0 : }// Returns the inverse of the complementary error function
     121             : 
     122           0 : function erfcinv(p: number): number {
     123           0 :   var j = 0;
     124           0 :   var x, err, t, pp;
     125           0 :   if (p >= 2) {
     126           0 :     return -100;
     127           0 :   }
     128           0 :   if (p <= 0) {
     129           0 :     return 100;
     130           0 :   }
     131           0 :   pp = (p < 1) ? p : 2 - p;
     132           0 :   t = Math.sqrt(-2 * Math.log(pp / 2));
     133           0 :   x = -0.70711 * ((2.30753 + t * 0.27061) /
     134           0 :       (1 + t * (0.99229 + t * 0.04481)) - t);
     135           0 :   for (; j < 2; j++) {
     136           0 :     err = erfc(x) - pp;
     137           0 :     x += err / (1.12837916709551257 * Math.exp(-x * x) - x * err);
     138           0 :   }
     139           0 :   return (p < 1) ? x : -x;
     140           0 : }
     141             : 
     142             : // Distribution
     143           1 : export class Normal implements UnivariateContinuousDistribution {
     144             :   mean: number;
     145             :   std: number;
     146           1 :   constructor(mean: number, std: number) {
     147           3 :     this.mean = mean;
     148           3 :     this.std = std;
     149           1 :   }
     150           0 :   pdf(x: number): number {
     151           0 :     return Math.exp(
     152           0 :       -0.5 * Math.log(2 * Math.PI) -
     153           0 :         Math.log(this.std) -
     154           0 :         Math.pow(x - this.mean, 2) / (2 * this.std * this.std),
     155           0 :     );
     156           0 :   }
     157           0 :   cdf(x: number): number {
     158           0 :     return 0.5 *
     159           0 :       (1 + erf((x - this.mean) / Math.sqrt(2 * this.std * this.std)));
     160           0 :   }
     161             : 
     162           0 :   inv(p: number): number {
     163           0 :     return -1.41421356237309505 * this.std * erfcinv(2 * p) + this.mean;
     164           0 :   }
     165           1 :   sample(): number {
     166       11001 :     return rnorm(this.mean, this.std);
     167           1 :   }
     168           1 :   sampleN(n: number): Array<number> {
     169           3 :     return range(n).map((_) => this.sample());
     170           1 :   }
     171           0 :   variance() {
     172           0 :     return this.std * this.std;
     173           0 :   }
     174           1 : }
     175             : 
     176             : ////////// Helper functions //////////
     177             : //////////////////////////////////////
     178             : 
     179           0 : export function lgamma(x: number) {
     180           0 :   var j = 0;
     181           0 :   var cof = [
     182           0 :     76.18009172947146,
     183           0 :     -86.50532032941677,
     184           0 :     24.01409824083091,
     185           0 :     -1.231739572450155,
     186           0 :     0.1208650973866179e-2,
     187           0 :     -0.5395239384953e-5,
     188           0 :   ];
     189           0 :   var ser = 1.000000000190015;
     190           0 :   var xx, y, tmp;
     191           0 :   tmp = (y = xx = x) + 5.5;
     192           0 :   tmp -= (xx + 0.5) * log(tmp);
     193           0 :   for (; j < 6; j++) {
     194           0 :     ser += cof[j] / ++y;
     195           0 :   }
     196           0 :   return log(2.5066282746310005 * ser / xx) - tmp;
     197           0 : }
     198             : 
     199           0 : export function lfactorial(n: number) {
     200           0 :   return n < 0 ? NaN : lgamma(n + 1);
     201           0 : }
     202             : 
     203           0 : export function lchoose(n: number, k: number) {
     204           0 :   return lfactorial(n) - lfactorial(k) - lfactorial(n - k);
     205           0 : }
     206             : 
     207           0 : export function lbeta(a: number, b: number) {
     208           0 :   return lgamma(a) + lgamma(b) - lgamma(a + b);
     209           0 : }
     210             : 
     211           1 : let log = Math.log;
     212           1 : let abs = Math.abs;
     213           1 : let pow = Math.pow;
     214           1 : let sqrt = Math.sqrt;
     215           1 : let pi = Math.PI;
     216             : 
     217             : ////////// Continous distributions //////////
     218             : /////////////////////////////////////////////
     219             : 
     220           0 : export function beta(x: number, shape1: number, shape2: number) {
     221           0 :   if (x > 1 || x < 0) {
     222           0 :     return -Infinity;
     223           0 :   }
     224           0 :   if (shape1 === 1 && shape2 === 1) {
     225           0 :     return 0;
     226           0 :   } else {
     227           0 :     return (shape1 - 1) * log(x) + (shape2 - 1) * log(1 - x) -
     228           0 :       lbeta(shape1, shape2);
     229           0 :   }
     230           0 : }
     231             : 
     232           0 : export function cauchy(x: number, location: number, scale: number) {
     233           0 :   return log(scale) - log(pow(x - location, 2) + pow(scale, 2)) - log(pi);
     234           0 : }
     235             : 
     236           1 : export function norm(x: number, mean: number, sd: number) {
     237      258292 :   return -0.5 * log(2 * pi) - log(sd) - pow(x - mean, 2) / (2 * sd * sd);
     238           1 : } // A bivariate Normal distribution parameterized by arrays of two means and SDs, and
     239             : // the correlation.
     240             : 
     241           0 : export function bivarnorm(x: any, mean: any, sd: any, corr: number) {
     242           0 :   var z = pow(x[0] - mean[0], 2) / pow(sd[0], 2) +
     243           0 :     pow(x[1] - mean[1], 2) / pow(sd[1], 2) -
     244           0 :     (2 * corr * (x[0] - mean[0]) * (x[1] - mean[1])) / (sd[0] * sd[1]);
     245           0 :   var normalizing_factor = -(log(2) + log(pi) + log(sd[0]) + log(sd[1]) +
     246           0 :     0.5 * log(1 - pow(corr, 2)));
     247           0 :   var bivar_log_dens = normalizing_factor - z / (2 * (1 - pow(corr, 2)));
     248           0 :   return bivar_log_dens;
     249           0 : }
     250             : 
     251           0 : export function laplace(x: number, location: number, scale: number) {
     252           0 :   return (-abs(x - location) / scale) - log(2 * scale);
     253           0 : }
     254             : 
     255           1 : let dexp = laplace;
     256             : 
     257           0 : export function gamma(x: number, shape: number, rate: number) {
     258           0 :   var scale = 1 / rate;
     259           0 :   if (x < 0) {
     260           0 :     return -Infinity;
     261           0 :   }
     262           0 :   if ((x === 0 && shape === 1)) {
     263           0 :     return -log(scale);
     264           0 :   } else {
     265           0 :     return (shape - 1) * log(x) - x / scale - lgamma(shape) -
     266           0 :       shape * log(scale);
     267           0 :   }
     268           0 : }
     269             : 
     270           0 : export function invgamma(x: number, shape: number, scale: number) {
     271           0 :   if (x <= 0) {
     272           0 :     return -Infinity;
     273           0 :   }
     274           0 :   return -(shape + 1) * log(x) - scale / x - lgamma(shape) + shape * log(scale);
     275           0 : }
     276             : 
     277           0 : export function lnorm(x: number, meanlog: number, sdlog: number) {
     278           0 :   if (x <= 0) {
     279           0 :     return -Infinity;
     280           0 :   }
     281           0 :   return -log(x) - 0.5 * log(2 * pi) - log(sdlog) -
     282           0 :     pow(log(x) - meanlog, 2) / (2 * sdlog * sdlog);
     283           0 : }
     284             : 
     285           0 : export function pareto(x: number, scale: number, shape: number) {
     286           0 :   if (x < scale) {
     287           0 :     return -Infinity;
     288           0 :   }
     289           0 :   return log(shape) + shape * log(scale) - (shape + 1) * log(x);
     290           0 : }
     291             : 
     292           0 : export function t(x: number, location: number, scale: number, df: number) {
     293           0 :   df = df > 1e100 ? 1e100 : df;
     294           0 :   return lgamma((df + 1) / 2) - lgamma(df / 2) - log(sqrt(pi * df) * scale) +
     295           0 :     log(pow(1 + (1 / df) * pow((x - location) / scale, 2), -(df + 1) / 2));
     296           0 : } // This is a direct javascript translation of the R code used to evaluate
     297             : // the log density of a weibull distribution:
     298             : // https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/nmath/dweibull.c
     299             : 
     300           0 : export function weibull(x: number, shape: number, scale: number) {
     301           0 :   if (x < 0) return -Infinity;
     302           0 :   if (x === 0 && shape < 1) return Infinity;
     303           0 :   var tmp1 = pow(x / scale, shape - 1);
     304           0 :   var tmp2 = tmp1 * (x / scale);
     305           0 :   return -tmp2 + log(shape * tmp1 / scale);
     306           0 : } // This is a direct javascript translation of the R code used to evaluate
     307             : // the log density of a logistic distribution:
     308             : // https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/nmath/dlogis.c
     309             : 
     310           0 : export function logis(x: number, location: number, scale: number) {
     311           0 :   x = abs((x - location) / scale);
     312           0 :   var e = Math.exp(-x);
     313           0 :   var f = 1.0 + e;
     314           0 :   return -(x + log(scale * f * f));
     315           0 : }
     316             : 
     317           0 : export function dirichlet(x: any, alpha: any) {
     318           0 :   var sum_alpha = 0;
     319           0 :   var sum_lgamma_alpha = 0;
     320           0 :   var sum_alpha_sub_1_log_x = 0;
     321           0 :   var n = alpha.length;
     322           0 :   for (var i = 0; i < n; i++) {
     323           0 :     sum_alpha += alpha[i];
     324           0 :     sum_lgamma_alpha += lgamma(alpha[i]);
     325           0 :     sum_alpha_sub_1_log_x += (alpha[i] - 1) * log(x[i]);
     326           0 :   }
     327           0 :   return lgamma(sum_alpha) - sum_lgamma_alpha + sum_alpha_sub_1_log_x;
     328           0 : }
     329             : 
     330           0 : export function exp(x: number, rate: number) {
     331           0 :   return x < 0 ? -Infinity : log(rate) - rate * x;
     332           0 : }
     333             : 
     334           1 : export function unif(x: number, min: number, max: number) {
     335           0 :   return (x < min || x > max) ? -Infinity : log(1 / (max - min));
     336           1 : } ////////// Discrete distributions //////////
     337             : ////////////////////////////////////////////
     338             : 
     339           0 : export function bern(x: number, prob: number) {
     340           0 :   return !(x === 0 || x === 1)
     341           0 :     ? -Infinity
     342           0 :     : log(x * prob + (1 - x) * (1 - prob));
     343           0 : }
     344             : 
     345           0 : export function cat(x: number, probs: any) {
     346           0 :   if (x < 1 || x > probs.length) {
     347           0 :     return -Infinity;
     348           0 :   } else {
     349           0 :     return log(probs[x - 1]);
     350           0 :   }
     351           0 : }
     352             : 
     353           0 : export function binom(x: number, size: number, prob: number) {
     354           0 :   if (x > size || x < 0) {
     355           0 :     return -Infinity;
     356           0 :   }
     357           0 :   if (prob === 0 || prob === 1) {
     358           0 :     return (size * prob) === x ? 0 : -Infinity;
     359           0 :   }
     360           0 :   return lchoose(size, x) + x * log(prob) + (size - x) * log(1 - prob);
     361           0 : }
     362             : 
     363           0 : export function ultinom(x: any, probs: any) {
     364           0 :   var n = x.length;
     365           0 :   var size = 0;
     366           0 :   var tmp_term = 0;
     367           0 :   for (var i = 0; i < n; i++) {
     368           0 :     if (probs[i] === 0) {
     369           0 :       if (x[i] !== 0) {
     370           0 :         return -Infinity;
     371           0 :       }
     372           0 :     } else {
     373           0 :       size += x[i];
     374           0 :       tmp_term += x[i] * log(probs[i]) - lgamma(x[i] + 1);
     375           0 :     }
     376           0 :   }
     377           0 :   return lgamma(size + 1) + tmp_term;
     378           0 : }
     379             : 
     380           0 : export function nbinom(x: number, size: number, prob: number) {
     381           0 :   if (x < 0) {
     382           0 :     return -Infinity;
     383           0 :   }
     384           0 :   return lchoose(x + size - 1, size - 1) + x * log(1 - prob) + size * log(prob);
     385           0 : }
     386             : 
     387           0 : export function hyper(x: number, m: number, n: number, k: number) {
     388           0 :   if (x < 0 || x > k) {
     389           0 :     return -Infinity;
     390           0 :   } else {
     391           0 :     return lchoose(m, x) + lchoose(n, k - x) - lchoose(m + n, k);
     392           0 :   }
     393           0 : }
     394             : 
     395           0 : export function pois(x: number, lambda: number) {
     396           0 :   return x < 0 ? -Infinity : log(lambda) * x - lambda - lfactorial(x);
     397           0 : }

Generated by: LCOV version 1.15