We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
LightCurveEstimator fails if no dataset contribute in a time bin
**Gammapy version**
dev

**Bug description**
If you run the `LightCurveEstimator` and no dataset are found to contribute in a time interval, it will fail because the `FluxPoints` produced has no `npred` quantity attached. 
This is because the `ParameterEstimator.estimate_ts` only return `ts` as `nan` before estimating the `npred`. 
https://github.com/gammapy/gammapy/blob/d1ede2663429253b72011b9eda256ed2b48f8e9c/gammapy/estimators/parameter.py#L110


**Expected behavior**
The estimator should run and return `nan` for `npred` as well.

**To Reproduce**
If you want to test, use the light curve validation, script and change the energy range to e.g. 0.5-0.7 TeV in line https://github.com/gammapy/gammapy-benchmarks/blob/8d02a809583a8ddf3d222c5b7db2b542c7f2addf/validation/lightcurve/make.py#L108

it will yield:
```
Traceback (most recent call last):
  File "make.py", line 278, in <module>
    cli()
  File "/Users/terrier/Code/anaconda3/envs/gammapy-dev/lib/python3.7/site-packages/click/core.py", line 1137, in __call__
    return self.main(*args, **kwargs)
  File "/Users/terrier/Code/anaconda3/envs/gammapy-dev/lib/python3.7/site-packages/click/core.py", line 1062, in main
    rv = self.invoke(ctx)
  File "/Users/terrier/Code/anaconda3/envs/gammapy-dev/lib/python3.7/site-packages/click/core.py", line 1668, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/terrier/Code/anaconda3/envs/gammapy-dev/lib/python3.7/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/terrier/Code/anaconda3/envs/gammapy-dev/lib/python3.7/site-packages/click/core.py", line 763, in invoke
    return __callback(*args, **kwargs)
  File "make.py", line 69, in run_analyses
    perform_analysis(analysis_type, short_observations, target_position, time_intervals)
  File "make.py", line 114, in perform_analysis
    lc = lc_maker.run(datasets)
  File "/Users/terrier/Code/gammapy-dev/gammapy/gammapy/estimators/lightcurve.py", line 126, in run
    fp._data[name], dataset_names=datasets.names
  File "/Users/terrier/Code/gammapy-dev/gammapy/gammapy/maps/maps.py", line 46, in __getitem__
    return self._data[key]
KeyError: 'npred'
```

