Skip to content

A library made to prototype, explore, and describe probability spaces.

Notifications You must be signed in to change notification settings

ZachJDev/Probility

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Probility

Probility is an evolving library that aims to provide an easy-to-understand interface for creating probability distributions and generating data from those distributions.

Probility provides both a class to interface with data collections and a set of functions to generate practical tests of outcomes and theoretical enumerations of expected outcomes for complex probability rules.

To see the current power of Probility, please check out the Examples Folder and run the sample code in Node. Example output is provided in the Markdown files, but running the code and looking at how the examples are constructed should give a much clearer picture of how everything works behind the scenes.

Why Probility?

My game-designer coworker trying to work out various combinations of die rolls using an Excel table initially inspired this project. I hope that, as it works now, Probility provides an easy way to interface with, describe, and explore probabilistic collections of data. In the future, I hope to make it even more robust by allowing developers to store and manipulate different states of a single Probility instance.

Usage

Probility can be installed from npm with the following command:

npm install probility

API Reference

class Probility(array, [options])

The main class for Probility collections. It takes an array and an optional options object during initialization.

It can be assigned directly:

const Probility = require('Probility')
const d6 = new Probility([1, 2, 3, 4, 5, 6])
d6.choose() // returns a random number from the array

Or as a parent class:

class SixSidedDie extends Probility {
    constructor() {
        super([1, 2, 3, 4, 5, 6])
    }

    roll() {
        return this.choose()
    }

...
}

Or as a dependency for an entirely new class.

class SixSidedDie {
    constructor(faces = [1, 2, 3, 4, 5, 6]) {
        this.prob = new Probility(faces) // Access all the Probility methods in the prob property
    }

    roll() {
        return this.prob.choose()
    }

...
}

In addition to passing an array with discrete objects, an alternative syntax exists to describe the ratios of the included choices:

class Urn extends Probility {
    constructor(stateDescription, options) {
        super(stateDescription, options)
    }

    pick() {
        return this.choose()
    }
}

// creating a new instance with an array describing the state:

const quarterUrn = new Urn([{"25%": "red ball"}, {"25%": "green ball"},
    {"1/4": "purple ball"},
    {"remainder": "black ball"}], {parseArray: true, total: 400})

// parseArray must be true. If not, we'd have a collection of four objects
// with equal probability to be chosen. Setting total to 400 here means that
// the pool will consist of 100 of each type of ball. Without setting the total,
// the pool would consist of one type of each ball.

array:

The array can either be a collection of discrete choices (like the sixSidedDie example), or a description of the probability state using ratios, percents, or the remainder identifier (like in the urn example) or whole numbers. Right now, whole numbers cannot be mixed with ratios or percents and cannot also have a remainder object in the same array. To describe the state in the second way, the parseArray option must be true when calling the constructor.

Probility will handle most types of objects as choices, including functions:

const loudCoin = new Probility([(str) => str.toUpperCase(), (str) => str.toLowerCase()]);
loudCoin.choose()("Hello") // HELLO
loudCoin.choose()("Hello") // hello

options:

Currently, there are three accepted options: parseArray: boolean, usePool: boolean and total: number. Setting parseArray to true will cause the Probility constructor to interpret the array as an array of objects describing the amount of a given choice. It is false by default, which will cause the constructor to read the array as a collection of discrete objects with equal probabilities.

usePool : false will skip the pool initialization and cause the chooseFomPool() method to return an error. Because of the different implementations of chooseWithSample() and chooseFromPool(), the best option will be based on each use case: In general, usePool: true is a better option for collections with many evenly -distributed choices, e.g. representing a deck of playing cards. usePool: false is a better option for collections with fewer and non-evenly-distributed choices, e.g. an urn with a two colors of balls with a constantly-changing ratio between them. The choose() method will detect which of the two choose implementations to use based on this option.

total can be set to a number representing the total number of options in a state description array (i.e., a parsed array). It will not override the totals if whole numbers are used; it is useful when describing a collection with ratios and percents that will later need choices added or removed. A high total will correlate to a large pool, so space concerns may factor into it's usefulness for your use case.

