Esempio n. 1
0
    def estimate_min_excess(self, dataset):
        """Estimate minimum excess to reach the given significance.

        Parameters
        ----------
        dataset : `SpectrumDataset`
            Spectrum dataset

        Returns
        -------
        excess : `RegionNDMap`
            Minimal excess
        """
        n_off = dataset.counts_off.data

        stat = WStatCountsStatistic(
                n_on=np.ones_like(n_off),
                n_off=n_off,
                alpha=dataset.alpha.data)
        excess_counts =stat.excess_matching_significance(self.sigma)
        is_gamma_limited = excess_counts < self.gamma_min
        excess_counts[is_gamma_limited] = self.gamma_min
        excess = dataset.background.copy()
        excess.data = excess_counts
        return excess
Esempio n. 2
0
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)
Esempio n. 3
0
    def estimate_min_excess(self, dataset):
        """Estimate minimum excess to reach the given significance.

        Parameters
        ----------
        dataset : `SpectrumDataset`
            Spectrum dataset

        Returns
        -------
        excess : `RegionNDMap`
            Minimal excess
        """
        n_off = dataset.counts_off.data

        stat = WStatCountsStatistic(
            n_on=dataset.alpha.data * n_off, n_off=n_off, alpha=dataset.alpha.data
        )
        excess_counts = stat.n_sig_matching_significance(self.n_sigma)
        is_gamma_limited = excess_counts < self.gamma_min
        excess_counts[is_gamma_limited] = self.gamma_min
        bkg_syst_limited = (
            excess_counts < self.bkg_syst_fraction * dataset.background.data
        )
        excess_counts[bkg_syst_limited] = (
            self.bkg_syst_fraction * dataset.background.data[bkg_syst_limited]
        )
        excess = Map.from_geom(geom=dataset._geom, data=excess_counts)
        return excess
Esempio n. 4
0
def convolved_map_dataset_counts_statistics(dataset, kernel):
    """Return CountsDataset objects containing smoothed maps from the MapDataset"""
    # Kernel is modified later make a copy here
    kernel = copy.deepcopy(kernel)
    kernel.normalize("peak")

    # fft convolution adds numerical noise, to ensure integer results we call
    # np.rint
    n_on_conv = np.rint(dataset.counts.convolve(kernel.array).data)

    if isinstance(dataset, MapDatasetOnOff):
        background = dataset.background
        background.data[dataset.acceptance_off.data == 0] = 0.0
        background_conv = background.convolve(kernel.array).data

        n_off_conv = dataset.counts_off.convolve(kernel.array).data

        with np.errstate(invalid="ignore", divide="ignore"):
            alpha_conv = background_conv / n_off_conv

        return WStatCountsStatistic(n_on_conv.data, n_off_conv.data,
                                    alpha_conv.data)
    else:
        background_conv = dataset.npred().convolve(kernel.array).data
        return CashCountsStatistic(n_on_conv.data, background_conv.data)
Esempio n. 5
0
def convolved_map_dataset_counts_statistics(dataset, kernel, mask,
                                            correlate_off):
    """Return CountsDataset objects containing smoothed maps from the MapDataset"""
    # Kernel is modified later make a copy here
    kernel = copy.deepcopy(kernel)
    kernel.normalize("peak")

    # fft convolution adds numerical noise, to ensure integer results we call
    # np.rint
    n_on = dataset.counts * mask
    n_on_conv = np.rint(n_on.convolve(kernel.array).data)

    if isinstance(dataset, MapDatasetOnOff):
        n_off = dataset.counts_off * mask
        npred_sig = dataset.npred_signal() * mask
        acceptance_on = dataset.acceptance * mask
        acceptance_off = dataset.acceptance_off * mask

        npred_sig_convolve = npred_sig.convolve(kernel.array)
        acceptance_on_convolve = acceptance_on.convolve(kernel.array)
        if correlate_off:
            n_off = n_off.convolve(kernel.array)
            acceptance_off = acceptance_off.convolve(kernel.array)

        with np.errstate(invalid="ignore", divide="ignore"):
            alpha = acceptance_on_convolve / acceptance_off

        return WStatCountsStatistic(n_on_conv.data, n_off.data, alpha.data,
                                    npred_sig_convolve.data)
    else:

        npred = dataset.npred() * mask
        background_conv = npred.convolve(kernel.array)
        return CashCountsStatistic(n_on_conv.data, background_conv.data)