**Other information**
Any other information you think will be useful for us to fix the issue can go here.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gammapy/estimators/parameter.py`
Content:
```
1 # Licensed under a 3-clause BSD style license - see LICENSE.rst
2 import logging
3 import numpy as np
4 from gammapy.datasets import Datasets
5 from gammapy.modeling import Fit
6 from .core import Estimator
7 
8 log = logging.getLogger(__name__)
9 
10 
11 class ParameterEstimator(Estimator):
12     """Model parameter estimator.
13 
14     Estimates a model parameter for a group of datasets. Compute best fit value,
15     symmetric and delta TS for a given null value. Additionally asymmetric errors
16     as well as parameter upper limit and fit statistic profile can be estimated.
17 
18     Parameters
19     ----------
20     n_sigma : int
21         Sigma to use for asymmetric error computation. Default is 1.
22     n_sigma_ul : int
23         Sigma to use for upper limit computation. Default is 2.
24     null_value : float
25         Which null value to use for the parameter
26     selection_optional : list of str
27         Which additional quantities to estimate. Available options are:
28 
29             * "all": all the optional steps are executed
30             * "errn-errp": estimate asymmetric errors on parameter best fit value.
31             * "ul": estimate upper limits.
32             * "scan": estimate fit statistic profiles.
33 
34         Default is None so the optionnal steps are not executed.
35     fit : `Fit`
36         Fit instance specifying the backend and fit options.
37     reoptimize : bool
38         Re-optimize other free model parameters. Default is True.
39     """
40 
41     tag = "ParameterEstimator"
42     _available_selection_optional = ["errn-errp", "ul", "scan"]
43 
44     def __init__(
45         self,
46         n_sigma=1,
47         n_sigma_ul=2,
48         null_value=1e-150,
49         selection_optional=None,
50         fit=None,
51         reoptimize=True
52     ):
53         self.n_sigma = n_sigma
54         self.n_sigma_ul = n_sigma_ul
55         self.null_value = null_value
56         self.selection_optional = selection_optional
57 
58         if fit is None:
59             fit = Fit()
60 
61         self.fit = fit
62         self.reoptimize = reoptimize
63 
64     def estimate_best_fit(self, datasets, parameter):
65         """Estimate parameter assymetric errors
66 
67         Parameters
68         ----------
69         datasets : `~gammapy.datasets.Datasets`
70             Datasets
71         parameter : `Parameter`
72             For which parameter to get the value
73 
74         Returns
75         -------
76         result : dict
77             Dict with the various parameter estimation values.
78         """
79         value, total_stat, success, error = np.nan, 0, False, np.nan
80 
81         if np.any(datasets.contributes_to_stat):
82             result = self.fit.run(datasets=datasets)
83             value, error = parameter.value, parameter.error
84             total_stat = result["optimize_result"].total_stat
85             success = result["optimize_result"].success
86 
87         return {
88             f"{parameter.name}": value,
89             "stat": total_stat,
90             "success": success,
91             f"{parameter.name}_err": error * self.n_sigma,
92         }
93 
94     def estimate_ts(self, datasets, parameter):
95         """Estimate parameter ts
96 
97         Parameters
98         ----------
99         datasets : `~gammapy.datasets.Datasets`
100             Datasets
101         parameter : `Parameter`
102             For which parameter to get the value
103 
104         Returns
105         -------
106         result : dict
107             Dict with the various parameter estimation values.
108         """
109         if not np.any(datasets.contributes_to_stat):
110             return {"ts": np.nan}
111 
112         stat = datasets.stat_sum()
113         npred = self.estimate_npred(datasets=datasets)
114 
115         with datasets.parameters.restore_status():
116             # compute ts value
117             parameter.value = self.null_value
118 
119             if self.reoptimize:
120                 parameter.frozen = True
121                 _ = self.fit.optimize(datasets=datasets)
122 
123             ts = datasets.stat_sum() - stat
124             npred_null = self.estimate_npred(datasets=datasets)
125 
126         return {
127             "ts": ts,
128             "npred": npred["npred"],
129             "npred_null": npred_null["npred"]
130         }
131 
132     def estimate_errn_errp(self, datasets, parameter):
133         """Estimate parameter assymetric errors
134 
135         Parameters
136         ----------
137         datasets : `~gammapy.datasets.Datasets`
138             Datasets
139         parameter : `Parameter`
140             For which parameter to get the value
141 
142         Returns
143         -------
144         result : dict
145             Dict with the various parameter estimation values.
146         """
147         if not np.any(datasets.contributes_to_stat):
148             return {
149                 f"{parameter.name}_errp": np.nan,
150                 f"{parameter.name}_errn": np.nan,
151             }
152 
153         self.fit.optimize(datasets=datasets)
154 
155         res = self.fit.confidence(
156             datasets=datasets,
157             parameter=parameter,
158             sigma=self.n_sigma,
159             reoptimize=self.reoptimize
160         )
161 
162         return {
163             f"{parameter.name}_errp": res["errp"],
164             f"{parameter.name}_errn": res["errn"],
165         }
166 
167     def estimate_scan(self, datasets, parameter):
168         """Estimate parameter stat scan.
169 
170         Parameters
171         ----------
172         datasets : `~gammapy.datasets.Datasets`
173             The datasets used to estimate the model parameter
174         parameter : `Parameter`
175             For which parameter to get the value
176 
177         Returns
178         -------
179         result : dict
180             Dict with the various parameter estimation values.
181 
182         """
183         scan_values = parameter.scan_values
184 
185         if not np.any(datasets.contributes_to_stat):
186             return {
187                 f"{parameter.name}_scan": scan_values,
188                 "stat_scan": scan_values * np.nan
189             }
190 
191         self.fit.optimize(datasets=datasets)
192 
193         profile = self.fit.stat_profile(
194             datasets=datasets,
195             parameter=parameter,
196             reoptimize=self.reoptimize
197         )
198 
199         return {
200             f"{parameter.name}_scan": scan_values,
201             "stat_scan": profile["stat_scan"],
202         }
203 
204     def estimate_ul(self, datasets, parameter):
205         """Estimate parameter ul.
206 
207         Parameters
208         ----------
209         datasets : `~gammapy.datasets.Datasets`
210             The datasets used to estimate the model parameter
211         parameter : `Parameter`
212             For which parameter to get the value
213 
214         Returns
215         -------
216         result : dict
217             Dict with the various parameter estimation values.
218 
219         """
220         if not np.any(datasets.contributes_to_stat):
221             return {f"{parameter.name}_ul": np.nan}
222 
223         self.fit.optimize(datasets=datasets)
224 
225         res = self.fit.confidence(
226             datasets=datasets,
227             parameter=parameter,
228             sigma=self.n_sigma_ul,
229             reoptimize=self.reoptimize
230         )
231         return {f"{parameter.name}_ul": res["errp"] + parameter.value}
232 
233     @staticmethod
234     def estimate_counts(datasets):
235         """Estimate counts for the flux point.
236 
237         Parameters
238         ----------
239         datasets : Datasets
240             Datasets
241 
242         Returns
243         -------
244         result : dict
245             Dict with an array with one entry per dataset with the sum of the
246             masked counts.
247         """
248         counts = []
249 
250         for dataset in datasets:
251             mask = dataset.mask
252             counts.append(dataset.counts.data[mask].sum())
253 
254         return {"counts": np.array(counts, dtype=int), "datasets": datasets.names}
255 
256     @staticmethod
257     def estimate_npred(datasets):
258         """Estimate npred for the flux point.
259 
260         Parameters
261         ----------
262         datasets : Datasets
263             Datasets
264 
265         Returns
266         -------
267         result : dict
268             Dict with an array with one entry per dataset with the sum of the
269             masked npred.
270         """
271         npred = []
272 
273         for dataset in datasets:
274             mask = dataset.mask
275             npred.append(dataset.npred().data[mask].sum())
276 
277         return {"npred": np.array(npred), "datasets": datasets.names}
278 
279     def run(self, datasets, parameter):
280         """Run the parameter estimator.
281 
282         Parameters
283         ----------
284         datasets : `~gammapy.datasets.Datasets`
285             The datasets used to estimate the model parameter
286         parameter : `str` or `Parameter`
287             For which parameter to run the estimator
288 
289         Returns
290         -------
291         result : dict
292             Dict with the various parameter estimation values.
293         """
294         datasets = Datasets(datasets)
295         parameter = datasets.parameters[parameter]
296 
297         with datasets.parameters.restore_status():
298 
299             if not self.reoptimize:
300                 datasets.parameters.freeze_all()
301                 parameter.frozen = False
302 
303             result = self.estimate_best_fit(datasets, parameter)
304             result.update(self.estimate_ts(datasets, parameter))
305 
306             if "errn-errp" in self.selection_optional:
307                 result.update(self.estimate_errn_errp(datasets, parameter))
308 
309             if "ul" in self.selection_optional:
310                 result.update(self.estimate_ul(datasets, parameter))
311 
312             if "scan" in self.selection_optional:
313                 result.update(self.estimate_scan(datasets, parameter))
314 
315         result.update(self.estimate_counts(datasets))
316         return result
317
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/gammapy/estimators/parameter.py b/gammapy/estimators/parameter.py
--- a/gammapy/estimators/parameter.py
+++ b/gammapy/estimators/parameter.py
@@ -74,7 +74,12 @@
         Returns
         -------
         result : dict
