5.8 Normalizing Histograms
Table of Contents
Scaling histograms
When you normalize a histogram in ROOT, you change the overall scale of the bin contents without altering the relative shape of the distribution. The main tool for this is the Scale() method of TH1.
Suppose you have a histogram that has already been filled, for example:
TH1F *h = new TH1F("h", "Example", 100, 0, 10);
// ... fill h ...You can multiply all bin contents and their associated errors by a constant factor $c$ with:
h->Scale(c);This is equivalent to performing, for every bin $i$,
$$
n_i' = c \, n_i
$$
where $n_i$ is the original content of bin $i$, and $n_i'$ is the new content after scaling.
If your histogram has stored bin errors, for example because you called Sumw2() before filling or because you used weights, then Scale() also scales the errors. For each bin error $\sigma_i$ you get
$$
\sigma_i' = c \, \sigma_i .
$$
If you want ROOT to track uncertainties properly when you fill with weights and then scale, always enable the storage of sum of squared weights before filling:
h->Sumw2(); // call this before filling the histogram
// then fill h
Important rule: Use h->Sumw2() before filling if you care about bin errors and plan to apply weights or scaling. Otherwise, errors may not be computed correctly.
Scaling is typically used to:
- Convert counts to physical units, for example counts to counts per second or counts per unit mass.
- Normalize histograms so that they represent probabilities instead of raw event counts.
- Make samples of different sizes comparable by giving them the same total area or entries.
Note that Scale() affects the original histogram. If you want to keep the unscaled version, clone it first:
TH1F *h_scaled = (TH1F*)h->Clone("h_scaled");
h_scaled->Scale(c);Unit normalization
Unit normalization is a special case of scaling where you choose the scale factor so that the total area under the histogram becomes 1. Interpreted as a function of $x$, the histogram then represents a probability density estimate rather than simple counts.
For a simple, unweighted histogram, the total number of entries $N$ is given by:
double N = h->GetEntries();If all events are filled with weight 1 and the bin width is constant, a simple approximate normalization is to scale by $1/N$:
if (h->GetEntries() > 0) {
h->Scale(1.0 / h->GetEntries());
}After this operation, the sum of all bin contents is 1. However, if the bin widths are not equal, or if you want a proper probability density with respect to the $x$ variable, you should normalize by the integral of the histogram, which already includes the bin widths.
ROOT provides Integral() for this purpose:
double integral = h->Integral(); // by default, includes all bins except under/overflow
if (integral > 0) {
h->Scale(1.0 / integral);
}Mathematically, if bin $i$ has width $\Delta x_i$ and content $n_i$, the integral is
$$
I = \sum_i n_i \, ,
$$
when Integral() is called without special options. After scaling by $1/I$, you obtain
$$
\sum_i n_i' = 1 \quad \text{with} \quad n_i' = \frac{n_i}{I} .
$$
If your histogram was filled with weights, or has variable bin widths, using Integral() is usually the better choice than simply dividing by GetEntries().
Sometimes you want to include underflow and overflow bins in the normalization. You can do this by explicitly specifying the bin range:
int firstBin = 0; // includes underflow bin
int lastBin = h->GetNbinsX() + 1; // includes overflow bin
double integral_all = h->Integral(firstBin, lastBin);
if (integral_all > 0) {
h->Scale(1.0 / integral_all);
}
Normalization rule: To turn a histogram into a probability distribution, scale by the inverse of its integral:
$$
h \rightarrow \frac{1}{\int h} \, h .
$$
Use h->Scale(1.0 / h->Integral()); in ROOT, and consider whether to include underflow and overflow bins.
Be aware that normalizing a histogram affects the interpretation of errors. If the original bin content $n_i$ has an uncertainty $\sigma_i$, then after scaling by $1/I$ you have
$$
n_i' = \frac{n_i}{I}, \quad \sigma_i' = \frac{\sigma_i}{I} .
$$
In most simple cases where counts follow Poisson statistics, and $I$ is close to the total number of events, this corresponds to transforming counts into estimated probabilities with associated uncertainties.
Comparing distributions
A common reason to normalize histograms is to compare the shapes of different distributions, even if they come from samples with different total statistics or correspond to different exposure times or integrated luminosities.
Suppose you have two histograms, h_data and h_mc, that cover the same $x$ range and binning, but have different numbers of entries:
TH1F *h_data = ...; // filled with data
TH1F *h_mc = ...; // filled with simulationIf you draw them directly, the one with more total events will appear larger everywhere. This hides differences in shapes. To compare shapes, scale at least one of them.
One simple choice is to normalize both to unit area:
double int_data = h_data->Integral();
double int_mc = h_mc->Integral();
if (int_data > 0) h_data->Scale(1.0 / int_data);
if (int_mc > 0) h_mc->Scale(1.0 / int_mc);After this, both histograms have area 1 and can be overlaid for shape comparison:
h_data->SetLineColor(kBlack);
h_mc->SetLineColor(kRed);
h_data->Draw("HIST");
h_mc->Draw("HIST SAME");In this scenario, any difference in the curves reflects a genuine difference in shape, not a trivial difference in sample size.
In some cases, you want to match one histogram to another by using a scale factor that reflects a known exposure or a theoretical cross section. Assume h_mc was generated with a cross section $\sigma_{\text{gen}}$ and number of generated events $N_{\text{gen}}$, but you want to scale it to a target luminosity $L$ so that it represents the expected yield. The scale factor is
$$
c = \frac{L \, \sigma_{\text{gen}}}{N_{\text{gen}}} .
$$
Then you apply
h_mc->Scale(c);
After this, the total area of h_mc corresponds to the expected number of events at luminosity $L$, but the shape is unchanged.
Another useful technique is to scale a simulation histogram to have the same total number of entries as the data, which allows a direct overlay:
double nData = h_data->Integral();
double nMC = h_mc->Integral();
if (nMC > 0) {
h_mc->Scale(nData / nMC);
}
Now the integral of h_mc matches that of h_data. Any differences in the bin contents reflect shape differences rather than overall normalization.
You can summarize common normalization choices as follows:
| Goal | Scale factor for histogram $h$ |
|---|---|
| Unit area (probability density) | $1 / h\text{.Integral()}$ |
| Match another histogram $g$ | $g\text{.Integral()} / h\text{.Integral()}$ |
| Expected yield at luminosity $L$ | $(L \, \sigma_{\text{gen}}) / N_{\text{gen}}$ |
| Convert counts to rate (per second) | $1 / T$ where $T$ is the measurement time |
Key comparison rule: To compare shapes, normalize histograms to a common reference, for example unit area or the same total integral. Do not rely on raw counts if the samples have different sizes or exposures.
When comparing normalized histograms, keep an eye on statistical uncertainties. If one histogram has far fewer events, its fluctuations will be larger, even after normalization. ROOT draws the statistical error bars automatically if you use options like "E" when drawing:
h_data->Draw("E");
h_mc->Draw("HIST SAME");The combination of proper normalization and visible error bars gives a clear and honest comparison of distributions in ROOT.
Views: 13
KAHIBARO