Esempio n. 6
0
    def info_dict(self, in_safe_energy_range=True, **kwargs):
        """Info dict with summary statistics, summed over energy

        Parameters
        ----------
        in_safe_energy_range : bool
            Whether to sum only in the safe energy range

        Returns
        -------
        info_dict : dict
            Dictionary with summary info.
        """
        info = super().info_dict(in_safe_energy_range)
        mask = self.mask_safe.data if in_safe_energy_range else slice(None)

        # TODO: handle energy dependent a_on / a_off
        info["a_on"] = self.acceptance.data[0, 0, 0].copy()

        if self.counts_off is not None:
            info["n_off"] = self.counts_off.data[mask].sum()
            info["a_off"] = self.acceptance_off.data[0, 0, 0].copy()
        else:
            info["n_off"] = 0
            info["a_off"] = 1

        info["alpha"] = self.alpha.data[0, 0, 0].copy()
        info["significance"] = WStatCountsStatistic(
            self.counts.data[mask].sum(),
            self.counts_off.data[mask].sum(),
            self.alpha.data[0, 0, 0],
        ).significance

        return info
Esempio n. 7
0
def calculate_sensitivity_lima(n_on_events, n_background, alpha, n_bins_energy,
                               n_bins_gammaness, n_bins_theta2):
    """
    Sensitivity calculation using the Li & Ma formula
    eq. 17 of Li & Ma (1983).
    https://ui.adsabs.harvard.edu/abs/1983ApJ...272..317L/abstract

    We calculate the sensitivity in bins of energy, gammaness and
    theta2

    Parameters
    ---------
    n_on_events:   `numpy.ndarray` number of ON events in the signal region
    n_background:   `numpy.ndarray` number of events in the background region
    alpha: `float` inverse of the number of off positions
    n_bins_energy: `int` number of bins in energy
    n_bins_gammaness: `int` number of bins in gammaness
    n_bins_theta2: `int` number of bins in theta2

    Returns
    ---------
    sensitivity: `numpy.ndarray` sensitivity in percentage of Crab units
    n_excesses_5sigma: `numpy.ndarray` number of excesses corresponding to 
                a 5 sigma significance

    """

    stat = WStatCountsStatistic(n_on=n_on_events,
                                n_off=n_background,
                                alpha=alpha)

    n_excesses_5sigma = stat.excess_matching_significance(5)

    for i in range(0, n_bins_energy):
        for j in range(0, n_bins_gammaness):
            for k in range(0, n_bins_theta2):
                if n_excesses_5sigma[i][j][k] < 10:
                    n_excesses_5sigma[i][j][k] = 10

                if n_excesses_5sigma[i, j,
                                     k] < 0.05 * n_background[i][j][k] / 5:
                    n_excesses_5sigma[i, j,
                                      k] = 0.05 * n_background[i][j][k] / 5

    sensitivity = n_excesses_5sigma / n_on_events * 100  # percentage of Crab

    return n_excesses_5sigma, sensitivity
Esempio n. 8
0
def calculate_sensitivity_lima_ebin(n_excesses, n_background, alpha, n_bins_energy):
    """
    Sensitivity calculation using the Li & Ma formula
    eq. 17 of Li & Ma (1983).
    https://ui.adsabs.harvard.edu/abs/1983ApJ...272..317L/abstract

    Parameters
    ---------
    n_excesses:   `numpy.ndarray` number of excess events in the signal region
    n_background: `numpy.ndarray` number of events in the background region
    alpha:        `float` inverse of the number of off positions
    n_bins_energy:`int` number of bins in energy

    Returns
    ---------
    sensitivity: `numpy.ndarray` sensitivity in percentage of Crab units
    n_excesses_5sigma: `numpy.ndarray` number of excesses corresponding to 
                a 5 sigma significance

    """

    if any(len(a) != n_bins_energy for a in (n_excesses, n_background, alpha)):
        raise ValueError(
            'Excess, background and alpha arrays must have the same length')

    stat = WStatCountsStatistic(
        n_on=np.ones_like(n_background),
        n_off=n_background,
        alpha=alpha)

    n_excesses_5sigma = stat.excess_matching_significance(5)

    for i in range(0, n_bins_energy):
        # If the excess needed to get 5 sigma is less than 10,
        # we force it to be at least 10
        if n_excesses_5sigma[i] < 10:
            n_excesses_5sigma[i] = 10
        # If the excess needed to get 5 sigma is less than 5%
        # of the background, we force it to be at least 5% of
        # the background
        if n_excesses_5sigma[i] < 0.05 * n_background[i] * alpha[i]:
            n_excesses_5sigma[i] = 0.05 * n_background[i] * alpha[i]

    sensitivity = n_excesses_5sigma / n_excesses * 100  # percentage of Crab

    return n_excesses_5sigma, sensitivity