-            Dict with the various parameter estimation values.
+            Dict with the various parameter estimation values. Entries are:
+
+                * parameter.name: best fit parameter value
+                * "stat": best fit total stat.
+                * "success": boolean flag for fit success
+                * parameter.name_err: covariance-based error estimate on parameter value
         """
         value, total_stat, success, error = np.nan, 0, False, np.nan
 
@@ -104,14 +109,20 @@
         Returns
         -------
         result : dict
-            Dict with the various parameter estimation values.
-        """
-        if not np.any(datasets.contributes_to_stat):
-            return {"ts": np.nan}
+            Dict with the TS of the best fit value compared to the null hypothesis. Entries are:
 
-        stat = datasets.stat_sum()
+                * TS : fit statistic difference with null hypothesis
+                * "npred" : predicted number of counts per dataset
+                * "npred_null" : predicted number of counts per dataset in the null hypothesis
+        """
         npred = self.estimate_npred(datasets=datasets)
 
+        if not np.any(datasets.contributes_to_stat):
+            stat = np.nan
+            npred["npred"][...] = np.nan
+        else:
+            stat = datasets.stat_sum()
+
         with datasets.parameters.restore_status():
             # compute ts value
             parameter.value = self.null_value
@@ -142,7 +153,10 @@
         Returns
         -------
         result : dict
-            Dict with the various parameter estimation values.
+            Dict with the parameter asymmetric errors. Entries are:
+
+                * parameter.name_errp : positive error on parameter value
+                * parameter.name_errn : negative error on parameter value
         """
         if not np.any(datasets.contributes_to_stat):
             return {
@@ -177,8 +191,10 @@
         Returns
         -------
         result : dict
-            Dict with the various parameter estimation values.
+            Dict with the parameter fit scan values. Entries are:
 
+                * parameter.name_scan : parameter values scan
+                * "stat_scan" : fit statistic values scan
         """
         scan_values = parameter.scan_values
 
@@ -214,8 +230,9 @@
         Returns
         -------
         result : dict
-            Dict with the various parameter estimation values.
-
+            Dict with the parameter ULs. Entries are:
+            
+                * parameter.name_ul : upper limit on parameter value
         """
         if not np.any(datasets.contributes_to_stat):
             return {f"{parameter.name}_ul": np.nan}

{"golden_diff": "diff --git a/gammapy/estimators/parameter.py b/gammapy/estimators/parameter.py\n--- a/gammapy/estimators/parameter.py\n+++ b/gammapy/estimators/parameter.py\n@@ -74,7 +74,12 @@\n         Returns\n         -------\n         result : dict\n-            Dict with the various parameter estimation values.\n+            Dict with the various parameter estimation values. Entries are:\n+\n+                * parameter.name: best fit parameter value\n+                * \"stat\": best fit total stat.\n+                * \"success\": boolean flag for fit success\n+                * parameter.name_err: covariance-based error estimate on parameter value\n         \"\"\"\n         value, total_stat, success, error = np.nan, 0, False, np.nan\n \n@@ -104,14 +109,20 @@\n         Returns\n         -------\n         result : dict\n-            Dict with the various parameter estimation values.\n-        \"\"\"\n-        if not np.any(datasets.contributes_to_stat):\n-            return {\"ts\": np.nan}\n+            Dict with the TS of the best fit value compared to the null hypothesis. Entries are:\n \n-        stat = datasets.stat_sum()\n+                * TS : fit statistic difference with null hypothesis\n+                * \"npred\" : predicted number of counts per dataset\n+                * \"npred_null\" : predicted number of counts per dataset in the null hypothesis\n+        \"\"\"\n         npred = self.estimate_npred(datasets=datasets)\n \n+        if not np.any(datasets.contributes_to_stat):\n+            stat = np.nan\n+            npred[\"npred\"][...] = np.nan\n+        else:\n+            stat = datasets.stat_sum()\n+\n         with datasets.parameters.restore_status():\n             # compute ts value\n             parameter.value = self.null_value\n@@ -142,7 +153,10 @@\n         Returns\n         -------\n         result : dict\n-            Dict with the various parameter estimation values.\n+            Dict with the parameter asymmetric errors. Entries are:\n+\n+                * parameter.name_errp : positive error on parameter value\n+                * parameter.name_errn : negative error on parameter value\n         \"\"\"\n         if not np.any(datasets.contributes_to_stat):\n             return {\n@@ -177,8 +191,10 @@\n         Returns\n         -------\n         result : dict\n-            Dict with the various parameter estimation values.\n+            Dict with the parameter fit scan values. Entries are:\n \n+                * parameter.name_scan : parameter values scan\n+                * \"stat_scan\" : fit statistic values scan\n         \"\"\"\n         scan_values = parameter.scan_values\n \n@@ -214,8 +230,9 @@\n         Returns\n         -------\n         result : dict\n-            Dict with the various parameter estimation values.\n-\n+            Dict with the parameter ULs. Entries are:\n+            \n+                * parameter.name_ul : upper limit on parameter value\n         \"\"\"\n         if not np.any(datasets.contributes_to_stat):\n             return {f\"{parameter.name}_ul\": np.nan}\n", "issue": "LightCurveEstimator fails if no dataset contribute in a time bin\n**Gammapy version**\r\ndev\r\n\r\n**Bug description**\r\nIf you run the `LightCurveEstimator` and no dataset are found to contribute in a time interval, it will fail because the `FluxPoints` produced has no `npred` quantity attached. \r\nThis is because the `ParameterEstimator.estimate_ts` only return `ts` as `nan` before estimating the `npred`. \r\nhttps://github.com/gammapy/gammapy/blob/d1ede2663429253b72011b9eda256ed2b48f8e9c/gammapy/estimators/parameter.py#L110\r\n\r\n\r\n**Expected behavior**\r\nThe estimator should run and return `nan` for `npred` as well.\r\n\r\n**To Reproduce**\r\nIf you want to test, use the light curve validation, script and change the energy range to e.g. 0.5-0.7 TeV in line https://github.com/gammapy/gammapy-benchmarks/blob/8d02a809583a8ddf3d222c5b7db2b542c7f2addf/validation/lightcurve/make.py#L108\r\n\r\nit will yield:\r\n```\r\nTraceback (most recent call last):\r\n  File \"make.py\", line 278, in <module>\r\n    cli()\r\n  File \"/Users/terrier/Code/anaconda3/envs/gammapy-dev/lib/python3.7/site-packages/click/core.py\", line 1137, in __call__\r\n    return self.main(*args, **kwargs)\r\n  File \"/Users/terrier/Code/anaconda3/envs/gammapy-dev/lib/python3.7/site-packages/click/core.py\", line 1062, in main\r\n    rv = self.invoke(ctx)\r\n  File \"/Users/terrier/Code/anaconda3/envs/gammapy-dev/lib/python3.7/site-packages/click/core.py\", line 1668, in invoke\r\n    return _process_result(sub_ctx.command.invoke(sub_ctx))\r\n  File \"/Users/terrier/Code/anaconda3/envs/gammapy-dev/lib/python3.7/site-packages/click/core.py\", line 1404, in invoke\r\n    return ctx.invoke(self.callback, **ctx.params)\r\n  File \"/Users/terrier/Code/anaconda3/envs/gammapy-dev/lib/python3.7/site-packages/click/core.py\", line 763, in invoke\r\n    return __callback(*args, **kwargs)\r\n  File \"make.py\", line 69, in run_analyses\r\n    perform_analysis(analysis_type, short_observations, target_position, time_intervals)\r\n  File \"make.py\", line 114, in perform_analysis\r\n    lc = lc_maker.run(datasets)\r\n  File \"/Users/terrier/Code/gammapy-dev/gammapy/gammapy/estimators/lightcurve.py\", line 126, in run\r\n    fp._data[name], dataset_names=datasets.names\r\n  File \"/Users/terrier/Code/gammapy-dev/gammapy/gammapy/maps/maps.py\", line 46, in __getitem__\r\n    return self._data[key]\r\nKeyError: 'npred'\r\n```\r\n\r\n**Other information**\r\nAny other information you think will be useful for us to fix the issue can go here.\r\n\n", "before_files": [{"content": "# Licensed under a 3-clause BSD style license - see LICENSE.rst\nimport logging\nimport numpy as np\nfrom gammapy.datasets import Datasets\nfrom gammapy.modeling import Fit\nfrom .core import Estimator\n\nlog = logging.getLogger(__name__)\n\n\nclass ParameterEstimator(Estimator):\n    \"\"\"Model parameter estimator.\n\n    Estimates a model parameter for a group of datasets. Compute best fit value,\n    symmetric and delta TS for a given null value. Additionally asymmetric errors\n    as well as parameter upper limit and fit statistic profile can be estimated.\n\n    Parameters\n    ----------\n    n_sigma : int\n        Sigma to use for asymmetric error computation. Default is 1.\n    n_sigma_ul : int\n        Sigma to use for upper limit computation. Default is 2.\n    null_value : float\n        Which null value to use for the parameter\n    selection_optional : list of str\n        Which additional quantities to estimate. Available options are:\n\n            * \"all\": all the optional steps are executed\n            * \"errn-errp\": estimate asymmetric errors on parameter best fit value.\n            * \"ul\": estimate upper limits.\n            * \"scan\": estimate fit statistic profiles.\n\n        Default is None so the optionnal steps are not executed.\n    fit : `Fit`\n        Fit instance specifying the backend and fit options.\n    reoptimize : bool\n        Re-optimize other free model parameters. Default is True.\n    \"\"\"\n\n    tag = \"ParameterEstimator\"\n    _available_selection_optional = [\"errn-errp\", \"ul\", \"scan\"]\n\n    def __init__(\n        self,\n        n_sigma=1,\n        n_sigma_ul=2,\n        null_value=1e-150,\n        selection_optional=None,\n        fit=None,\n        reoptimize=True\n    ):\n        self.n_sigma = n_sigma\n        self.n_sigma_ul = n_sigma_ul\n        self.null_value = null_value\n        self.selection_optional = selection_optional\n\n        if fit is None:\n            fit = Fit()\n\n        self.fit = fit\n        self.reoptimize = reoptimize\n\n    def estimate_best_fit(self, datasets, parameter):\n        \"\"\"Estimate parameter assymetric errors\n\n        Parameters\n        ----------\n        datasets : `~gammapy.datasets.Datasets`\n            Datasets\n        parameter : `Parameter`\n            For which parameter to get the value\n\n        Returns\n        -------\n        result : dict\n            Dict with the various parameter estimation values.\n        \"\"\"\n        value, total_stat, success, error = np.nan, 0, False, np.nan\n\n        if np.any(datasets.contributes_to_stat):\n            result = self.fit.run(datasets=datasets)\n            value, error = parameter.value, parameter.error\n            total_stat = result[\"optimize_result\"].total_stat\n            success = result[\"optimize_result\"].success\n\n        return {\n            f\"{parameter.name}\": value,\n            \"stat\": total_stat,\n            \"success\": success,\n            f\"{parameter.name}_err\": error * self.n_sigma,\n        }\n\n    def estimate_ts(self, datasets, parameter):\n        \"\"\"Estimate parameter ts\n\n        Parameters\n        ----------\n        datasets : `~gammapy.datasets.Datasets`\n            Datasets\n        parameter : `Parameter`\n            For which parameter to get the value\n\n        Returns\n        -------\n        result : dict\n            Dict with the various parameter estimation values.\n        \"\"\"\n        if not np.any(datasets.contributes_to_stat):\n            return {\"ts\": np.nan}\n\n        stat = datasets.stat_sum()\n        npred = self.estimate_npred(datasets=datasets)\n\n        with datasets.parameters.restore_status():\n            # compute ts value\n            parameter.value = self.null_value\n\n            if self.reoptimize:\n                parameter.frozen = True\n                _ = self.fit.optimize(datasets=datasets)\n\n            ts = datasets.stat_sum() - stat\n            npred_null = self.estimate_npred(datasets=datasets)\n\n        return {\n            \"ts\": ts,\n            \"npred\": npred[\"npred\"],\n            \"npred_null\": npred_null[\"npred\"]\n        }\n\n    def estimate_errn_errp(self, datasets, parameter):\n        \"\"\"Estimate parameter assymetric errors\n\n        Parameters\n        ----------\n        datasets : `~gammapy.datasets.Datasets`\n            Datasets\n        parameter : `Parameter`\n            For which parameter to get the value\n\n        Returns\n        -------\n        result : dict\n            Dict with the various parameter estimation values.\n        \"\"\"\n        if not np.any(datasets.contributes_to_stat):\n            return {\n                f\"{parameter.name}_errp\": np.nan,\n                f\"{parameter.name}_errn\": np.nan,\n            }\n\n        self.fit.optimize(datasets=datasets)\n\n        res = self.fit.confidence(\n            datasets=datasets,\n            parameter=parameter,\n            sigma=self.n_sigma,\n            reoptimize=self.reoptimize\n        )\n\n        return {\n            f\"{parameter.name}_errp\": res[\"errp\"],\n            f\"{parameter.name}_errn\": res[\"errn\"],\n        }\n\n    def estimate_scan(self, datasets, parameter):\n        \"\"\"Estimate parameter stat scan.\n\n        Parameters\n        ----------\n        datasets : `~gammapy.datasets.Datasets`\n            The datasets used to estimate the model parameter\n        parameter : `Parameter`\n            For which parameter to get the value\n\n        Returns\n        -------\n        result : dict\n            Dict with the various parameter estimation values.\n\n        \"\"\"\n        scan_values = parameter.scan_values\n\n        if not np.any(datasets.contributes_to_stat):\n            return {\n                f\"{parameter.name}_scan\": scan_values,\n                \"stat_scan\": scan_values * np.nan\n            }\n\n        self.fit.optimize(datasets=datasets)\n\n        profile = self.fit.stat_profile(\n            datasets=datasets,\n            parameter=parameter,\n            reoptimize=self.reoptimize\n        )\n\n        return {\n            f\"{parameter.name}_scan\": scan_values,\n            \"stat_scan\": profile[\"stat_scan\"],\n        }\n\n    def estimate_ul(self, datasets, parameter):\n        \"\"\"Estimate parameter ul.\n\n        Parameters\n        ----------\n        datasets : `~gammapy.datasets.Datasets`\n            The datasets used to estimate the model parameter\n        parameter : `Parameter`\n            For which parameter to get the value\n\n        Returns\n        -------\n        result : dict\n            Dict with the various parameter estimation values.\n\n        \"\"\"\n        if not np.any(datasets.contributes_to_stat):\n            return {f\"{parameter.name}_ul\": np.nan}\n\n        self.fit.optimize(datasets=datasets)\n\n        res = self.fit.confidence(\n            datasets=datasets,\n            parameter=parameter,\n            sigma=self.n_sigma_ul,\n            reoptimize=self.reoptimize\n        )\n        return {f\"{parameter.name}_ul\": res[\"errp\"] + parameter.value}\n\n    @staticmethod\n    def estimate_counts(datasets):\n        \"\"\"Estimate counts for the flux point.\n\n        Parameters\n        ----------\n        datasets : Datasets\n            Datasets\n\n        Returns\n        -------\n        result : dict\n            Dict with an array with one entry per dataset with the sum of the\n            masked counts.\n        \"\"\"\n        counts = []\n\n        for dataset in datasets:\n            mask = dataset.mask\n            counts.append(dataset.counts.data[mask].sum())\n\n        return {\"counts\": np.array(counts, dtype=int), \"datasets\": datasets.names}\n\n    @staticmethod\n    def estimate_npred(datasets):\n        \"\"\"Estimate npred for the flux point.\n\n        Parameters\n        ----------\n        datasets : Datasets\n            Datasets\n\n        Returns\n        -------\n        result : dict\n            Dict with an array with one entry per dataset with the sum of the\n            masked npred.\n        \"\"\"\n        npred = []\n\n        for dataset in datasets:\n            mask = dataset.mask\n            npred.append(dataset.npred().data[mask].sum())\n\n        return {\"npred\": np.array(npred), \"datasets\": datasets.names}\n\n    def run(self, datasets, parameter):\n        \"\"\"Run the parameter estimator.\n\n        Parameters\n        ----------\n        datasets : `~gammapy.datasets.Datasets`\n            The datasets used to estimate the model parameter\n        parameter : `str` or `Parameter`\n            For which parameter to run the estimator\n\n        Returns\n        -------\n        result : dict\n            Dict with the various parameter estimation values.\n        \"\"\"\n        datasets = Datasets(datasets)\n        parameter = datasets.parameters[parameter]\n\n        with datasets.parameters.restore_status():\n\n            if not self.reoptimize:\n                datasets.parameters.freeze_all()\n                parameter.frozen = False\n\n            result = self.estimate_best_fit(datasets, parameter)\n            result.update(self.estimate_ts(datasets, parameter))\n\n            if \"errn-errp\" in self.selection_optional:\n                result.update(self.estimate_errn_errp(datasets, parameter))\n\n            if \"ul\" in self.selection_optional:\n                result.update(self.estimate_ul(datasets, parameter))\n\n            if \"scan\" in self.selection_optional:\n                result.update(self.estimate_scan(datasets, parameter))\n\n        result.update(self.estimate_counts(datasets))\n        return result\n", "path": "gammapy/estimators/parameter.py"}], "after_files": [{"content": "# Licensed under a 3-clause BSD style license - see LICENSE.rst\nimport logging\nimport numpy as np\nfrom gammapy.datasets import Datasets\nfrom gammapy.modeling import Fit\nfrom .core import Estimator\n\nlog = logging.getLogger(__name__)\n\n\nclass ParameterEstimator(Estimator):\n    \"\"\"Model parameter estimator.\n\n    Estimates a model parameter for a group of datasets. Compute best fit value,\n    symmetric and delta TS for a given null value. Additionally asymmetric errors\n    as well as parameter upper limit and fit statistic profile can be estimated.\n\n    Parameters\n    ----------\n    n_sigma : int\n        Sigma to use for asymmetric error computation. Default is 1.\n    n_sigma_ul : int\n        Sigma to use for upper limit computation. Default is 2.\n    null_value : float\n        Which null value to use for the parameter\n    selection_optional : list of str\n        Which additional quantities to estimate. Available options are:\n\n            * \"all\": all the optional steps are executed\n            * \"errn-errp\": estimate asymmetric errors on parameter best fit value.\n            * \"ul\": estimate upper limits.\n            * \"scan\": estimate fit statistic profiles.\n\n        Default is None so the optionnal steps are not executed.\n    fit : `Fit`\n        Fit instance specifying the backend and fit options.\n    reoptimize : bool\n        Re-optimize other free model parameters. Default is True.\n    \"\"\"\n\n    tag = \"ParameterEstimator\"\n    _available_selection_optional = [\"errn-errp\", \"ul\", \"scan\"]\n\n    def __init__(\n        self,\n        n_sigma=1,\n        n_sigma_ul=2,\n        null_value=1e-150,\n        selection_optional=None,\n        fit=None,\n        reoptimize=True\n    ):\n        self.n_sigma = n_sigma\n        self.n_sigma_ul = n_sigma_ul\n        self.null_value = null_value\n        self.selection_optional = selection_optional\n\n        if fit is None:\n            fit = Fit()\n\n        self.fit = fit\n        self.reoptimize = reoptimize\n\n    def estimate_best_fit(self, datasets, parameter):\n        \"\"\"Estimate parameter assymetric errors\n\n        Parameters\n        ----------\n        datasets : `~gammapy.datasets.Datasets`\n            Datasets\n        parameter : `Parameter`\n            For which parameter to get the value\n\n        Returns\n        -------\n        result : dict\n            Dict with the various parameter estimation values. Entries are:\n\n                * parameter.name: best fit parameter value\n                * \"stat\": best fit total stat.\n                * \"success\": boolean flag for fit success\n                * parameter.name_err: covariance-based error estimate on parameter value\n        \"\"\"\n        value, total_stat, success, error = np.nan, 0, False, np.nan\n\n        if np.any(datasets.contributes_to_stat):\n            result = self.fit.run(datasets=datasets)\n            value, error = parameter.value, parameter.error\n            total_stat = result[\"optimize_result\"].total_stat\n            success = result[\"optimize_result\"].success\n\n        return {\n            f\"{parameter.name}\": value,\n            \"stat\": total_stat,\n            \"success\": success,\n            f\"{parameter.name}_err\": error * self.n_sigma,\n        }\n\n    def estimate_ts(self, datasets, parameter):\n        \"\"\"Estimate parameter ts\n\n        Parameters\n        ----------\n        datasets : `~gammapy.datasets.Datasets`\n            Datasets\n        parameter : `Parameter`\n            For which parameter to get the value\n\n        Returns\n        -------\n        result : dict\n            Dict with the TS of the best fit value compared to the null hypothesis. Entries are:\n\n                * TS : fit statistic difference with null hypothesis\n                * \"npred\" : predicted number of counts per dataset\n                * \"npred_null\" : predicted number of counts per dataset in the null hypothesis\n        \"\"\"\n        npred = self.estimate_npred(datasets=datasets)\n\n        if not np.any(datasets.contributes_to_stat):\n            stat = np.nan\n            npred[\"npred\"][...] = np.nan\n        else:\n            stat = datasets.stat_sum()\n\n        with datasets.parameters.restore_status():\n            # compute ts value\n            parameter.value = self.null_value\n\n            if self.reoptimize:\n                parameter.frozen = True\n                _ = self.fit.optimize(datasets=datasets)\n\n            ts = datasets.stat_sum() - stat\n            npred_null = self.estimate_npred(datasets=datasets)\n\n        return {\n            \"ts\": ts,\n            \"npred\": npred[\"npred\"],\n            \"npred_null\": npred_null[\"npred\"]\n        }\n\n    def estimate_errn_errp(self, datasets, parameter):\n        \"\"\"Estimate parameter assymetric errors\n\n        Parameters\n        ----------\n        datasets : `~gammapy.datasets.Datasets`\n            Datasets\n        parameter : `Parameter`\n            For which parameter to get the value\n\n        Returns\n        -------\n        result : dict\n            Dict with the parameter asymmetric errors. Entries are:\n\n                * parameter.name_errp : positive error on parameter value\n                * parameter.name_errn : negative error on parameter value\n        \"\"\"\n        if not np.any(datasets.contributes_to_stat):\n            return {\n                f\"{parameter.name}_errp\": np.nan,\n                f\"{parameter.name}_errn\": np.nan,\n            }\n\n        self.fit.optimize(datasets=datasets)\n\n        res = self.fit.confidence(\n            datasets=datasets,\n            parameter=parameter,\n            sigma=self.n_sigma,\n            reoptimize=self.reoptimize\n        )\n\n        return {\n            f\"{parameter.name}_errp\": res[\"errp\"],\n            f\"{parameter.name}_errn\": res[\"errn\"],\n        }\n\n    def estimate_scan(self, datasets, parameter):\n        \"\"\"Estimate parameter stat scan.\n\n        Parameters\n        ----------\n        datasets : `~gammapy.datasets.Datasets`\n            The datasets used to estimate the model parameter\n        parameter : `Parameter`\n            For which parameter to get the value\n\n        Returns\n        -------\n        result : dict\n            Dict with the parameter fit scan values. Entries are:\n\n                * parameter.name_scan : parameter values scan\n                * \"stat_scan\" : fit statistic values scan\n        \"\"\"\n        scan_values = parameter.scan_values\n\n        if not np.any(datasets.contributes_to_stat):\n            return {\n                f\"{parameter.name}_scan\": scan_values,\n                \"stat_scan\": scan_values * np.nan\n            }\n\n        self.fit.optimize(datasets=datasets)\n\n        profile = self.fit.stat_profile(\n            datasets=datasets,\n            parameter=parameter,\n            reoptimize=self.reoptimize\n        )\n\n        return {\n            f\"{parameter.name}_scan\": scan_values,\n            \"stat_scan\": profile[\"stat_scan\"],\n        }\n\n    def estimate_ul(self, datasets, parameter):\n        \"\"\"Estimate parameter ul.\n\n        Parameters\n        ----------\n        datasets : `~gammapy.datasets.Datasets`\n            The datasets used to estimate the model parameter\n        parameter : `Parameter`\n            For which parameter to get the value\n\n        Returns\n        -------\n        result : dict\n            Dict with the parameter ULs. Entries are:\n            \n                * parameter.name_ul : upper limit on parameter value\n        \"\"\"\n        if not np.any(datasets.contributes_to_stat):\n            return {f\"{parameter.name}_ul\": np.nan}\n\n        self.fit.optimize(datasets=datasets)\n\n        res = self.fit.confidence(\n            datasets=datasets,\n            parameter=parameter,\n            sigma=self.n_sigma_ul,\n            reoptimize=self.reoptimize\n        )\n        return {f\"{parameter.name}_ul\": res[\"errp\"] + parameter.value}\n\n    @staticmethod\n    def estimate_counts(datasets):\n        \"\"\"Estimate counts for the flux point.\n\n        Parameters\n        ----------\n        datasets : Datasets\n            Datasets\n\n        Returns\n        -------\n        result : dict\n            Dict with an array with one entry per dataset with the sum of the\n            masked counts.\n        \"\"\"\n        counts = []\n\n        for dataset in datasets:\n            mask = dataset.mask\n            counts.append(dataset.counts.data[mask].sum())\n\n        return {\"counts\": np.array(counts, dtype=int), \"datasets\": datasets.names}\n\n    @staticmethod\n    def estimate_npred(datasets):\n        \"\"\"Estimate npred for the flux point.\n\n        Parameters\n        ----------\n        datasets : Datasets\n            Datasets\n\n        Returns\n        -------\n        result : dict\n            Dict with an array with one entry per dataset with the sum of the\n            masked npred.\n        \"\"\"\n        npred = []\n\n        for dataset in datasets:\n            mask = dataset.mask\n            npred.append(dataset.npred().data[mask].sum())\n\n        return {\"npred\": np.array(npred), \"datasets\": datasets.names}\n\n    def run(self, datasets, parameter):\n        \"\"\"Run the parameter estimator.\n\n        Parameters\n        ----------\n        datasets : `~gammapy.datasets.Datasets`\n            The datasets used to estimate the model parameter\n        parameter : `str` or `Parameter`\n            For which parameter to run the estimator\n\n        Returns\n        -------\n        result : dict\n            Dict with the various parameter estimation values.\n        \"\"\"\n        datasets = Datasets(datasets)\n        parameter = datasets.parameters[parameter]\n\n        with datasets.parameters.restore_status():\n\n            if not self.reoptimize:\n                datasets.parameters.freeze_all()\n                parameter.frozen = False\n\n            result = self.estimate_best_fit(datasets, parameter)\n            result.update(self.estimate_ts(datasets, parameter))\n\n            if \"errn-errp\" in self.selection_optional:\n                result.update(self.estimate_errn_errp(datasets, parameter))\n\n            if \"ul\" in self.selection_optional:\n                result.update(self.estimate_ul(datasets, parameter))\n\n            if \"scan\" in self.selection_optional:\n                result.update(self.estimate_scan(datasets, parameter))\n\n        result.update(self.estimate_counts(datasets))\n        return result\n", "path": "gammapy/estimators/parameter.py"}]}