PlotInfo.py
Go to the documentation of this file.
1 
2 """
3  models/PlotInfo.py:
4  Models related to storage of plot internal information.
5 
6  Original author: J. Wolcott <jwolcott@fnal.gov>
7  Date: November 2016
8 """
9 
10 import numbers
11 import sys
12 
13 import ROOT
14 
15 from models.Organizational import PlotSet
16 
18  """ Stores exposure (POT and/or livetime) information for a plot """
19 
20  EXPOSURE_TYPES = [
21  "POT",
22  "livetime",
23  ]
24  def __init__(self, **kwargs):
25 
26  exposures = {exp_type: kwargs[exp_type] if exp_type in kwargs else None for exp_type in Exposure.EXPOSURE_TYPES}
27  if not any(exposures.values()):
28  raise Exception("At least one of these exposure types must be specified to construct an Exposure: %s" % Exposure.EXPOSURE_TYPES)
29 
30  for exp_type, exposure in exposures.iteritems():
31  setattr(self, exp_type, exposure)
32 
33  def _clone(self):
34  return Exposure(**{prop: getattr(self, prop) for prop in Exposure.EXPOSURE_TYPES})
35 
36  def __iadd__(self, other):
37  assert all(hasattr(other, prop) for prop in Exposure.EXPOSURE_TYPES)
38 
39  # make sure that if an exposure type is None for one, it's also None for the other.
40  # otherwise the addition shouldn't proceed (i.e., known + unknown = ??)
41  assert all( getattr(self, prop) is None == getattr(other, prop) for prop in Exposure.EXPOSURE_TYPES )
42 
43  for prop in Exposure.EXPOSURE_TYPES:
44  if getattr(self, prop) is None:
45  continue
46 
47  setattr(self, prop, getattr(self, prop) + getattr(other, prop))
48 
49  return self
50 
51  def __imul__(self, other):
52  if not isinstance(other, numbers.Number):
53  raise TypeError("Can only multiple Exposures by scalar numbers, not %s" % type(other))
54 
55  for prop in Exposure.EXPOSURE_TYPES:
56  if getattr(self, prop) is None:
57  continue
58 
59  setattr(self, prop, other * getattr(self, prop))
60 
61  return self
62 
63  def __add__(self, other):
64  new_exp = self._clone()
65  new_exp += other
66  return new_exp
67 
68  def __mul__(self, other):
69  new_exp = self._clone()
70  new_exp *= other
71  return new_exp
72 
73  def __isub__(self, other):
74  return self + (-1) * other
75 
76  def __sub__(self, other):
77  new_exp = self._clone()
78  new_exp -= other
79  return new_exp
80 
81  def __idiv__(self, other):
82  self *= (1/other)
83  return self
84 
85  def __div__(self, other):
86  return (1/other) * self
87 
88 
90  """ Type just for collecting information about a canvas that will be shown on a page. """
91  INFORMATION = [
92  "name",
93  "titles",
94  "labels",
95  "exposures",
96  "means",
97  "nums_entries",
98  "peak_locations",
99  "RMSs",
100  ]
101 
103  def __init__(self, required_method_names, wrapper = None):
104  self.required_method_names = [required_method_names,] if isinstance(required_method_names, basestring) else required_method_names
105  self.wrapper = wrapper
106 
107  if self.wrapper is None and len(self.required_method_names) != 1:
108  raise Exception("Can't auto-construct method wrapper with multiple or unspecified required methods: '%s'" % self.required_method_names)
109 
110  def __call__(self, h):
111  if not all(hasattr(h, m) for m in self.required_method_names):
112  return None
113 
114  if self.wrapper is None:
115  return getattr(h, iter(self.required_method_names).next())()
116  else:
117  return self.wrapper(h)
118 
119  GETTERS = {
120  "exposures": _MethodWrapper("exposure", wrapper=lambda h: h.exposure),
121  "labels": _MethodWrapper("GetName"),
122  "means": _MethodWrapper(("GetMean",), wrapper=lambda h: None if isinstance(h, (ROOT.TH2, ROOT.TH3)) else h.GetMean() ),
123  "nums_entries": _MethodWrapper("GetSumOfWeights"),
124  "overflows": _MethodWrapper(("GetBinContent", "GetNbinsX"), wrapper=lambda h: None if isinstance(h, (ROOT.TH2, ROOT.TH3)) else h.GetBinContent(h.GetNbinsX()) or None),
125  "peak_locations": _MethodWrapper(("GetMaximumBin", "GetBinCenter"), wrapper=lambda h: None if isinstance(h, (ROOT.TH2, ROOT.TH3)) else h.GetBinCenter(h.GetMaximumBin())),
126  "underflows": _MethodWrapper(("GetBinContent",), wrapper=lambda h: None if isinstance(h, (ROOT.TH2, ROOT.TH3)) else h.GetBinContent(0) or None),
127  "RMSs": _MethodWrapper(("GetRMS",), wrapper=lambda h: None if isinstance(h, (ROOT.TH2, ROOT.TH3)) else h.GetRMS()),
128  "titles": _MethodWrapper("GetTitle"),
129  }
130 
131  def __init__(self, name, **kwargs):
132  self.name = name
133 
134  # maybe they gave me the plots themselves
135  if "plots" in kwargs:
136 # print kwargs["plots"]
137  for prop_name, getter in CanvasSummary.GETTERS.iteritems():
138  setattr(self, prop_name, [getter(h) for h in kwargs["plots"]])
139  del kwargs["plots"]
140 
141  self.num_plots = max( len(kwargs[prop]) for prop in CanvasSummary.INFORMATION if prop in kwargs and not isinstance(kwargs[prop], basestring) )
142 
143  # or maybe they passed me the information directly
144  for attr in CanvasSummary.INFORMATION:
145  # don't overwrite values obtained from a plot with None
146  val = kwargs[attr] if attr in kwargs else [None,]*self.num_plots
147  if any(val) or not hasattr(self, attr):
148  setattr(self, attr, val)
149  if attr in kwargs:
150  del kwargs[attr]
151 
152  if len(kwargs) > 0:
153  raise AttributeError("Unexpected argument to %s: '%s'", (self.__class__, attr))
154 
156  def __init__(self, name, p_value=None, statistic=None, other_properties={}):
157  self.name = name
158  self.statistic = statistic
159  self.p_value = p_value
160  self.other_properties = other_properties
161 
163  """ Makes and stores information about the comparison between a pair of plots that are ostensibly the same distribution. """
164 
165  COMPARISONS_TO_DO = (
166  "AndersonDarling",
167  "Chi2",
168 # "KS", # ROOT's TH1::KolgomorovTest() doesn't calculate p-values sensibly for histograms...
169  )
170 
171  SORT_STATISTIC_PRIORITY = ["Chi2", "AndersonDarling"]
172 
173  def __init__(self, plots):
174  assert len(plots) == 2, "Can only build comparisons of pairs of plots. You gave me %d plots instead..." % len(plots)
175 
176  self.plot_keys = PlotSet()
177  for k in plots:
178  self.plot_keys.add(k)
179 
181  for comparison_test in ComparisonSummary.COMPARISONS_TO_DO:
182  self.comparison_stats[comparison_test] = getattr(ComparisonSummary, comparison_test)(*plots.values())
183 
184  @staticmethod
185  def Chi2(plot1, plot2):
186  stat_container = PlotComparisonStatistic(name="#chi^{2}")
187 
188  # todo: could make this work for (e.g.) TGraphs too
189  if not all(isinstance(p, ROOT.TH1) for p in (plot1, plot2)):
190  return stat_container
191 
192  # silly to have to call the method 3 times, but that's the only way to get all the info
193  pval = plot1.Chi2Test(plot2, "WW") # just assume they're weighted. doesn't hurt if all the weights are 1
194  chi2 = plot1.Chi2Test(plot2, "WW CHI2")
195  chi2norm = plot1.Chi2Test(plot2, "WW CHI2/NDF")
196  ndf = 0 if chi2norm == 0 else chi2 / chi2norm
197 
198  stat_container.statistic = chi2
199  stat_container.p_value = pval
200  stat_container.other_properties = {"ndf": ndf}
201 
202  return stat_container
203 
204 
205  @staticmethod
206  def AndersonDarling(plot1, plot2):
207  stat_container = PlotComparisonStatistic(name="AndersonDarling")
208 
209  # todo: could make this work for (e.g.) TGraphs too
210  if not all(isinstance(p, ROOT.TH1) for p in (plot1, plot2)):
211  return stat_container
212 
213  # note that the Anderson-Darling, like most goodness-of-fit tests,
214  # doesn't make a lot of sense for > 1 dim distributions.
215  # (they're technically unbinned tests in the first place...)
216  if any(p.GetDimension() > 1 for p in (plot1, plot2)):
217  return stat_container
218 
219  # gotta pass the data in ROOT::Fit::BinData objects...
220  data_structures = []
221  for plot in (plot1, plot2):
222  val_sum = 0
223  ds = ROOT.Fit.BinData(plot.GetNbinsX())
224  for bin_num in range(1, plot.GetNbinsX()+1):
225  val = 0
226  if plot.GetBinContent(bin_num) != 0:
227  val = plot.GetBinContent(bin_num)
228  ds.Add(plot.GetXaxis().GetBinCenter(bin_num), val, plot.GetBinError(bin_num))
229 
230  val_sum += val
231 
232  # if no bins were nonzero, we can't do the Anderson-Darling comparison.
233  # also, if it will overflow when stuffed into a UInt_t, also abort.
234  # (the statistic calculator throws away empty bins, and the sum of the values,
235  # which is interpreted as a number of events,
236  # gets coerced to an integer which is used to index an array.)
237  if ds.Size() < 1 or val_sum < 1 or val_sum > 4294967295:
238  return stat_container
239 
240  data_structures.append(ds)
241 
242 
243  pval = ROOT.Double()
244  statistic = ROOT.Double()
245  args = data_structures
246  args.append(pval)
247  args.append(statistic)
248  ROOT.Math.GoFTest.AndersonDarling2SamplesTest(*args)
249  stat_container.statistic = float(statistic)
250  stat_container.p_value = float(pval)
251 
252  return stat_container
def __init__(self, name, p_value=None, statistic=None, other_properties={})
Definition: PlotInfo.py:156
def __mul__(self, other)
Definition: PlotInfo.py:68
def __idiv__(self, other)
Definition: PlotInfo.py:81
def __imul__(self, other)
Definition: PlotInfo.py:51
def __div__(self, other)
Definition: PlotInfo.py:85
def __isub__(self, other)
Definition: PlotInfo.py:73
def __init__(self, kwargs)
Definition: PlotInfo.py:24
Definition: novas.h:112
def __add__(self, other)
Definition: PlotInfo.py:63
def __init__(self, required_method_names, wrapper=None)
Definition: PlotInfo.py:103
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
def __init__(self, name, kwargs)
Definition: PlotInfo.py:131
def __iadd__(self, other)
Definition: PlotInfo.py:36
def __sub__(self, other)
Definition: PlotInfo.py:76
char name[SIZE_OF_OBJ_NAME]
Definition: novas.h:116
T max(sqlite3 *const db, std::string const &table_name, std::string const &column_name)
Definition: statistics.h:68
void next()
Definition: show_event.C:84