Scale of `THStack` not correct when using histograms of very different sizes and log scale

I am using a THStack for its nice features of automatically scaling the y axis limit to accomodate all graphs. However, this fails when the two graphs are on very different scales and (I think) if some bin reaches below zero.

Here is an example demonstrating the problem in PyROOT, but it also appears with equivalent code in C++:

import ROOT

stack = ROOT.THStack()
h1 = ROOT.TH1D("h1", "h1", 100, -10, 10)
h1.FillRandom("gaus", 100000)
stack.Add(h1)
h2 = ROOT.TH1D("h2", "h2", 100, -10, 10)
h2.FillRandom("gaus", 100)
h2.SetLineColor(ROOT.kRed)
stack.Add(h2)

c = ROOT.TCanvas()
stack.Draw("nostackep")
c.SetLogy()
print stack.GetMinimum(), stack.GetMaximum()

The problem is, stack.GetMinimum() returns 0, so it doesn’t know what to use as lower bound. Therefore this is what is produced by this code:

Whereas I was expecting a graph where all data points (with positive values, which all are in this example) are visible, something like this:

Your two histograms minima are 0. So the rendering in log scale is not possible as log(0) is undefined. Therefore, to nevertheless plot something, ROOT takes a percentage of the maximum as lower limit to render the stack. A way to control this would be to define a minimum for the stack. A small positive value like 0.1 …

{
   auto *stack = new THStack();
   auto *h1    = new TH1D("h1", "h1", 100, -10, 10);
   h1->FillRandom("gaus", 100000);
   stack->Add(h1);
   auto *h2 = new TH1D("h2", "h2", 100, -10, 10);
   h2->FillRandom("gaus", 100);
   h2->SetLineColor(kRed);
   stack->Add(h2);
   stack->SetMinimum(0.1);

   auto *c = new TCanvas();
   stack->Draw("nostack ep");
   c->SetLogy();
   printf("%g\n",stack->GetHistogram()->GetMinimum());
}

Yes, I also realized this was the problem, but was hoping to find an automatic solution, because if I have to specify it every time, then the whole advantage of the THStack is lost.

Maybe I’ll write a small function that looks for the smallest value, excluding empty bins…

This small (Python) function seems to do the job. I think behavior similar to this is more what you expect, because having some bins empty might happen quite often, especially if you pot multiple histograms on top of each other:

import ROOT

class THStack(ROOT.THStack):
    def GetNonZeroMinimum(self):
        min_val = self.GetMaximum()
        for hist in self:
            for val in hist:
                if val != 0:
                    min_val = min(min_val, val)
        return min_val

stack = THStack()
h1 = ROOT.TH1D("h1", "h1", 100, -10, 10)
h1.FillRandom("gaus", 100000)
stack.Add(h1)
h2 = ROOT.TH1D("h2", "h2", 100, -10, 10)
h2.FillRandom("gaus", 100)
h2.SetLineColor(ROOT.kRed)
stack.Add(h2)

c = ROOT.TCanvas()
stack.Draw("nostackep")
c.SetLogy()
stack.SetMinimum(stack.GetNonZeroMinimum() / 2.)
c.Update()
print stack.GetMinimum(), stack.GetMaximum()

This produce this graph:

Interestingly, it still prints the minimum as 0.0, so I guess it always calculates it when calling stack.GetMinimum()?

Yes it does calculate it again.

Would it make sense to implement something like what I wrote above also in ROOT, instead of just taking some percent of the maximum value?

Yes that would be better I guess.

What about something like this:

Double_t THStack::GetNonZeroMinimum(Option_t *option)
{
   Double_t them = 0, themin = GetMaximum();
   if (!fHists) return 0;
   Int_t nhists = fHists->GetSize();
   TH1 *h;
   if (!opt.Contains("nostack")) {
      BuildStack();
      h = (TH1 *)fStack->At(nhists - 1);
      themin = h->GetMinimum();
      if (themin == 0.) {
         Double_t val;
         themin = h->GetMaximum();
         for (Int_t j = 1; j <= h->GetNbinsX(); i++) {
            val = h->GetBinContent(i);
            if (val != 0. && val < themin) themin = val;
         }
      }
   } else {
      for (Int_t i = 0; i < nhists; i++) {
         h = (TH1 *)fHists->At(i);
         them = h->GetMinimum();
         if (them == 0.) {
            Double_t val;
            them = h->GetMaximum();
            for (Int_t j = 1; j <= h->GetNbinsX(); i++) {
               val = h->GetBinContent(i);
               if (val != 0. && val < them) them = val;
            }
         }
         if (them < themin) themin = them;
      }
   }
   return themin;
}

The redundant part after if (themin == 0.) could be outsourced into a TH1::GetNonZeroMinimum function if desired.

Thanks for the suggestion ! I will investigate it.

May be we do not need a new method to do that. An option in the current GetMinimum() will also fly.

This already exist:

root [0] hpx->GetMinimum()
(double) 0.000000
root [1] hpx->GetMinimum(0.)
(double) 1.000000

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.