In addition to modelling continuous conditional densities, the SLGP implementation can be used to estimate conditional probability mass functions when the response takes values on a discrete support.
For illustration, we return to the quakes catalogue, a
dataset shipped with base R (R Core Team 2025): 1000 seismic
events of body-wave magnitude above 4.0 recorded since 1964 in the
Fiji-Tonga region, originating from the Harvard PRIM-H project. Each
event carries its epicentre (lat, long), its
hypocentre depth (40-680 km), its magnitude
mag, and the number of stations that detected
it. Here, we consider earthquake magnitude mag as the
response variable. In this dataset, magnitudes are reported on a
discretized scale, making them suitable for illustrating the
discrete-output formulation.
In this vignette, we demonstrate how to model the conditional distribution
\[ \mathbb{P}(T_x = k \mid X=x), \]
where \(T_x\) denotes the recorded magnitude and \(x\) is the earthquake longitude.
data("quakes")
library(tidyr)
library(dplyr)
# Bin data together for visualisation purpose
df <- quakes %>%
mutate(long_bin = cut(long, breaks = seq(165, 190, by = 2.5), include.lowest = FALSE)) %>%
group_by(long_bin) %>%
mutate(long_bin = paste0(long_bin, "\nn=", n()))%>%
ungroup()%>%
mutate(long_bin = factor(long_bin,
levels = sort(unique(long_bin), decreasing = FALSE))) %>%
data.frame()
range_response <- c(4, 7)
range_x <- c(165, 190)We represent the data.
library(ggplot2)
library(ggpubr)
library(viridis)
scatter_plot <- ggplot(df, aes(x = long, y = mag)) +
geom_point(alpha = 0.5, color = "navy") +
labs(x = "Longitude (°)",
y = "Magnitude",
title = "Observed earthquake magnitudes") +
theme_bw()+
coord_cartesian(xlim=range_x,
ylim=range_response)
# Compute normalized frequencies per long_bin
df_bar <- df %>%
count(long_bin, mag) %>%
group_by(long_bin) %>%
mutate(prop = n / sum(n))
# Histogram: Distribution of mag by 'long' bin
hist_plot <- ggplot(df_bar, aes(x = mag)) +
geom_bar(mapping=aes(y = prop), stat = "identity",
fill = "darkgrey", color = "grey50", lwd = 0.18, alpha = 0.7) +
geom_rug(data = df, aes(x = mag),
sides = "b", color = "navy", alpha = 0.5) +
facet_wrap(~ long_bin, scales = "free_y", nrow=2) +
labs(x = "Magnitude",
y = "Probability density",
title = "Histogram of 'magnitude' by 'long' group") +
theme_bw()+
coord_cartesian(xlim=range_response,
ylim=c(0, 0.5))
ggarrange(scatter_plot, hist_plot, ncol = 2, nrow = 1,
widths = c(0.3, 0.7))
A visual representation of the event magnitudes depending on the
longitude in the quakes catalogue.
The model is fitted in the same way as for a continuous response, except that prediction is performed using the discrete-output option. The response domain is defined by the observed magnitude range, and the SLGP represents a probability mass function over the discrete support.
We use a Random Fourier Feature approximation of a Matérn-\(5/2\) kernel.
library(SLGP)
modelMAP <- slgp(mag~long, # Use a formula with two indexing variables
data=df,
method="MAP", #Maximum a posteriori estimation scheme
basisFunctionsUsed = "RFF",
interpolateBasisFun="WNN", # Accelerate inference
hyperparams = list(lengthscale=c(0.1, 0.1),
sigma2=1),
nIntegral = 31,
sigmaEstimationMethod = "heuristic",
# Set to heuristic for numerical stability
predictorsLower= c(range_x[1]),
predictorsUpper= c(range_x[2]),
responseRange= range_response,
opts_BasisFun = list(nFreq=150,
MatParam=5/2),
discrete=TRUE)We can represent the conditional densities. We first use the standard
plot() method for SLGP objects.
plot( modelMAP,
newdata = data.frame(long = seq(range_x[1], range_x[2], length.out = 6)),
draw = "mean",
panels = TRUE,
n_response = 31,
discrete = TRUE)Conditional magnitude probabilities across longitude under the MAP estimate of the SLGP.
selected_values <- c(167, 180, 185)
gap <- 0.5
df_filtered <- df %>%
mutate(interval=findInterval(long, c(0,
selected_values[1]-gap,
selected_values[1]+gap,
selected_values[2]-gap,
selected_values[2]+gap,
selected_values[3]-gap,
selected_values[3]+gap)))%>%
filter(interval %in% c(2, 4, 6))%>%
group_by(interval)%>%
mutate(category = paste0("long close to ", c("", selected_values[1],
"", selected_values[2],
"", selected_values[3])[interval],
"\nn=", n()))
names <- sort(unique(df_filtered$category))
dfGrid <- data.frame(expand.grid(selected_values,
seq(range_response[1], range_response[2],, 31)))
colnames(dfGrid) <- c("long", "mag")
predMAP <- predict(modelMAP, newdata = dfGrid, discrete=TRUE, nIntegral=31)
colnames(predMAP) <- c("long", "mag", "MAP estimator")
predMAP <- predMAP%>%
pivot_longer(-c("long", "mag"))
predMAP$category <-ifelse(predMAP$long==selected_values[1], names[1],
ifelse(predMAP$long==selected_values[2], names[2], names[3]))
df_emp <- df_filtered %>%
count(category, mag) %>%
group_by(category) %>%
mutate(prob = n / sum(n)) %>%
ungroup()
ggplot(mapping=aes(x = mag)) +
geom_col(data = df_emp, aes(y = prob), width = 0.09, fill = "darkgrey",
color = "grey50", linewidth = 0.2, alpha = 0.7)+
geom_step(data=predMAP, mapping=aes(y=value, group=name, col=name),
lwd=1.1, direction = "mid")+
facet_wrap(~ category, scales = "free_y", nrow=1) +
labs(x = "Magnitude",
y = "Probability",
title = "Binned 'magnitude' histograms vs SLGP MAP estimates at bins centers") +
theme_bw()+
theme(legend.position="bottom",
legend.direction = "horizontal",
legend.title = element_blank())+
coord_cartesian(xlim=range_response,
ylim=c(0, 0.2)) Empirical magnitude distributions within longitude bins and SLGP MAP estimates at the corresponding bin centers.