Source code for pgmpy.factors.continuous.discretize

from abc import ABCMeta, abstractmethod

import numpy as np
from scipy import integrate


[docs] class BaseDiscretizer(ABCMeta): """ Base class for the discretizer classes in pgmpy. The discretizer classes are used to discretize a continuous random variable distribution into discrete probability masses. Parameters ---------- factor: A ContinuousNode or a ContinuousFactor object the continuous node or factor representing the distribution to be discretized. low, high: float the range over which the function will be discretized. cardinality: int the number of states required in the discretized output. Examples -------- >>> from scipy.stats import norm >>> from pgmpy.factors.continuous import ContinuousNode >>> normal = ContinuousNode(norm(0, 1).pdf) >>> from pgmpy.discretize import BaseDiscretizer >>> class ChildDiscretizer(BaseDiscretizer): ... def get_discrete_values(self): ... pass >>> discretizer = ChildDiscretizer(normal, -3, 3, 10) >>> discretizer.factor <pgmpy.factors.continuous.ContinuousNode.ContinuousNode object at 0x04C98190> >>> discretizer.cardinality 10 >>> discretizer.get_labels() ['x=-3.0', 'x=-2.4', 'x=-1.8', 'x=-1.2', 'x=-0.6', 'x=0.0', 'x=0.6', 'x=1.2', 'x=1.8', 'x=2.4'] """ def __init__(self, factor, low, high, cardinality): self.factor = factor self.low = low self.high = high self.cardinality = cardinality
[docs] @abstractmethod def get_discrete_values(self): """ This method implements the algorithm to discretize the given continuous distribution. It must be implemented by all the subclasses of BaseDiscretizer. Returns ------- A list of discrete values or a DiscreteFactor object. """ pass
[docs] def get_labels(self): """ Returns a list of strings representing the values about which the discretization method calculates the probability masses. Default value is the points - [low, low+step, low+2*step, ......... , high-step] unless the method is overridden by a subclass. Examples -------- >>> from pgmpy.factors import ContinuousNode >>> from pgmpy.discretize import BaseDiscretizer >>> class ChildDiscretizer(BaseDiscretizer): ... def get_discrete_values(self): ... pass >>> from scipy.stats import norm >>> node = ContinuousNode(norm(0).pdf) >>> child = ChildDiscretizer(node, -5, 5, 20) >>> chld.get_labels() ['x=-5.0', 'x=-4.5', 'x=-4.0', 'x=-3.5', 'x=-3.0', 'x=-2.5', 'x=-2.0', 'x=-1.5', 'x=-1.0', 'x=-0.5', 'x=0.0', 'x=0.5', 'x=1.0', 'x=1.5', 'x=2.0', 'x=2.5', 'x=3.0', 'x=3.5', 'x=4.0', 'x=4.5'] """ step = (self.high - self.low) / self.cardinality labels = [ f"x={str(i)}" for i in np.round(np.arange(self.low, self.high, step), 3) ] return labels
[docs] class RoundingDiscretizer(BaseDiscretizer): """ This class uses the rounding method for discretizing the given continuous distribution. For the rounding method, The probability mass is, cdf(x+step/2)-cdf(x), for x = low cdf(x+step/2)-cdf(x-step/2), for low < x <= high where, cdf is the cumulative density function of the distribution and step = (high-low)/cardinality. Examples -------- >>> import numpy as np >>> from pgmpy.factors.continuous import ContinuousNode >>> from pgmpy.factors.continuous import RoundingDiscretizer >>> std_normal_pdf = lambda x : np.exp(-x*x/2) / (np.sqrt(2*np.pi)) >>> std_normal = ContinuousNode(std_normal_pdf) >>> std_normal.discretize(RoundingDiscretizer, low=-3, high=3, ... cardinality=12) [0.001629865203424451, 0.009244709419989363, 0.027834684208773178, 0.065590616803038182, 0.120977578710013, 0.17466632194020804, 0.19741265136584729, 0.17466632194020937, 0.12097757871001302, 0.065590616803036905, 0.027834684208772664, 0.0092447094199902269] """
[docs] def get_discrete_values(self): step = (self.high - self.low) / self.cardinality # for x=[low] discrete_values = [ self.factor.cdf(self.low + step / 2) - self.factor.cdf(self.low) ] # for x=[low+step, low+2*step, ........., high-step] points = np.linspace(self.low + step, self.high - step, self.cardinality - 1) discrete_values.extend( [ self.factor.cdf(i + step / 2) - self.factor.cdf(i - step / 2) for i in points ] ) return discrete_values
[docs] class UnbiasedDiscretizer(BaseDiscretizer): """ This class uses the unbiased method for discretizing the given continuous distribution. The unbiased method for discretization is the matching of the first moment method. It involves calculating the first order limited moment of the distribution which is done by the _lim_moment method. For this method, The probability mass is, (E(x) - E(x + step))/step + 1 - cdf(x), for x = low (2 * E(x) - E(x - step) - E(x + step))/step, for low < x < high (E(x) - E(x - step))/step - 1 + cdf(x), for x = high where, E(x) is the first limiting moment of the distribution about the point x, cdf is the cumulative density function and step = (high-low)/cardinality. References ---------- Klugman, S. A., Panjer, H. H. and Willmot, G. E., Loss Models, From Data to Decisions, Fourth Edition, Wiley, section 9.6.5.2 (Method of local moment matching) and exercise 9.41. Examples -------- >>> import numpy as np >>> from pgmpy.factors import ContinuousNode >>> from pgmpy.factors.continuous import UnbiasedDiscretizer # exponential distribution with rate = 2 >>> exp_pdf = lambda x: 2*np.exp(-2*x) if x>=0 else 0 >>> exp_node = ContinuousNode(exp_pdf) >>> exp_node.discretize(UnbiasedDiscretizer, low=0, high=5, cardinality=10) [0.39627368905806137, 0.4049838434034298, 0.13331784003148325, 0.043887287876647259, 0.014447413395300212, 0.0047559685431339703, 0.0015656350182896128, 0.00051540201980112557, 0.00016965346326140994, 3.7867260839208328e-05] """
[docs] def get_discrete_values(self): lev = self._lim_moment step = (self.high - self.low) / (self.cardinality - 1) # for x=[low] discrete_values = [ (lev(self.low) - lev(self.low + step)) / step + 1 - self.factor.cdf(self.low) ] # for x=[low+step, low+2*step, ........., high-step] points = np.linspace(self.low + step, self.high - step, self.cardinality - 2) discrete_values.extend( [(2 * lev(i) - lev(i - step) - lev(i + step)) / step for i in points] ) # for x=[high] discrete_values.append( (lev(self.high) - lev(self.high - step)) / step - 1 + self.factor.cdf(self.high) ) return discrete_values
def _lim_moment(self, u, order=1): """ This method calculates the kth order limiting moment of the distribution. It is given by - E(u) = Integral (-inf to u) [ (x^k)*pdf(x) dx ] + (u^k)(1-cdf(u)) where, pdf is the probability density function and cdf is the cumulative density function of the distribution. Reference --------- Klugman, S. A., Panjer, H. H. and Willmot, G. E., Loss Models, From Data to Decisions, Fourth Edition, Wiley, definition 3.5 and equation 3.8. Parameters ---------- u: float The point at which the moment is to be calculated. order: int The order of the moment, default is first order. """ def fun(x): return np.power(x, order) * self.factor.pdf(x) return integrate.quad(fun, -np.inf, u)[0] + np.power(u, order) * ( 1 - self.factor.cdf(u) )
[docs] def get_labels(self): labels = list( f"x={str(i)}" for i in np.round(np.linspace(self.low, self.high, self.cardinality), 3) ) return labels