Question about the scaling factor in ROOT’s weighted Poisson log-likelihood (WLS fit)

Hi ROOT experts,

I have been studying the implementation of the weighted log-likelihood fit (“WL” option) in ROOT (v6.36.04), and I would like to understand the statistical motivation behind one part of the implementation.

From the source code, ROOT defines the effective scaling factor as

s_i = sigma_i^2 / y_i

and

n_eff = y_i / s_i = y_i^2 / sigma_i^2
mu_eff = mu_i / s_i

which gives

l_i = s_i * [ n_eff * log(n_eff / mu_eff) + mu_eff - n_eff ]

I have independently implemented this expression and verified numerically that it reproduces ROOT’s MinFCN exactly for my weighted histogram fits.

While studying this implementation, I became curious about the role of the final scaling factor.

Once the effective quantities are defined as

n_eff = y_i / s_i
mu_eff = mu_i / s_i

it seems natural to consider the effective Poisson deviance

l_i = n_eff * log(n_eff / mu_eff) + mu_eff - n_eff by itself.

However, ROOT instead minimizes

l_i = s_i * [ n_eff * log(n_eff / mu_eff) + mu_eff - n_eff ]

While studying this implementation, I became curious about the role of the final scaling factor.

The effective count formalism is also discussed in the paper G. Böhm and G. Zech, Statistics of weighted Poisson events and its applications.

In a section on the Scaled Poisson Distribution (SPD), it is introduced for parameter estimation, where the effective quantities are defined in essentially the same way as in ROOT.

However, the likelihood in the paper does not appear to include the additional scaling factor s_i that multiplies the effective Poisson deviance.

So my question is:

Why does ROOT include the final scaling factor s_i?

  1. If the effective quantities (n_eff and mu_eff) are already introduced, why is the effective Poisson deviance by itself not sufficient?

  2. Would omitting the factor s_i lead to an incorrect likelihood or estimator? If so, could you explain what statistical property would be lost?

If there is a derivation, technical note, or publication explaining the origin of this factor, I would greatly appreciate a reference.

tagging @jonas @StephanH

I saw it, but I haven’t found the source code yet. :sweat_smile:

@Byungchul_Yu which code are you talking about specifically? There’s at least three places that deal with Poisson likelihoods.

Thanks! @StephanH I’m referring to the same implementation discussed in this earlier ROOT Forum thread:

https://root-forum.cern.ch/t/weighted-log-likelihood-estimation-for-low-statistics-binned-histogram/58191

@moneta Lorenzo’s reply there includes the relevant source code and a detailed explanation of how the weighted likelihood is implemented. My current question is a follow-up to that discussion: I understand how ROOT evaluates the expression numerically, but I’m trying to understand the statistical motivation for the scaling factor.

Hello @Byungchul_Yu ,

@jonas and me had a look, but for the moment, we can’t explain the factor. Jonas will check more in the coming days.

Hi @jonas, @moneta and @StephanH,

Just following up on this, as the topic will close automatically soon.
Have you had a chance to look into the statistical motivation behind the scaling factor s_i?
Any updates or insights would be greatly appreciated!

Hello, @Byungchul_Yu !

I took a closer look at the implementation of TH1::Fit method when option "WL" is specified.
The relevant code is inside ROOT::Fit::FitUtil::EvaluatePoissonLogL.
I will try to address your questions.

Math derivation

Firstly, let’s map variables from the code to analytical quantities using your designations.

// FitUtil.cxx

// bin content: y_i = sum w
auto y = *data.ValuePtr(i);

// model prediction: mu_i
fval = func(x, p);

// standard deviation: sigma_i = sqrt(sum w^2)
double error = data.Error(i); 

// effective weight or scale factor: s_i = sigma_i^2 / y_i
weight = (error * error) / y;

Following Böhm & Zech, a weighted bin content Y_i distribution can be modeled via a Scaled Poisson Distribution (SPD):

Y_i = s_{i, \text{true}} X_i, \, X_i \sim \text{Pois}(n_\text{eff,true})

where bin statistics are

y_i = \sum_{j} w_j, \quad \sigma_i^2 = \sum_{j} w_j^2

The estimated effective parameters are:

