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.
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.
Probility can be installed from npm with the following command:
npm install probility
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.
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
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.
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 }
// }
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 }
// }
Returns the number of unique choices in a Probility instance.
const d6 = new Probility([1, 2, 3, 4, 5, 6]);
d6.numUniqueChoices // 6
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]
Returns the number of total choices.
const weightedD6 = new Probility([1, 2, 3, 4, 5, 6, 6, 6, 6, 6])
weightedD6.numTotalChoices // 10
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()
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"
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]
Shorthand for instance.add(choice, 1)
.
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]
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'
}
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"
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"
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
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
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
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 ]