Skip to content

Commit 3abfab2

Browse files
committed
variograms: suggestions @florisvdh*
- cls style - removed `sp` - extra explanation Matérn range
1 parent 923a09b commit 3abfab2

File tree

3 files changed

+51
-46
lines changed

3 files changed

+51
-46
lines changed

content/tutorials/spatial_variograms/index.md

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
---
22
title: "An Algorithmic Approach to Variograms"
33
description: "Variograms, an algorithm to analyze spatial interdependence of measurement locations, implemented step by step in R."
4-
date: "2025-03-11"
4+
date: "2025-03-28"
55
authors: [falkmielke]
66
categories: ["r", "statistics", "development"]
77
tags: ["r", "spatial", "co-variance", "de-trending", "binning", "regression", "analysis", "gis"]
88
number-sections: true
9+
link-citations: true
910
params:
1011
math: true
1112
format:
@@ -64,11 +65,10 @@ Enjoy!
6465

6566
``` r
6667
void <- suppressPackageStartupMessages
67-
# library("sp") |> void()
6868

6969
# our beloved tidyverse components:
7070
library("dplyr") |> void()
71-
library("ggplot2") |> void()
71+
library("ggplot2") |> void()
7272
library("ggridges") |> void() # density ridges
7373
library("parallel") |> void() # parallel processing, for bootstrapping
7474

@@ -108,14 +108,13 @@ data <- data.frame(
108108
knitr::kable(head(data, 5))
109109
```
110110

111-
| x | y | z\_ |
112-
|----------:|-----------:|-----------:|
113-
| -54.38015 | -116.22783 | -0.5146002 |
114-
| 73.80611 | -34.32992 | 1.6650901 |
115-
| -23.30191 | -53.95508 | 2.6631489 |
116-
| 98.05246 | -40.49404 | -0.9586115 |
117-
| 112.75962 | -51.10675 | -5.5283416 |
118-
111+
| x | y | z\_ |
112+
|-------:|--------:|--------:|
113+
| -54.38 | -116.23 | -0.5146 |
114+
| 73.81 | -34.33 | 1.6651 |
115+
| -23.30 | -53.96 | 2.6631 |
116+
| 98.05 | -40.49 | -0.9586 |
117+
| 112.76 | -51.11 | -5.5283 |
119118

120119
The raw data `z_` is calculated as random numbers from a uniform distribution (with a given sample size and data range).
121120

@@ -144,11 +143,11 @@ data$z <- data$z_ + a_slope * data$a + b_slope * data$b
144143
knitr::kable(head(data, 3))
145144
```
146145

147-
| x | y | z\_ | a | b | z |
148-
|----------:|-----------:|-----------:|-----------:|----:|----------:|
149-
| -54.38015 | -116.22783 | -0.5146002 | -0.5698005 | 0 | -1.111294 |
150-
| 73.80611 | -34.32992 | 1.6650901 | 0.3231664 | 1 | 2.788907 |
151-
| -23.30191 | -53.95508 | 2.6631489 | -0.2538895 | 0 | 2.397276 |
146+
| x | y | z\_ | a | b | z |
147+
|-------:|--------:|--------:|--------:|----:|-------:|
148+
| -54.38 | -116.23 | -0.5146 | -0.5698 | 0 | -1.111 |
149+
| 73.81 | -34.33 | 1.6651 | 0.3232 | 1 | 2.789 |
150+
| -23.30 | -53.96 | 2.6631 | -0.2539 | 0 | 2.397 |
152151

153152
There is no noise applied to those covariates `a` and `b`, moderate noise on the raw data `z`, so the two additional effects should be recover-able by a statistical model.
154153

@@ -231,12 +230,12 @@ smooth <- function(data, sigma = NULL) {
231230

232231
dist <- Euclid_wrap(data)
233232
# sigma <- extent / 3
234-
weight <- dnorm(dist, 0, sigma)
233+
weight <- dnorm(dist, 0, sigma)
235234
weight <- weight / colSums(weight)
236235
# do.call("cbind", rep(list(data$z), length(data$z)))
237236

238237
zmoothed <- weight %*% data$z
239-
return(zmoothed[,1])
238+
return(zmoothed[, 1])
240239
}
241240
```
242241