Esempio n. 9
0
def test_wstat_basic(n_on, n_off, alpha, result):
    stat = WStatCountsStatistic(n_on, n_off, alpha)
    excess = stat.excess
    significance = stat.significance
    p_value = stat.p_value

    assert_allclose(excess, result[0])
    assert_allclose(significance, result[1], atol=1e-5)
    assert_allclose(p_value, result[2], atol=1e-5)
def test_wstat_basic(n_on, n_off, alpha, result):
    stat = WStatCountsStatistic(n_on, n_off, alpha)
    excess = stat.n_sig
    sqrt_ts = stat.sqrt_ts
    p_value = stat.p_value

    assert_allclose(excess, result[0], rtol=1e-4)
    assert_allclose(sqrt_ts, result[1], rtol=1e-4)
    assert_allclose(p_value, result[2], rtol=1e-4)
Esempio n. 11
0
def test_lima_gammapy():
    pytest.importorskip('gammapy')
    from gammapy.stats import WStatCountsStatistic
    from pyirf.statistics import li_ma_significance

    n_ons = [100, 50, 10]
    n_offs = [10, 20, 30]
    alphas = [2, 1, 0.2]
    for n_on, n_off, alpha in zip(n_ons, n_offs, alphas):
        sig_gammapy = WStatCountsStatistic(n_on, n_off, alpha).sqrt_ts
        assert np.isclose(li_ma_significance(n_on, n_off, alpha), sig_gammapy)
Esempio n. 12
0
def test_wstat_with_musig(n_on, n_off, alpha, mu_sig, result):

    stat = WStatCountsStatistic(n_on, n_off, alpha, mu_sig)
    excess = stat.excess
    significance = stat.significance
    p_value = stat.p_value
    del_ts = stat.delta_ts

    assert_allclose(excess, result[0], rtol=1e-4)
    assert_allclose(significance, result[1], rtol=1e-4)
    assert_allclose(p_value, result[2], rtol=1e-4)
    assert_allclose(del_ts, result[3], rtol=1e-4)
def test_wstat_with_musig(n_on, n_off, alpha, mu_sig, result):

    stat = WStatCountsStatistic(n_on, n_off, alpha, mu_sig)
    excess = stat.n_sig
    sqrt_ts = stat.sqrt_ts
    p_value = stat.p_value
    del_ts = stat.ts

    assert_allclose(excess, result[0], rtol=1e-4)
    assert_allclose(sqrt_ts, result[1], rtol=1e-4)
    assert_allclose(p_value, result[2], rtol=1e-4)
    assert_allclose(del_ts, result[3], rtol=1e-4)
Esempio n. 14
0
    def info_dict(self, in_safe_data_range=True):
        """Info dict with summary statistics, summed over energy

        Parameters
        ----------
        in_safe_data_range : bool
            Whether to sum only in the safe energy range

        Returns
        -------
        info_dict : dict
            Dictionary with summary info.
        """
        info = super().info_dict(in_safe_data_range)

        if self.mask_safe and in_safe_data_range:
            mask = self.mask_safe.data.astype(bool)
        else:
            mask = slice(None)

        counts_off = np.nan
        if self.counts_off is not None:
            counts_off = self.counts_off.data[mask].sum()

        info["counts_off"] = counts_off

        acceptance = 1
        if self.acceptance:
            # TODO: handle energy dependent a_on / a_off
            acceptance = self.acceptance.data[mask].sum()

        info["acceptance"] = acceptance

        acceptance_off = np.nan
        if self.acceptance_off:
            acceptance_off = acceptance * counts_off / info["background"]

        info["acceptance_off"] = acceptance_off

        alpha = np.nan
        if self.acceptance_off and self.acceptance:
            alpha = np.mean(self.alpha.data[mask])

        info["alpha"] = alpha

        info["sqrt_ts"] = WStatCountsStatistic(
            info["counts"],
            info["counts_off"],
            acceptance / acceptance_off,
        ).sqrt_ts
        info["stat_sum"] = self.stat_sum()
        return info
