Repository: gammapy/gammapy

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.



Discussion:

Resolving patch:
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 @@ def estimate_best_fit(self, datasets, parameter):
         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 @@ def estimate_ts(self, datasets, parameter):
         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 @@ def estimate_errn_errp(self, datasets, parameter):
         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 @@ def estimate_scan(self, datasets, parameter):
         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 @@ def estimate_ul(self, datasets, parameter):
         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}


Test patch:
diff --git a/gammapy/estimators/tests/test_parameter_estimator.py b/gammapy/estimators/tests/test_parameter_estimator.py
--- a/gammapy/estimators/tests/test_parameter_estimator.py
+++ b/gammapy/estimators/tests/test_parameter_estimator.py
@@ -1,10 +1,10 @@
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
 import pytest
+import numpy as np
 from numpy.testing import assert_allclose
 from gammapy.datasets import Datasets, SpectrumDatasetOnOff
 from gammapy.estimators.parameter import ParameterEstimator
 from gammapy.modeling.models import PowerLawSpectralModel, SkyModel
-from gammapy.modeling import Fit
 from gammapy.utils.testing import requires_data
 
 pytest.importorskip("iminuit")
@@ -71,3 +71,33 @@ def test_parameter_estimator_3d_no_reoptimization(crab_datasets_fermi):
     assert_allclose(result["amplitude"], 0.018251, rtol=1e-3)
     assert_allclose(result["amplitude_scan"].shape, 10)
     assert_allclose(result["amplitude_scan"][0], 0.017282, atol=1e-3)
+
+@requires_data()
+def test_parameter_estimator_no_data(crab_datasets_1d, pwl_model):
+    datasets = crab_datasets_1d
+
+    model = SkyModel(spectral_model=pwl_model, name="Crab")
+    model.spectral_model.amplitude.scan_n_values = 10
+
+    for dataset in datasets:
+        dataset.mask_safe.data[...] = False
+        dataset.models = model
+
+    estimator = ParameterEstimator(selection_optional="all")
+
+    result = estimator.run(datasets, parameter="amplitude")
+
+    assert np.isnan(result["amplitude"])
+    assert np.isnan(result["amplitude_err"])
+    assert np.isnan(result["amplitude_errp"])
+    assert np.isnan(result["amplitude_errn"])
+    assert np.isnan(result["amplitude_ul"])
+    assert np.isnan(result["ts"])
+    assert np.isnan(result["npred"])
+    assert_allclose(result["npred_null"], 0)
+    assert_allclose(result["counts"], 0)
+
+    # Add test for scan
+    assert_allclose(result["amplitude_scan"].shape, 10)
+    assert np.all(np.isnan(result["stat_scan"]))
+