Source code for Stats_Analysis.Base_Dist.UniformDistribution_Class
import numpy as np
from scipy.stats import uniform
from scipy.integrate import quad
import matplotlib.pyplot as plt
# Truncation is required for defining a valid probability distribution so it is built into the start rather than an optional extra
# inherently defined over a finite interval [lower_bound, upper_bound]
[docs]
class UniformDistribution:
"""
Uniform distribution probability distribution.
This class supports computation of the PDF and CDF for scalar and array inputs, defined over a finite interval [lower_bound, upper_bound].
Parameters
----------
lower_bound : float
The lower bound of the uniform distribution.
upper_bound : float
The upper bound of the uniform distribution.
Raises
------
ValueError
If lower_bound >= upper_bound.
"""
[docs]
def __init__(self, lower_bound, upper_bound):
"""
Initialize the uniform distribution over the interval [lower_bound, upper_bound].
"""
if lower_bound >= upper_bound:
raise ValueError("Lower bound must be less than upper bound.")
self.lower_bound = lower_bound
self.upper_bound = upper_bound
# Scale parameter for scipy.stats.uniform
self.scipy_scale = upper_bound - lower_bound
# Exploit scipys uniform distribution as underlying distribution
self.dist = uniform(loc=lower_bound, scale=self.scipy_scale)
[docs]
def pdf(self, X):
"""
Calculate the Probability Density Function (PDF).
Parameters
----------
X : float or np.ndarray
The value(s) at which to evaluate the PDF.
Returns
-------
float or np.ndarray
The normalized PDF value(s) which are 0 for X outside [lower_bound, upper_bound].
"""
return self.dist.pdf(X)
# In the case of the uniform there is nothing to fit/optimise so the PDF is the same
[docs]
def pdf_fitting(self, X):
"""
Calculate the Probability Density Function (PDF) for a fit.
Parameters
----------
X : float or np.ndarray
The value(s) at which to evaluate the PDF.
Returns
-------
float or np.ndarray
The normalized PDF value(s) which are 0 for X outside [lower_bound, upper_bound].
"""
return self.dist.pdf(X)
[docs]
def cdf(self, X):
"""
Compute the Cumulative Distribution Function (CDF).
Parameters
----------
X : float or np.ndarray
The value(s) at which to evaluate the CDF.
Returns
-------
float or np.ndarray
The CDF value(s).
"""
return self.dist.cdf(X)
[docs]
def cdf_fitting(self, X):
"""
Calculate the Probability Density Function (PDF) for a fit.
Parameters
----------
X : float or np.ndarray
The value(s) at which to evaluate the PDF.
Returns
-------
float or np.ndarray
The normalized PDF value(s) which are 0 for X outside [lower_bound, upper_bound].
"""
return self.dist.cdf(X)
[docs]
def normalisation_check(self):
"""
Perform a numerical integration using scipy.integrate.quad to check the normalization of the PDF.
If the PDF has been truncated:
It is first performed over the region the PDF is defined [lower_bound, upper_bound]
It is then performed over the entire real line (-infinity to infinity).
Prints the results of the numerical integrations.
"""
if self.lower_bound is None:
lower_bound = -np.inf
else:
lower_bound = self.lower_bound
if self.upper_bound is None:
upper_bound = np.inf
else:
upper_bound = self.upper_bound
if self.lower_bound is not None or self.upper_bound is not None:
print(f"Normalisation over the region the PDF is defined/truncated: [{lower_bound},{upper_bound}]")
integral_bounded, error_bounded = quad(lambda x: self.pdf(x), lower_bound, upper_bound)
print(f"Integral: {integral_bounded} \u00B1 {error_bounded}")
print(f"Normalisation over the whole real line: [infinity to infinity]")
integral_inf, error_inf = quad(lambda x: self.pdf(x), -np.inf, np.inf)
print(f"Integral: {integral_inf} \u00B1 {error_inf}")
[docs]
def plot_dist(self):
"""
Plot the PDF and CDF for the Crystal Ball distribution.
If both lower and upper bounds are set:
The PDF and CDF are plotted between[lower_bound, upper_bound].
If both lower and upper bounds are not set:
The PDF and CDF is plotted between [mu - 5*sigma, mu + 5*sigma]
"""
X = np.linspace(self.lower_bound-0.1*(self.upper_bound-self.lower_bound), self.upper_bound+0.1*(self.upper_bound-self.lower_bound), 1000)
# LHS Plot: the PDF
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(X, self.pdf(X), color='black', linestyle='-', label='PDF')
plt.xlim(X[0], X[-1])
plt.xlabel('X', fontsize=14)
plt.ylabel('Normal PDF(X)', fontsize=14)
plt.xticks(fontsize=12)
plt.yticks(fontsize=12)
plt.legend(fontsize=14)
plt.legend()
# RHS plot: the CDF
plt.subplot(1, 2, 2)
plt.plot(X, self.cdf(X), color='black', linestyle='-', label='CDF')
plt.xlim(X[0], X[-1])
plt.xlabel('X', fontsize=14)
plt.ylabel('Normal CDF(X)', fontsize=14)
plt.xticks(fontsize=12)
plt.yticks(fontsize=12)
plt.legend(fontsize=14)
plt.legend()
plt.tight_layout()
plt.show()