"""A module to aid working with histograms."""
from __future__ import annotations
# stdlib
import logging
from typing import Tuple, Optional, Union, Iterable
# ext
import numpy as np
log = logging.getLogger(__name__)
[docs]class SystematicComparison:
"""Systematic template histogram comparison.
Attributes
----------
nominal : numpy.ndarray
Nominal histogram bin counts.
up : numpy.ndarray
Up variation histogram bin counts.
down : numpy.ndarray
Down variation histogram bin counts.
percent_diff_up : numpy.ndarray
Percent difference between nominal and up varation.
percent_diff_down : numpy.ndaray
Percent difference between nominald and down variation.
"""
def __init__(
self,
nominal: np.ndaray,
up: np.ndarray,
down: np.ndarray,
) -> None:
"""Class constructor."""
self.nominal = nominal
self.up = up
self.down = down
self.percent_diff_up = (up - nominal) / nominal * 100.0
self.percent_diff_down = (down - nominal) / nominal * 100.0
@property
def percent_diff_min(self) -> float:
"""float: minimum for percent difference."""
return np.amin([self.percent_diff_up, self.percent_diff_down])
@property
def percent_diff_max(self) -> float:
"""float: maximum for percent difference."""
return np.amax([self.percent_diff_up, self.percent_diff_down])
@property
def template_max(self) -> float:
"""float: maximum height of a variation."""
return np.amax([self.up, self.down])
[docs] @staticmethod
def one_sided(nominal: np.ndarray, up: np.ndarray) -> SystematicComparison:
"""Generate components of a systematic comparion plot.
Parameters
----------
nominal : numpy.ndarray
Histogram bin counts for the nominal template.
up : numpy.ndarray
Histogram bin counts for the "up" variation.
Returns
-------
SystematicComparison
The complete description of the comparison
"""
diffs = nominal - up
down = nominal + diffs
return SystematicComparison(nominal, up, down)
[docs]def bin_centers(bin_edges: np.ndarray) -> np.ndarray:
"""Get bin centers given bin edges.
Parameters
----------
bin_edges : numpy.ndarray
edges defining binning
Returns
-------
numpy.ndarray
the centers associated with the edges
Examples
--------
>>> import numpy as np
>>> from tdub.hist import bin_centers
>>> bin_edges = np.linspace(25, 225, 11)
>>> centers = bin_centers(bin_edges)
>>> bin_edges
array([ 25., 45., 65., 85., 105., 125., 145., 165., 185., 205., 225.])
>>> centers
array([ 35., 55., 75., 95., 115., 135., 155., 175., 195., 215.])
"""
return (bin_edges[1:] + bin_edges[:-1]) * 0.5
[docs]def edges_and_centers(
bins: Union[int, Iterable], range: Optional[Tuple[float, float]] = None
) -> np.array:
"""Create arrays for edges and bin centers.
Parameters
----------
bins : int or sequence of scalers
the number of bins or sequence representing bin edges
range : tuple(float, float), optional
the minimum and maximum defining the bin range (used if bins is integral)
Returns
-------
:py:obj:`numpy.ndarray`
the bin edges
:py:obj:`numpy.ndarray`
the bin centers
Examples
--------
from bin multiplicity and a range
>>> from tdub.hist import edges_and_centers
>>> edges, centers = edges_and_centers(bins=20, range=(25, 225))
from pre-existing edges
>>> edges, centers = edges_and_centers(np.linspace(0, 10, 21))
"""
if isinstance(bins, int):
if range is None:
raise ValueError("for integral bins we require the range argument")
edges = np.linspace(range[0], range[1], bins + 1)
else:
edges = np.asarray(bins)
if not np.all(edges[1:] >= edges[:-1]):
raise ValueError("bins edges must monotonically increase")
centers = bin_centers(edges)
return edges, centers