s_i = \frac{\sigma_i^2}{y_i},\quad n_\text{eff} = \frac{y_i^2}{\sigma_i^2} = \frac{y_i}{s_i},\quad \mu_\text{eff} = \frac{\mu_i}{s_i}

where \mu_i is model prediction for bin content.

The effective Poisson log-likelihood (dropping factorial term) is

l(\mu_\text{eff} \mid n_\text{eff}) = n_\text{eff} \log(\mu_\text{eff}) - \mu_\text{eff}

The Poisson deviance is

\Delta l = l(n_\text{eff} \mid n_\text{eff}) - l(\mu_\text{eff} \mid n_\text{eff}) = n_\text{eff} \log\frac{n_\text{eff}}{\mu_\text{eff}} - n_\text{eff} + \mu_\text{eff}

We can factor out s_i^{-1} by substituting back n_\text{eff} = y_i/s_i,\, \mu_\text{eff} = \mu_i/s_i

\begin{align*} \Delta l &= \frac{y_i}{s_i} \log\frac{y_i}{\mu_i} - \frac{y_i}{s_i} + \frac{\mu_i}{s_i} =\\ &= \frac{1}{s_i} \times \left( -y_i \log\frac{\mu_i}{y_i} + \mu_i - y_i \right) \end{align*}

Now compare to ROOT::Fit::FitUtil::EvaluatePoissonLogL implementation:

// FitUtil.cxx
// parts of log-likelihood calculation
nloglike -= weight * y * ( ROOT::Math::Util::EvalLog(fval/y) );
nloglike += weight  *  ( fval - y);

Since \mathtt{weight} = s_i, ROOT computes

\Delta l_\text{ROOT} = s_i \times \left( -y_i \log\frac{\mu_i}{y_i} + \mu_i - y_i \right)

In the limit (y_i - \mu_i) \ll \mu_i the correct deviance yields weighted Pearson’s \chi^2:

\Delta l \approx s_i^{-1} \left( -y_i \left(- \frac{y_i - \mu_i}{\mu_i} \right ) + \mu_i - y_i \right) = \frac{y_i}{\sigma_i^2} \times \frac{(y_i - \mu_i)^2}{\mu_i} \approx \frac{(y_i - \mu_i)^2}{\sigma_i^2}

For ROOT’s implementation, the limit becomes

\Delta l_\text{ROOT} \approx \frac{\sigma_i^2}{y_i} \times \frac{(y_i - \mu_i)^2}{\mu_i}

This inverts statistical weights: for example, large effective weights (s_i > 1) indicate overdispersion compared to Poisson distribution.
Such bins should have a reduced impact on objective function to minimize, but instead it is amplified.

Addressing questions

To sum up, regarding your first question, effective Poisson deviance is sufficient.

Regarding your second question, dropping s_i^{-1} factor (not s_i) yields basic Poisson deviance which disregards the variance of the weights inside bin.
It will produce biased point estimates with increased variance.
Also it will likely underestimate or overestimate error bounds if s_i > 1 or s_i < 1 over all bins.
Notably, I see that ROOT applies sandwich correction to covariance matrix in Fitter::ApplyWeightCorrection which fixes error bounds, but this doesn’t fix bias and increased variance of point estimates.

Possible bug

@StephanH and @jonas ,
I suspect that in the implementation inside ROOT::Fit::FitUtil::EvaluatePoissonLogL might be wrong.
Instead of

// FitUtil.cxx
// parts of log-likelihood calculation
nloglike -= weight * y * ( ROOT::Math::Util::EvalLog(fval/y) );
nloglike += weight  *  ( fval - y);

it should be

// FitUtil.cxx
// parts of log-likelihood calculation
nloglike -= (1.0/weight) * y * ( ROOT::Math::Util::EvalLog(fval/y) );
nloglike += (1.0/weight)  *  ( fval - y);

This behavior with multiplying log-likelihood with wrong prefactor appears both in v6.34.04 and v6.40.02.
If this is truly unintended, it might seriously affect fit convergence and quality in case of large weight variances inside bins.
Please let me know if I am missing something important in ROOT’s implementation.

For a reference, I suggest looking there:

Important: in the link they define scaling factor differently:

s_\text{scikit} = \frac{\sum w}{\sum w^2}

while convention in this topic is

s = \frac{\sum w^2}{\sum w}

Hi @Ako_b,

