diff --git a/docs/usage.rst b/docs/usage.rst index 410f0002..31a183d9 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -370,5 +370,7 @@ multiple dependent variables and a (different) subset of the bins has missing co In this case the uncertainties should be set to zero for the missing bins with a non-numeric central value like ``'-'``. The warning message can be suppressed by passing an optional argument ``zero_uncertainties_warning=False`` when defining an instance of the ``Variable`` class. +Furthermore, note that `None` can be used to suppress the uncertainty for individual bins in cases where +the uncertainty components may only apply to a subset of the values. -.. _`Uncertainties`: https://hepdata-submission.readthedocs.io/en/latest/data_yaml.html#uncertainties \ No newline at end of file +.. _`Uncertainties`: https://hepdata-submission.readthedocs.io/en/latest/data_yaml.html#uncertainties diff --git a/hepdata_lib/__init__.py b/hepdata_lib/__init__.py index e919b5f7..47282b69 100644 --- a/hepdata_lib/__init__.py +++ b/hepdata_lib/__init__.py @@ -235,6 +235,7 @@ def add_uncertainty(self, uncertainty): self.uncertainties.append(uncertainty) def make_dict(self): + # pylint: disable=too-many-branches """ Return all data in this Variable as a dictionary. @@ -277,6 +278,8 @@ def make_dict(self): # if at least one of the uncertainties is not zero. if nonzero_uncs[i]: for unc in self.uncertainties: + if unc.values[i] is None: + continue if unc.is_symmetric: valuedict['errors'].append({ "symerror": diff --git a/hepdata_lib/helpers.py b/hepdata_lib/helpers.py index e1df117e..9e35973c 100644 --- a/hepdata_lib/helpers.py +++ b/hepdata_lib/helpers.py @@ -258,7 +258,8 @@ def any_uncertainties_nonzero(uncertainties, size): for unc in uncertainties: # Treat one-sided uncertainties as - values = np.array(unc.values) + tmp = 0 if unc.is_symmetric else (0,0) + values = np.array([tmp if v is None else v for v in unc.values]) values[values.astype(str)==''] = 0 values = values.astype(float) @@ -273,15 +274,17 @@ def sanitize_value(value): Handle conversion of input types for internal storage. :param value: User-side input value to sanitize. - :type value: string, int, or castable to float + :type value: string, int, NoneType, or castable to float - Strings and integers are left alone, + Strings, integers and None are left alone, everything else is converted to float. """ if isinstance(value,str): return value if isinstance(value,int): return value + if value is None: + return value return float(value) diff --git a/tests/test_uncertainty.py b/tests/test_uncertainty.py index e121e3bf..b982c76c 100644 --- a/tests/test_uncertainty.py +++ b/tests/test_uncertainty.py @@ -115,3 +115,18 @@ def test_zero_uncertainties(self): # Check that 'errors' key is missing only if zero uncertainties self.assertTrue(all('errors' in dictionary['values'][i] for i in [0, 1, 3])) self.assertTrue('errors' not in dictionary['values'][2]) + + def test_inhomogenous_uncertainties(self): + '''Test cases where an uncertainty only applies to a subset of the bins''' + var = Variable("testvar", is_independent=False, is_binned=False, values=[1,2,3], + zero_uncertainties_warning=False) + unc_a = Uncertainty('errorA', is_symmetric=True) + unc_a.values = [ 0.1, 0.2, None ] + var.add_uncertainty(unc_a) + unc_b = Uncertainty('errorB', is_symmetric=True) + unc_b.values = [ 0.1, 0.2, 0.3 ] + var.add_uncertainty(unc_b) + dictionary = var.make_dict() + self.assertTrue(len([ errs['label'] for i in [0,1,2] \ + for errs in dictionary['values'][i]['errors'] \ + if errs['label'] == 'errorA'])==2)