11.7. Fit Residuals
Table of Contents
Calculating residuals
Residuals are the differences between your data and the values predicted by your fit function. They show where the fit works well and where it fails. For a data point with coordinate $x_i$, measured value $y_i$, and fit function $f(x)$, the residual is
$$r_i = y_i - f(x_i).$$
If the data point has an uncertainty $\sigma_i$ on $y_i$, you can also use the normalized (or pull) residual
$$r_i^{\text{norm}} = \frac{y_i - f(x_i)}{\sigma_i}.$$
Key definitions
Residual:
$$r_i = y_i - f(x_i).$$
Normalized residual (pull):
$$r_i^{\text{norm}} = \dfrac{y_i - f(x_i)}{\sigma_i}.$$
In ROOT, the way you compute residuals depends on whether you fit a histogram or a graph.
For a histogram TH1 h with a fit TF1 f already applied, residuals are naturally defined per bin. For bin index i you have a bin center x = h->GetBinCenter(i) and content y = h->GetBinContent(i). The predicted value from the fit is f->Eval(x). You can compute and store residuals in a separate histogram, which is usually created with the same binning as the original. For example, for simple unnormalized residuals:
TH1D* hRes = (TH1D*)h->Clone("hRes");
hRes->SetTitle("Residuals;X;Data - Fit");
hRes->Reset();
int nbins = h->GetNbinsX();
for (int i = 1; i <= nbins; ++i) {
double x = h->GetBinCenter(i);
double y = h->GetBinContent(i);
double yfit = f->Eval(x);
double r = y - yfit;
hRes->SetBinContent(i, r);
}
If you want normalized residuals for a histogram, you should divide by the bin error returned by GetBinError(i), which typically comes from Poisson counting statistics or from Sumw2 if you use weights. The code then looks like:
TH1D* hPull = (TH1D*)h->Clone("hPull");
hPull->SetTitle("Pulls;X;(Data - Fit) / #sigma");
hPull->Reset();
for (int i = 1; i <= nbins; ++i) {
double x = h->GetBinCenter(i);
double y = h->GetBinContent(i);
double err = h->GetBinError(i);
if (err <= 0) continue; // skip empty or ill-defined bins
double yfit = f->Eval(x);
double rnorm = (y - yfit) / err;
hPull->SetBinContent(i, rnorm);
}
The same idea applies when fitting TGraph or TGraphErrors objects. You loop over points instead of bins and then fill a residual or pull histogram. For a TGraphErrors* g, for instance:
int n = g->GetN();
double* xs = g->GetX();
double* ys = g->GetY();
double* yerrs = g->GetEY();
TH1D* hPull = new TH1D("gPull","Pulls; (Data - Fit) / #sigma; Entries",
40, -5, 5);
for (int i = 0; i < n; ++i) {
double x = xs[i];
double y = ys[i];
double err = yerrs ? yerrs[i] : 0.0;
if (err <= 0) continue;
double yfit = f->Eval(x);
double rnorm = (y - yfit) / err;
hPull->Fill(rnorm);
}This separates the residual computation from the visualization and lets you inspect how the residuals themselves are distributed.
Plotting residual distributions
Residual plots help you to see patterns that are hidden in the main fit plot. With ROOT you often use two complementary types of residual displays: residuals as a function of $x$, and the distribution of residual values in a histogram.
A common layout uses a canvas divided into two pads. The upper pad shows the data with the fitted function, and the lower pad shows the residuals as a function of $x$. For histogram fits, you can do something like:
TCanvas* c = new TCanvas("c","Fit with residuals",800,800);
c->Divide(1,2);
// Top pad: data and fit
c->cd(1);
gPad->SetPad(0.0,0.3,1.0,1.0); // top 70% of canvas
h->Draw("E"); // draw with errors
f->Draw("SAME");
// Bottom pad: residuals vs X
c->cd(2);
gPad->SetPad(0.0,0.0,1.0,0.3); // bottom 30%
gPad->SetGridy(); // horizontal grid helps reading
hRes->SetStats(0);
hRes->Draw("E");
You can replace hRes by your pull histogram hPull. For normalized residuals, you often set the y axis range to a few sigma, for example from β5 to +5, so you can quickly see outliers:
hPull->GetYaxis()->SetRangeUser(-5,5);
hPull->Draw("E");Residuals versus $x$ should fluctuate around zero with no obvious structure. Systematic trends, such as residuals mostly positive in one region and mostly negative in another, are a strong indication that the model does not capture the data correctly or that the fit range is not suitable.
To examine the distribution of residuals themselves, you can draw the pull histogram as a 1D distribution. This shows how often a given residual value appears, independent of $x$:
TCanvas* c2 = new TCanvas("c2","Pull distribution",600,400);
hPull->SetTitle("Pull distribution;(Data - Fit) / #sigma;Entries");
hPull->Draw();You can optionally fit this distribution with a Gaussian to test whether the residuals behave as expected. For properly estimated uncertainties and a good model, the normalized residuals should be close to a standard normal distribution with mean near zero and width near one:
hPull->Fit("gaus");The fitted mean and sigma from this Gaussian give you a quick check of your error model. A mean significantly different from zero suggests a bias in the fit. A width significantly larger than one suggests that the data uncertainties are underestimated or that the model is missing fluctuations. A width much smaller than one suggests that the uncertainties are overestimated.
In summary, plotting residuals against $x$ reveals local structures, while plotting the distribution of normalized residuals shows whether the residuals globally behave like random statistical fluctuations. Both views are valuable when you interpret ROOT fits to histograms or graphs.
Views: 9
KAHIBARO