Thank you very much for your detailed response. In particular, you addressed exactly the second point I was trying to understand in my original question, and I really appreciate you bringing @moneta, @StephanH, and @jonas into the discussion to help clarify the ROOT implementation.

Before going further, I would like to make sure that I am following the notation used in your reply. Using the notation from my original post, at this point we define

s_i = \frac{\sigma_i^2}{y_i} = \frac{\sum_j w_j^2}{\sum_j w_j}.

I am currently using ROOT v6.30.02. To understand exactly what the WL option was doing in my analysis, I independently reconstructed the MinFCN bin by bin using

n_{\mathrm{eff},i} = \frac{y_i}{s_i} = \frac{y_i^2}{\sigma_i^2},

and

\mu_{\mathrm{eff},i} = \frac{\mu_i}{s_i}.

For each bin, I reproduced the ROOT WL contribution to the MinFCN using

s_i \left[ n_{\mathrm{eff},i} \ln \left( \frac{n_{\mathrm{eff},i}} {\mu_{\mathrm{eff},i}} \right) + \mu_{\mathrm{eff},i} - n_{\mathrm{eff},i} \right] = s_i \, \Delta\ell_i^{\mathrm{SPD}}.

Numerically, summing these bin contributions reproduces the MinFCN returned by the ROOT WL fit in my v6.30.02 setup.

However, since the quantity in the brackets is

n_{\mathrm{eff},i} \ln \left( \frac{n_{\mathrm{eff},i}} {\mu_{\mathrm{eff},i}} \right) + \mu_{\mathrm{eff},i} - n_{\mathrm{eff},i} = \Delta\ell_i^{\mathrm{SPD}} = \frac{1}{s_i} \Delta\ell_i^{\mathrm{Poisson}},

the additional factor of s_i outside the brackets cancels the 1/s_i factor already contained inside the brackets, giving

s_i \, \Delta\ell_i^{\mathrm{SPD}} = \Delta\ell_i^{\mathrm{Poisson}}.

This was exactly the part that motivated my second question: if the 1/s_i dependence carries the information about the variance of the weights in each bin, I could not understand why the additional s_i factor should cancel it.

From your response, my understanding is that this cancellation should not occur if the goal is to use the scaled-Poisson approximation.

I also noticed what appears to be an additional complication when comparing ROOT versions. Based on the discussion so far, my understanding is:

  • ROOT v6.30.02, which I am currently using: the implementation I reproduced is effectively

    s_i \left( \frac{1}{s_i} \Delta\ell_i^{\mathrm{Poisson}} \right) = \Delta\ell_i^{\mathrm{Poisson}}.
  • ROOT v6.34.04 and v6.40.02, based on your inspection: the implementation appears instead to evaluate

    s_i \, \Delta\ell_i^{\mathrm{Poisson}}.

If I am understanding all of this correctly, it seems that the behavior of the WL option may have changed between ROOT versions, and both implementations appear to differ from the scaled-Poisson prescription discussed above.

So I would really appreciate clarification on the following single point:

  • If the scaled-Poisson deviance is sufficient by itself, then no additional prefactor of s_i should be multiplied in front of it. Is there a ROOT version in which the WL method is implemented in this way, with the scaled-Poisson deviance used directly, and with the parameter uncertainties/covariance also calculated consistently?

Thank you again for looking into this and for helping clarify the issue.

With warm regards,
Byungchul Yu

Hi @Ako_b, @moneta, @StephanH, @jonas

I’m just following up on my previous message regarding the implementation of the WL option across different ROOT versions.

To recap briefly, I’m trying to confirm whether there is a specific ROOT version where the scaled-Poisson deviance is used directly (without the extra prefactor s_i canceling or multiplying it), along with consistent calculation of parameter uncertainties and the covariance matrix.

I completely understand that everyone is busy, but any clarification or guidance on this point when you have a moment would be greatly appreciated!

Thank you again for your time and help.

Byungchul Yu

Hi, @Byungchul_Yu !

Regarding ROOT versions, it seems that the log-likelihood evaluation code is the same for versions from v6.30.02 to 6.40.02.

To get definitive results, I’ve decided to run a small numerical experiment.

Theoretical setup

Consider a histogram with two bins and a model predicting mean number of events in each bin \mu.

Fix true value \mu_0.
First bin gets number of events

y_1 \sim \mathrm{Poisson}(\mu_0).

Model prediction for the first bin is \mu.

Fix true value of a scale factor s_0.
The second bin gets weighted number of events

y_2 \sim \mathrm{Poisson}(\mu_0/s_0) \times s_0.

In case of 0 < s_0 < 1 the second bin will get larger amount of events \mu_0/s_0 with small weights s_0.
Note that model prediction for mean number of events for the second bin is \mu too.

Let’s denote objective function D to minimize for this problem as

D = k_1 \, \Delta\ell_1^{\mathrm{Poisson}} + k_2 \, \Delta\ell_2^{\mathrm{Poisson}}

where k_i are arbitrary weights.

Estimator for \mu is

\hat{\mu} = \mathrm{argmin}\,D(\mu)

Let’s derive it assuming that y_i are large enough to not hit parameter boundary \mu = 0:

\begin{align} \frac{d}{d\mu}D(\mu) &= \frac{d}{d\mu} \sum_i k_i \Delta\ell_i^{\mathrm{Poisson}} = \\ &= \frac{d}{d\mu} \sum_i k_i \left( -y_i \log\frac{\mu}{y_i} + \mu - y_i \right) = \\ &= \sum_i k_i \left( -\frac{y_i}{\mu} + 1 \right) = 0 \end{align}

From there it follows that

\hat{\mu} = \frac{\sum_i k_i y_i}{\sum_i k_i}

which is greater than zero under assumption that y_i > 0.

Natural choices for k_i as a function of s_i = \frac{\sigma_i^2}{y_i} from this topic are limited to

k_i = \begin{cases} s_i, & \text{apparent from ROOT source code} \\ 1, & \text{Poisson likelihood} \\ \frac{1}{s_i}, & \text{Scaled Poisson likelihood} \end{cases}

Theoretical Estimator Performance

Note that by definition of how y_i are generated s_i are constant

s_1 = \frac{\sigma_1^2}{y_1} = \frac{\sum_{j=1}^{j=y_1} 1}{\sum_{j=1}^{j=y_1} 1} = 1,
s_2 = \frac{\sigma_2^2}{y_2} = \frac{\sum_{j=1}^{j=y_2} s_0^2}{\sum_{j=1}^{j=y_2} s_0} = s_0,

and hence k_i = \text{const}

Taking expectation and variance of estimator, we get

\mathrm{E}\hat{\mu} = \frac{k_1 \mathrm{E}y_1 + k_2 \mathrm{E}y_2}{k_1 + k_2} = \frac{k_1 \mu_0 + k_2 \mu_0/s_0 \times s_0}{k_1 + k_2} = \mu_0
\mathrm{Var}\,\hat{\mu} = \frac{k_1^2 \mathrm{Var}\,y_1 + k_2^2 \mathrm{Var}\,y_2}{(k_1 + k_2)^2} = \frac{k_1^2 \mu_0 + k_2^2 \mu_0/s_0 \times s_0^2}{(k_1 + k_2)^2} = \mu_0 \frac{k_1^2 + k_2^2 s_0}{(k_1 + k_2)^2}

We see that all estimators are unbiased, and choice of k_i affects only variance.

\mathrm{Var}\,\hat{\mu} = \mu_0 \times \begin{cases} \frac{1 + s_0^3}{(1 + s_0)^2}, & \text{ROOT source code} \\ \frac{1 + s_0}{4}, & \text{Poisson deviance} \\ \frac{s_0}{1 + s_0}, & \text{Scaled Poisson deviance} \end{cases}

It can be shown that for s_0 > -1

\frac{s_0}{1 + s_0} < \frac{1 + s_0}{4} < \frac{1 + s_0^3}{(1 + s_0)^2}

From another perspective, just by summing all events without weights we expect to see

N \approx \mu_0 (1 + 1/s_0)

events in total, and on the other hand

N = y_1 + y_2/s_0,

and we get rule of thumb estimator

\tilde{\mu} = \frac{y_1 + y_2/s_0}{1 + 1/s_0},

which coincides with Scaled Poisson estimator.

Numerical check

Let’s introduce two performance metrics under repeated toy experiments:

\text{bias} = \text{Mean}(\hat{\mu}) - \mu_0
\text{overdispersion} = \text{RMS}(\hat{\mu}) \left/ \sqrt{\frac{\mu_0 s_0}{1 + s_0}} \right.

the latter motivated by the fact that Scaled Poisson estimator should show the least variance.

I’ve written a script to compare distributions of \hat{\mu} given by TH1::Fit with WL option, and given by the three choices of the objective function.
True values are \mu_0 = 100, s_0 = 1/100 and 10000 toys are generated.
fit_WL.cxx (9.8 KB)

Results

Discussion

It can be clearly seen that ROOT performs similarly to objective function

or option 2, k_i = 1, despite the fact that as I see from the source code that ROOT’s objective function to minimize is

s_i^2 \, \Delta\ell_i^{\mathrm{SPD}} = s_i \Delta\ell_i^{\mathrm{Poisson}}

which is very confusing for me. It is likely that I interpreted the source code incorrectly.

Nevertheless, it is also clear that option 3, k_i = 1/s_i objective function

\Delta\ell_i^{\mathrm{SPD}} = \frac{1}{s_i} \Delta\ell_i^{\mathrm{Poisson}}

outperforms ROOT TH1::Fit with WL option in this specific setting.
This can be explained by the fact that s_0 \ll 1, and neglecting this factor exaggerates variance of the ROOT estimator.

Despite the fact that scale factor affects likelihood term for the second bin by two orders of magnitude, variance of the estimator is degraded only approximately by factor of 5, which is fully explained by overdispersion formula:

\sqrt{\frac{1 + s_0}{4} \times \frac{1 + s_0}{s_0}} = \frac{1 + s_0}{2\sqrt{s_0}} = 5.05

For s_0 = 1/T and large T, overdispersion degrades asymptotically quadratically, \propto \sqrt{T}

The effect of non-constant, random event weights on estimator performance is unknown at this point, but it is intuitively appealing that variance in weights inside bin would introduce additional overdispersion.
In practice I’d expect that effective statistics is large, meaning that s_0 \lesssim 1, and overdispersion does not degrade significantly.

For non-linear models I claim that mis-specification of weights for likelihood terms might emphasize wrong local minima or assign more weight to a steeper gradient descent direction, introducing bias and amplifying variance of resulting estimator.
The latter can be corrected by sandwich covariance estimator though.

Conclusion

The numerical check on this toy problem converged to @Byungchul_Yu suspicion that ROOT minimizes Poisson deviance.
In the simple setting with two bins and constant event weights there exists an estimator which minimizes Scaled Poisson deviance and outperforms ROOT’s TH1::Fit with WL option.

Dear @Byungchul_Yu , please check if the results of the scripts are similar for you.

I would like also to humbly invite other experts, @jonas and @StephanH to the discussion.

Hello again, @Byungchul_Yu !

I apologize for not actually directly addressing your last question:

Firstly, it appears that starting from the version v5.30 when WL feature was first introduced, the formula for the Scaled Poisson Deviance was incorrect, and ROOT’s objective function always have been

\Delta l^\text{ROOT} = s_i \times \Delta l^\text{Poisson} = s_i \times \left( -y_i \log\frac{\mu_i}{y_i} + \mu_i - y_i \right)

where \mu_i is model prediction for bin content, y_i = \sum w is bin content and s_i = \sum w^2/ \sum w is scale factor.

Secondly, I’ve tested EvaluatePoissonLogL function (which is back-end behind WL option) in debugger and discovered that actual ROOT’s minimization strategy is to perform unweighted fit first with objective function

\Delta l^\text{Poisson} = \left( -y_i \log\frac{\mu_i}{y_i} + \mu_i - y_i \right)

and then invoke \Delta l^\text{ROOT} variant to calculate corrected covariance matrix.
But since the \Delta l^\text{ROOT} not divides by s_i but multiplies by s_i in the first place, this correction also yields incorrect error bounds.
Unfortunately I can’t share gdb scripts to reproduce this behaviour right at the moment.

To summarize, I strongly suspect that current implementation WL is not reliable, and there was never a ROOT version with correct implementation in the past.
I would advise you to implement custom objective function adapted for your use case and statistical model, and you can look at the struct CustomNLL and function CustomFit in the script attached to the previous message.
You can also take a look at “User-defined fitting methods” section of ROOT’s fitting manual