Esempio n. 15
0
def calculate_sensitivity_lima(n_signal, n_background, alpha):
    """
    Sensitivity calculation using the Li & Ma formula
    eq. 17 of Li & Ma (1983).
    https://ui.adsabs.harvard.edu/abs/1983ApJ...272..317L/abstract

    We calculate the sensitivity in bins of energy and
    theta2

    Parameters
    ----------
    n_on_events:   `numpy.ndarray` number of ON events in the signal region
    n_background:   `numpy.ndarray` number of events in the background region
    alpha: `float` inverse of the number of off positions
    n_bins_energy: `int` number of bins in energy
    n_bins_theta2: `int` number of bins in theta2

    Returns
    -------
    sensitivity: `numpy.ndarray` sensitivity in percentage of Crab units
    n_excesses_5sigma: `numpy.ndarray` number of excesses corresponding to
                a 5 sigma significance

    """

    stat = WStatCountsStatistic(n_on=n_signal + alpha * n_background,
                                n_off=n_background,
                                alpha=alpha)
    n_excesses_5sigma = stat.n_sig_matching_significance(5)
    n_excesses_5sigma[n_excesses_5sigma < 10] = 10
    bkg_5percent = 0.05 * n_background * alpha
    n_excesses_5sigma[n_excesses_5sigma < bkg_5percent] = bkg_5percent[
        n_excesses_5sigma < bkg_5percent]

    sensitivity = n_excesses_5sigma / (n_signal) * 100  # percentage of Crab

    return n_excesses_5sigma, sensitivity
Esempio n. 16
0
def convolved_map_dataset_counts_statistics(dataset,
                                            kernel,
                                            apply_mask_fit=False):
    """Return CountsDataset objects containing smoothed maps from the MapDataset"""
    # Kernel is modified later make a copy here
    kernel = copy.deepcopy(kernel)
    kernel.normalize("peak")

    mask = np.ones(dataset.data_shape, dtype=bool)
    if dataset.mask_safe:
        mask *= dataset.mask_safe
    if apply_mask_fit:
        mask *= dataset.mask_fit

    # fft convolution adds numerical noise, to ensure integer results we call
    # np.rint
    n_on = dataset.counts * mask
    n_on = n_on.sum_over_axes(keepdims=True)
    n_on_conv = np.rint(n_on.convolve(kernel.array).data)

    if isinstance(dataset, MapDatasetOnOff):
        background = dataset.counts_off_normalised * mask
        background.data[dataset.acceptance_off.data == 0] = 0.0
        n_off = dataset.counts_off * mask

        background = background.sum_over_axes(keepdims=True)
        n_off = n_off.sum_over_axes(keepdims=True)

        background_conv = background.convolve(kernel.array)
        n_off_conv = n_off.convolve(kernel.array)

        npred_sig = dataset.npred_sig() * mask
        npred_sig = npred_sig.sum_over_axes(keepdims=True)
        mu_sig = npred_sig.convolve(kernel.array)

        with np.errstate(invalid="ignore", divide="ignore"):
            alpha_conv = background_conv / n_off_conv

        return WStatCountsStatistic(n_on_conv.data, n_off_conv.data,
                                    alpha_conv.data, mu_sig.data)
    else:

        npred = dataset.npred() * mask
        npred = npred.sum_over_axes(keepdims=True)
        background_conv = npred.convolve(kernel.array)
        return CashCountsStatistic(n_on_conv.data, background_conv.data)
