Skip to content

Instantly share code, notes, and snippets.

@noahlange
Last active January 21, 2016 18:42
Show Gist options
  • Save noahlange/c88bd3b74feb42ae3ada to your computer and use it in GitHub Desktop.
Save noahlange/c88bd3b74feb42ae3ada to your computer and use it in GitHub Desktop.
export abstract class Distribution {
/**
* Protected instance properties
*/
protected _mean: number;
protected _variance: number;
/**
* Accessors for variance and mean.
*/
get mean(): number {
return this._mean;
}
get variance(): number {
return this._variance;
}
/**
* To be overridden...
*/
public quantile(n: number): number {
return n;
}
/**
* To be overridden if necessary.
*/
public rand(n: number): number[] {
return Array(n).map(x => this.quantile(Math.random()));
}
}
import { Distribution } from './distribution';
import { factorial } from './util';
/**
* Describes a Poisson distribution.
*/
export class Poisson extends Distribution {
/**
* Creates a new instance of a Poisson distribution from an array of numbers.
*/
public static fit(data: number[]): Poisson {
const avg = data.reduce((a, b) => a + b, 0) / (data.length || 1);
return new Poisson(avg);
}
/**
* Probability mass function.
*/
public pmf(k: number): number {
return Math.pow(this._mean, k) * Math.exp(-1 * this._mean) / factorial(k);
}
/**
* Cumulative distribution function.
*/
public cdf(k: number): number {
// Creates a new array of k length.
return Array(k)
// Fill with `#pmf(index)`
.map((item, index) => this.pmf(index))
// Add together, starting from 0.
.reduce((a, b) => a + b, 0);
}
public quantile(p: number): number {
// Create block scoped variables for our total and index.
let total = 0;
let i = 0;
// While total < p, accumulate.
while (total < p) {
total += this.pmf(i);
i = i + 1;
}
// Return the previous index.
return i - 1;
}
/**
* Creates a new Poisson distribution.
*/
constructor(l: number) {
super();
this._mean = l;
this._variance = l;
}
}

Love what impreson put together, and I agree with much of his assessment. I'd like to offer a different - perhaps less classically JavaScript and more OOP - way of solving the problem.

I can see the attempts at classes in your code. You already have the constructor functions, but you'd be better served by putting the methods onto the prototype instead of attaching them to the object at creation. Using ES6 classes (in this case, I'm actually using TypeScript), you can cut out a lot of cruft.

First, a little bit about classes - it's syntatic sugar around the function prototype. We can create methods and properties on the object, like so...

function Poisson(l) {}

Poisson.fit = function(data) {}
Poisson.prototype.pmf = function(k) {}
Poisson.prototype.cdf = function(k) {}
Poisson.prototype.quantile = function(p) {}

Or we can create a class that lets us put it all in the same structure. There's a lot of debate as to whether or not classes are "good" for JS. I happen to like them.

class Poisson {
	static fit(data) {}
	pmf(k) {}
	cdf(k) {}
	quantile(p) {}
	constructor(l) {}
}

TypeScript has the notion of an abstract class - basically a class that provides some/limited functionality that cannot be instantiated.

First we're going to create an abstract class called Distribution to provide our #rand() method, as well as accessors to mean and variance. Let's start out in distribution.ts by exporting (see ES6 module syntax) an abstract class called Distribution.

export abstract class Distribution {

It appears that all of our distributions have means and variances, so let's store them on protected (i.e., accessible in subclasses but not from the outside) instance properties.

  /**
   * Instance properties for the mean and variance.
   */
  protected _mean: number;
  protected _variance: number;

We'll provide read-only access to the outside world via a get accessor.

  /**
   * Public getters for the _mean and _variance.
   */
  get mean(): number {
    return this._mean;
  }
  
  get variance(): number {
    return this._variance;
  }

Next we'll create our basically pointless #quantile() method to satisfy the typechecker when we call it in #rand().

  /**
   * To be overridden: here to satisfy the typechecker.
   */
  public quantile(n: number): number {
    return n;
  }

Then for #rand(), it appears most of the implementations are identical, so we'll provide a baseline implementation here we'll override on a case-by-case basis.

  /**
   * Fill an array of `n` length with calls to `#quantile()` with random values.
   */
  public rand(n: number): number[] {
    return Array(n).fill(this.quantile(Math.random());
  }
  
}

Then we can import our abstract class into a new file -- say, poisson.ts -- and create a subclass of it for a Poisson distribution. You'll notice factorial() is now living in a util module (along with median and mode); we'll need to import it.

import { Distribution } from './distribution';
import { factorial } from './util';

Then we'll create our class using the class and extends keywords. We're exporting it so we can use it with other modules - this is ES6 module syntax.

/**
 * Describes a Poisson distribution.
 */
export class Poisson extends Distribution {

Classes can have static methods - i.e., methods that exist on the class itself, and not on instances of the class. You've already been doing this with your ::fit() method, so I figured we'd go with what you were already doing. This will let you create a new distribution object by writing Poisson.fit(foo), where foo is an Array of numbers.

  /**
   * Creates a new instance of a Poisson distribution from an array of numbers.
   */
  public static fit(data: number[]): Poisson {
    const sum = data.reduce((a, b) => a + b, 0)
    const avg = sum / (data.length || 1);
    return new Poisson(avg);
  }

Let's continue with our constructor. This is called whenever const p = new Poisson() is invoked - either in the static ::fit() method or via normal object creation.

  /**
   * Creates a new Poisson distribution.
   */
  constructor(l: number) {
    // super because we're extending another class
    super();
    // Initialize our properties.
    this._mean = l;
    this._variance = l;
  }

We can write our PMF function pretty straightforwardly using the class instance's mean property. You'll note we're also using the imported factorial() function.

  /**
   * Probability mass function.
   */
  public pmf(k: number): number {
    return Math.pow(this._mean, k) * Math.exp(-1 * this._mean) / factorial(k);
  }

Next we have our cumulative distribution function. You'll notice we're using a probably unfamiliar Array(k) syntax to initialize an empty array of k slots. We can then fill each slot with the pmf(i), where i is the index (0, 1, 2, ... k). Then we reduce the array, starting with zero and adding each item together from left to right.

  /**
   * Cumulative distribution function.
   */
  public cdf(k: number): number {
    // Creates a new array of k length.
    return Array(k)
      // Fill with `#pmf(index)`
      .map((item, index) => this.pmf(index))
      // Add together, starting from 0.
      .reduce((a, b) => a + b, 0);
  }

After we've done that, we can go ahead and finish the class with the quantile method. You'll notice we're using let instead of var. It's one of two new variable types in ES6, along with const. If you can use const, use const, if you're going to be resetting the variable (either with a ++ or += or =), use let.

  public quantile(p: number): number {

    // Create block scoped variables for our total and index.    
    let total = 0, i = 0;
    
    // While total < p, accumulate.
    while (total < p) {
      total += this.pmf(i);
      i++;
    }

    // Return the index that caused the accumulator to exceed p.
    return i - 1;
  }
}

And that's it. Granted, there's way more conceptual overhead than with impreson's solution, but I feel that TypeScript, with its typing, is a superior choice for mathematics / engineering.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment