Coverage for python/lsst/sims/maf/metrics/moSummaryMetrics.py : 8%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import numpy as np
2import warnings
4from .moMetrics import BaseMoMetric
6__all__ = ['integrateOverH', 'ValueAtHMetric', 'MeanValueAtHMetric',
7 'MoCompletenessMetric', 'MoCompletenessAtTimeMetric']
10def integrateOverH(Mvalues, Hvalues, Hindex = 0.33):
11 """Function to calculate a metric value integrated over an Hrange, assuming a power-law distribution.
13 Parameters
14 ----------
15 Mvalues : numpy.ndarray
16 The metric values at each H value.
17 Hvalues : numpy.ndarray
18 The H values corresponding to each Mvalue (must be the same length).
19 Hindex : float, opt
20 The power-law index expected for the H value distribution.
21 Default is 0.33 (dN/dH = 10^(Hindex * H) ).
23 Returns
24 --------
25 numpy.ndarray
26 The integrated or cumulative metric values.
27 """
28 # Set expected H distribution.
29 # dndh = differential size distribution (number in this bin)
30 dndh = np.power(10., Hindex*(Hvalues-Hvalues.min()))
31 # dn = cumulative size distribution (number in this bin and brighter)
32 intVals = np.cumsum(Mvalues*dndh)/np.cumsum(dndh)
33 return intVals
36class ValueAtHMetric(BaseMoMetric):
37 """Return the metric value at a given H value.
39 Requires the metric values to be one-dimensional (typically, completeness values).
41 Parameters
42 ----------
43 Hmark : float, opt
44 The H value at which to look up the metric value. Default = 22.
45 """
46 def __init__(self, Hmark=22, **kwargs):
47 metricName = 'Value At H=%.1f' %(Hmark)
48 super(ValueAtHMetric, self).__init__(metricName=metricName, **kwargs)
49 self.Hmark = Hmark
51 def run(self, metricVals, Hvals):
52 # Check if desired H value is within range of H values.
53 if (self.Hmark < Hvals.min()) or (self.Hmark > Hvals.max()):
54 warnings.warn('Desired H value of metric outside range of provided H values.')
55 return None
56 if metricVals.shape[0] != 1:
57 warnings.warn('This is not an appropriate summary statistic for this data - need 1d values.')
58 return None
59 value = np.interp(self.Hmark, Hvals, metricVals[0])
60 return value
63class MeanValueAtHMetric(BaseMoMetric):
64 """Return the mean value of a metric at a given H.
66 Allows the metric values to be multi-dimensional (i.e. use a cloned H distribution).
68 Parameters
69 ----------
70 Hmark : float, opt
71 The H value at which to look up the metric value. Default = 22.
72 """
73 def __init__(self, Hmark=22, reduceFunc=np.mean, metricName=None, **kwargs):
74 if metricName is None:
75 metricName = 'Mean Value At H=%.1f' %(Hmark)
76 super(MeanValueAtHMetric, self).__init__(metricName=metricName, **kwargs)
77 self.Hmark = Hmark
78 self.reduceFunc = reduceFunc
80 def run(self, metricVals, Hvals):
81 # Check if desired H value is within range of H values.
82 if (self.Hmark < Hvals.min()) or (self.Hmark > Hvals.max()):
83 warnings.warn('Desired H value of metric outside range of provided H values.')
84 return None
85 value = np.interp(self.Hmark, Hvals, self.reduceFunc(metricVals.swapaxes(0, 1), axis=1))
86 return value
89class MoCompletenessMetric(BaseMoMetric):
90 """Calculate the fraction of the population that meets `threshold` value or higher.
91 This is equivalent to calculating the completeness (relative to the entire population) given
92 the output of a Discovery_N_Chances metric, or the fraction of the population that meets a given cutoff
93 value for Color determination metrics.
95 Any moving object metric that outputs a float value can thus have the 'fraction of the population'
96 with greater than X value calculated here, as a summary statistic.
98 Parameters
99 ----------
100 threshold : int, opt
101 Count the fraction of the population that exceeds this value. Default = 1.
102 nbins : int, opt
103 If the H values for the metric are not a cloned distribution, then split up H into this many bins.
104 Default 20.
105 minHrange : float, opt
106 If the H values for the metric are not a cloned distribution, then split up H into at least this
107 range (otherwise just use the min/max of the H values). Default 1.0
108 cumulative : bool, opt
109 If False, simply report the differential fractional value (or differential completeness).
110 If True, integrate over the H distribution (using IntegrateOverH) to report a cumulative fraction.
111 Default None which becomes True;
112 if metricName is set and starts with 'Differential' this will then set to False.
113 Hindex : float, opt
114 Use Hindex as the power law to integrate over H, if cumulative is True. Default 0.3.
115 """
116 def __init__(self, threshold=1, nbins=20, minHrange=1.0, cumulative=None, Hindex=0.33, **kwargs):
117 if cumulative is None: # if metricName does not start with 'differential', then cumulative->True
118 if 'metricName' not in kwargs:
119 self.cumulative = True
120 metricName = 'CumulativeCompleteness'
121 units = '<= H'
122 else: # 'metricName' in kwargs:
123 metricName = kwargs.pop('metricName')
124 if metricName.lower().startswith('differential'):
125 self.cumulative=False
126 units = '@ H'
127 else:
128 self.cumulative=True
129 units = '<= H'
130 else: # cumulative was set
131 self.cumulative = cumulative
132 if 'metricName' in kwargs:
133 metricName = kwargs.pop('metricName')
134 if metricName.lower().startswith('differential') and self.cumulative:
135 warnings.warn(f'Completeness metricName is {metricName} but cumulative is True')
136 else:
137 if self.cumulative:
138 metricName = 'CumulativeCompleteness'
139 units = '<= H'
140 else:
141 metricName = 'DifferentialCompleteness'
142 units = '@ H'
143 super().__init__(metricName=metricName, units=units, **kwargs)
144 self.threshold = threshold
145 # If H is not a cloned distribution, then we need to specify how to bin these values.
146 self.nbins = nbins
147 self.minHrange = minHrange
148 self.Hindex = Hindex
150 def run(self, metricValues, Hvals):
151 nSsos = metricValues.shape[0]
152 nHval = len(Hvals)
153 metricValH = metricValues.swapaxes(0, 1)
154 if nHval == metricValues.shape[1]:
155 # Hvals array is probably the same as the cloned H array.
156 completeness = np.zeros(len(Hvals), float)
157 for i, H in enumerate(Hvals):
158 completeness[i] = np.where(metricValH[i].filled(0) >= self.threshold)[0].size
159 completeness = completeness / float(nSsos)
160 else:
161 # The Hvals are spread more randomly among the objects (we probably used one per object).
162 hrange = Hvals.max() - Hvals.min()
163 minH = Hvals.min()
164 if hrange < self.minHrange:
165 hrange = self.minHrange
166 minH = Hvals.min() - hrange/2.0
167 stepsize = hrange / float(self.nbins)
168 bins = np.arange(minH, minH + hrange + stepsize/2.0, stepsize)
169 Hvals = bins[:-1]
170 n_all, b = np.histogram(metricValH[0], bins)
171 condition = np.where(metricValH[0] >= self.threshold)[0]
172 n_found, b = np.histogram(metricValH[0][condition], bins)
173 completeness = n_found.astype(float) / n_all.astype(float)
174 completeness = np.where(n_all==0, 0, completeness)
175 if self.cumulative:
176 completenessInt = integrateOverH(completeness, Hvals, self.Hindex)
177 summaryVal = np.empty(len(completenessInt), dtype=[('name', np.str_, 20), ('value', float)])
178 summaryVal['value'] = completenessInt
179 for i, Hval in enumerate(Hvals):
180 summaryVal['name'][i] = 'H <= %f' % (Hval)
181 else:
182 summaryVal = np.empty(len(completeness), dtype=[('name', np.str_, 20), ('value', float)])
183 summaryVal['value'] = completeness
184 for i, Hval in enumerate(Hvals):
185 summaryVal['name'][i] = 'H = %f' % (Hval)
186 return summaryVal
188class MoCompletenessAtTimeMetric(BaseMoMetric):
189 """Calculate the completeness (relative to the entire population) <= a given H as a function of time,
190 given the times of each discovery.
192 Input values of the discovery times can come from the Discovery_Time (child) metric or the
193 KnownObjects metric.
195 Parameters
196 ----------
197 times : numpy.ndarray like
198 The bins to distribute the discovery times into. Same units as the discovery time (typically MJD).
199 Hval : float, opt
200 The value of H to count completeness at (or cumulative completeness to).
201 Default None, in which case a value halfway through Hvals (the slicer H range) will be chosen.
202 cumulative : bool, opt
203 If True, calculate the cumulative completeness (completeness <= H).
204 If False, calculate the differential completeness (completeness @ H).
205 Default None which becomes 'True' unless metricName starts with 'differential'.
206 Hindex : float, opt
207 Use Hindex as the power law to integrate over H, if cumulative is True. Default 0.3.
208 """
210 def __init__(self, times, Hval=None, cumulative=None, Hindex=0.33, **kwargs):
211 self.Hval = Hval
212 self.times = times
213 self.Hindex = Hindex
214 if cumulative is None: # if metricName does not start with 'differential', then cumulative->True
215 if 'metricName' not in kwargs:
216 self.cumulative = True
217 metricName = 'CumulativeCompleteness@Time@H=%.2f' % self.Hval
218 else: # 'metricName' in kwargs:
219 metricName = kwargs.pop('metricName')
220 if metricName.lower().startswith('differential'):
221 self.cumulative=False
222 else:
223 self.cumulative=True
224 else: # cumulative was set
225 self.cumulative = cumulative
226 if 'metricName' in kwargs:
227 metricName = kwargs.pop('metricName')
228 if metricName.lower().startswith('differential') and self.cumulative:
229 warnings.warn(f'Completeness metricName is {metricName} but cumulative is True')
230 else:
231 if self.cumulative:
232 metricName = 'CumulativeCompleteness@Time@H=%.2f' % self.Hval
233 else:
234 metricName = 'DifferentialCompleteness@Time@H=%.2f' % self.Hval
235 self._setLabels()
236 super().__init__(metricName=metricName, units=self.units, **kwargs)
238 def _setLabels(self):
239 if self.Hval is not None:
240 if self.cumulative:
241 self.units = 'H <=%.1f' % (self.Hval)
242 else:
243 self.units = 'H = %.1f' % (self.Hval)
244 else:
245 self.units = 'H'
247 def run(self, discoveryTimes, Hvals):
248 if len(Hvals) != discoveryTimes.shape[1]:
249 warnings.warn("This summary metric expects cloned H distribution. Cannot calculate summary.")
250 return
251 nSsos = discoveryTimes.shape[0]
252 timesinH = discoveryTimes.swapaxes(0, 1)
253 completenessH = np.empty([len(Hvals), len(self.times)], float)
254 for i, H in enumerate(Hvals):
255 n, b = np.histogram(timesinH[i].compressed(), bins=self.times)
256 completenessH[i][0] = 0
257 completenessH[i][1:] = n.cumsum()
258 completenessH = completenessH / float(nSsos)
259 completeness = completenessH.swapaxes(0, 1)
260 if self.cumulative:
261 for i, t in enumerate(self.times):
262 completeness[i] = integrateOverH(completeness[i], Hvals)
263 # To save the summary statistic, we must pick out a given H value.
264 if self.Hval is None:
265 Hidx = len(Hvals) // 2
266 self.Hval = Hvals[Hidx]
267 self._setLabels()
268 else:
269 Hidx = np.where(np.abs(Hvals - self.Hval) == np.abs(Hvals - self.Hval).min())[0][0]
270 self.Hval = Hvals[Hidx]
271 self._setLabels()
272 summaryVal = np.empty(len(self.times), dtype=[('name', np.str_, 20), ('value', float)])
273 summaryVal['value'] = completeness[:, Hidx]
274 for i, time in enumerate(self.times):
275 summaryVal['name'][i] = '%s @ %.2f' % (self.units, time)
276 return summaryVal