Source code for nectarchain.utils.stats

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Code that implement Welford's algorithm for stats computation

This is inspired by the implementation done at https://github.com/a-mitani/welford
"""
from copy import deepcopy

import numpy as np
from ctapipe_io_nectarcam import constants as nc


[docs] class Stats: """class Stats Accumulator object for Welfords online / parallel variance algorithm. Examples -------- Example with only one variable >>> from nectarchain.utils.stats import Stats >>> s = Stats() >>> s.add(1) >>> s.add(2) >>> s.add(3) >>> s.mean 2 >>> s.std 1 """ def __init__(self, shape=(1,)): """__init__ Initialize with an optional data. For the calculation efficiency, Welford's method is not used on the initialization process. """ # Initialize instance attributes self._shape = shape self._count = np.zeros(shape, dtype=int) self._m = np.zeros(shape, dtype=float) self._s = np.zeros(shape, dtype=float) self._min = np.full(shape, np.inf) self._max = np.full(shape, -np.inf) def __str__(self): infos = "" infos += f"mean: {self.mean}" + "\n" infos += f"std: {self.stddev}" + "\n" infos += f"min: {self.min}" + "\n" infos += f"max: {self.max}" + "\n" infos += f"count: {self.count}" + "\n" infos += f"shape: {self.shape}" return infos def __repr__(self): return self.__str__() def copy(self): return deepcopy(self) def __add__(self, other): r = self.copy() r.merge(other) return r def __iadd__(self, other): self.merge(other) return self @property def shape(self): return self._shape @property def count(self): return self._count @property def mean(self): return self._m @property def variance(self): return self._getvars(ddof=1) @property def stddev(self): return np.sqrt(self._getvars(ddof=1)) @property def std(self): return self.stddev @property def min(self): return self._min @property def max(self): return self._max def get_lowcount_mask(self, mincount=3): return self._count < mincount
[docs] def add(self, element, validmask=None): """ Add entry. If mask is given, it will only update the entry from mask element Parameters ---------- element : np.array array of element to added to the stat object (must be similar shape as the Stats object) validmask : np.array array that indicate which value to use. Only element entry where validmask is True will be added. It must be a boolean array of the same shape as element """ # Welford's algorithm if validmask is None: self._count += 1 delta = element - self._m self._m += delta / self._count self._s += delta * (element - self._m) self._min = np.minimum(self._min, element) self._max = np.maximum(self._max, element) else: self._count[validmask] += 1 delta = element[validmask] - self._m[validmask] self._m[validmask] += delta / self._count[validmask] self._s[validmask] += delta * (element[validmask] - self._m[validmask]) self._min[validmask] = np.minimum(self._min, element)[validmask] self._max[validmask] = np.maximum(self._max, element)[validmask]
[docs] def merge(self, other): """Merge this accumulator with another one. Parameters ---------- other: nectarchain.utils.stats.Stats Another object of the same type which you want to combined the statistics """ if self._shape != other._shape: raise ValueError( f"Trying to merge from a different shape this: {self._shape}, " f"given: {other._shape}" ) # with warnings.catch_warnings(): count = self._count + other._count delta = self._m - other._m delta2 = delta * delta mean = (self._count * self._m + other._count * other._m) / count s = self._s + other._s + delta2 * (self._count * other._count) / count self._count = count self._m = mean self._s = s self._min = np.minimum(self._min, other._min) self._max = np.maximum(self._max, other._max)
def _getvars(self, ddof): # with warnings.catch_warnings(): # warnings.simplefilter("ignore", category=RuntimeWarning) variance = self._s / (self._count - ddof) variance[self.get_lowcount_mask(1 + ddof)] = np.nan return variance
[docs] class CameraStats(Stats): """class CameraStats Accumulator object for Welfords online / parallel variance algorithm, specialized for camera info """ def __init__(self, shape=(nc.N_GAINS, nc.N_PIXELS), *args, **kwargs): super().__init__(shape, *args, **kwargs)
[docs] class CameraSampleStats(Stats): """class CameraSampleStats Accumulator object for Welfords online / parallel variance algorithm, specialized for trace info Examples -------- Cumulating the rawdata from a run to get the average waveform:: >>> from nectarchain.utils.stats import CameraSampleStats >>> from ctapipe_io_nectarcam import NectarCAMEventSource >>> reader = NectarCAMEventSource(input_url='NectarCAM.Run4560.00??.fits.fz') >>> s = CameraSampleStats() >>> for event in reader: >>> s.add(event.r0.tel[0].waveform, >>> validmask=~evt.mon.tel[0].pixel_status.hardware_failing_pixels ) >>> print(s.mean) """ def __init__(self, shape=(nc.N_GAINS, nc.N_PIXELS, nc.N_SAMPLES), *args, **kwargs): super().__init__(shape, *args, **kwargs)