Probility.frequencyTest(callback, n)

Static. Calls the callback n times and returns a new Map of the results mapped to the number of times the result occurred out of n. The values of the Map are Rational Numbers, an included class:

const Probility = require('probility')

const d6 = new SixSidedDie();

Probility.frequencyTest(() => d6.roll(), 6000)

//  Map(6) {
//    6 => RationalNumber { numerator: 1019, denominator: 6000 },
//    1 => RationalNumber { numerator: 1012, denominator: 6000 },
//    5 => RationalNumber { numerator: 1040, denominator: 6000 },
//    3 => RationalNumber { numerator: 952, denominator: 6000 },
//    2 => RationalNumber { numerator: 1020, denominator: 6000 },
//    4 => RationalNumber { numerator: 957, denominator: 6000 }
// }

Probility.frequencyTest(() => {
    return d6.roll() % 2 === 0 ? "Even" : "Odd"
}, 6000)

// Map(2) {
//     'Odd' => RationalNumber { numerator: 2986, denominator: 6000 },
//     'Even' => RationalNumber { numerator: 3014, denominator: 6000 }
// }

Probility.frequencyEnumeration(array)

Static. Returns a mapping of all possible outcomes to their actual probability. It is meant to be used with Probility's . enumerate() method.

const
    Probility = require("Probility");

Probility.frequencyEnumeration(() => {
    return d6.enumerate((roll1) => {
        return d6.enumerate((roll2) => roll1 + roll2)
    })
});

// Map(11) {
//   2 => RationalNumber { numerator: 1, denominator: 36 },
//   3 => RationalNumber { numerator: 2, denominator: 36 },
//   4 => RationalNumber { numerator: 3, denominator: 36 },
//   5 => RationalNumber { numerator: 4, denominator: 36 },
//   6 => RationalNumber { numerator: 5, denominator: 36 },
//   7 => RationalNumber { numerator: 6, denominator: 36 },
//   8 => RationalNumber { numerator: 5, denominator: 36 },
//   9 => RationalNumber { numerator: 4, denominator: 36 },
//   10 => RationalNumber { numerator: 3, denominator: 36 },
//   11 => RationalNumber { numerator: 2, denominator: 36 },
//   12 => RationalNumber { numerator: 1, denominator: 36 }
// }

Probility.frequencyEnumeration(() => {
    return d6.enumerate((roll1) => {
        return d6.enumerate((roll2) => (roll1 + roll2) % 2 === 0 ? "Even" : "Odd")
    })
});

// Map(2) {
//     'Even' => RationalNumber { numerator: 18, denominator: 36 },
//     'Odd' => RationalNumber { numerator: 18, denominator: 36 }
// }

instance.numUniqueChoices

Returns the number of unique choices in a Probility instance.

const d6 = new Probility([1, 2, 3, 4, 5, 6]);
d6.numUniqueChoices // 6

instance.possibleChoices

Returns an array of the unique choices in a Probility instances.

const weightedD6 = new Probility([1, 2, 3, 4, 5, 6, 6, 6, 6, 6])
weightedD6.possibleChoices // [1,2,3,4,5,6]

instance.numTotalChoices

Returns the number of total choices.

const weightedD6 = new Probility([1, 2, 3, 4, 5, 6, 6, 6, 6, 6])
weightedD6.numTotalChoices // 10

instance.choose()

Returns a choice based on the probabilities of the choices in the collection. Will call chooseFromPool() or chooseFromSample() based on how the instance was created.

const weightedD6 = new Probility([1, 2, 3, 4, 5, 6, 6, 6, 6, 6])
const coin = new Probility([{"1/2": "heads"}, {"1/2": "tails"}], {parseArray: true, usePool: false});

coin.choose() // 'heads' -- calls chooseFromSample()
weightedD6.choose() // 6 -- calls chooseFromPool()

instance.initPool()

Initializes an instance's pool. Mostly used internally, but can be used to force a pool when an instance was not created with one.