Esempio n. 17
0
def sigmax(dsets, stacked=False):
    """
    Compute the max significance from the cumulated counts in a fluctuated 
    dataset. Note: the value can change from depending on the random 
    generation (this is not comparable to the mean maximal siginificance from
    a full simulation).

    Parameters
    ----------
    dsets : list of Dataset objects
        Input datasets

    Returns
    -------
    sig : float
        Significance

    """
    import mcsim_config as mcf
    from gammapy.stats import WStatCountsStatistic

    non = 0
    noff = 0
    sigmax = -999
    for i, ds in enumerate(dsets):
        if (stacked):  # Data have already been masked
            ns = ds.excess.data.flatten().sum()
            nb = ds.background.data.flatten().sum()
        else:  # Data should be masked
            ns = ds.excess.data[ds.mask_safe].flatten().sum()
            nb = ds.background.data[ds.mask_safe].flatten().sum()
        # ns   = ds.npred_signal().data.sum() # don't use this for merged ds !!!!
        # if stacked: # mask applied, and existing mask if for 1st element
        #     nb   = ds.npred_background().data.sum()
        # else:
        #     nb   = ds.npred_background().data[ds.mask_safe].sum()
        on = (ns + nb)
        off = (nb / mcf.alpha)
        non += on
        noff += off
        wstat = WStatCountsStatistic(n_on=non, n_off=noff, alpha=mcf.alpha)
        if wstat.sqrt_ts > sigmax: sigmax = wstat.sqrt_ts

    return sigmax
Esempio n. 18
0
"""Example plot showing the profile of the Wstat statistic and its connection to significance."""
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

# 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.vlines(
    excess,
    ymin=ymin,
    ymax=count_statistic.stat_max,
    linestyle="dashed",
    color="k",
    label="Best fit",
)
plt.hlines(count_statistic.stat_max,
           xmin=xmin,
Esempio n. 19
0
    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
Esempio n. 20
0
def test_wstat_excess_matching_significance(n_off, alpha, significance,
                                            result):
    stat = WStatCountsStatistic(1, n_off, alpha)
    excess = stat.excess_matching_significance(significance)

    assert_allclose(excess, result, atol=1e-3)
Esempio n. 21
0
def test_wstat_ul(n_on, n_off, alpha, result):
    stat = WStatCountsStatistic(n_on, n_off, alpha)
    ul = stat.compute_upper_limit()

    assert_allclose(ul, result[0], atol=1e-5)
def analyze_on_off(config):
    """
    Extracts the theta2 plot of a dataset taken with ON/OFF observations
    
    Parameters
    ----------
    config_file

    """
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 8))
    LOGGER.info("Running ON/OFF analysis")
    LOGGER.info("ON data runs: %s", config['analysis']['runs_on'])
    observation_time_on, data_on = merge_dl2_runs(
        config['input']['data_tag'], config['analysis']['runs_on'],
        config['input']['columns_to_read'], 4)
    LOGGER.info("ON observation time: %s", observation_time_on)
    LOGGER.info("OFF data runs: %s", config['analysis']['runs_off'])
    observation_time_off, data_off = merge_dl2_runs(
        config['input']['data_tag'], config['analysis']['runs_off'],
        config['input']['columns_to_read'], 4)
    LOGGER.info("OFF observation time: %s", observation_time_off)
    # observation_time_ratio = observation_time_on / observation_time_off
    # LOGGER.info('Observation time ratio %s', observation_time_ratio)

    selected_data_on = filter_events(data_on, config['preselection'])
    selected_data_off = filter_events(data_off, config['preselection'])

    theta2_on = np.array(compute_theta2(selected_data_on, (0, 0)))
    theta2_off = np.array(compute_theta2(selected_data_off, (0, 0)))

    theta2_cut = config['analysis']['selection']['theta2'][0]
    n_on = np.sum(theta2_on < theta2_cut)
    n_off = np.sum(theta2_off < theta2_cut)
    LOGGER.info('Number of observed ON and OFF events are:\n %s, %s', n_on,
                n_off)

    theta2_norm_min = config['analysis']['selection']['theta2'][1]
    theta2_norm_max = config['analysis']['selection']['theta2'][2]
    n_norm_on = np.sum((theta2_on > theta2_norm_min)
                       & (theta2_on < theta2_norm_max))
    n_norm_off = np.sum((theta2_off > theta2_norm_min)
                        & (theta2_off < theta2_norm_max))
    lima_norm = n_norm_on / n_norm_off
    stat = WStatCountsStatistic(n_on, n_off, lima_norm)
    lima_significance = stat.sqrt_ts.item()
    lima_excess = stat.n_sig
    LOGGER.info('Excess is %s', lima_excess)
    LOGGER.info('Excess significance is %s', lima_significance)
    plotting.plot_1d_excess(
        [('ON data', theta2_on, 1),
         (f'OFF data X {lima_norm:.2f}', theta2_off, lima_norm)],
        lima_significance, r'$\theta^2$ [deg$^2$]', theta2_cut, ax1)

    # alpha analysis
    LOGGER.info('Perform alpha analysis')
    alpha_on = np.array(compute_alpha(selected_data_on))
    alpha_off = np.array(compute_alpha(selected_data_off))
    alpha_cut = config['analysis']['selection']['alpha'][0]
    n_on = np.sum(alpha_on < alpha_cut)
    n_off = np.sum(alpha_off < alpha_cut)
    LOGGER.info('Number of observed ON and OFFevents are:\n %s, %s', n_on,
                n_off)

    alpha_norm_min = config['analysis']['selection']['alpha'][1]
    alpha_norm_max = config['analysis']['selection']['alpha'][2]
    n_norm_on = np.sum((alpha_on > alpha_norm_min)
                       & (alpha_on < alpha_norm_max))
    n_norm_off = np.sum((alpha_off > alpha_norm_min)
                        & (alpha_off < alpha_norm_max))
    lima_norm = n_norm_on / n_norm_off
    stat = WStatCountsStatistic(n_on, n_off, lima_norm)
    lima_significance = stat.sqrt_ts.item()
    lima_excess = stat.n_sig
    LOGGER.info('Excess is %s', lima_excess)
    LOGGER.info('Excess significance is %s', lima_significance)
    plotting.plot_1d_excess(
        [('ON data', alpha_on, 1),
         (f'OFF data X {lima_norm:.2f}', alpha_off, lima_norm)],
        lima_significance, r'$\alpha$ [deg]', alpha_cut, ax2, 0, 90, 90)
    if config['output']['interactive'] is True:
        LOGGER.info(
            'Interactive mode ON, plots will be only shown, but not saved')
        plt.show()
    else:
        LOGGER.info('Interactive mode OFF, no plots will be displayed')
        plt.ioff()
        plt.savefig(f"{config['output']['directory']}/on_off.png")
        plt.close()
