 Program Talk - Source Code Browser
```import itertools
from operator import mul

import numpy as np

from pgmpy.factors.discrete import DiscreteFactor
from pgmpy.independencies import Independencies
from pgmpy.extern.six.moves import range, zip
from pgmpy.extern import six

class JointProbabilityDistribution(DiscreteFactor):
"""
Base class for Joint Probability Distribution

Public Methods
--------------
conditional_distribution(values)
create_bayesian_model()
get_independencies()
pmap()
marginal_distribution(variables)
minimal_imap()
is_imap(model)
"""

def __init__(self, variables, cardinality, values):
"""
Initialize a Joint Probability Distribution class.

Defined above, we have the following mapping from variable
assignments to the index of the row vector in the value field:

+-----+-----+-----+-------------------------+
|  x1 |  x2 |  x3 |    P(x1, x2, x2)        |
+-----+-----+-----+-------------------------+
| x1_0| x2_0| x3_0|    P(x1_0, x2_0, x3_0)  |
+-----+-----+-----+-------------------------+
| x1_1| x2_0| x3_0|    P(x1_1, x2_0, x3_0)  |
+-----+-----+-----+-------------------------+
| x1_0| x2_1| x3_0|    P(x1_0, x2_1, x3_0)  |
+-----+-----+-----+-------------------------+
| x1_1| x2_1| x3_0|    P(x1_1, x2_1, x3_0)  |
+-----+-----+-----+-------------------------+
| x1_0| x2_0| x3_1|    P(x1_0, x2_0, x3_1)  |
+-----+-----+-----+-------------------------+
| x1_1| x2_0| x3_1|    P(x1_1, x2_0, x3_1)  |
+-----+-----+-----+-------------------------+
| x1_0| x2_1| x3_1|    P(x1_0, x2_1, x3_1)  |
+-----+-----+-----+-------------------------+
| x1_1| x2_1| x3_1|    P(x1_1, x2_1, x3_1)  |
+-----+-----+-----+-------------------------+

Parameters
----------
variables: list
List of scope of Joint Probability Distribution.
cardinality: list, array_like
List of cardinality of each variable
value: list, array_like
List or array of values of factor.
A Joint Probability Distribution's values are stored in a row
vector in the value using an ordering such that the left-most
variables as defined in the variable field cycle through their
values the fastest.

Examples
--------
>>> import numpy as np
>>> from pgmpy.factors.discrete import JointProbabilityDistribution
>>> prob = JointProbabilityDistribution(['x1', 'x2', 'x3'], [2, 2, 2], np.ones(8)/8)
>>> print(prob)
x1    x2    x3      P(x1,x2,x3)
----  ----  ----  -------------
x1_0  x2_0  x3_0         0.1250
x1_0  x2_0  x3_1         0.1250
x1_0  x2_1  x3_0         0.1250
x1_0  x2_1  x3_1         0.1250
x1_1  x2_0  x3_0         0.1250
x1_1  x2_0  x3_1         0.1250
x1_1  x2_1  x3_0         0.1250
x1_1  x2_1  x3_1         0.1250
"""
if np.isclose(np.sum(values), 1):
super(JointProbabilityDistribution, self).__init__(variables, cardinality, values)
else:
raise ValueError("The probability values doesn't sum to 1.")

def __repr__(self):
var_card = ", ".join(['{var}:{card}'.format(var=var, card=card)
for var, card in zip(self.variables, self.cardinality)])
var_card=var_card)

def __str__(self):
if six.PY2:
return self._str(phi_or_p='P', tablefmt='pqsl')
else:
return self._str(phi_or_p='P')

def marginal_distribution(self, variables, inplace=True):
"""
Returns the marginal distribution over variables.

Parameters
----------
variables: string, list, tuple, set, dict
Variable or list of variables over which marginal distribution needs
to be calculated
inplace: Boolean (default True)
If False return a new instance of JointProbabilityDistribution

Examples
--------
>>> import numpy as np
>>> from pgmpy.factors.discrete import JointProbabilityDistribution
>>> values = np.random.rand(12)
>>> prob = JointProbabilityDistribution(['x1', 'x2', 'x3'], [2, 3, 2], values/np.sum(values))
>>> prob.marginal_distribution(['x1', 'x2'])
>>> print(prob)
x1    x2      P(x1,x2)
----  ----  ----------
x1_0  x2_0      0.1502
x1_0  x2_1      0.1626
x1_0  x2_2      0.1197
x1_1  x2_0      0.2339
x1_1  x2_1      0.1996
x1_1  x2_2      0.1340
"""
return self.marginalize(list(set(list(self.variables)) -
set(variables if isinstance(
variables, (list, set, dict, tuple)) else [variables])),
inplace=inplace)

def check_independence(self, event1, event2, event3=None, condition_random_variable=False):
"""
Check if the Joint Probability Distribution satisfies the given independence condition.

Parameters
----------
event1: list
random variable whose independence is to be checked.
event2: list
random variable from which event1 is independent.
values: 2D array or list like or 1D array or list like
A 2D list of tuples of the form (variable_name, variable_state).
A 1D list or array-like to condition over randome variables (condition_random_variable must be True)
The values on which to condition the Joint Probability Distribution.
condition_random_variable: Boolean (Default false)
If true and event3 is not None than will check independence condition over random variable.

For random variables say X, Y, Z to check if X is independent of Y given Z.
event1 should be either X or Y.
event2 should be either Y or X.
event3 should Z.

Examples
--------
>>> from pgmpy.factors.discrete import JointProbabilityDistribution as JPD
>>> prob = JPD(['I','D','G'],[2,2,3],
[0.126,0.168,0.126,0.009,0.045,0.126,0.252,0.0224,0.0056,0.06,0.036,0.024])
>>> prob.check_independence(['I'], ['D'])
True
>>> prob.check_independence(['I'], ['D'], [('G', 1)])  # Conditioning over G_1
False
>>> # Conditioning over random variable G
>>> prob.check_independence(['I'], ['D'], ('G',), condition_random_variable=True)
False
"""
JPD = self.copy()
if isinstance(event1, six.string_types):
raise TypeError('Event 1 should be a list or array-like structure')

if isinstance(event2, six.string_types):
raise TypeError('Event 2 should be a list or array-like structure')

if event3:
if isinstance(event3, six.string_types):
raise TypeError('Event 3 cannot of type string')

elif condition_random_variable:
if not all(isinstance(var, six.string_types) for var in event3):
raise TypeError('Event3 should be a 1d list of strings')
event3 = list(event3)
# Using the definition of conditional independence
# If P(X,Y|Z) = P(X|Z)*P(Y|Z)
# This can be expanded to P(X,Y,Z)*P(Z) == P(X,Z)*P(Y,Z)
phi_z = JPD.marginal_distribution(event3, inplace=False).to_factor()
for variable_pair in itertools.product(event1, event2):
phi_xyz = JPD.marginal_distribution(event3 + list(variable_pair), inplace=False).to_factor()
phi_xz = JPD.marginal_distribution(event3 + [variable_pair], inplace=False).to_factor()
phi_yz = JPD.marginal_distribution(event3 + [variable_pair], inplace=False).to_factor()
if phi_xyz * phi_z != phi_xz * phi_yz:
return False
return True
else:
JPD.conditional_distribution(event3)

for variable_pair in itertools.product(event1, event2):
if (JPD.marginal_distribution(variable_pair, inplace=False) !=
JPD.marginal_distribution(variable_pair, inplace=False) *
JPD.marginal_distribution(variable_pair, inplace=False)):
return False
return True

def get_independencies(self, condition=None):
"""
Returns the independent variables in the joint probability distribution.
Returns marginally independent variables if condition=None.
Returns conditionally independent variables if condition!=None

Parameter
---------
condition: array_like
Random Variable on which to condition the Joint Probability Distribution.

Examples
--------
>>> import numpy as np
>>> from pgmpy.factors.discrete import JointProbabilityDistribution
>>> prob = JointProbabilityDistribution(['x1', 'x2', 'x3'], [2, 3, 2], np.ones(12)/12)
>>> prob.get_independencies()
(x1 _|_ x2)
(x1 _|_ x3)
(x2 _|_ x3)
"""
JPD = self.copy()
if condition:
JPD.conditional_distribution(condition)
independencies = Independencies()
for variable_pair in itertools.combinations(list(JPD.variables), 2):
if (JPD.marginal_distribution(variable_pair, inplace=False) ==
JPD.marginal_distribution(variable_pair, inplace=False) *
JPD.marginal_distribution(variable_pair, inplace=False)):
return independencies

def conditional_distribution(self, values, inplace=True):
"""
Returns Conditional Probability Distribution after setting values to 1.

Parameters
----------
values: list or array_like
A list of tuples of the form (variable_name, variable_state).
The values on which to condition the Joint Probability Distribution.
inplace: Boolean (default True)
If False returns a new instance of JointProbabilityDistribution

Examples
--------
>>> import numpy as np
>>> from pgmpy.factors.discrete import JointProbabilityDistribution
>>> prob = JointProbabilityDistribution(['x1', 'x2', 'x3'], [2, 2, 2], np.ones(8)/8)
>>> prob.conditional_distribution([('x1', 1)])
>>> print(prob)
x2    x3      P(x2,x3)
----  ----  ----------
x2_0  x3_0      0.2500
x2_0  x3_1      0.2500
x2_1  x3_0      0.2500
x2_1  x3_1      0.2500
"""
JPD = self if inplace else self.copy()
JPD.reduce(values)
JPD.normalize()
if not inplace:
return JPD

def copy(self):
"""
Returns A copy of JointProbabilityDistribution object

Examples
---------
>>> import numpy as np
>>> from pgmpy.factors.discrete import JointProbabilityDistribution
>>> prob = JointProbabilityDistribution(['x1', 'x2', 'x3'], [2, 3, 2], np.ones(12)/12)
>>> prob_copy = prob.copy()
>>> prob_copy.values == prob.values
True
>>> prob_copy.variables == prob.variables
True
>>> prob_copy.variables = 'y'
>>> prob_copy.variables == prob.variables
False
"""
return JointProbabilityDistribution(self.scope(), self.cardinality, self.values)

def minimal_imap(self, order):
"""
Returns a Bayesian Model which is minimal IMap of the Joint Probability Distribution
considering the order of the variables.

Parameters
----------
order: array-like
The order of the random variables.

Examples
--------
>>> import numpy as np
>>> from pgmpy.factors.discrete import JointProbabilityDistribution
>>> prob = JointProbabilityDistribution(['x1', 'x2', 'x3'], [2, 3, 2], np.ones(12)/12)
>>> bayesian_model = prob.minimal_imap(order=['x2', 'x1', 'x3'])
>>> bayesian_model
<pgmpy.models.models.models at 0x7fd7440a9320>
>>> bayesian_model.edges()
[('x1', 'x3'), ('x2', 'x3')]
"""
from pgmpy.models import BayesianModel

def get_subsets(u):
for r in range(len(u) + 1):
for i in itertools.combinations(u, r):
yield i

G = BayesianModel()
for variable_index in range(len(order)):
u = order[:variable_index]
for subset in get_subsets(u):
if (len(subset) < len(u) and
self.check_independence([order[variable_index]], set(u) - set(subset), subset, True)):
G.add_edges_from([(variable, order[variable_index]) for variable in subset])
return G

def is_imap(self, model):
"""
Checks whether the given BayesianModel is Imap of JointProbabilityDistribution

Parameters
-----------
model : An instance of BayesianModel Class, for which you want to
check the Imap

Returns
--------
boolean : True if given bayesian model is Imap for Joint Probability Distribution
False otherwise
Examples
--------
>>> from pgmpy.models import BayesianModel
>>> from pgmpy.factors.discrete import TabularCPD
>>> from pgmpy.factors.discrete import JointProbabilityDistribution
>>> diff_cpd = TabularCPD('diff', 2, [[0.2], [0.8]])
>>> intel_cpd = TabularCPD('intel', 3, [[0.5], [0.3], [0.2]])
...                        [[0.1,0.1,0.1,0.1,0.1,0.1],
...                         [0.1,0.1,0.1,0.1,0.1,0.1],
...                         [0.8,0.8,0.8,0.8,0.8,0.8]],
...                        evidence=['diff', 'intel'],
...                        evidence_card=[2, 3])
>>> val = [0.01, 0.01, 0.08, 0.006, 0.006, 0.048, 0.004, 0.004, 0.032,
0.04, 0.04, 0.32, 0.024, 0.024, 0.192, 0.016, 0.016, 0.128]
>>> JPD = JointProbabilityDistribution(['diff', 'intel', 'grade'], [2, 3, 3], val)
>>> JPD.is_imap(bm)
True
"""
from pgmpy.models import BayesianModel
if not isinstance(model, BayesianModel):
raise TypeError("model must be an instance of BayesianModel")
factors = [cpd.to_factor() for cpd in model.get_cpds()]
factor_prod = six.moves.reduce(mul, factors)
JPD_fact = DiscreteFactor(self.variables, self.cardinality, self.values)
if JPD_fact == factor_prod:
return True
else:
return False

def to_factor(self):
"""
Returns JointProbabilityDistribution as a DiscreteFactor object

Examples
--------
>>> import numpy as np
>>> from pgmpy.factors.discrete import JointProbabilityDistribution
>>> prob = JointProbabilityDistribution(['x1', 'x2', 'x3'], [2, 3, 2], np.ones(12)/12)
>>> phi = prob.to_factor()
>>> type(phi)
pgmpy.factors.DiscreteFactor.DiscreteFactor
"""
return DiscreteFactor(self.variables, self.cardinality, self.values)

def pmap(self):
pass
```