Error analysis
By default, ScalingProblem estimates an error for every free parameter right after the fit. The keyword error selects the method:
error | the error of a parameter is | accounts for |
|---|---|---|
true, :profile (default) | the displacement at which the quality, re-fitted over the other parameters, reaches error_threshold times its minimum | correlations between the parameters |
:conditional | the same, with the other parameters held at their optimum | nothing; smaller or equal to the profile |
:bootstrap | the standard deviation of the parameters fitted to n_bootstrap resampled data sets | the error bars of the data |
false | not computed (zeros) |
Threshold widths: profile and conditional
The quality function has its minimum
:conditionalkeeps the other parameters at their optimal values. If the parameters are correlated, the quality rises quickly along this cut, and the error is too small.:profilere-fits the other parameters at every displacement, i.e. it follows the valley of the quality function. With one free parameter, both methods agree.
The difference is easiest to see in the quality landscape of two correlated parameters. We fit synthetic data with error bars (
using ScalingCollapse
using Random
F(z) = 1 / (1 + exp(z)) + 0.3 * tanh(z)^2
Ls = [16, 32, 64, 128]
xs = collect(range(0.9, 1.1; length=41))
rng = Xoshiro(2)
ys = Vector{Float64}[]
es = Vector{Float64}[]
for L in Ls
y = F.((xs .- 1.0) .* L^(1 / 0.8))
e = fill(0.01, length(xs))
push!(ys, y .+ e .* randn(rng, length(y)))
push!(es, e)
end
sp = ScalingProblem(xs, ys, es, Ls;
sf=ScalingFunction(:x; p_names=["x_c", "nu"]),
p_space=[0.95:0.005:1.05, 0.4:0.1:1.6],
dx=[-1.0, 3.0],
)ScalingProblem
Scaling function:
x -> (x - x_c) * L^(1/nu)
y -> y
Optimal parameters:
x_c = 0.9997731574541192 ± 0.0003007106262075519
nu = 0.8131509031302264 ± 0.04765214351797969
Optimization interval dx:
-1.0 < x < 3.0
Searched parameter space:
x_c ∈ 0.95:0.005:1.05
nu ∈ 0.4:0.1:1.6parameter_errors computes the errors of a solved problem with another method, without changing it:
σ_profile = sp.optimal_ps_error
σ_conditional = parameter_errors(sp; method=:conditional)
σ_bootstrap = parameter_errors(sp; method=:bootstrap, n_bootstrap=200, rng=Xoshiro(1))
[σ_profile σ_conditional σ_bootstrap]2×3 Matrix{Float64}:
0.000300711 0.000238242 0.000101552
0.0476521 0.03673 0.0138282residuals evaluates the quality on a grid around the optimum (by default
using CairoMakie
ps, S = residuals(sp; N_steps=81)
x_c, nu = sp.optimal_ps
fig = Figure(size=(620, 460))
ax = Axis(fig[1, 1]; xlabel=L"x_c", ylabel=L"\nu", title="quality landscape")
hm = heatmap!(ax, ps[1], ps[2], log10.(S ./ sp.minimum); colormap=:Blues)
contour!(ax, ps[1], ps[2], S ./ sp.minimum; levels=[1.3], color=:black, linewidth=1.5,
linestyle=:dash)
Colorbar(fig[1, 2], hm; label=L"\log_{10}(S / S_\mathrm{min})")
# error bars through the optimum, from the widest to the narrowest
colors = Makie.wong_colors()
for (σ, label, color, width) in ((σ_profile, "profile", :black, 6),
(σ_conditional, "conditional", colors[1], 4), (σ_bootstrap, "bootstrap", colors[2], 2))
lines!(ax, [x_c - σ[1], x_c + σ[1]], [nu, nu]; color, linewidth=width, label)
lines!(ax, [x_c, x_c], [nu - σ[2], nu + σ[2]]; color, linewidth=width)
end
scatter!(ax, [x_c], [nu]; color=:white, strokecolor=:black, strokewidth=1.5, markersize=10)
axislegend(ax; position=:rb)
fig

What the threshold widths mean
Profile and conditional errors are widths defined by a threshold on the quality, not standard deviations with a known coverage. The quality is quadratic around its minimum, so the width grows as error_threshold (one number, or one per free parameter).
The default 1.3 is a convention. In a benchmark on synthetic data with known parameters (about 3700 fits of monotone curves and peaks, with and without error bars):
If the data deviate from scaling only by noise, the widths are about 4 times the statistical error, i.e. the scatter of the fitted parameters between independent noisy data sets. A threshold of about
, withthe number of data points in the overlap window, would match it. Since the default is a fixed ratio, the widths do not shrink when more points are added. With corrections to scaling, the deviation from the true parameters is often larger than the statistical error. On the test cases with exactly known parameters, the deviations at 1.3 are typically about one width, and up to about four.
So the default widths are a conservative estimate of the statistical error and of the order of the systematic deviation of typical data, but no bound on it. They are useful to compare fits and to judge how well the data constrain a parameter. For a statistical error, use error=:bootstrap (needs error bars).
When the profile finds a better fit
The profile re-fits the other parameters at every displacement. If one of these fits finds a lower quality than minimum inside p_space, the original fit was not at the minimum. The constructor (and parameter_errors!) then re-optimizes from there, with a warning, and restarts the analysis, up to three times; optimal_ps and minimum change accordingly. parameter_errors throws an error instead, since it does not change the problem.
When an error is NaN
An error is NaN, with a warning that names the reason, if
the quality never reaches the threshold on one side (within 60 outward steps),
the quality becomes undefined first, because the rescaled data no longer overlap within
dx,the profile finds a lower quality outside
p_space: widenp_space,the re-optimizations of the profile run out (then all errors are
NaN).
Bootstrap errors
With error bars, error=:bootstrap estimates statistical errors: it draws n_bootstrap synthetic data sets in which every y value is replaced by a Gaussian random number centered on it, with its error bar as width, fits each of them (starting from optimal_ps), and reports the standard deviation of the fitted parameters.
using Random
ScalingProblem(xs, ys, es, Ls;
sf=ScalingFunction(:x; p_names=["x_c", "nu"]),
error=:bootstrap, n_bootstrap=500, rng=Xoshiro(1),
)It costs about
n_bootstrapfits. Its errors scatter between random seeds by a relative amount of about, i.e. 7% for the default of 100 samples, enough for one significant digit; use 500 or more for a reliable second digit. Pass a seeded random number generator
rngfor reproducible errors.It assumes independent Gaussian errors, and it only captures the statistical noise of the data. Systematic deviations from the scaling form, e.g. corrections to scaling, are not part of it. In the example above, the bootstrap errors are much smaller than the threshold widths; the percolation example shows a case where the true values lie far outside the bootstrap errors.
optimal_psandminimumremain those of the original data.
Computing errors after the fit
Fitting with error=false skips the error analysis, which is useful while exploring. parameter_errors then computes errors with any method, without changing the problem; its keywords default to the settings of the problem. parameter_errors! does the same, but stores the errors and settings in the problem:
sp_fast = ScalingProblem(xs, ys, es, Ls;
sf=ScalingFunction(:x; p_names=["x_c", "nu"]),
p_space=[0.95:0.005:1.05, 0.4:0.1:1.6],
dx=[-1.0, 3.0],
error=false,
)
parameter_errors!(sp_fast; method=:profile)
sp_fastScalingProblem
Scaling function:
x -> (x - x_c) * L^(1/nu)
y -> y
Optimal parameters:
x_c = 0.9997731574541192 ± 0.0003007106262075519
nu = 0.8131509031302264 ± 0.04765214351797969
Optimization interval dx:
-1.0 < x < 3.0
Searched parameter space:
x_c ∈ 0.95:0.005:1.05
nu ∈ 0.4:0.1:1.6