diff --git a/src/uproot/behaviors/TH1.py b/src/uproot/behaviors/TH1.py index 2f7c453a1..f09055a56 100644 --- a/src/uproot/behaviors/TH1.py +++ b/src/uproot/behaviors/TH1.py @@ -109,7 +109,12 @@ def weighted(self): """ True if the histogram has weights (``fSumw2``); False otherwise. """ - raise NotImplementedError(repr(self)) + sumw2 = self.member("fSumw2") + return ( + sumw2 is not None + and len(sumw2) > 0 + and len(sumw2) == self.member("fNcells") + ) @property def kind(self): @@ -180,7 +185,7 @@ def counts(self, flow=False): """ return self.values(flow=flow) - def to_boost(self, metadata=boost_metadata, axis_metadata=boost_axis_metadata): + def to_boost(self, metadata=None, axis_metadata=None): """ Args: metadata (dict of str \u2192 str): Metadata to collect (keys) and @@ -190,9 +195,63 @@ def to_boost(self, metadata=boost_metadata, axis_metadata=boost_axis_metadata): Converts the histogram into a ``boost-histogram`` object. """ - raise NotImplementedError(repr(self)) + if axis_metadata is None: + axis_metadata = boost_axis_metadata + if metadata is None: + metadata = boost_metadata + + boost_histogram = uproot.extras.boost_histogram() + + values = self.values(flow=True) - def to_hist(self, metadata=boost_metadata, axis_metadata=boost_axis_metadata): + sumw2 = None + if self.weighted: # ensures self.member("fSumw2") exists + sumw2 = self.member("fSumw2") + sumw2 = numpy.asarray(sumw2, dtype=sumw2.dtype.newbyteorder("=")) + sumw2 = numpy.reshape(sumw2, values.shape) + storage = boost_histogram.storage.Weight() + else: + if issubclass(values.dtype.type, numpy.integer): + storage = boost_histogram.storage.Int64() + else: + storage = boost_histogram.storage.Double() + + axes = [ + _boost_axis(self.member(name), axis_metadata) + for name in ["fXaxis", "fYaxis", "fZaxis"][0 : len(self.axes)] + ] + out = boost_histogram.Histogram(*axes, storage=storage) + for k, v in metadata.items(): + setattr(out, k, self.member(v)) + + assert len(axes) <= 3, "Only 1D, 2D, and 3D histograms are supported" + assert len(values.shape) == len( + axes + ), "Number of dimensions must match number of axes" + for i, axis in enumerate(axes): + if not isinstance( + axis, + (boost_histogram.axis.IntCategory, boost_histogram.axis.StrCategory), + ): + continue + slicer = (slice(None),) * i + (slice(1, None),) + values = values[slicer] + if sumw2 is not None: + sumw2 = sumw2[slicer] + + view = out.view(flow=True) + if sumw2 is not None: + assert ( + sumw2.shape == values.shape + ), "weights (fSumw2) and values should have same shape" + view.value = values + view.variance = sumw2 + else: + view[...] = values + + return out + + def to_hist(self, metadata=None, axis_metadata=None): """ Args: metadata (dict of str \u2192 str): Metadata to collect (keys) and @@ -203,7 +262,7 @@ def to_hist(self, metadata=boost_metadata, axis_metadata=boost_axis_metadata): Converts the histogram into a ``hist`` object. """ return uproot.extras.hist().Hist( - self.to_boost(metadata=boost_metadata, axis_metadata=boost_axis_metadata) + self.to_boost(metadata=metadata, axis_metadata=axis_metadata) ) # Support direct conversion to histograms, such as bh.Histogram(self) or hist.Hist(self) @@ -227,11 +286,6 @@ def axis(self, axis=0): # default axis for one-dimensional is intentional else: raise ValueError("axis must be 0 (-1) or 'x' for a TH1") - @property - def weighted(self): - sumw2 = self.member("fSumw2", none_if_missing=True) - return sumw2 is not None and len(sumw2) == self.member("fNcells") - @property def kind(self): return "COUNT" @@ -291,37 +345,3 @@ def to_numpy(self, flow=False, dd=False): return values, (xedges,) else: return values, xedges - - def to_boost(self, metadata=boost_metadata, axis_metadata=boost_axis_metadata): - boost_histogram = uproot.extras.boost_histogram() - - values = self.values(flow=True) - - sumw2 = self.member("fSumw2", none_if_missing=True) - - if sumw2 is not None and len(sumw2) == self.member("fNcells"): - sumw2 = numpy.asarray(sumw2, dtype=sumw2.dtype.newbyteorder("=")) - sumw2 = numpy.reshape(sumw2, values.shape) - storage = boost_histogram.storage.Weight() - else: - if issubclass(values.dtype.type, numpy.integer): - storage = boost_histogram.storage.Int64() - else: - storage = boost_histogram.storage.Double() - - xaxis = _boost_axis(self.member("fXaxis"), axis_metadata) - out = boost_histogram.Histogram(xaxis, storage=storage) - for k, v in metadata.items(): - setattr(out, k, self.member(v)) - - if isinstance(xaxis, boost_histogram.axis.StrCategory): - values = values[1:] - - view = out.view(flow=True) - if sumw2 is not None and len(sumw2) == len(values): - view.value = values - view.variance = sumw2 - else: - view[...] = values - - return out diff --git a/src/uproot/behaviors/TH2.py b/src/uproot/behaviors/TH2.py index 0541ec4fa..40d14126b 100644 --- a/src/uproot/behaviors/TH2.py +++ b/src/uproot/behaviors/TH2.py @@ -9,7 +9,6 @@ import numpy import uproot -from uproot.behaviors.TH1 import boost_axis_metadata, boost_metadata class TH2(uproot.behaviors.TH1.Histogram): @@ -32,11 +31,6 @@ def axis(self, axis): else: raise ValueError("axis must be 0 (-2), 1 (-1) or 'x', 'y' for a TH2") - @property - def weighted(self): - sumw2 = self.member("fSumw2", none_if_missing=True) - return sumw2 is not None and len(sumw2) == self.member("fNcells") - @property def kind(self): return "COUNT" @@ -100,40 +94,3 @@ def to_numpy(self, flow=False, dd=False): return values, (xedges, yedges) else: return values, xedges, yedges - - def to_boost(self, metadata=boost_metadata, axis_metadata=boost_axis_metadata): - boost_histogram = uproot.extras.boost_histogram() - - values = self.values(flow=True) - - sumw2 = self.member("fSumw2", none_if_missing=True) - - if sumw2 is not None and len(sumw2) == self.member("fNcells"): - sumw2 = numpy.asarray(sumw2, dtype=sumw2.dtype.newbyteorder("=")) - sumw2 = numpy.transpose(numpy.reshape(sumw2, values.shape[::-1])) - storage = boost_histogram.storage.Weight() - else: - if issubclass(values.dtype.type, numpy.integer): - storage = boost_histogram.storage.Int64() - else: - storage = boost_histogram.storage.Double() - - xaxis = uproot.behaviors.TH1._boost_axis(self.member("fXaxis"), axis_metadata) - yaxis = uproot.behaviors.TH1._boost_axis(self.member("fYaxis"), axis_metadata) - out = boost_histogram.Histogram(xaxis, yaxis, storage=storage) - for k, v in metadata.items(): - setattr(out, k, self.member(v)) - - if isinstance(xaxis, boost_histogram.axis.StrCategory): - values = values[1:, :] - if isinstance(yaxis, boost_histogram.axis.StrCategory): - values = values[:, 1:] - - view = out.view(flow=True) - if sumw2 is not None and len(sumw2) == len(values): - view.value = values - view.variance = sumw2 - else: - view[...] = values - - return out diff --git a/src/uproot/behaviors/TH3.py b/src/uproot/behaviors/TH3.py index f5c1ebad0..9f176b9f4 100644 --- a/src/uproot/behaviors/TH3.py +++ b/src/uproot/behaviors/TH3.py @@ -9,7 +9,6 @@ import numpy import uproot -from uproot.behaviors.TH1 import boost_axis_metadata, boost_metadata class TH3(uproot.behaviors.TH1.Histogram): @@ -36,11 +35,6 @@ def axis(self, axis): "axis must be 0 (-3), 1 (-2), 2 (-1) or 'x', 'y', 'z' for a TH3" ) - @property - def weighted(self): - sumw2 = self.member("fSumw2", none_if_missing=True) - return sumw2 is not None and len(sumw2) == self.member("fNcells") - @property def kind(self): return "COUNT" @@ -109,43 +103,3 @@ def to_numpy(self, flow=False, dd=False): return values, (xedges, yedges, zedges) else: return values, xedges, yedges, zedges - - def to_boost(self, metadata=boost_metadata, axis_metadata=boost_axis_metadata): - boost_histogram = uproot.extras.boost_histogram() - - values = self.values(flow=True) - - sumw2 = self.member("fSumw2", none_if_missing=True) - - if sumw2 is not None and len(sumw2) == self.member("fNcells"): - sumw2 = numpy.asarray(sumw2, dtype=sumw2.dtype.newbyteorder("=")) - sumw2 = numpy.transpose(numpy.reshape(sumw2, values.shape[::-1])) - storage = boost_histogram.storage.Weight() - else: - if issubclass(values.dtype.type, numpy.integer): - storage = boost_histogram.storage.Int64() - else: - storage = boost_histogram.storage.Double() - - xaxis = uproot.behaviors.TH1._boost_axis(self.member("fXaxis"), axis_metadata) - yaxis = uproot.behaviors.TH1._boost_axis(self.member("fYaxis"), axis_metadata) - zaxis = uproot.behaviors.TH1._boost_axis(self.member("fZaxis"), axis_metadata) - out = boost_histogram.Histogram(xaxis, yaxis, zaxis, storage=storage) - for k, v in metadata.items(): - setattr(out, k, self.member(v)) - - if isinstance(xaxis, boost_histogram.axis.StrCategory): - values = values[1:, :, :] - if isinstance(yaxis, boost_histogram.axis.StrCategory): - values = values[:, 1:, :] - if isinstance(zaxis, boost_histogram.axis.StrCategory): - values = values[:, :, 1:] - - view = out.view(flow=True) - if sumw2 is not None and len(sumw2) == len(values): - view.value = values - view.variance = sumw2 - else: - view[...] = values - - return out diff --git a/src/uproot/writing/identify.py b/src/uproot/writing/identify.py index fcc4872c6..e94a96f77 100644 --- a/src/uproot/writing/identify.py +++ b/src/uproot/writing/identify.py @@ -261,7 +261,11 @@ def to_writable(obj): try: # using flow=True if supported data = obj.values(flow=True) - fSumw2 = obj.variances(flow=True) + fSumw2 = ( + obj.variances(flow=True) + if obj.storage_type == boost_histogram.storage.Weight + else None + ) # and flow=True is different from flow=False (obj actually has flow bins) data_noflow = obj.values(flow=False) @@ -285,19 +289,23 @@ def to_writable(obj): data = numpy.zeros((s[0] + 2, s[1] + 2, s[2] + 2), dtype=d) data[1:-1, 1:-1, 1:-1] = tmp - tmp = obj.variances() - s = tmp.shape - if tmp is None: - fSumw2 = None - elif ndim == 1: - fSumw2 = numpy.zeros(s[0] + 2, dtype=">f8") - fSumw2[1:-1] = tmp - elif ndim == 2: - fSumw2 = numpy.zeros((s[0] + 2, s[1] + 2), dtype=">f8") - fSumw2[1:-1, 1:-1] = tmp - elif ndim == 3: - fSumw2 = numpy.zeros((s[0] + 2, s[1] + 2, s[2] + 2), dtype=">f8") - fSumw2[1:-1, 1:-1, 1:-1] = tmp + tmp = ( + obj.variances() + if obj.storage_type == boost_histogram.storage.Weight + else None + ) + fSumw2 = None + if tmp is not None: + s = tmp.shape + if ndim == 1: + fSumw2 = numpy.zeros(s[0] + 2, dtype=">f8") + fSumw2[1:-1] = tmp + elif ndim == 2: + fSumw2 = numpy.zeros((s[0] + 2, s[1] + 2), dtype=">f8") + fSumw2[1:-1, 1:-1] = tmp + elif ndim == 3: + fSumw2 = numpy.zeros((s[0] + 2, s[1] + 2, s[2] + 2), dtype=">f8") + fSumw2[1:-1, 1:-1, 1:-1] = tmp else: # continuing to use flow=True, because it is supported diff --git a/tests/test_0167-use-the-common-histogram-interface.py b/tests/test_0167-use-the-common-histogram-interface.py index f233d7afe..a49b5ce36 100644 --- a/tests/test_0167-use-the-common-histogram-interface.py +++ b/tests/test_0167-use-the-common-histogram-interface.py @@ -9,7 +9,12 @@ def test_axis(): with uproot.open(skhep_testdata.data_path("uproot-hepdata-example.root")) as f: - f["hpx"].axes[0] == f["hpx"].axis(0) == f["hpx"].axis(-1) == f["hpx"].axis("x") + assert ( + f["hpx"].axes[0] + == f["hpx"].axis(0) + == f["hpx"].axis(-1) + == f["hpx"].axis("x") + ) axis = f["hpx"].axis() assert len(axis) == 100 assert axis[0] == (-4.0, -3.92) @@ -52,7 +57,7 @@ def test_axis(): ) with uproot.open(skhep_testdata.data_path("uproot-issue33.root")) as f: - f["cutflow"].axes[0] == f["cutflow"].axis(0) == f["cutflow"].axis("x") + assert f["cutflow"].axes[0] == f["cutflow"].axis(0) == f["cutflow"].axis("x") axis = f["cutflow"].axis() assert len(axis) == 7 assert axis[0] == "Dijet" @@ -143,3 +148,10 @@ def test_boost_2(): # assert f["cutflow"].to_boost().title == "dijethad" # assert f["cutflow"].to_boost().axes[0].name == "xaxis" # assert f["cutflow"].to_boost().axes[0].title == "" + + +def test_issue_0722(): + boost_histogram = pytest.importorskip("boost_histogram") + + with uproot.open(skhep_testdata.data_path("uproot-issue-722.root")) as f: + f["hist"].to_boost() diff --git a/tests/test_0422-hist-integration.py b/tests/test_0422-hist-integration.py index 1fd7b5a4e..717c15f9f 100644 --- a/tests/test_0422-hist-integration.py +++ b/tests/test_0422-hist-integration.py @@ -1,6 +1,5 @@ # BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/main/LICENSE -import array import os import numpy as np @@ -191,6 +190,313 @@ def test_issue_0659(tmp_path): f = ROOT.TFile(newfile) h_opened2 = f.Get("h") h2_opened2 = f.Get("h2") - h_opened2.GetBinContent(0) == 0.0 - h2_opened2.GetBinContent(0) == 0.0 + assert h_opened2.GetBinContent(0) == 0.0 + assert h2_opened2.GetBinContent(0) == 0.0 f.Close() + + +def test_issue_722(tmp_path): + newfile = os.path.join(tmp_path, "newfile.root") + + h = ROOT.TH1D("h", "h", 10, 0.0, 1.0) + h.FillRandom("gaus", 10000) + + assert len(h.GetSumw2()) == 0 + + fout = ROOT.TFile(newfile, "RECREATE") + h.Write() + fout.Close() + + # open with uproot + with uproot.open(newfile) as fin: + h1 = fin["h"] + + assert len(h1.axes) == 1 + assert h1.axis(0).edges().tolist() == pytest.approx( + [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] + ) + assert len(h1.member("fSumw2")) == 0 + + # convert to hist + h2 = h1.to_hist() + assert str(h2.storage_type) == "" + + # write and read again + with uproot.recreate(newfile) as fout2: + fout2["h"] = h2 + + with uproot.open(newfile) as fin2: + h3 = fin2["h"] + + assert len(h3.member("fSumw2")) == 0 + + +def test_hist_weights_from_root(tmp_path): + newfile = os.path.join(tmp_path, "newfile.root") + + h = ROOT.TH1D("h", "h", 20, 0.0, 5.0) + for _ in range(1000): + # fill with random values and random weights + h.Fill(5.0 * np.random.random(), np.random.random()) + + assert len(h.GetSumw2()) == 22 # 20 bins + 2, should not be 0 since we have weights + + fout = ROOT.TFile(newfile, "RECREATE") + h.Write() + fout.Close() + + with uproot.open(newfile) as fin: + h1 = fin["h"] + + assert len(h1.member("fSumw2")) == 22 + + h2 = h1.to_hist() + assert str(h2.storage_type) == "" + + # write and read again + with uproot.recreate(newfile) as fout2: + fout2["h"] = h2 + + with uproot.open(newfile) as fin2: + h3 = fin2["h"] + + assert len(h3.member("fSumw2")) == 22 + + +def test_hist_weights_labels_from_root(tmp_path): + newfile = os.path.join(tmp_path, "newfile.root") + + h_weights = ROOT.TH1D( + "h_weights", "histogram with weights with labels", 12, 0.0, 5.0 + ) + h_noweights = ROOT.TH1D( + "h_noweights", "histogram without weights with labels", 12, 0.0, 5.0 + ) + h_noweights_nolabels = ROOT.TH1D( + "h_noweights_nolabels", "histogram without weights", 12, 0.0, 5.0 + ) + for _ in range(1000): + # fill with random values and random weights + h_weights.Fill(5.0 * np.random.random(), np.random.random()) + h_noweights.Fill(5.0 * np.random.random()) + h_noweights_nolabels.Fill(5.0 * np.random.random()) + + assert h_weights.GetNbinsX() == h_noweights.GetNbinsX() + for i in range(h_weights.GetNbinsX()): + h_weights.GetXaxis().SetBinLabel(i + 1, f"label_{i}") + h_noweights.GetXaxis().SetBinLabel(i + 1, f"label_{i}") + + assert len(h_weights.GetSumw2()) == 14 # 12 bins + 2 + assert len(h_noweights.GetSumw2()) == 0 + assert len(h_noweights_nolabels.GetSumw2()) == 0 + assert h_weights.GetXaxis().GetLabels().GetSize() == 12 + assert h_noweights.GetXaxis().GetLabels().GetSize() == 12 + + fout = ROOT.TFile(newfile, "RECREATE") + h_weights.Write() + h_noweights.Write() + h_noweights_nolabels.Write() + fout.Close() + + with uproot.open(newfile) as fin: + h_weights1 = fin["h_weights"] + h_noweights1 = fin["h_noweights"] + h_noweights_nolabels1 = fin["h_noweights_nolabels"] + + assert len(h_weights1.member("fSumw2")) == 14 + assert len(h_noweights1.member("fSumw2")) == 0 + assert len(h_noweights_nolabels1.member("fSumw2")) == 0 + + h_weights2 = h_weights1.to_hist() + h_noweights2 = h_noweights1.to_hist() + h_noweights_nolabels2 = h_noweights_nolabels1.to_hist() + assert str(h_weights2.storage_type) == "" + assert str(h_noweights2.storage_type) == "" + assert ( + str(h_noweights_nolabels2.storage_type) + == "" + ) + + +def test_hist_weights_2D(tmp_path): + newfile = os.path.join(tmp_path, "newfile.root") + + h_2D_noweights_nolabels = ROOT.TH2D( + "h_2D_noweights_nolabels", "2D", 20, 0.0, 5.0, 10, -10.0, 10.0 + ) + h_2D_weights_nolabels = ROOT.TH2D( + "h_2D_weights_nolabels", "2D", 20, 0.0, 5.0, 10, -10.0, 10.0 + ) + h_2D_noweights_labels = ROOT.TH2D( + "h_2D_noweights_labels", "2D", 20, 0.0, 5.0, 10, -10.0, 10.0 + ) + h_2D_weights_labels = ROOT.TH2D( + "h_2D_weights_labels", "2D", 20, 0.0, 5.0, 10, -10.0, 10.0 + ) + + for _ in range(1000): + # fill with random values and random weights + h_2D_noweights_nolabels.Fill( + 5.0 * np.random.random(), 10.0 * np.random.random() + ) + h_2D_weights_nolabels.Fill( + 5.0 * np.random.random(), 10.0 * np.random.random(), np.random.random() + ) + h_2D_noweights_labels.Fill(5.0 * np.random.random(), 10.0 * np.random.random()) + h_2D_weights_labels.Fill( + 5.0 * np.random.random(), 10.0 * np.random.random(), np.random.random() + ) + + assert ( + h_2D_noweights_nolabels.GetNbinsX() + == h_2D_weights_nolabels.GetNbinsX() + == h_2D_noweights_labels.GetNbinsX() + == h_2D_weights_labels.GetNbinsX() + == 20 + ) + assert ( + h_2D_noweights_nolabels.GetNbinsY() + == h_2D_weights_nolabels.GetNbinsY() + == h_2D_noweights_labels.GetNbinsY() + == h_2D_weights_labels.GetNbinsY() + == 10 + ) + for i in range(h_2D_weights_labels.GetNbinsX()): + h_2D_weights_labels.GetXaxis().SetBinLabel(i + 1, f"label_{i}") + h_2D_noweights_labels.GetXaxis().SetBinLabel(i + 1, f"label_{i}") + for i in range(h_2D_noweights_labels.GetNbinsY()): + # add y labels to this one + h_2D_noweights_labels.GetYaxis().SetBinLabel(i + 1, f"label_{i}") + + assert ( + len(h_2D_weights_nolabels.GetSumw2()) + == len(h_2D_weights_labels.GetSumw2()) + == 264 + ) + assert ( + len(h_2D_noweights_nolabels.GetSumw2()) + == len(h_2D_noweights_labels.GetSumw2()) + == 0 + ) + + assert ( + h_2D_weights_labels.GetXaxis().GetLabels().GetSize() + == h_2D_noweights_labels.GetXaxis().GetLabels().GetSize() + == 20 + ) + assert h_2D_noweights_labels.GetYaxis().GetLabels().GetSize() == 10 + + fout = ROOT.TFile(newfile, "RECREATE") + h_2D_noweights_nolabels.Write() + h_2D_weights_nolabels.Write() + h_2D_noweights_labels.Write() + h_2D_weights_labels.Write() + fout.Close() + + with uproot.open(newfile) as fin: + h_2D_noweights_nolabels = fin["h_2D_noweights_nolabels"] + h_2D_weights_nolabels = fin["h_2D_weights_nolabels"] + h_2D_noweights_labels = fin["h_2D_noweights_labels"] + h_2D_weights_labels = fin["h_2D_weights_labels"] + + h_2D_noweights_nolabels.to_hist() + h_2D_weights_nolabels.to_hist() + h_2D_noweights_labels.to_hist() + h_2D_weights_labels.to_hist() + + +def test_hist_weights_3D(tmp_path): + newfile = os.path.join(tmp_path, "newfile.root") + + h_3D_noweights_nolabels = ROOT.TH3D( + "h_3D_noweights_nolabels", "3D", 20, 0.0, 5.0, 10, -10.0, 10.0, 5, -5.0, 5.0 + ) + h_3D_weights_nolabels = ROOT.TH3D( + "h_3D_weights_nolabels", "3D", 20, 0.0, 5.0, 10, -10.0, 10.0, 5, -5.0, 5.0 + ) + h_3D_noweights_labels = ROOT.TH3D( + "h_3D_noweights_labels", "3D", 20, 0.0, 5.0, 10, -10.0, 10.0, 5, -5.0, 5.0 + ) + h_3D_weights_labels = ROOT.TH3D( + "h_3D_weights_labels", "3D", 20, 0.0, 5.0, 10, -10.0, 10.0, 5, -5.0, 5.0 + ) + + for _ in range(1000): + # fill with random values and random weights + h_3D_noweights_nolabels.Fill( + 5.0 * np.random.random(), 10.0 * np.random.random(), 2 * np.random.random() + ) + h_3D_weights_nolabels.Fill( + 5.0 * np.random.random(), + 10.0 * np.random.random(), + 2 * np.random.random(), + np.random.random(), + ) + h_3D_noweights_labels.Fill( + 5.0 * np.random.random(), 10.0 * np.random.random(), 2 * np.random.random() + ) + h_3D_weights_labels.Fill( + 5.0 * np.random.random(), + 10.0 * np.random.random(), + 2 * np.random.random(), + np.random.random(), + ) + + assert ( + h_3D_noweights_nolabels.GetNbinsX() + == h_3D_weights_nolabels.GetNbinsX() + == h_3D_noweights_labels.GetNbinsX() + == h_3D_weights_labels.GetNbinsX() + == 20 + ) + assert ( + h_3D_noweights_nolabels.GetNbinsY() + == h_3D_weights_nolabels.GetNbinsY() + == h_3D_noweights_labels.GetNbinsY() + == h_3D_weights_labels.GetNbinsY() + == 10 + ) + assert ( + h_3D_noweights_nolabels.GetNbinsZ() + == h_3D_weights_nolabels.GetNbinsZ() + == h_3D_noweights_labels.GetNbinsZ() + == h_3D_weights_labels.GetNbinsZ() + == 5 + ) + for i in range(h_3D_noweights_labels.GetNbinsX()): + h_3D_weights_labels.GetXaxis().SetBinLabel(i + 1, f"label_{i}") + for i in range(h_3D_noweights_labels.GetNbinsZ()): + # add z labels to this one + h_3D_noweights_labels.GetZaxis().SetBinLabel(i + 1, f"label_{i}") + + assert ( + len(h_3D_weights_nolabels.GetSumw2()) + == len(h_3D_weights_labels.GetSumw2()) + == 1848 + ) + assert ( + len(h_3D_noweights_nolabels.GetSumw2()) + == len(h_3D_noweights_labels.GetSumw2()) + == 0 + ) + + assert h_3D_weights_labels.GetXaxis().GetLabels().GetSize() == 20 + assert h_3D_noweights_labels.GetZaxis().GetLabels().GetSize() == 5 + + fout = ROOT.TFile(newfile, "RECREATE") + h_3D_noweights_nolabels.Write() + h_3D_weights_nolabels.Write() + h_3D_noweights_labels.Write() + h_3D_weights_labels.Write() + fout.Close() + + with uproot.open(newfile) as fin: + h_3D_noweights_nolabels = fin["h_3D_noweights_nolabels"] + h_3D_weights_nolabels = fin["h_3D_weights_nolabels"] + h_3D_noweights_labels = fin["h_3D_noweights_labels"] + h_3D_weights_labels = fin["h_3D_weights_labels"] + + h_3D_noweights_nolabels.to_hist() + h_3D_weights_nolabels.to_hist() + h_3D_noweights_labels.to_hist() + h_3D_weights_labels.to_hist()