# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/main/LICENSE """ This module defines the behaviors of ``TAxis``, an axis of a histogram or profile plot. """ from __future__ import absolute_import try: from collections.abc import Sequence except ImportError: from collections import Sequence import numpy class AxisTraits(object): """ Describes read-only properties of a histogram axis. For example, ``axis.traits.discrete`` is True if the histogram has labels; False otherwise. """ def __init__(self, axis): self._axis = axis def __repr__(self): return "AxisTraits({0})".format(repr(self._axis)) @property def circular(self): """ True if the axis "wraps around" (always False for ROOT histograms). """ return False @property def discrete(self): """ True if bins are discrete: if they have string-valued labels. """ fNbins = self._axis.member("fNbins") fLabels = self._axis.member("fLabels", none_if_missing=True) return fLabels is not None and len(fLabels) == fNbins class TAxis(Sequence): def __len__(self): """ The number of bins in the axis. """ return self.member("fNbins") def __getitem__(self, where): """ Returns the label at ``where`` if it exists or the interval at ``where``. The indexing assumes that ``flow=False``. """ fNbins = self.member("fNbins") fXbins = self.member("fXbins", none_if_missing=True) fLabels = self.member("fLabels", none_if_missing=True) if fLabels is not None and len(fLabels) == fNbins: return str(fLabels[where]) elif fXbins is None or len(fXbins) != fNbins + 1: fXmin, fXmax = self.member("fXmin"), self.member("fXmax") low = (fXmax - fXmin) * (where) / float(fNbins) + fXmin high = (fXmax - fXmin) * (where + 1) / float(fNbins) + fXmin return low, high else: return fXbins[where], fXbins[where + 1] def __iter__(self): """ Iterate over the output of ``__getitem__``. """ fNbins = self.member("fNbins") fLabels = self.member("fLabels", none_if_missing=True) if fLabels is not None and len(fLabels) == fNbins: for x in fLabels: yield str(x) else: for low, high in self.intervals(): yield low, high def __eq__(self, other): """ Two axes are equal if they have the same type and ``list(self) == list(other)``. """ if type(self) is not type(other): return False self_fNbins = self.member("fNbins") other_fNbins = other.member("fNbins") if self_fNbins != other_fNbins: return False self_fLabels = self.member("fLabels", none_if_missing=True) other_fLabels = other.member("fLabels", none_if_missing=True) self_labeled = self_fLabels is not None and len(self_fLabels) == self_fNbins other_labeled = other_fLabels is not None and len(other_fLabels) == other_fNbins if self_labeled and other_labeled: return all(x == y for x, y in zip(self_fLabels, other_fLabels)) elif not self_labeled and not other_labeled: return numpy.array_equal(self.edges(), other.edges()) else: return False def __ne__(self, other): """ Some versions of Python don't automatically negate __eq__. """ return not self.__eq__(other) @property def traits(self): """ Describes read-only properties of a histogram axis. For example, ``axis.traits.discrete`` is True if the histogram has labels; False otherwise. """ return AxisTraits(self) @property def low(self): """ The low edge of the first normal (finite-width) bin. For ROOT histograms, numerical edges exist even if the axis also has string-valued labels. """ return self.member("fXmin") @property def high(self): """ The high edge of the last normal (finite-width) bin. For ROOT histograms, numerical edges exist even if the axis also has string-valued labels. """ return self.member("fXmax") @property def width(self): """ The average bin width (or only bin width if the binning is uniform). """ fNbins = self.member("fNbins") fXbins = self.member("fXbins", none_if_missing=True) if fXbins is None or len(fXbins) != fNbins + 1: return (self.member("fXmax") - self.member("fXmin")) / fNbins else: return self.widths().mean() def labels(self, flow=False): """ Args: flow (bool): If True, include ``"underflow"`` and ``"overflow"`` before and after the normal (finite-width) bin labels (if they exist). If string-valued labels exist, this returns them as a Python list of Python strings. Otherwise, this returns None. Setting ``flow=True`` increases the length of the output by two. """ fNbins = self.member("fNbins") fLabels = self.member("fLabels", none_if_missing=True) if fLabels is not None and len(fLabels) == fNbins: out = [str(x) for x in fLabels] if flow: return ["underflow"] + out + ["overflow"] else: return out else: return None def edges(self, flow=False): """ Args: flow (bool): If True, include ``-inf`` and ``inf`` before and after the normal (finite-width) bin edges. Returns numerical edges between bins as a one-dimensional ``numpy.ndarray`` of ``numpy.float64``. Even with ``flow=False``, the number of edges is *one greater than* the number of normal (finite-width) bins because they represent "fenceposts" between the bins, including one below and one above the full range. Setting ``flow=True`` increases the length of the output by two. For ROOT histograms, numerical edges exist even if the axis also has string-valued labels. """ fNbins = self.member("fNbins") fXbins = self.member("fXbins", none_if_missing=True) if fXbins is None or len(fXbins) != fNbins + 1: fXbins = numpy.linspace( self.member("fXmin"), self.member("fXmax"), fNbins + 1 ) if flow: out = numpy.empty(fNbins + 3, dtype=numpy.float64) out[0] = -numpy.inf out[-1] = numpy.inf out[1:-1] = fXbins else: out = numpy.asarray(fXbins, dtype=fXbins.dtype.newbyteorder("=")) return out def intervals(self, flow=False): """ Args: flow (bool): If True, include ``[-inf, min]`` and ``[max, inf]`` before and after the normal (finite-width) intervals. Returns low, high pairs for each bin interval as a two-dimensional ``numpy.ndarray`` of ``numpy.float64``. With ``flow=False``, the number of intervals is equal to the number of normal (finite-width) bins. Setting ``flow=True`` increases the length of the output by two. For ROOT histograms, numerical intervals exist even if the axis also has string-valued labels. """ fNbins = self.member("fNbins") fXbins = self.member("fXbins", none_if_missing=True) if fXbins is None or len(fXbins) != fNbins + 1: fXbins = numpy.linspace( self.member("fXmin"), self.member("fXmax"), fNbins + 1 ) if flow: out = numpy.empty((fNbins + 2, 2), dtype=numpy.float64) out[0, 0] = -numpy.inf out[-1, 1] = numpy.inf out[1:, 0] = fXbins out[:-1, 1] = fXbins else: out = numpy.empty((fNbins, 2), dtype=numpy.float64) out[:, 0] = fXbins[:-1] out[:, 1] = fXbins[1:] return out def centers(self, flow=False): """ Args: flow (bool): If True, include ``-inf`` and ``inf`` before and after the normal (finite) bin centers. Returns bin center positions as a one-dimensional ``numpy.ndarray`` of ``numpy.float64``. With ``flow=False``, the number of bin centers is equal to the number of normal (finite-width) bins. Setting ``flow=True`` increases the length of the output by two. For ROOT histograms, numerical bin centers exist even if the axis also has string-valued labels. """ edges = self.edges(flow=flow) return (edges[1:] + edges[:-1]) / 2.0 def widths(self, flow=False): """ Args: flow (bool): If True, include ``-inf`` and ``inf`` before and after the normal (finite) bin widths. Returns bin widths as a one-dimensional ``numpy.ndarray`` of ``numpy.float64``. With ``flow=False``, the number of bin widths is equal to the number of normal (finite-width) bins. Setting ``flow=True`` increases the length of the output by two. For ROOT histograms, numerical bin widths exist even if the axis also has string-valued labels. """ fNbins = self.member("fNbins") fXbins = self.member("fXbins", none_if_missing=True) if not flow and (fXbins is None or len(fXbins) != fNbins + 1): width = (self.member("fXmax") - self.member("fXmin")) / fNbins return numpy.broadcast_to(width, (fNbins,)) else: edges = self.edges(flow=flow) return edges[1:] - edges[:-1]