22.4. Missing Histograms
Table of Contents
Understanding Missing Histograms
When a histogram you expect to see is empty or does not appear at all, ROOT is almost never at fault. The cause is usually a small logic or lifecycle problem in your code. This chapter focuses on recognizing the typical symptoms, locating the cause, and fixing missing or empty histograms in a structured way.
Symptoms of Missing Histograms
There are a few common situations that users describe as “missing histograms”.
In the first case, you drew a histogram but the plot is completely blank. The axes may be visible, or sometimes not, and GetEntries() returns zero. This usually means the histogram exists but was never filled, or that all data fell into underflow or overflow bins.
In the second case, ROOT does not find the histogram object at all. For example, myHist->Draw() causes a crash because myHist is a null pointer, or file->Get("hE") returns a null pointer. Here the histogram never existed in memory, its name is different from what you expect, or it was never written to the file.
In the third case, you see the histogram during the session, but after reloading a ROOT file it has disappeared. Either the histogram was not written, was written in a different directory, or the file was closed incorrectly so the directory structure was not saved.
Recognizing which of these situations you are in will strongly guide the debugging steps.
Checking Histogram Creation
The first step is to make sure that the histogram actually exists as an object. For a standard 1D histogram, you typically call a constructor such as
$$
\text{TH1F} *h = \text{new TH1F}("h", "title", 100, 0.0, 10.0)\,.
$$
If you declare a histogram as a pointer but never assign it, it remains null. For instance, TH1F *h; by itself does not create a histogram. You must call new or construct it as a non pointer object such as TH1F h("h", "title", 100, 0.0, 10.0);.
Whenever you suspect that the object may not exist, check for null pointers. For example:
if (!h) {
std::cout << "Histogram h does not exist" << std::endl;
}
If h is a local object that goes out of scope at the end of a function, you may see it during macro execution but then lose it through ROOT object ownership rules when the function ends. In compiled code, a local histogram like
void mymacro() {
TH1F h("h","title",100,0,10);
// ...
}
ceases to exist after mymacro() returns. If you try to draw it after the function has returned, you will either see nothing or get a crash. To keep a histogram alive, either allocate it on the heap with new, store it in a directory or file, or design your macro so you draw or save it before the function ends.
For interactive work inside the ROOT prompt, CINT or Cling sometimes keep objects alive in internal lists, which can hide lifetime problems. If your code works interactively but fails when compiled, suspect object lifetime. Ensure that important histograms outlive the analysis and drawing steps that use them.
Always check that your histogram pointer is non null before using it, and make sure histograms are created before any filling or drawing code runs.
Verifying Fill Logic
If a histogram exists but is empty, the next step is to check whether the Fill() method is called at all, and whether the values passed to Fill() are inside the histogram range.
A typical analysis uses a loop over events:
for (Long64_t i = 0; i < nEntries; ++i) {
tree->GetEntry(i);
h->Fill(x);
}
If the loop never runs, perhaps because nEntries is zero, the histogram will stay empty. Verify that the number of events is what you expect, for example by printing tree->GetEntries() or logging the loop counter.
Within the loop, use temporary printouts to see if your code actually reaches the filling line:
std::cout << "Entry " << i << ", x = " << x << std::endl;
h->Fill(x);
If you do not see these lines, then the loop or the body section that calls Fill() is not executed. Conditional statements such as if might block filling. For example:
if (x > 0 && x < 10) {
h->Fill(x);
}If your data never satisfies the condition, the histogram stays empty. Print some sample values and check that your cuts are sensible.
After the loop, always inspect basic histogram properties:
std::cout << "Entries: " << h->GetEntries() << std::endl;
If this number is zero, no Fill() calls reached the histogram. If it is non zero, the missing content is more likely related to binning or drawing.
If GetEntries() is zero after your analysis loop, either the loop never executed, or the code inside it never called Fill() for any event.
Binning Issues and Out of Range Fills
A very common cause of apparently empty histograms is that your bin range does not match the data. A histogram that covers the interval [0, 1] will not show any data if the actual variable values are around 100. In that case, all entries go to overflow, which does not appear by default on the plot.
For a histogram created with
TH1F *h = new TH1F("h", "title", 100, 0.0, 1.0);
the valid range is from 0.0 to 1.0. Any value less than 0.0 goes into the underflow bin, index 0. Any value greater than 1.0 goes into the overflow bin, index nbins + 1. Although the histogram GetEntries() will show the total number of Fill() calls, the visible bins may all be zero.
To diagnose this, check representative values of the variable you are filling. Print the minimum and maximum values seen in the loop and compare to the histogram range:
double minx = +1e30;
double maxx = -1e30;
for (Long64_t i = 0; i < nEntries; ++i) {
tree->GetEntry(i);
if (x < minx) minx = x;
if (x > maxx) maxx = x;
h->Fill(x);
}
std::cout << "x range: [" << minx << ", " << maxx << "]" << std::endl;
If this range is much wider than the histogram axis, redefine the histogram with appropriate bounds. For example, if x ranges from 90 to 110, you might use TH1F("h","title",100, 80, 120).
ROOT provides ways to query underflow and overflow bin contents explicitly. For a 1D histogram:
int nb = h->GetNbinsX();
double under = h->GetBinContent(0);
double over = h->GetBinContent(nb + 1);
If most of your statistics are in under or over, adjust the bin range.
Always choose histogram ranges that actually cover your data, or you will hide most entries in underflow and overflow bins.
Another subtle issue occurs if you accidentally pass uninitialized variables to Fill(). If the variable used is not set correctly in each event, it may contain random values, possibly NaN. Filling a histogram with NaN values will not increment any bin. Check that every variable passed to Fill() is assigned a meaningful value before use.
Drawing and Canvas Issues
Sometimes a histogram is filled correctly, but it does not show up on the screen because of how you draw it or how you manage canvases.
First, ensure you draw the histogram on a visible canvas. If you create a TCanvas in a macro and let it go out of scope, the window may disappear when the macro ends, especially in compiled code. For example:
void mymacro() {
TCanvas *c = new TCanvas("c","c",800,600);
h->Draw();
}
This is usually safe, because the canvas is allocated with new. However, if you create it as a local object without new:
void mymacro() {
TCanvas c("c","c",800,600);
h->Draw();
}
then c is destroyed when the function returns, and nothing remains on screen. If you run a macro in batch mode (root -b -q), you must save the canvas to a file explicitly, because there is no interactive window.
Second, make sure you draw the object you think you are drawing. It is easy to fill h1 and then accidentally draw h2. If one of them is empty, you may believe the filling failed. Confirm by printing the histogram pointer before drawing:
std::cout << h->GetName() << " entries: " << h->GetEntries() << std::endl;
h->Draw();If the histogram has entries but the plot looks empty, check the axis ranges. You might have applied manual axis limits that exclude all data, for instance by calling
h->GetXaxis()->SetRangeUser(10, 20);while all data lies between 0 and 5. In this case, the histogram appears flat. Reset to automatic ranges or set them correctly.
Canvas update behavior can also create confusion. In interactive ROOT, histograms are usually drawn immediately. In scripts, you may need to call gPad->Update(); or c->Update(); after modifications to see changes. When using logarithmic axes, drawing a histogram that has bins with zero or negative content can cause scale issues. On a log axis, ROOT cannot display negative or zero values. If all bins are zero and you set SetLogy(1), the plot will appear blank even though the histogram exists.
Before suspecting missing fills, confirm that you are drawing the correct histogram on a live canvas, with appropriate axis ranges and axis scales.
Histograms Not Written to Files
A frequent confusion arises when histograms are visible during a session but do not appear in the saved ROOT file, or when loading a file later you cannot retrieve them.
To save histograms you must explicitly write them to a TFile or a TDirectory. A simple pattern is:
TFile *f = new TFile("out.root", "RECREATE");
TH1F *h = new TH1F("h", "title", 100, 0, 10);
// fill h
h->Write();
f->Close();
If you do not call Write() or you close the file in a wrong way, objects might not be stored. The "RECREATE" mode overwrites any existing file with the same name. If you reopen a file in "RECREATE" mode later in the macro, all previously written histograms will disappear.
When checking a file, first verify it is not a null pointer and that it is open:
TFile *f = TFile::Open("out.root");
if (!f || f->IsZombie()) {
std::cout << "Problem opening out.root" << std::endl;
}Then look at its contents:
f->ls();
This command lists all objects in the current directory. If your histogram name is different from what you think, ls() will reveal the actual name. For example, if you created TH1F h("h","title",...) as a stack object and did not call h.SetName("hNew"), ROOT may auto generate a name or you may be using the title by mistake.
To retrieve a histogram, you must use the exact name:
TH1F *h = (TH1F*) f->Get("h");
if (!h) {
std::cout << "Histogram h not found in file" << std::endl;
}
The Get() method is case sensitive. Get("H") and Get("h") are different. A common bug is to confuse the histogram title and its name. The first string in the constructor is the internal name, the second is the title that appears on plots:
TH1F *h = new TH1F("energy", "Energy spectrum", 100, 0, 10);
// name is "energy", title is "Energy spectrum"
To load it, you must use "energy", not "Energy spectrum".
Directories can also hide histograms. If you create a subdirectory in a ROOT file with f->mkdir("subdir") and then change directory with f->cd("subdir"), histograms you create afterwards are stored in that subdirectory. To list them, use f->cd("subdir"); gDirectory->ls();, or inspect with the ROOT browser.
A histogram that is not explicitly written with Write() to an open file, in the correct directory, using a stable name, will not be present when you reopen the file.
Name Conflicts and Overwriting
Name clashes can silently hide your intended histogram behind another object with the same name. In a single directory, ROOT requires that object names are unique. If you create multiple histograms with the same name, later ones overwrite earlier ones in the directory list. For instance:
TH1F *h = new TH1F("h","first",100,0,10);
// ...
TH1F *h2 = new TH1F("h","second",100,0,10);
In memory, you now have two pointers, h and h2, both holding different histograms. However, only the last created object with name "h" will be registered under that name in the current directory. If you write both to file with Write(), the second will overwrite the first on disk.
If you see only one histogram where you expected several, check that each has a unique name. You can use formatted names to avoid clashes:
TH1F *h = new TH1F(Form("h_run%d", runNumber),
Form("Run %d spectrum", runNumber),
100, 0, 10);
In interactive sessions, re running a macro that uses the same histogram names can quietly replace objects. If you use the ROOT browser, you may notice only the latest version. When debugging missing histograms, check for duplicate names with gDirectory->ls() and adopt a consistent naming convention in your analysis code.
Every histogram in the same ROOT directory must have a unique name. Reusing names will overwrite previous histograms and can make earlier results appear to be missing.
Typical Debugging Checklist
When you face missing or empty histograms, it helps to follow a fixed checklist. This reduces guessing and quickly pinpoints the problem.
First, verify object existence. Ensure that the histogram is constructed before use, that its pointer is non null, and that it remains in scope or allocated on the heap for as long as needed.
Second, print the number of entries after the filling code. If GetEntries() is zero, the filling code did not execute. Check loop bounds and conditional statements. Add temporary printouts inside the event loop to confirm that Fill() is called for at least some events.
Third, compare your histogram range to the actual data range. Compute minimal and maximal values of the variable filled into the histogram. If the data lie far outside the histogram axis, adjust the binning. Inspect underflow and overflow bin contents to see whether entries are accumulating outside the visible region.
Fourth, make sure you draw the correct object on a canvas that remains alive. Print the name and entries of the histogram just before drawing. If necessary, remove manual axis range settings that might be hiding content, and ensure that log scales are only used when bin contents are positive.
Fifth, if you are loading from a file, check that you have written the histogram properly. Confirm file mode, call Write() after filling, close the file, and later open it and inspect with ls(). Use the correct object name when calling Get(), and check for non null pointers after retrieval.
Finally, examine naming patterns and directories for potential overwriting. Ensure that no different histograms share the same name within a directory. If you use subdirectories, move to the right directory before listing or retrieving objects.
By systematically applying this checklist, you will resolve almost all issues where histograms seem to be missing, and you will also strengthen your understanding of ROOT object lifetimes, file structure, and plotting behavior, which will help prevent similar problems in more complex analyses.
Views: 13
KAHIBARO