Estimating Nugget, Sill & Range Parameters
TL;DR: In scikit-gstat, set use_nugget=True, fit the model, and read V.parameters as [range, sill, nugget]. The partial sill is sill - nugget. Constrain implausible fits with the bounded solver fit_method="trf", and grade spatial dependence with the nugget-to-sill ratio: below 0.25 is strong, 0.25 to 0.75 moderate, above 0.75 weak.
Why This Matters
The nugget, sill, and range are the three numbers that summarize spatial continuity, and they are the payload every kriging system consumes. Reading them correctly is what separates a defensible model from a curve that merely looks plausible, and it is the central skill in working with theoretical variogram models inside the wider variogram modeling and semivariance analysis workflow. A misplaced nugget inflates prediction variance; a range fitted to a sparse tail smooths your surface into meaninglessness. This page shows how to constrain, read, and sanity-check each parameter.
A variogram model decomposes total variance into structured and unstructured parts. With nugget , partial sill , and total sill :
where is the model shape (spherical, exponential, or Gaussian) and is the effective range. The ratio is the fraction of variance that is spatially unstructured.
Environment
pip install "scikit-gstat>=1.0" "numpy>=1.23"
import numpy as np
from skgstat import Variogram
Step-by-Step Implementation
Step 1 — Fit with a free nugget
rng = np.random.default_rng(23)
coords = rng.uniform(0, 600, size=(210, 2))
vals = (
14.0
+ 5.0 * np.sin(coords[:, 0] / 120.0)
+ rng.normal(0, 1.1, size=len(coords)) # injects nugget-scale noise
)
V = Variogram(coords, vals, model="spherical", n_lags=14,
maxlag="median", use_nugget=True)
use_nugget=True adds the nugget as a free parameter. Without it, the model is forced through the origin and any measurement error is wrongly absorbed into the sill and range.
Step 2 — Read the fitted parameters
effective_range, sill, nugget = V.parameters
partial_sill = sill - nugget
print(f"nugget : {nugget:6.3f}")
print(f"partial sill : {partial_sill:6.3f}")
print(f"total sill : {sill:6.3f}")
print(f"effective range: {effective_range:6.1f}")
print(V.describe()) # full dict including estimator and fit metrics
V.parameters returns [range, sill, nugget] where sill is the total sill. Subtract the nugget to obtain the partial sill, the spatially structured share of variance.
Step 3 — Constrain the fit with a bounded solver
# The sample variance is the natural anchor for a plausible sill.
sample_var = float(np.var(vals))
print(f"sample variance: {sample_var:.2f}")
# fit_method="trf" runs a bounded trust-region least-squares fit that
# automatically keeps range, sill and nugget non-negative and within the
# lag domain, instead of the unconstrained Levenberg-Marquardt ("lm") solver.
V = Variogram(
coords, vals, model="spherical", n_lags=14, maxlag="median",
use_nugget=True,
fit_method="trf",
)
print("bounded fit:", np.round(V.parameters, 3))
fit_method="trf" swaps the unconstrained Levenberg-Marquardt solver ("lm") for the trust-region-reflective one, which holds every parameter to a physically sensible, non-negative range bounded by the lag geometry. That alone stops the optimiser from chasing a sparse tail into an absurd range or returning a negative nugget. Compare the fitted sill against sample_var as your plausibility check.
Step 4 — Weight the fit toward well-sampled lags
V.fit_sigma = "linear" # down-weight far, sparse lags in the fit
V.fit()
print("distance-weighted fit:", np.round(V.parameters, 3))
fit_sigma controls how much each experimental point influences the least-squares fit. "linear", "exp", or "sqrt" give near-origin lags more weight, which matters because those lags are the best sampled and most physically meaningful.
Step 5 — Grade spatial dependence with the ratio
effective_range, sill, nugget = V.parameters
ratio = nugget / sill
if ratio < 0.25:
grade = "strong spatial dependence"
elif ratio < 0.75:
grade = "moderate spatial dependence"
else:
grade = "weak spatial dependence"
print(f"nugget-to-sill ratio: {ratio:.2f} -> {grade}")
Interpreting the Output
Each parameter carries a distinct physical meaning. The nugget is the semivariance extrapolated to zero lag; it captures measurement error plus variation at scales finer than your sampling interval. The partial sill is the variance that is genuinely spatially structured. The total sill is their sum, and for a stationary variable it should sit close to the sample variance. The range is the distance beyond which pairs are effectively uncorrelated.
| Parameter | scikit-gstat source | Interpretation |
|---|---|---|
| Nugget | V.parameters[2] |
Micro-scale variance plus measurement error |
| Partial sill | sill - nugget |
Spatially structured variance |
| Total sill | V.parameters[1] |
Full variance plateau ≈ sample variance |
| Range | V.parameters[0] |
Correlation distance (effective range) |
| Nugget-to-sill | nugget / sill |
Spatial-dependence strength grade |
The nugget-to-sill ratio is the headline diagnostic. A ratio under 0.25 signals strong spatial structure that kriging can exploit sharply; between 0.25 and 0.75 is moderate; above 0.75 warns that most variance is unstructured, and kriging will barely outperform the global mean. A total sill far above the sample variance usually means a residual trend, a cue to revisit stationarity and trend analysis.
Critical Best Practices
Prefer a free nugget over a forced zero
Real sampling carries measurement error and micro-scale variation. use_nugget=True gives that variance a home; forcing the model through the origin smears it into the range and sill and produces overconfident kriging variances. Force a zero nugget only with a strong physical justification.
Anchor the sill to the sample variance
For a stationary variable the fitted total sill should land near np.var(vals). If it drifts far above, suspect a trend; far below, suspect that maxlag truncates the plateau. Fitting with fit_method="trf" keeps the sill non-negative and anchored, so a wild sill points to a data problem rather than a solver one.
Weight the fit toward the origin
The short lags are both the best sampled and the ones kriging weights most heavily. Setting fit_sigma to "linear" or "exp" prevents a noisy far tail from dragging the range and nugget away from the values that actually govern local prediction.
Report the ratio, not just the numbers
The nugget-to-sill ratio communicates spatial-dependence strength in a single figure that is comparable across variables and studies. Always report it alongside the raw parameters so readers can judge whether spatial modelling is even warranted, or whether a simpler spatial regression model would suffice.
Bound the range within the domain
A fitted range longer than your study area is meaningless: you cannot observe correlation beyond the extent you sampled. The trust-region solver bounds the range by the lag geometry set through maxlag, so keep maxlag at or below half the domain and treat a range that pins to that ceiling as a signal to revisit binning.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Negative nugget in the fit | Unbounded optimiser overshoot | Use fit_method="trf" with a non-negative nugget bound |
| Total sill far above sample variance | Residual trend or non-stationarity | Detrend before fitting; check np.var(vals) |
| Range longer than the domain | Fit driven by sparse far lags | Lower maxlag; fit with fit_method="trf" |
| Nugget-to-sill ratio near 1 | Weak or no spatial structure | Confirm data quality; reconsider spatial modelling |
| Range or nugget goes negative | Unconstrained solver overshoot | Set fit_method="trf" for a bounded fit |
| Sill below the experimental plateau | maxlag truncates the curve |
Increase maxlag toward half the domain |
Next Steps
With the parameters interpreted, compare which model family fits your cloud best in fitting spherical, exponential and Gaussian variogram models, and check whether direction changes the range in detecting and modeling geometric anisotropy in Python.
Related
- Theoretical Variogram Models — the model families whose parameters you are reading
- Fitting Spherical, Exponential & Gaussian Variogram Models — choosing the model these parameters belong to
- Detecting & Modeling Geometric Anisotropy in Python — when the range depends on direction
← Back to Theoretical Variogram Models
Frequently Asked Questions
What is the difference between the sill and the partial sill?
The total sill is the semivariance plateau the model reaches at large lags and roughly equals the sample variance of a stationary variable. The partial sill is that plateau minus the nugget, meaning the portion of variance that is spatially structured rather than attributed to nugget effect.
How do I interpret the nugget-to-sill ratio?
The nugget-to-sill ratio grades spatial dependence: below about 0.25 indicates strong spatial structure, between 0.25 and 0.75 moderate structure, and above 0.75 weak structure where most variance is unstructured noise or fine-scale variation your sampling cannot resolve.
Should I always let scikit-gstat fit the nugget freely?
Usually yes: set use_nugget=True so measurement error and micro-scale variation land in the nugget instead of distorting the range and sill. Force a zero nugget only when you have strong physical grounds to believe the variable is perfectly continuous at the origin.