Esempio n. 23
0
"""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,
def analyze_wobble(config):
    """
    Extracts the theta2 plot of a dataset taken with wobble observations
    
    Parameters
    ----------
    config_file

    """

    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 8))
    n_points = config['analysis']['parameters']['n_points']
    theta2_cut = config['analysis']['selection']['theta2'][0]
    LOGGER.info(
        "Running wobble analysis with %s off-source observation points",
        n_points)
    LOGGER.info("Analyzing runs %s", config['analysis']['runs'])
    observation_time, data = merge_dl2_runs(config['input']['data_tag'],
                                            config['analysis']['runs'],
                                            config['input']['columns_to_read'])
    LOGGER.debug('\nPreselection:\n%s', config['preselection'])
    for key, value in config['preselection'].items():
        LOGGER.debug('\nParameter: %s, range: %s, value type: %s', key, value,
                     type(value))

    selected_data = filter_events(data, filters=config['preselection'])
    # Add theta2 to selected data
    true_source_position = extract_source_position(
        selected_data, config['input']['observed_source'])
    plotting.plot_wobble(true_source_position, n_points, ax1)
    named_datasets = []
    named_datasets.append(
        ('ON data',
         np.array(compute_theta2(selected_data, true_source_position)), 1))
    n_on = np.sum(named_datasets[0][1] < theta2_cut)
    n_off = 0
    rotation_angle = 360. / n_points
    origin_x = selected_data['reco_src_x']
    origin_y = selected_data['reco_src_y']
    for off_point in range(1, n_points):
        t_off_data = selected_data.copy()
        off_xy = rotate(tuple(zip(origin_x, origin_y)),
                        rotation_angle * off_point)
        t_off_data['reco_src_x'] = [xy[0] for xy in off_xy]
        t_off_data['reco_src_y'] = [xy[1] for xy in off_xy]
        named_datasets.append(
            (f'OFF {rotation_angle * off_point}',
             np.array(compute_theta2(t_off_data, true_source_position)), 1))
        n_off += np.sum(named_datasets[-1][1] < theta2_cut)

    stat = WStatCountsStatistic(n_on, n_off, 1. / (n_points - 1))

    # API change for attributes significance and excess in the new gammapy version: https://docs.gammapy.org/dev/api/gammapy.stats.WStatCountsStatistic.html
    lima_significance = stat.sqrt_ts.item()
    lima_excess = stat.n_sig
    LOGGER.info('Observation time %s', observation_time)
    LOGGER.info('Number of "ON" events %s', n_on)
    LOGGER.info('Number of "OFF" events %s', n_off)
    LOGGER.info('ON/OFF observation time ratio %s', 1. / (n_points - 1))
    LOGGER.info('Excess is %s', lima_excess)
    LOGGER.info('Li&Ma significance %s', lima_significance)
    plotting.plot_1d_excess(named_datasets, lima_significance,
                            r'$\theta^2$ [deg$^2$]', theta2_cut, ax2)

    if config['output']['interactive'] is True:
        LOGGER.info(
            'Interactive mode ON, plots will be only shown, but not saved')
        plt.show()
    else:
        LOGGER.info('Interactive mode OFF, no plots will be displayed')
        plt.ioff()
        plt.savefig(f"{config['output']['directory']}/wobble.png")
        plt.close()
Esempio n. 25
0
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
Esempio n. 26
0
    def aperture_photometry(self):
        """
        Perform an aperture photometry simulation and analysis.
        The counts are summed-up over enegy from the SpectrumDataset object
        list (The SpectrumDatasetOnOff objects are not created).
        The siginificance is computed under the assmption of a measured
        background, i.e. with possble fluctuation.
        Note that WStatCountsStatistic returns a significance with the
        sign of the excess (negative excess give negative significance).
        If the count number is not fluctuated, the excess can only be
        positive since it is obtained from a physical flux. Excess at zero
        gives a significance at zeo.

        More on the various statistics here :
            https://docs.gammapy.org/0.17/stats/fit_statistics.html

        Parameters
        ----------

        Returns
        -------
        sigma : numpy array, float
            One significance per time slice
        nxs : numpy array, float
            One excess count per time slice
        nbck : numy array, float
            One background count per time slice

        """

        sigma_vs_time = []
        # nxs   = []
        # nbck  = []
        non_vs_time  = []
        noff_vs_time = []

        non  = 0
        noff = 0
        sig  = 0
        ns   = 0
        nb   = 0

        header = True # for debuging

        # cumulate on and off counts along slices
        for ds_site in self.dset_list:

            # Cumulate on and off counts from potentially several sites
            for ds in ds_site:
                ns   = ds.npred_signal().data[ds.mask_safe].sum()
                nb   = ds.npred_background().data[ds.mask_safe].sum()

                if self.nosignal: ns = 0
                                    
                non  += (ns+nb)
                noff += (nb/mcf.alpha)
                
                if (self.dbg>2):
                    if header: print()
                    header = check_dataset(ds,
                                           deeper      = (True if self.dbg>3 else False),
                                           masked      = True,
                                           show_header = header)

            # Fluctuate (summed site if required)
            if (self.fluctuate):
                non   = self.rnd_state.poisson(non)
                noff  = self.rnd_state.poisson(noff)

            # Compute significance and background/excess counts

            wstat = WStatCountsStatistic(n_on  = non,
                                         n_off = noff,
                                         alpha = mcf.alpha)

            sig =  wstat.sqrt_ts # ? check
                
            # Much faster to append list than arrays
            # Array appending create a new object
            sigma_vs_time.append(sig)
            non_vs_time.append(non)
            noff_vs_time.append(noff)
            # nxs.append(ns)
            # nbck.append(nb)

            # End of loop over slices / datasets

        # Access to arrays is much faster than access to lists
        sigma_vs_time   = np.array(sigma_vs_time)
        non_vs_time = np.array(non_vs_time)
        noff_vs_time  = np.array(noff_vs_time)

        return (sigma_vs_time, non_vs_time, noff_vs_time)