@@ -294,8 +293,7 @@ This is demonstrated in the [examples documented with the function](https://www.
294293
In our example, one could use
295294

296295
``` r
297-
data_sf <- data # sf::st_as_sf(data, coords = c("x", "y"), crs = 31370)
298-
sp::coordinates(data_sf) = ~x+y
296+
data_sf <- sf::st_as_sf(data, coords = c("x", "y"), crs = 31370, remove = FALSE)
299297
v <- gstat::variogram(z ~ x + y, data = data_sf)
300298
v.fit <- gstat::fit.variogram(v, gstat::vgm("Mat"))
301299
v.fit
@@ -465,8 +463,8 @@ It might make sense to incorporate categorical variables into the binning.
465463
With binning comes the immediate question also part of step 3:
466464
**what do we actually quantify as "difference"?**
467465

468-
Conventionally, *variance* (VAR) is the mean squared difference of observed values (or a subset of observations, e.g. in a group or bin) from their mean.
469-
It is implemented in R with the `var` function (note that R implements the "sample variance", i.e. the formula normalizing by `n - 1` for [Bessel's correction](https://en.wikipedia.org/wiki/Bessel%27s_correction)).
466+
Conventionally, *variance* (VAR) is the mean squared difference of observed values (or a subset of observations, e.g. in a group or bin) from their mean.
467+
It is implemented in R with the `var` function (note that R implements the "sample variance", i.e. the formula normalizing by `n - 1` for [Bessel's correction](https://en.wikipedia.org/wiki/Bessel%27s_correction)).
470468
However, applying this formula for variograms is ~~wrong~~ unconventional!
471469

472470
In *variograms*, the mean is replaced by a given point on the landscape (we want to look at differences from that focus point), and then we iterate over adjacent points.
@@ -479,7 +477,7 @@ Personally, I find "constant between samples" a bit fishy: is it "constant betwe
479477
If something is "independent of location", why bother computing a spatial interpolation?
480478
The answer lies somewhere between (among?) the very exact maths hidden in the literature.
481479

482-
And, anyways, if the assumption holds, we get a neat formula for semivariance ("Method-of-Moments Estimator" according to Cressie 1993, 69, eqn. 2.4.2), which goes back to Matheron (1962).
480+
And, anyways, if the assumption holds, we get a neat formula for semivariance ("Method-of-Moments Estimator" according to [Cressie, 1993, p. 69](#ref-Cressie1993), eqn. 2.4.2), which goes back to Matheron ([1962](#ref-Matheron1962)).
483481

484482
We define the **semivariance** \\(\gamma\\):
485483

@@ -597,7 +595,7 @@ There are some general convenience wrappers for classical regression in R, thoug
597595
However, [base-r `optim` does all we need](https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/optim) (*sometimes*).
598596
There are [other libraries](https://cran.r-project.org/web/views/Optimization.html).
599597

600-
We choose between well-known "Nelder-Mead" optimization algorithm (Nelder and Mead 1965), or the more versatile ["L-BFGS-B"](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html) (Zhu et al. 1997; Byrd et al. 1995).
598+
We choose between well-known "Nelder-Mead" optimization algorithm ([Nelder & Mead, 1965](#ref-nelder1965)), or the more versatile ["L-BFGS-B"](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html) ([Byrd *et al.*, 1995](#ref-byrd1995); [Zhu *et al.*, 1997](#ref-zhu1997)).
601599
They are interchangeable to some degree, yet the latter allows to define logical parameter boundaries to facilitate optimization convergence.
602600

603601
``` r
@@ -628,7 +626,7 @@ wrap_target_function_distanceweighted <- function(x, y, regressor, parameters) {
628626

629627
# this can turn regression output into a usable function.
630628
# εὕρηκα, functional programming!
631-
create_prediction_function <- function(regressor, results) {
629+
create_prediction_function<- function(regressor, results) {
632630
fcn <- function(x) {
633631
regressor(x, results$par)
634632
}
@@ -677,7 +675,7 @@ optimizer_results <- optim(
677675
print_regression_results(optimizer_results, label = "linear")
678676
```
679677

680-
[1] "linear regression: convergence 0 at (0.4287, 4e-04), mse 10.7"
678+
[1] "linear regression: convergence 0 at (0.4287, 0.0004), mse 10.7"
681679

682680
``` r
683681
predictor_function <- create_prediction_function(linear_function, optimizer_results)
@@ -699,7 +697,7 @@ Observations:
699697

700698
- The regression fits the data more or less well, quantified by the mean square error (`mse`).
701699
- Optimizer did converge (`convergence 0`, [see "convergence" here](https://stat.ethz.ch/R-manual/R-devel/library/stats/html/optim.html)), which should not be overrated (the regression might still be irrelevant).
702-
- Parameters can be measured, in this case intercept (\\(0.43\\)) and slope (\\(4\times 10^{-4}\\)).
700+
- Parameters can be measured, in this case intercept (\\(0.43\\)) and slope (\\(0.0004\\)).
703701

704702
We can do better, of course.
705703

@@ -745,8 +743,8 @@ alt="Figure 8: Although it is inverted, scaled, y-shifted, and centered on zero
745743
<figcaption>Although it is inverted, scaled, y-shifted, and centered on zero, you will certainly recognize our beloved bell-curve: the Gaussian. Note that we will only use the right half to proceed.</figcaption><br>
746744

747745
I still have a hard time to associate anything maths-related to the words `nugget` and `sill`: they could equally well be some ancient greek letters spelled out in a non-greek way, such as `σίγμα`.
748-
Historically, they stem from what I think were the earliest applications of variogram-like analysis, as my colleague Hans Van Calster confirmed me when reviewing this tutorial:
749-
> nugget comes from "gold" nugget in mining. In sampling gold, the chances of finding a nugget of gold from adjacent locations may differ a lot - hence they have a large "nugget" effect (large differences at very small distances).
746+
Historically, they stem from what I think were the earliest applications of variogram-like analysis, as my colleague Hans Van Calster confirmed me when reviewing this tutorial:
747+
\> nugget comes from "gold" nugget in mining. In sampling gold, the chances of finding a nugget of gold from adjacent locations may differ a lot - hence they have a large "nugget" effect (large differences at very small distances).
750748
We have to accept that they are frequently encountered in the variogram literature.
751749

752750
- The `nugget` is the value our function takes at the zero intercept, i.e. baseline variance, i.e. the lowest difference we can get (often defined by measurement uncertainty).
@@ -770,9 +768,9 @@ optimizer_results <- optim(
770768
upper = c(2 * zrange, extent / 4, zrange), # prevent crazy outcomes
771769
fn = function(parameters) {
772770
wrap_target_function_distanceweighted(x, y, gauss_function, parameters)
773-
},
771+
},
774772
control = list("fnscale" = 1e-8),
775-
method = "L-BFGS-B"
773+
method = "L-BFGS-B"
776774
)
777775

778776
predictor_function <- create_prediction_function(
@@ -952,7 +950,7 @@ where \\(\tau^2\\) controls the spatial variance,
952950
\\(K_\nu\\) represents the modified Bessel function of the second kind,
953951
and \\(\kappa\\) represents the decorrelation rate.
954952
The parameter \\(\nu\\) is set to \\(1\\) to take advantage of the Stochastic Partial Differential Equation (SPDE) approximation to the GRF
955-
to greatly increase computational efficiency (Lindgren, Rue, and Lindström 2011).
953+
to greatly increase computational efficiency ([Lindgren *et al.*, 2011](#ref-Lindgren2011)).
956954
Internally, the parameters \\(\kappa\\) and \\(\tau\\) are converted to range and marginal standard deviation \\(\sigma\\) as
957955
\\[range=\frac{8}{\kappa}\\]
958956
and
@@ -983,9 +981,13 @@ matern_function <- function(d, parameters) {
983981

984982
I initially had trouble fitting this function, because I simplified (leaving out `nugget` and `nu`); the version above is quite flexible to fit our variogram.
985983
Note that the function is not defined at zero, which is why I filter `NA`.
986-
Sigma (\\(\sigma\\)) is related to the turning point of the Matérn function.
987984
The Matérn implementation does not allow decreasing or oscillating semivariance (sometimes seen in real data), but on the other hand decreasing semivariance would griefly violate Tobler's observation.
988985

986+
Note that there are different definitions of the Matérn range and the parameter `sigma`.
987+
Make sure you know which range your toolbox of choice is reporting.
988+
Here and below, I will report the range that is determined by `sigma` in the above function definition.
989+
That sigma (\\(\sigma\\)) is related to the turning point of the Matérn function.
990+
989991
Any regression function demands a specific plot function:
990992

991993
``` r
@@ -1476,17 +1478,17 @@ Thank you for reading!
14761478

14771479
# References
14781480

1479-
Byrd, Richard H., Peihuang Lu, Jorge Nocedal, and Ciyou Zhu. 1995. "A Limited Memory Algorithm for Bound Constrained Optimization." *SIAM Journal on Scientific Computing* 16 (5): 1190--1208. <https://doi.org/10.1137/0916069>.
1481+
Byrd R.H., Lu P., Nocedal J. & Zhu C. (1995). A Limited Memory Algorithm for Bound Constrained Optimization. SIAM Journal on Scientific Computing 16 (5): 1190--1208. <https://doi.org/10.1137/0916069>.
14801482

1481-
Cressie, Noel. 1993. *Statistics for Spatial Data*. John Wiley & Sons.
1483+
Cressie N. (1993). Statistics for spatial data. John Wiley & Sons.
14821484

1483-
Lindgren, Finn, Håvard Rue, and Johan Lindström. 2011. "An Explicit Link Between Gaussian Fields and Gaussian Markov Random Fields: The Stochastic Partial Differential Equation Approach." *Journal of the Royal Statistical Society: Series B (Statistical Methodology)* 73 (4): 423--98. <https://doi.org/10.1111/j.1467-9868.2011.00777.x>.
1485+
Lindgren F., Rue H. & Lindström J. (2011). An explicit link between Gaussian fields and Gaussian Markov random fields: the stochastic partial differential equation approach. Journal of the Royal Statistical Society: Series B (Statistical Methodology) 73 (4): 423--498. <https://doi.org/10.1111/j.1467-9868.2011.00777.x>.
14841486

1485-
Matheron, Georges. 1962. *Traité de Géostatistique Appliquée*. Memoires du Bureau de Recherches Geologiques et Minieres, Editions Technip, Paris.
1487+
Matheron G. (1962). Traité de géostatistique appliquée. No. 14. Memoires du Bureau de Recherches Geologiques et Minieres, Editions Technip, Paris.
14861488

1487-
Nelder, J. A., and R. Mead. 1965. "A Simplex Method for Function Minimization." *The Computer Journal* 7 (4): 308--13. <https://doi.org/10.1093/comjnl/7.4.308>.
1489+
Nelder J.A. & Mead R. (1965). A Simplex Method for Function Minimization. The Computer Journal 7 (4): 308--313. <https://doi.org/10.1093/comjnl/7.4.308>.
14881490

1489-
Zhu, Ciyou, Richard H. Byrd, Peihuang Lu, and Jorge Nocedal. 1997. "Algorithm 778: L-BFGS-B: Fortran Subroutines for Large-Scale Bound-Constrained Optimization." *ACM Transactions on Mathematical Software* 23 (4): 550--60. <https://doi.org/10.1145/279232.279236>.
1491+
Zhu C., Byrd R.H., Lu P. & Nocedal J. (1997). Algorithm 778: L-BFGS-B: Fortran subroutines for large-scale bound-constrained optimization. ACM Transactions on Mathematical Software 23 (4): 550--560. <https://doi.org/10.1145/279232.279236>.
14901492

14911493
Useful links:
14921494

-4.31 KB
Loading

content/tutorials/spatial_variograms/spatial_variograms.qmd

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ title: ""
33
author: ""
44
date: ""
55
bibliography: references_csl.json
6+
link-citations: true
7+
csl: '`r cslfile <- file.path("./research-institute-for-nature-and-forest.csl"); download.file("https://github.com/inbo/styles/raw/master/research-institute-for-nature-and-forest.csl", cslfile); cslfile`'
68
number-sections: true
79
format:
810
html:
@@ -22,11 +24,12 @@ format:
2224
---
2325
title: "An Algorithmic Approach to Variograms"
2426
description: "Variograms, an algorithm to analyze spatial interdependence of measurement locations, implemented step by step in R."
25-
date: "2025-03-11"
27+
date: "2025-03-28"
2628
authors: [falkmielke]
2729
categories: ["r", "statistics", "development"]
2830
tags: ["r", "spatial", "co-variance", "de-trending", "binning", "regression", "analysis", "gis"]
2931
number-sections: true
32+
link-citations: true
3033
params:
3134
math: true
3235
format:
@@ -38,8 +41,6 @@ format:
3841
```
3942

4043

41-
42-
4344
# Introduction
4445

4546
> Everything is related to everything else, but near things are more related than distant things.
@@ -102,7 +103,6 @@ Enjoy!
102103
```{r setup}
103104
#| eval: true
104105
void <- suppressPackageStartupMessages
105-
# library("sp") |> void()
106106
107107
# our beloved tidyverse components:
108108
library("dplyr") |> void()
@@ -340,8 +340,7 @@ In our example, one could use
340340

341341
```{r eval=FALSE}
342342
#| eval: false
343-
data_sf <- data # sf::st_as_sf(data, coords = c("x", "y"), crs = 31370)
344-
sp::coordinates(data_sf) = ~x+y
343+
data_sf <- sf::st_as_sf(data, coords = c("x", "y"), crs = 31370, remove = FALSE)
345344
v <- gstat::variogram(z ~ x + y, data = data_sf)
346345
v.fit <- gstat::fit.variogram(v, gstat::vgm("Mat"))
347346
v.fit
@@ -1059,9 +1058,13 @@ matern_function <- function(d, parameters) {
10591058

10601059
I initially had trouble fitting this function, because I simplified (leaving out `nugget` and `nu`); the version above is quite flexible to fit our variogram.
10611060
Note that the function is not defined at zero, which is why I filter `NA`.
1062-
Sigma ($\sigma$) is related to the turning point of the Matérn function.
10631061
The Matérn implementation does not allow decreasing or oscillating semivariance (sometimes seen in real data), but on the other hand decreasing semivariance would griefly violate Tobler's observation.
10641062

1063+
Note that there are different definitions of the Matérn range and the parameter `sigma`.
1064+
Make sure you know which range your toolbox of choice is reporting.
1065+
Here and below, I will report the range that is determined by `sigma` in the above function definition.
1066+
That sigma ($\sigma$) is related to the turning point of the Matérn function.
1067+
10651068

10661069
Any regression function demands a specific plot function:
10671070

0 commit comments

Comments
 (0)