dit
dit is a Python package for information theory on discrete random variables.
d = discrete i = information t = theory
However, the more precise statement (at this point) is that dit is a Python package for sigma-algebras defined on finite sets.
Classes
An abstract representation of a Cartesian product sample space. |
|
A distribution backed by an xarray DataArray. |
|
This object enumerates all the uniform probability distributions over n variables each with cardinality k |
|
Helper class for building new random variables. |
|
An abstract representation of a sample space. |
|
Functions
Return an iterator of distributions over the 2**(alphabet_size`**`outcome_length) possible combinations of joint events. |
|
Returns a NumPy array of the distribution's pmf. |
|
Returns a distribution built from a Bayesian network. |
|
Construct a version of dist where each variable has been passed through an erasure channel. |
|
Returns a new distribution with an expanded sample space. |
|
Construct the order-k Fejér-Riesz approximation of a distribution, a nonnegative analogue of the Lancaster approximation. |
|
Returns a new distribution with an added random variable at index index. |
|
Returns a joint distribution P(X,Y) built from P(X) and P(Y|X). |
|
Construct the unnormalized generalized Kirkwood superposition approximation of a distribution. |
|
Construct the order-k Lancaster approximation of a distribution, the additive analogue of the Kirkwood approximation. |
|
Create a mixture distribution: \(\sum p_i d_i\) |
|
Create a mixture distribution: \(\sum p_i d_i\) |
|
Returns dist but with modified outcomes, after passing them to ctor. |
|
Construct a noisy version of dist. |
|
Returns a new distribution which is the product of marginals. |
|
Returns a new distribution with pruned sample space. |
|
Return a uniform distribution over a random subset of the alphabet_size`**`outcome_length possible joint events. |
|
Returns a random distribution drawn uniformly from the simplex. |
|
Returns a random scalar distribution over n outcomes. |
|
Returns a generator over distributions, determined by a grid. |
|
Produces a uniform distribution over outcomes. |
|
Returns a uniform distribution. |
|
Returns a uniform distribution with the same outcome length, alphabet size, and base as dist. |
|
Returns a uniform scalar distribution over n outcomes. |
Reference
- class CartesianProduct(alphabets, product=None)[source]
Bases:
SampleSpaceAn abstract representation of a Cartesian product sample space.
- coalesce(rvs, extract=False)[source]
Returns a new sample space after coalescing the specified indexes.
Given n lists, each consisting of indexes from the original sample space, the coalesced sample space has n indexes, where the alphabet for each index is a tuple of the indexes that defined the ith list.
- Parameters:
rvs (sequence) – A sequence whose elements are also sequences. Each inner sequence defines an index in the new distribution as a combination of indexes in the original distribution. The length of rvs must be at least one. The inner sequences need not be pairwise mutually exclusive with one another, and each can contain repeated indexes.
extract (bool) – If the length of rvs is 1 and extract is True, then instead of the new outcomes being 1-tuples, we extract the sole element to create a sample space over the indexes in rvs[0].
- Returns:
ss – The coalesced sample space.
- Return type:
Examples
- If we have a joint distribution over 3 random variables such as:
Z = (X,Y,Z)
- and would like a new sample space over 6 random variables:
Z = (X,Y,Z,X,Y,Z)
- then this is achieved as:
coalesce([[0,1,2,0,1,2]], extract=True)
- If you want:
Z = ((X,Y), (Y,Z))
- Then you do:
coalesce([[0,1],[1,2]])
Notes
Generally, the outcomes of the new sample space will be tuples instead of matching the class of the original sample space. This is because some classes are not recursive containers. For example, one cannot have a string of strings where each string consists of more than one character. Note however, that it is perfectly valid to have a tuple of tuples. Either way, the elements within each tuple of the sample space will still match the class of the original sample space.
See also
marginal,marginalize
- class Distribution(data, pmf=None, rv_names=None, free_vars=None, given_vars=None, base='linear', sample_space=None, sparse=True, trim=True, sort=True, validate=True, prng=None)[source]
Bases:
objectA distribution backed by an xarray DataArray.
The distribution tracks which dimensions are “free” (joint) variables and which are “given” (conditioned on). This allows natural algebraic operations:
Multiplying
p(X,Y) * p(Z|X,Y)yieldsp(X,Y,Z)Summing over a free variable marginalizes it out
Dividing by a marginal conditions on it
- data
The underlying probability array.
- Type:
xr.DataArray
- free_vars
The names of the free (joint) variables. For
p(X,Y|Z)this is{'X','Y'}.
- given_vars
The names of the conditioned variables. For
p(X,Y|Z)this is{'Z'}.
- ops
The operations instance for the current probability base.
- Type:
Operations
Notes
Normalization convention: for a distribution
p(X,Y|Z), summing over all free variables (X and Y) for each fixed value of Z should yield 1.- classmethod from_distribution(dist, rv_names=None)[source]
Create an Distribution from an existing distribution, optionally renaming its random variables.
- Parameters:
dist (Distribution) – The source distribution.
rv_names (list of str, optional) – Names for each random variable. If None, uses the distribution’s existing rv_names, or defaults to
'X0','X1', etc.
- Returns:
xrd
- Return type:
- classmethod from_array(arr, dim_names, alphabets, free_vars=None, given_vars=None, base='linear')[source]
Create an Distribution from a numpy array.
- Parameters:
arr (np.ndarray) – The probability array.
alphabets (list of list) – The alphabet (coordinate values) for each dimension.
free_vars (set-like of str, optional) – Names of the free variables.
given_vars (set-like of str, optional) – Names of the conditioned variables.
base (str or float) – Probability base (
'linear',2,'e', …).
- Returns:
xrd
- Return type:
- classmethod from_factors(marginal, conditional)[source]
Build a joint distribution from a marginal and a conditional.
p(X,Y) = p(X) * p(Y|X)- Parameters:
marginal (Distribution) – The marginal distribution, e.g.
p(X).conditional (Distribution) – The conditional distribution, e.g.
p(Y|X).
- Returns:
joint – The resulting joint distribution.
- Return type:
- property dims
All dimension (variable) names as a tuple, in array order.
- property shape
Shape of the underlying array.
- property all_vars
All variable names as a frozenset.
- property alphabet
Tuple of alphabets, one per dimension (in array-dimension order).
This mirrors
dit.Distribution.alphabet.
- property outcomes
Tuple of outcomes in lexicographic order.
When sparse (the default), only non-zero probability outcomes are included. After
make_dense(), all outcomes are included.For 1-D numerical distributions, outcomes are the coordinate values directly (e.g.
(0, 1, 2)). For multi-variable distributions or distributions with non-numeric coordinates, each outcome is a tuple whose elements correspond to the dimensions indimsorder.
- property pmf
1-D numpy array of probabilities corresponding to
outcomes.Returns values in the current base (log if the distribution is in log space, linear otherwise), matching
dit.Distribution.pmf.
- to_dict()[source]
Return a dictionary mapping outcomes to probabilities.
- Returns:
d –
{outcome_tuple: float}- Return type:
- is_joint()[source]
True if this distribution describes more than one random variable.
Returns False for 1-D distributions (single RV) and conditional distributions.
- is_numerical()[source]
True if all coordinate values across all dimensions are numeric.
When True, operations like
mean(),std(), andvariance()are well-defined.
- is_symbolic()[source]
True if the probabilities are symbolic (sympy) expressions.
Symbolic distributions store their pmf as an
object-dtype array of sympy expressions rather than floats, enabling exact/algebraic computation of information measures.
- atoms(patoms=False)[source]
Yield atoms of the probability space.
- Parameters:
patoms (bool) – If True, yield only positive-probability atoms.
- property rvs
List of RV groupings, one per free variable.
Each element is a single-element list containing a sequential integer index (0-based among the free dims), matching the
dit.Distribution.rvsconvention. Integer indices are used (rather than names) so that helpers likeflattendo not recursively split multi-character strings.
- make_dense()[source]
Switch to dense mode so that
outcomesandpmfinclude all outcomes (including zero-probability ones).- Returns:
Always returns 0 (the DataArray is inherently dense).
- Return type:
- make_sparse(trim=True)[source]
Switch to sparse mode so that
outcomesandpmfinclude only non-zero probability outcomes (the default).
- zipped(mode='pmf')[source]
Iterator over
(outcome, probability)tuples.- Parameters:
mode (str) –
'pmf'to iterate over non-zero outcomes (default),'atoms'to iterate over the full sample space,'patoms'is treated identically to'pmf'(provided fordit.Distributioncompatibility).- Yields:
outcome (scalar or tuple)
probability (float)
- to_string(digits=None, exact=None, tol=1e-09, show_mask=False, str_outcomes=False)[source]
Return a string representation compatible with dit.Distribution format.
- Parameters:
digits (int or None) – Round probabilities. None for no rounding.
exact (bool or None) – If True, display as fractions. None uses ditParams.
tol (float) – Fraction tolerance when exact=True.
show_mask (bool) – Ignored (kept for API compatibility).
str_outcomes (bool) – If True, attempt to join tuple outcomes into strings.
- to_html(digits=None, exact=None, tol=1e-09)[source]
Return an HTML representation compatible with dit.Distribution format.
- validate(atol=1e-09)[source]
Validate normalisation.
For a joint distribution
p(X,Y), the total sum should be 1. For a conditionalp(X|Y), the sum over X for each Y should be 1.- Parameters:
atol (float) – Absolute tolerance.
- Returns:
valid
- Return type:
- Raises:
ValueError – If the distribution is not properly normalised.
- marginal(*args)[source]
Marginalise to keep only the specified free variables.
Given (conditioned) variables are always kept.
Supports two call signatures:
marginal('X', 'Y')– positional variable namesmarginal(['X', 'Y'])– list of names (or integer indices)
- Parameters:
*args (str, or a single list/tuple) – The free variable names to keep. Integer indices are auto-resolved to dimension names.
- Returns:
result
- Return type:
- marginalize(*args)[source]
Marginalise out (remove) the specified free variables.
Supports two call signatures:
marginalize('X')– positional variable namesmarginalize(['X'])– list of names (or integer indices)
- Parameters:
*args (str, or a single list/tuple) – The free variable names to remove.
- Returns:
result
- Return type:
- coalesce(rvs, extract=False)[source]
Return a new distribution after coalescing random variables.
Each inner sequence in rvs defines one new random variable as a combination of original variables. The result is a joint
Distributionoverlen(rvs)new random variables whose outcomes are tuples (or the inner values whenextract=Truewith a single group).- Parameters:
rvs (sequence of sequences) – Each inner sequence contains variable names (or integer indices).
extract (bool) – If
Trueandlen(rvs) == 1, the single group’s values are used directly as outcomes instead of being wrapped in 1-tuples.
- Returns:
d
- Return type:
- condition_on(*cond_vars, rvs=None, crvs=None)[source]
Condition on the specified free variables.
Supports two call signatures:
Native:
condition_on('X', 'Y')– positional var names. Returns a single conditionalDistribution.dit-compat: any of these forms triggers the dit-compatible return format
(marginal, list_of_conditionals):condition_on(crvs=['X'], rvs=['Y'])condition_on(['X'], rvs=['Y'])(positional crvs)condition_on(crvs=['X'])
The returned list contains one
Distributionper outcome of the conditioning variable.
- Parameters:
*cond_vars (str, or a single list/tuple) – Variable names to condition on. If a single list/tuple is passed and
rvsis provided, it is interpreted ascrvs(dit-compat positional form).rvs (list, optional) – Variables to keep in the conditional (dit-compat API).
crvs (list, optional) – Variables to condition on (dit-compat API).
- Returns:
result – A single conditional distribution (native), or a
(marginal, list_of_Distributions)tuple (dit-compat).- Return type:
Examples
>>> p_xyz.condition_on('Z') # native: returns p(X,Y|Z) >>> p_xyz.condition_on('X', 'Y') # native: returns p(Z|X,Y) >>> marg, cdists = p_xyz.condition_on(crvs=['Z']) # dit-compat >>> marg, cdists = p_xyz.condition_on(['Z'], rvs=['X']) # dit-compat
- classmethod from_ndarray(ndarray, base=None, prng=None)[source]
Construct from a multi-dimensional numpy ndarray interpreted as a pmf.
Each axis represents a random variable, and the index along that axis is the variable’s value. For example, a (2, 3) array has two variables with alphabet sizes 2 and 3 respectively.
- classmethod from_rv_discrete(ssrv, base=None, prng=None)[source]
Construct from a
scipy.stats.rv_discreteinstance.- Parameters:
ssrv (scipy.stats.rv_discrete) – A frozen discrete random variable with
.xkand.pkattributes (as produced byrv_discrete(values=...)).base (str or float, optional) – Probability base. Defaults to
'linear'.prng (random state, optional)
- entropy(base=2)[source]
Compute the (conditional) entropy.
For
p(X,Y)returnsH(X,Y). Forp(X|Y)returnsH(X|Y)computed as the average per-slice entropy:(1/|Y|) * sum_y H(X|Y=y).Note: the true conditional entropy
H(X|Y) = sum_y p(y) H(X|Y=y)requires knowledge of the marginalp(Y), which is not stored. Use a joint distribution andH(X,Y) - H(Y)for the exact value.
- mutual_information(var_x, var_y, base=2)[source]
Compute the mutual information
I(X;Y).Only valid for joint distributions.
- sel(**kwargs)[source]
Fix variables to specific values (label-based selection).
- Parameters:
**kwargs – Variable-name to value mappings.
- Returns:
result – If all dimensions are selected, returns a float (probability or log probability, depending on the distribution’s base). Otherwise returns a reduced Distribution.
- Return type:
Examples
>>> p_xyz.sel(Y='0') # p(X,Z) at Y=0 (un-normalised slice) >>> p_xyz.sel(X='0', Y='1') # p(Z) at X=0,Y=1
- copy(base=None)[source]
Return a deep copy of this distribution.
- to_distribution()[source]
Deprecated. Returns self since Distribution is now the sole distribution class.
- to_numpy()[source]
Return the underlying data as a numpy array.
- Returns:
Copy of the probability values (linear or log, depending on base).
- Return type:
np.ndarray
- property DataArray
Return the underlying xr.DataArray (read-only view).
- Returns:
The probability array. Modifying it may affect this distribution.
- Return type:
xr.DataArray
- is_approx_equal(other, atol=1e-09, rtol=None)[source]
Check approximate equality of two distributions.
Compares by sample space and per-outcome probabilities, ignoring dimension names. This matches the old
dit.Distributionbehavior.- Parameters:
other (Distribution) – Distribution to compare against.
atol (float, optional) – Absolute tolerance for value comparison (default: 1e-9).
rtol (float, optional) – Ignored (kept for signature compatibility).
- Returns:
eq
- Return type:
- class DistributionEnumerator(n, k)[source]
Bases:
objectThis object enumerates all the uniform probability distributions over n variables each with cardinality k
- class RVFunctions(d)[source]
Bases:
objectHelper class for building new random variables.
Each new random variable is a function of the existing random variables. So for each outcome in the original distribution, there can be only one possible value for the new random variable.
Some methods may make assumptions about the sample space. For example, the
xor()method assumes the sample space consists of 0-like and 1-like outcomes.- xor(indices)[source]
Returns a callable which returns the logical XOR of the given indices.
Outcomes are assumed to be strings of ‘0’ and ‘1’, or tuples of 0 and 1. The returned function handles both cases appropriately.
- Parameters:
indices (list) – A list of two indices used to take the XOR.
- Returns:
func – A callable implementing the XOR function. It receives a single argument, the outcome and returns an outcome for the calculation.
- Return type:
function
Examples
>>> d = dit.Distribution(['00', '01', '10', '11'], [1/4]*4) >>> bf = dit.RVFunctions(d) >>> d = dit.insert_rvf(d, bf.xor([0,1])) >>> d.outcomes ('000', '011', '101', '110')
- from_mapping(mapping, force=True)[source]
Returns a callable implementing a random variable via a mapping.
- Parameters:
mapping (dict) – A mapping from outcomes to values of the new random variable.
force (bool) – Ideally, the values of mapping should be satisfy the requirements of all outcomes (hashable, ordered sequences), but if force is True, we will attempt to use the distribution’s outcome constructor and make sure that they are. If they are not, then the outcomes will be placed into a 1-tuple. This is strictly a convenience for users. As an example, suppose the outcomes are strings, the values of mapping can also be strings without issue. However, if the outcomes are tuples of integers, then the values should also be tuples. When force is True, then the values can be integers and then they will be transformed into 1-tuples.
- Returns:
func – A callable implementing the desired function. It receives a single argument, the outcome, and returns an outcome for the calculation.
- Return type:
function
Examples
>>> d = dit.Distribution(['00', '01', '10', '11'], [1/4]*4) >>> bf = dit.RVFunctions(d) >>> mapping = {'00': '0', '01': '1', '10': '1', '11': '0'} >>> d = dit.insert_rvf(d, bf.from_mapping(mapping)) >>> d.outcomes ('000', '011', '101', '110')
Same example as above but now with tuples.
>>> d = dit.Distribution([(0,0), (0,1), (1,0), (1,1)], [1/4]*4) >>> bf = dit.RVFunctions(d) >>> mapping = {(0,0): 0, (0,1): 1, (1,0): 1, (1,1): 0} >>> d = dit.insert_rvf(d, bf.from_mapping(mapping, force=True)) >>> d.outcomes ((0, 0, 0), (0, 1, 1), (1, 0, 1), (1, 1, 0))
See also
- from_partition(partition)[source]
Returns a callable implementing a function specified by a partition.
The partition must divide the sample space of the distribution. The number of equivalence classes, n, determines the number of values for the random variable. The values are integers from 0 to n-1, but if the outcome class of the distribution is string, then this function will use the first n letters of
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'as values for the random variable. So random variables with more than 62 outcomes are not supported by this function.- Parameters:
partition (list) – A list of iterables. The outer list is required to determine the order of the new random variable
- Returns:
func – A callable implementing the desired function. It receives a single argument, the outcome, and returns an outcome of the new random variable that is specified by the partition.
- Return type:
function
Examples
>>> d = dit.Distribution(['00', '01', '10', '11'], [1/4]*4) >>> bf = dit.RVFunctions(d) >>> partition = (('00','11'), ('01', '10')) >>> d = dit.insert_rvf(d, bf.from_partition(partition)) >>> d.outcomes ('000', '011', '101', '110')
- from_hexes(hexes)[source]
Returns a callable implementing a boolean function on up to 4-bits.
Outcomes are assumed to be strings of ‘0’ and ‘1’, or tuples of 0 and 1. The returned function handles both cases appropriately.
The original outcomes are represented in base-16 as one of the letters in ‘0123456789ABCDEF’ (not case sensitive). Then, each boolean function is a specification of the outcomes for which it be should true. The random variable will be false for the complement of this set—so this function additional assumes full support. For example, if the random variable is a function of 3-bits and should be true only for the outcomes 2=’010’ or 7=’111’, then hexes should be ‘27’. This nicely handles 1- and 2-, and 4-bit inputs in a similar fashion.
- Parameters:
hexes (str) – A string of base-16 characters, each element represents an (up to) 4-bit outcome for which the random variable should be true.
- Returns:
func – A callable implementing the desired function. It receives a single argument, the outcome, and returns an outcome for the calculation.
- Return type:
function
Examples
>>> outcomes = ['000', '001', '010', '011', '100', '101', '110', '111'] >>> pmf = [1/8] * 8 >>> d = dit.Distribution(outcomes, pmf) >>> bf = dit.RVFunctions(d) >>> d = dit.insert_rvf(d, bf.from_hexes('27')) >>> d.outcomes ('0000', '0010', '0101', '0110', '1000', '1010', '1100', '1111')
- class SampleSpace(samplespace, product=None)[source]
Bases:
ScalarSampleSpaceAn abstract representation of a sample space.
A sized, iterable, container.
- coalesce(rvs, extract=False)[source]
Returns a new sample space after coalescing the specified indexes.
Given n lists, each consisting of indexes from the original sample space, the coalesced sample space has n indexes, where the alphabet for each index is a tuple of the indexes that defined the ith list.
- Parameters:
rvs (sequence) – A sequence whose elements are also sequences. Each inner sequence defines an index in the new distribution as a combination of indexes in the original distribution. The length of rvs must be at least one. The inner sequences need not be pairwise mutually exclusive with one another, and each can contain repeated indexes.
extract (bool) – If the length of rvs is 1 and extract is True, then instead of the new outcomes being 1-tuples, we extract the sole element to create a sample space over the indexes in rvs[0].
- Returns:
ss – The coalesced sample space.
- Return type:
Examples
- If we have a joint distribution over 3 random variables such as:
Z = (X,Y,Z)
- and would like a new sample space over 6 random variables:
Z = (X,Y,Z,X,Y,Z)
- then this is achieved as:
coalesce([[0,1,2,0,1,2]], extract=True)
- If you want:
Z = ((X,Y), (Y,Z))
- Then you do:
coalesce([[0,1],[1,2]])
Notes
Generally, the outcomes of the new sample space will be tuples instead of matching the class of the original sample space. This is because some classes are not recursive containers. For example, one cannot have a string of strings where each string consists of more than one character. Note however, that it is perfectly valid to have a tuple of tuples. Either way, the elements within each tuple of the sample space will still match the class of the original sample space.
See also
- marginal(rvs)[source]
Returns a marginal distribution.
- all_dist_structures(outcome_length, alphabet_size)[source]
Return an iterator of distributions over the 2**(alphabet_size`**`outcome_length) possible combinations of joint events.
- copypmf(d, base=None, mode='asis')[source]
Returns a NumPy array of the distribution’s pmf.
- Parameters:
d (distribution) – The distribution from which the pmf is copied.
base (float, 'linear', 'e', None) – The desired base of the probabilities. If None, then the probabilities maintain their current base.
mode (['dense', 'sparse', 'asis']) – A specification of how the pmf should be construted. ‘dense’ means that the pmf should contain the entire sample space. ‘sparse’ means the pmf should only contain nonnull probabilities. ‘asis’ means to make a copy of the pmf, as it exists in the distribution.
- Returns:
pmf – The pmf of the distribution.
- Return type:
NumPy array
- distribution_from_bayesnet(digraph, nodes=None, sample_space=None, attr='dist')[source]
Returns a distribution built from a Bayesian network.
Each node represents a random variable
X_i. Every node must store its conditional probability distributionP(X_i | Y_i)whereY_irepresents the parents ofX_i. If a node has no in-degree, then it must store the probability distributionP(X_i).- Parameters:
digraph (NetworkX digraph) – A directed graph, representing the Bayesian graphical model.
nodes (list, None) – The order of the nodes that will determine the random variable order. If None, then we use sorted(digraph.nodes()), which assumes the nodes are sortable. The reason we assume they are sortable is because the parent values must correspond to the node order and thus, we need an unambiguous ordering that the user could have known ahead of time.
sample_space (SampleSpace) – If provided, this specifies the outcomes of the distribution to be constructed. The distributions stored on the nodes are assumed to be compatible with this space. If functions are stored on the nodes, then this parameter must be provided.
attr (str) –
The attribute for each node that holds the conditional distributions. The attribute value can take a variety of forms.
It can be a function. The function must take two arguments. The first is the value of random variable for the current node. The second is a dictionary of keyed by parents whose values are the values of the random variables corresponding to the parents. The function should return the probability P(node_val|parent_vals).
It can be a list, such as [parents, dists], that holds the parents and the conditional distributions: dists[i] = P(X | Y_i = parents[i]). It can also be a dict-like structure so that dists[y] is a distribution representing P(X | Y = y)`. If the node has no in-degree, then the attribute value should store the distribution only. When using distributions, each should have random variable names assigned that match the nodes in the graph, or alternatively, all nodes in the graph should be integers and then random variable names are not necessary. The order of elements within these lists (or the dict) does not matter, but for nodes that have parents, the order of the random variables that specify the parents, must match the order of
nodes. So for example, if the node order is [2, 1, 0] and node 1 has parents 0 and 2. Then the parents for node 1 will be such that the first element corresponds to node 2 and the second to node 0, since 2 precedes 0 in the node order. All distributions will be assumed to have the same base, and this base will determine the base of the constructed distribution.
- Returns:
dist – The joint distribution.
- Return type:
Examples
The Xor logic gate has the following structure:
>>> g = nx.DiGraph() >>> g.add_edge(0, 2) >>> g.add_edge(1, 2)
Let’s add distributions to it using functions.
>>> uniform = lambda node_val, parents: 0.5 >>> def xor(node_val, parents): ... if '1' == parents[0] == parents[1]: ... desired_output = '1' ... else: ... desired_output = '0' ... return int(node_val == desired_output) ... >>> g.nodes[0]['dist'] = uniform >>> g.nodes[1]['dist'] = uniform >>> g.nodes[2]['dist'] = xor >>> ss = ['000', '001', '010', '011', '100', '101', '110', '111'] >>> d = dit.distribution_from_bayesnet(g, sample_space=ss)
Alternatively, we could add distributions using Distribution objects.
>>> uniform = dit.uniform_distribution(1, 2) >>> sample_space1 = [(0,), (1,)] >>> one = dit.Distribution(sample_space1, [0, 1]) >>> zero = dit.Distribution(sample_space1, [1, 0]) >>> sample_space2 = [(0, 0), (0, 1), (1, 0), (1, 1)] >>> xor = [ sample_space2, [zero, one, one, zero]] >>> g.nodes[0]['dist'] = uniform >>> g.nodes[1]['dist'] = uniform >>> g.nodes[2]['dist'] = xor >>> d = dit.distribution_from_bayesnet(g)
We can add noise whenever the output would normally be 1.
>>> noisy = dit.Distribution(sample_space, [.1, .9]) >>> dists = [zero, noisy, noisy, zero] >>> g.nodes[2]['dist'][1] = dists >>> d = dit.distribution_from_bayesnet(g)
- erasure(dist, epsilon=0.5)[source]
Construct a version of dist where each variable has been passed through an erasure channel.
- Parameters:
dist (Distribution) – The distribution to fuzz.
epsilon (float, 0 <= epsilon <= 1) – The erasure probability.
- Returns:
erased – The erased distribution.
- Return type:
- expanded_samplespace(d, alphabets=None, union=True)[source]
Returns a new distribution with an expanded sample space.
Expand the sample space so that it is the Cartesian product of the alphabets for each random variable.
- Parameters:
d (distribution) – The distribution used to create the pruned distribution.
alphabets (list) – A list of alphabets, with length equal to the outcome length in d. Each alphabet specifies the alphabet to be used for a single index random variable. The sample space of the new distribution will be the Cartesian product of these alphabets.
union (bool) – If True, then the alphabet for each random variable is unioned. The unioned alphabet is then used for each random variable.
- Returns:
ed – The distribution with an expanded sample space.
- Return type:
distribution
- fejer_riesz_approximation(dist, rvs=None, order=None)[source]
Construct the order-k Fejér-Riesz approximation of a distribution, a nonnegative analogue of the Lancaster approximation.
The Lancaster approximation truncates the interaction expansion of the density ratio \(p / \pi\), where \(\pi(x) = \prod_i p(x_i)\), and so may be negative. In the spirit of the Fejér-Riesz theorem, which writes a nonnegative trigonometric polynomial as \(|h|^2\) [Fejer16], this approximation instead truncates the amplitude \(s = \sqrt{p / \pi}\) and squares the result:
\[s_k(x) = \sum_{|S| \leq k} c_S \, \mathbb{E}_\pi[s \mid x_S], \qquad q_k(x) = \frac{\pi(x) \, s_k(x)^2}{Z_k},\]with the coefficients \(c_S\) of
lancaster_approximation(). \(s_k\) is the \(L^2(\pi)\) projection of \(s\) onto functions of at most \(k\) variables [Lan69], so \(Z_k = \|s_k\|^2_\pi \leq \|s\|^2_\pi = 1\). Order 0 is the product of marginals, and \(q_k = p\) at every order when \(p\) is itself a product of marginals. Unlike the Lancaster approximation, \(q_k\) is a probability distribution, but it need not match the marginals of \(p\). No canonical literature source is known for this construction.- Parameters:
dist (Distribution) – The distribution to approximate.
rvs (list, None) – A list of lists. Each inner list specifies the indexes of the random variables forming one variable of the approximation. If None, then each random variable is its own variable.
order (int, None) – The largest interaction size k kept in the amplitude. Must satisfy 0 <= k <= n - 1. If None, defaults to n - 1.
- Returns:
approx – The dense, normalized approximation over the outcomes of dist coalesced to rvs.
- Return type:
- Raises:
ditException – Raised if order is out of range.
Examples
>>> d = dit.Distribution(['000', '010', '100', '111'], [1/4] * 4) >>> dit.fejer_riesz_approximation(d).pmf.round(4) array([0.3879, 0.0248, 0.1946, 0.0248, 0.1946, 0.0248, 0.0083, 0.1404])
- insert_rvf(d, func, index=-1)[source]
Returns a new distribution with an added random variable at index index.
The new random variable must be a function of the other random variables. By this, we mean that the entropy of the new random variable conditioned on the original random variables should be zero.
- Parameters:
dist (Distribution) – The distribution used to construct the new distribution.
func (callable | list of callable) – A function which takes a single argument—the value of the previous random variables—and returns a new random variable. Note, the return value will be added to the outcome using __add__, and so it should be a hashable, orderable sequence (as every outcome must be). If a list of callables is provided, then multiple random variables are added simultaneously and will appear in the same order as the list.
index (int) – The index at which to insert the random variable. A value of -1 is will append the random variable to the end.
- Returns:
d – The new distribution.
- Return type:
Examples
>>> d = dit.Distribution(['00', '01', '10', '11'], [1/4]*4) >>> def xor(outcome): ... return str(int(outcome[0] != outcome[1])) ... >>> d2 = dit.insert_rvf(d, xor) >>> d.outcomes ('000', '011', '101', '110')
- joint_from_factors(mdist, cdists, strict=True)[source]
Returns a joint distribution P(X,Y) built from P(X) and P(Y|X).
- Parameters:
mdist (Distribution) – The marginal distribution P(X).
cdists (list of Distribution) – The list of conditional distributions P(Y|X=x).
strict (bool) – If True, require that the marginal and conditional masks are complementary. If False, concatenate X before Y.
- Returns:
d – The joint distribution P(X,Y).
- Return type:
- kirkwood_approximation(dist, rvs=None, order=None)[source]
Construct the unnormalized generalized Kirkwood superposition approximation of a distribution.
For variables \(X_0, \ldots, X_{n-1}\) the order-\(k\) approximation truncates the Möbius (interaction) expansion of \(\log p\) at subsets of size at most \(k\) [KYKG07, Wat60]:
\[\tilde{p}_k(x) = \prod_{1 \leq |S| \leq k} p(x_S)^{c_S}, \qquad c_S = (-1)^{k-|S|} \binom{n-|S|-1}{k-|S|}.\]The default order \(k = n-1\) is the standard Kirkwood approximation, e.g. \(p(x, y) p(x, z) p(y, z) / \left(p(x) p(y) p(z)\right)\) for three variables. Order 1 is the product of marginals and order 0 is constant one. The approximation is not normalized: with \(Z\) its total mass,
kirkwood_mutual_information()is \(D_{KL}(p \| \tilde{p}_k / Z)\). Wherever a marginal used by the product vanishes, the approximation is zero. Seelancaster_approximation()for the additive analogue.- Parameters:
dist (Distribution) – The distribution to approximate.
rvs (list, None) – A list of lists. Each inner list specifies the indexes of the random variables forming one variable of the approximation. If None, then each random variable is its own variable.
order (int, None) – The largest marginal size k used by the approximation. Must satisfy 0 <= k <= n - 1. If None, defaults to n - 1.
- Returns:
approx – The dense, unnormalized approximation over the outcomes of dist coalesced to rvs.
- Return type:
- Raises:
ditException – Raised if order is out of range.
Examples
>>> d = dit.example_dists.giant_bit(3, 2) >>> dit.kirkwood_approximation(d).pmf array([1., 0., 0., 0., 0., 0., 0., 1.])
- lancaster_approximation(dist, rvs=None, order=None)[source]
Construct the order-k Lancaster approximation of a distribution, the additive analogue of the Kirkwood approximation.
Lancaster’s expansion of \(X_0, \ldots, X_{n-1}\) writes the joint distribution relative to the product of its marginals [Lan69]:
\[p(x) = \prod_i p(x_i) \sum_{T} \delta_T(x_T), \qquad \delta_T(x_T) = \sum_{S \subseteq T} (-1)^{|T|-|S|} \frac{p(x_S)}{\prod_{i \in S} p(x_i)},\]where \(\delta_\emptyset = 1\) and \(\delta_{\{i\}} = 0\). \(\prod_i p(x_i) \, \delta_{\{0, \ldots, n-1\}}\) is the Lancaster interaction measure \(\Delta_L p = \prod_i (p^*_{X_i} - p_{X_i})\) [SGB13]. Truncating the expansion at \(|T| \leq k\) gives
\[p_k(x) = \sum_{|S| \leq k} c_S \, p(x_S) \prod_{i \notin S} p(x_i), \qquad c_S = (-1)^{k-|S|} \binom{n-|S|-1}{k-|S|},\]with the same coefficients as
kirkwood_approximation(), but combined arithmetically rather than geometrically. The default order \(k = n-1\) is \(p - \Delta_L p\), e.g. for three variables\[p_2(x, y, z) = p(x, y) p(z) + p(x, z) p(y) + p(y, z) p(x) - 2 p(x) p(y) p(z).\]Orders 0 and 1 are both the product of marginals. Every order sums to one, and its marginals of size at most k agree with those of \(p\), but it may be negative. For \(n \leq 3\), the default order reproduces \(p\) whenever \(p\) factors nontrivially, although the converse fails. For \(n > 3\) even that implication fails, which motivates Streitberg’s correction [Str90]. No canonical literature source is known for orders below \(n-1\).
- Parameters:
dist (Distribution) – The distribution to approximate.
rvs (list, None) – A list of lists. Each inner list specifies the indexes of the random variables forming one variable of the approximation. If None, then each random variable is its own variable.
order (int, None) – The largest interaction size k kept by the approximation. Must satisfy 0 <= k <= n - 1. If None, defaults to n - 1.
- Returns:
approx – The dense, possibly signed, approximation over the outcomes of dist coalesced to rvs.
- Return type:
- Raises:
ditException – Raised if order is out of range.
Examples
>>> d = dit.Distribution(['000', '010', '100', '111'], [1/4] * 4) >>> dit.lancaster_approximation(d).pmf array([ 0.3125, -0.0625, 0.1875, 0.0625, 0.1875, 0.0625, 0.0625, 0.1875])
- mixture_distribution(dists, weights, merge=False)[source]
Create a mixture distribution: \(\sum p_i d_i\)
- Parameters:
dists ([Distribution]) – List of distributions to mix. Each distribution is assumed to have the same base and sample space.
weights ([float]) – List of weights to use while mixing dists. The weights are assumed to be probability represented in the base of the distributions.
merge (bool) – If True then distributions will be mixed even if they do not share the same sample space. The idea is that each of the input distributions is reinterpreted on a common, merged sample space. If False, then an exception will be raised if incompatible distributions are mixed.
- Returns:
mix – The mixture distribution.
- Return type:
- Raises:
DitException – Raised if there dists and weights have unequal lengths.
InvalidNormalization – Raised if the weights do not sum to unity.
InvalidProbability – Raised if the weights are not valid probabilities.
IncompatibleOutcome – Raised if the sample spaces for each distribution are not compatible.
- mixture_distribution2(dists, weights)[source]
Create a mixture distribution: \(\sum p_i d_i\)
This version assumes that the pmf for each distribution is of the same form, and as a result, will be faster than mixture_distribution. Explicitly, it assumes that the sample space is ordered exactly the same for each distribution and that the outcomes currently represented in the pmf are the same as well. Using it in any other case will result in incorrect output or an exception.
- Parameters:
dists ([Distribution]) – List of distributions to mix. Each distribution is assumed to have the same base and sample space.
weights ([float]) – List of weights to use while mixing dists. The weights are assumed to be probability represented in the base of the distributions.
- Returns:
mix – The mixture distribution.
- Return type:
- Raises:
DitException – Raised if there dists and weights have unequal lengths.
InvalidNormalization – Raised if the weights do not sum to unity.
InvalidProbability – Raised if the weights are not valid probabilities.
IncompatibleDistribution – Raised if the sample spaces for each distribution are not compatible.
- modify_outcomes(dist, ctor)[source]
Returns dist but with modified outcomes, after passing them to ctor.
- Parameters:
dist (Distribution, Distribution) – The distribution to be modified.
ctor (callable) – The constructor that receives an existing outcome and returns a new modified outcome.
- Returns:
d – The modified distribution.
- Return type:
Examples
Convert joint tuple outcomes to strings. >>> d = dit.uniform_distribution(3, [‘0’, ‘1’]) >>> d2 = dit.modify_outcomes(d, lambda x: ‘’.join(x))
Increment scalar outcomes by 1. >>> d = dit.uniform_scalar_distribution(5) >>> d2 = dit.modify_outcomes(d, lambda x: x + 1)
- noisy(dist, noise=0.5)[source]
Construct a noisy version of dist.
- Parameters:
dist (Distribution) – The distribution to fuzz.
noise (float, 0 <= noise <= 1) – The noise level.
- Returns:
fuzzy – The noisy distribution.
- Return type:
- product_distribution(dist, rvs=None, base=None)[source]
Returns a new distribution which is the product of marginals.
- Parameters:
dist (distribution) – The original distribution.
rvs (sequence) – A sequence whose elements are also sequences. Each inner sequence defines the marginal distribution used to create the new distribution.
base (float, 'linear', 'e') – The desired base for the distribution probabilities.
- Returns:
d – The product distribution.
- Return type:
- pruned_samplespace(d, sample_space=None)[source]
Returns a new distribution with pruned sample space.
The pruning is such that zero probability outcomes are removed.
- Parameters:
d (distribution) – The distribution used to create the pruned distribution.
sample_space (set) – A list of outcomes with zero probability that should be kept in the sample space. If None, then all outcomes with zero probability will be removed.
- Returns:
pd – The distribution with a pruned sample space.
- Return type:
distribution
- random_dist_structure(outcome_length, alphabet_size)[source]
Return a uniform distribution over a random subset of the alphabet_size`**`outcome_length possible joint events.
- Parameters:
- Returns:
d – A uniform distribution over a random subset of joint events.
- Return type:
- random_distribution(outcome_length, alphabet_size, base=None, alpha=None, prng=None)[source]
Returns a random distribution drawn uniformly from the simplex.
The distribution is sampled uniformly over the space of distributions on the n-simplex, where n is equal to alphabet_size**outcome_length. If alpha is not None, then the distribution is sampled from the Dirichlet distribution with parameter alpha.
- Parameters:
outcome_length (int) – The length of the outcomes.
alphabet_size (int, list) – The alphabet used to construct the outcomes of the distribution. If an integer, then the alphabet will consist of integers from 0 to k-1 where k is the alphabet size. If a list, then the elements are used as the alphabet.
base (float, 'linear', 'e') – The desired base for the distribution probabilities.
alpha (list | None) – The concentration parameters defining that the Dirichlet distribution used to draw the random distribution. If None, then each of the concentration parameters are set equal to 1.
- Returns:
d – A uniform sampled distribution.
- Return type:
Distribution.
- random_scalar_distribution(n, base=None, alpha=None, prng=None)[source]
Returns a random scalar distribution over n outcomes.
The distribution is sampled uniformly over the space of distributions on the n-simplex. If alpha is not None, then the distribution is sampled from the Dirichlet distribution with parameter alpha.
- Parameters:
n (int | list) – The number of outcomes, or a list containing the outcomes.
base (float, 'linear', 'e') – The desired base for the distribution probabilities.
alpha (list | None) – The concentration parameters defining that the Dirichlet distribution used to draw the random distribution. If None, then each of the concentration parameters are set equal to 1.
- simplex_grid(length, subdivisions, using=None, inplace=False)[source]
Returns a generator over distributions, determined by a grid.
The grid is “triangular” in Euclidean space.
The total number of points on the grid is:
(subdivisions + length - 1)! / (subdivisions)! / (length-1)!
and is equivalent to the total number of ways
nindistinguishable items can be placed intokdistinguishable slots, where n=`subdivisions` and k=`length`.- Parameters:
length (int) – The number of elements in each distribution. The dimensionality of the simplex is length-1.
subdivisions (int) – The number of subdivisions for the interval [0, 1]. Each component will take on values at the boundaries of the subdivisions. For example, one subdivision means each component can take the values 0 or 1 only. Two subdivisions corresponds to \([[0, 1/2], [1/2, 1]]\) and thus, each component can take the values 0, 1/2, or 1. A common use case is to exponentially increase the number of subdivisions at each level. That is, subdivisions would be: 2**0, 2**1, 2**2, 2**3, …
using (None, callable, or distribution) – If None, then scalar distributions on integers are yielded. If using is a distribution, then each yielded distribution is a copy of using with its pmf set appropriately. For other callables, a tuple of the pmf is passed to the callable and then yielded.
inplace (bool) – If True, then each yielded distribution is the same Python object, but with a new probability mass function. If False, then each yielded distribution is a unique Python object and can be safely stored for other calculations after the generator has finished. This keyword has an effect only when using is None or some distribution.
Examples
>>> list(dit.simplex_grid(2, 2, using=tuple)) [(0.0, 1.0), (0.25, 0.75), (0.5, 0.5), (0.75, 0.25), (1.0, 0.0)]
- uniform(outcomes, base=None)[source]
Produces a uniform distribution over outcomes.
- Parameters:
outcomes (iterable) – The set of outcomes with which to construct the distribution.
base (float, 'linear', 'e') – The desired base for the distribution probabilities.
- Returns:
d – A uniform distribution over outcomes.
- Return type:
- Raises:
ditException – Raised if the elements of outcomes do not all have the same length.
TypeError – Raised if outcomes is not iterable.
- uniform_distribution(outcome_length, alphabet_size, base=None)[source]
Returns a uniform distribution.
- Parameters:
outcome_length (int) – The length of the outcomes.
alphabet_size (int, list of lists) – The alphabets used to construct the outcomes of the distribution. If an integer, then the alphabet for each random variable will be the same, consisting of integers from 0 to k-1 where k is the alphabet size. If a list, then the elements are used as the alphabet for each random variable. If the list has a single element, then it will be used as the alphabet for each random variable.
base (float, 'linear', 'e') – The desired base for the distribution probabilities.
- Returns:
d – A uniform distribution.
- Return type:
Distribution.
Examples
Each random variable has the same standardized alphabet: [0,1] >>> d = dit.uniform_distribution(2, 2)
Each random variable has its own alphabet. >>> d = dit.uniform_distribution(2, [[0,1],[1,2]])
Both random variables have [‘H’,’T’] as an alphabet. >>> d = dit.uniform_distribution(2, [[‘H’,’T’]])
- uniform_like(dist)[source]
Returns a uniform distribution with the same outcome length, alphabet size, and base as dist.
- Parameters:
dist (Distribution) – The distribution to mimic.
- uniform_scalar_distribution(n, base=None)[source]
Returns a uniform scalar distribution over n outcomes.