const coin = new Probility([{"1/2": "heads"}, {"1/2": "tails"}], {parseArray: true, usePool: false});
coin.chooseFromPool(); // throws an error
coin.initPool();
coin.chooseFromPool() // "heads"

instance.add(choice, num)

Returns the Probility instance. Adds a number num of choice to the possible choices and reinitializes the pool, if using one.

const d6 = new Probility([1, 2, 3, 4, 5, 6])
d6.add(7, 1);
d6.possibleChoices // [1,2,3,4,5,6,7]

instance.addOne(choice)

Shorthand for instance.add(choice, 1).

instance.remove(predicateCallback, num)

Returns the Probility instance. iterates over the array and removes up to num choices that return true from predicateCallback(choice).

const smallDeck = new Probility(["1H", "6D", "JS", "KS"]);

// Removing a specific choice:

smallDeck.remove(card => card === "JS", 1);
smallDeck.possibleChoices // ["1H", "6D", "KS"]

// Removing non-specific items:

const slipsOfPaper = new Probility([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
slipsOfPaper.remove(paper => paper % 2 === 0, 3)
slipsOfPaper.possibleChoices // [1,3,5,7,8,9,10,11,12]

instance.listAllProbabilities()

Returns a Map of the probabilities of the unique choices.

const slipsOfPaper = new Probility([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
slipsOfPaper.listAllProbabilities()
// Map(12) {
//   1 => '1 / 12',
//   2 => '1 / 12',
//   3 => '1 / 12',
//   4 => '1 / 12',
//   5 => '1 / 12',
//   6 => '1 / 12',
//   7 => '1 / 12',
//   8 => '1 / 12',
//   9 => '1 / 12',
//   10 => '1 / 12',
//   11 => '1 / 12',
//   12 => '1 / 12'
}

instance.singleChoiceProbability(choice)

Returns a RationalNumber representing the probability of the given choice from the instance.

const weightedDie = new Probility([1, 2, 3, 4, 4, 4, 5, 6])
weightedDie.singleChoiceProbability(4).toString() // "3 / 8"

instance.probabilityOf(predicateCallback)

Returns a RationalNumber representing the probability that a choice will return true from predicateCallback (choice). In other words, the number of choices that return true over the total number of choices.

const slipsOfPaper = new Probility([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
slipsOfPaper.probabilityOf(paper => paper % 2 === 0).simplify().toString() // "1 / 2"

instance.getRandomChoice()

Returns a random choice from the collection of unique choices in the collection. With this method, all unique choices have an equal probability of being chosen.

const slipsOfPaper = new Probility([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
slipsOfPaper.getRandomChoice() // 10

instance.getRandomNumberFromTotal()

Returns a random number between 0 and the total number of all choices in a collection (exclusive).

const bigUrn = new Probility([{"50%": "White Ball"}, {"50%": "Black Ball"}], {parseArray; true, total: 4000})
bigUrn.getRandomNumberFromTotal() // 3320

instance.getRandomNumberFromUnique()

Returns a random number between 0 and the number of unique choices in a collection (exclusive).

const bigUrn = new Probility([{"50%": "White Ball"}, {"50%": "Black Ball"}], {parseArray; true, total: 4000})
bigUrn.getRandomNumberFromUnique() // 1

instance.enumerate(callback)

Returns an array of values returned from calling the callback with each value. Under the hood, this uses Array. flatMap, so they can be easily chained. (Though of course, enumerating can become resource-hungry quickly; it's essentially permuting all possible choices with each call to this method.)

const d6 = new Probility([1, 2, 3, 4, 5, 6]);
d6.enumerate(roll => {
    return roll
})
// [1,2,3,4,5,6]

d6.enumerate(roll1 => {
    return d6.enumerate(roll2 => {
        return roll1 + roll2
    })
})
//[2,  3,  4, 5, 6,  7,  3,  4, 5, 6,  7,  8, 4, 5,  6,  7,  8,  9,
// 5,  6,  7, 8, 9, 10,  6,  7, 8, 9, 10, 11, 7, 8,  9, 10, 11, 12 ]

About

A library made to prototype, explore, and describe probability spaces.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published