def test_wstat_errors(n_on, n_off, alpha, result): stat = WStatCountsStatistic(n_on, n_off, alpha) errn = stat.compute_errn() errp = stat.compute_errp() assert_allclose(errn, result[0], atol=1e-5) assert_allclose(errp, result[1], atol=1e-5)
def make_theta_squared_table(observations, theta_squared_axis, position, position_off=None): """Make theta squared distribution in the same FoV for a list of `Observation` objects. The ON theta2 profile is computed from a given distribution, on_position. By default, the OFF theta2 profile is extracted from a mirror position radially symmetric in the FOV to pos_on. The ON and OFF regions are assumed to be of the same size, so the normalisation factor between both region alpha = 1. Parameters ---------- observations: `~gammapy.data.Observations` List of observations theta_squared_axis : `~gammapy.maps.geom.MapAxis` Axis of edges of the theta2 bin used to compute the distribution position : `~astropy.coordinates.SkyCoord` Position from which the on theta^2 distribution is computed position_off : `astropy.coordinates.SkyCoord` Position from which the OFF theta^2 distribution is computed. Default: reflected position w.r.t. to the pointing position Returns ------- table : `~astropy.table.Table` Table containing the on counts, the off counts, acceptance, off acceptance and alpha for each theta squared bin. """ if not theta_squared_axis.edges.unit.is_equivalent("deg2"): raise ValueError("The theta2 axis should be equivalent to deg2") table = Table() table["theta2_min"] = theta_squared_axis.edges[:-1] table["theta2_max"] = theta_squared_axis.edges[1:] table["counts"] = 0 table["counts_off"] = 0 table["acceptance"] = 0.0 table["acceptance_off"] = 0.0 alpha_tot = np.zeros(len(table)) livetime_tot = 0 for observation in observations: separation = position.separation(observation.events.radec) counts, _ = np.histogram(separation**2, theta_squared_axis.edges) table["counts"] += counts if not position_off: # Estimate the position of the mirror position pos_angle = observation.pointing_radec.position_angle(position) sep_angle = observation.pointing_radec.separation(position) position_off = observation.pointing_radec.directional_offset_by( pos_angle + Angle(np.pi, "rad"), sep_angle) # Angular distance of the events from the mirror position separation_off = position_off.separation(observation.events.radec) # Extract the ON and OFF theta2 distribution from the two positions. counts_off, _ = np.histogram(separation_off**2, theta_squared_axis.edges) table["counts_off"] += counts_off # Normalisation between ON and OFF is one acceptance = np.ones(theta_squared_axis.nbin) acceptance_off = np.ones(theta_squared_axis.nbin) table["acceptance"] += acceptance table["acceptance_off"] += acceptance_off alpha = acceptance / acceptance_off alpha_tot += alpha * observation.observation_live_time_duration.to_value( "s") livetime_tot += observation.observation_live_time_duration.to_value( "s") alpha_tot /= livetime_tot table["alpha"] = alpha_tot stat = WStatCountsStatistic(table["counts"], table["counts_off"], table["alpha"]) table["excess"] = stat.n_sig table["sqrt_ts"] = stat.sqrt_ts table["excess_errn"] = stat.compute_errn() table["excess_errp"] = stat.compute_errp() table.meta["ON_RA"] = position.icrs.ra table.meta["ON_DEC"] = position.icrs.dec return table
def make_prof(self, sp_datasets): """ Utility to make the profile in each region Parameters ---------- sp_datasets : `~gammapy.datasets.MapDatasets` of `~gammapy.datasets.SpectrumDataset` or \ `~gammapy.datasets.SpectrumDatasetOnOff` the dataset to use for profile extraction Returns -------- results : list of dictionary the list of results (list of keys: x_min, x_ref, x_max, alpha, counts, background, excess, ts, sqrt_ts, \ err, errn, errp, ul, exposure, solid_angle) """ results = [] distance = self._get_projected_distance() for index, spds in enumerate(sp_datasets): old_model = None if spds.models is not None: old_model = spds.models spds.models = SkyModel(spectral_model=self.spectrum) e_reco = spds.counts.geom.axes["energy"].edges # ToDo: When the function to_spectrum_dataset will manage the masks, use the following line # mask = spds.mask if spds.mask is not None else slice(None) mask = slice(None) if isinstance(spds, SpectrumDatasetOnOff): stats = WStatCountsStatistic( spds.counts.data[mask][:, 0, 0], spds.counts_off.data[mask][:, 0, 0], spds.alpha.data[mask][:, 0, 0], ) else: stats = CashCountsStatistic( spds.counts.data[mask][:, 0, 0], spds.npred_background().data[mask][:, 0, 0], ) result = { "x_min": distance.edges[index], "x_max": distance.edges[index + 1], "x_ref": distance.center[index], "energy_edge": e_reco, } if isinstance(spds, SpectrumDatasetOnOff): result["alpha"] = stats.alpha result.update( { "counts": stats.n_on, "background": stats.mu_bkg, "excess": stats.n_sig, } ) result["ts"] = stats.ts result["sqrt_ts"] = stats.sqrt_ts result["err"] = stats.error * self.n_sigma if "errn-errp" in self.selection_optional: result["errn"] = stats.compute_errn(self.n_sigma) result["errp"] = stats.compute_errp(self.n_sigma) if "ul" in self.selection_optional: result["ul"] = stats.compute_upper_limit(self.n_sigma_ul) npred = spds.npred().data[mask][:, 0, 0] e_reco_lo = e_reco[:-1] e_reco_hi = e_reco[1:] flux = ( stats.n_sig / npred * spds.models[0].spectral_model.integral(e_reco_lo, e_reco_hi).value ) result["flux"] = flux result["flux_err"] = stats.error / stats.n_sig * flux if "errn-errp" in self.selection_optional: result["flux_errn"] = np.abs(result["errn"]) / stats.n_sig * flux result["flux_errp"] = result["errp"] / stats.n_sig * flux if "ul" in self.selection_optional: result["flux_ul"] = result["ul"] / stats.n_sig * flux solid_angle = spds.counts.geom.solid_angle() result["solid_angle"] = ( np.full(result["counts"].shape, solid_angle.to_value("sr")) * u.sr ) results.append(result) if old_model is not None: spds.models = old_model return results
"""Example plot showing the profile of the WStat statistic and its connection to excess errors.""" import numpy as np import matplotlib.pyplot as plt from gammapy.stats import WStatCountsStatistic count_statistic = WStatCountsStatistic(n_on=13, n_off=11, alpha=0.5) excess = count_statistic.excess errn = count_statistic.compute_errn(1.0) errp = count_statistic.compute_errp(1.0) errn_2sigma = count_statistic.compute_errn(2.0) errp_2sigma = count_statistic.compute_errp(2.0) # We compute the WStat statistic profile mu_signal = np.linspace(-2.5, 26, 100) stat_values = count_statistic._stat_fcn(mu_signal) xmin, xmax = -2.5, 26 ymin, ymax = 0, 15 plt.figure(figsize=(5, 5)) plt.plot(mu_signal, stat_values, color="k") plt.xlim(xmin, xmax) plt.ylim(ymin, ymax) plt.xlabel(r"Number of expected signal event, $\mu_{sig}$") plt.ylabel(r"WStat value, TS ") plt.hlines( count_statistic.stat_max + 1, xmin=excess + errn,