Repository: scikit-hep/pyhf

Issue:
Different ordering of channels between model.config.channels and mode.config.channel_nbins
# Description

We've recently observed that the ordering of channels outputted from `model.config.channels` differs from the order obtained with `model.config.channel_nbins`. This isn't really a bug, but more a quirk which we thought would be worth bringing to the attention of the developers. We ran into some issues when breaking up the `expected_data` list into individual channels by looping through the `model.config.channel_nbins` ordering, rather than the `model.config.channels` ordering (the `model.config.channels` order matches that of the model). Hopefully this issue helps save another user some time in the future, at very least. 

# Expected Behavior

We expected that the order of the channels in the two dictionaries from `model.config.channels` and `model.config.channel_nbins` would be the same. 

# Actual Behavior

The ordering of the channels is different. As an example, we are generating workspaces using two categories, and we're separating our data by year (2016, 2017, and 2018). This gives us six channels total. The outputs are: 
```
(Pdb) model.config.channels
['vbf_channel_16_high_cat', 'vbf_channel_16_low_cat', 'vbf_channel_17_high_cat', 'vbf_channel_17_low_cat', 'vbf_channel_18_high_cat', 'vbf_channel_18_low_cat']

(Pdb) model.config.channel_nbins
{'vbf_channel_16_low_cat': 12, 'vbf_channel_16_high_cat': 18, 'vbf_channel_17_low_cat': 12, 'vbf_channel_17_high_cat': 18, 'vbf_channel_18_low_cat': 12, 'vbf_channel_18_high_cat': 18}
```
I believe that `model.config.channels` is possibly re-ordering the channels so that the names are in alphabetical order. I have not confirmed this, though. The workspace .json file is filled with the ordering produced by `model.config.channel_nbins`. 

# Steps to Reproduce

I'm using pyhf version 0.6.2 along with python 3.8.8. 
I can make a dummy workspace for this issue, but I thought that since this is a pretty small issue, it might be overkill. Please let me know if this would be helpful, though. 

# Checklist

- [ ] Run `git fetch` to get the most up to date version of `master`
- [X] Searched through existing Issues to confirm this is not a duplicate issue
- [X] Filled out the Description, Expected Behavior, Actual Behavior, and Steps to Reproduce sections above or have edited/removed them in a way that fully describes the issue


Thanks! -Rachel 



Discussion:

Resolving patch:
diff --git a/src/pyhf/mixins.py b/src/pyhf/mixins.py
--- a/src/pyhf/mixins.py
+++ b/src/pyhf/mixins.py
@@ -42,6 +42,9 @@ def __init__(self, *args, **kwargs):
         self.samples = sorted(list(set(self.samples)))
         self.parameters = sorted(list(set(self.parameters)))
         self.modifiers = sorted(list(set(self.modifiers)))
+        self.channel_nbins = {
+            channel: self.channel_nbins[channel] for channel in self.channels
+        }
 
         self.channel_slices = {}
         begin = 0


Test patch:
diff --git a/tests/test_mixins.py b/tests/test_mixins.py
--- a/tests/test_mixins.py
+++ b/tests/test_mixins.py
@@ -45,3 +45,12 @@ def test_channel_summary_mixin_empty():
     assert mixin.modifiers == []
     assert mixin.parameters == []
     assert mixin.samples == []
+
+
+def test_channel_nbins_sorted_as_channels(spec):
+    assert "channels" in spec
+    spec["channels"].append(spec["channels"][0].copy())
+    spec["channels"][-1]["name"] = "a_make_first_in_sort_channel2"
+    mixin = pyhf.mixins._ChannelSummaryMixin(channels=spec["channels"])
+    assert mixin.channels == ["a_make_first_in_sort_channel2", "channel1"]
+    assert list(mixin.channel_nbins.keys()) == mixin.channels