---
title: Validate and compare clusterings
name: validate-compare
description: Separate structural validity, single-partition diagnostics, and pairwise agreement.
output:
rmarkdown::html_vignette:
toc: yes
toc_depth: 2
css: albers.css
includes:
in_header: albers-header.html
params:
family: red
base_size: 13
content_width: 72
preset: homage
resource_files:
- albers.css
- albers.js
- albers-header.html
- fonts
vignette: |
%\VignetteIndexEntry{Validate and compare clusterings}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include=FALSE}
if (requireNamespace("albersdown", quietly = TRUE)) albersdown::albers_register_fonts()
knitr::opts_chunk$set(
collapse = TRUE, comment = "#>", fig.align = "center", fig.retina = 2,
out.width = "100%", fig.width = 8, fig.asp = 0.58,
message = FALSE, warning = FALSE
)
options(width = 90)
suppressPackageStartupMessages({
library(neurocluster)
library(neuroim2)
})
method_colors <- c(ReNA = "#0072B2", G3S = "#E69F00")
```
```{r albers-classes, echo=FALSE, results='asis'}
cat(sprintf(paste0(
''
), params$family, params$preset, params$content_width))
```
# Which question are you asking? {#questions}
Three questions are easy to conflate:
1. **Is the object internally consistent?** Check it with `validate_cluster4d()`.
2. **What properties does one partition have?** Measure them with `cluster_metrics()`.
3. **How do partitions differ on the same voxels?** Use `compare_cluster4d()`.
A structurally valid result can still be scientifically poor, and two partitions
can agree closely while both miss the relevant organization. Keep the three
questions separate in an analysis report.
```{r validation-data}
syn <- generate_synthetic_volume(
scenario = "gaussian_blobs", dims = c(16, 16, 8),
n_clusters = 4, n_time = 40, noise_sd = 0.08, seed = 42
)
rena_fit <- cluster4d(
syn$vec, syn$mask, 4, method = "rena",
connectivity = 26, parallel = FALSE
)
g3s_fit <- cluster4d(
syn$vec, syn$mask, 4, method = "g3s",
connectivity = 26, parallel = FALSE
)
```
# Is the result internally consistent? {#validate}
Supply the original vector and mask for the strongest check. The validator then
recomputes feature centers, physical coordinate centers, voxel ordering, and
geometry rather than trusting stored summaries.
```{r validate-result}
validation <- validate_cluster4d(rena_fit, syn$vec, syn$mask)
validation[c("valid", "warnings", "errors")]
```
```{r check-validation, include=FALSE}
g3s_validation <- validate_cluster4d(g3s_fit, syn$vec, syn$mask)
stopifnot(
validation$valid, g3s_validation$valid,
length(validation$errors) == 0L,
identical(rena_fit$labels, rena_fit$cluster),
identical(nrow(rena_fit$centers), rena_fit$actual_k),
identical(dim(rena_fit$coord_centers), c(rena_fit$actual_k, 3L))
)
```
`valid = TRUE` means the object satisfies its declared contract. It does not
certify anatomical meaning, reproducibility across subjects, or suitability for
a downstream model.
# What does one partition measure? {#single-partition}
`cluster_metrics()` exposes explicit voxel-level estimands. With known labels it
reports adjusted Rand index, variation of information in bits, and pairwise
Dice. With a canonical `cluster4d_result`, spatial dispersion is reconstructed
in physical millimetres from its provenance.
```{r cluster-metrics}
rena_metrics <- cluster_metrics(rena_fit, truth = syn$truth)
rena_metrics[c(
"n_clusters", "size_summary", "ari_truth",
"variation_of_information_truth_bits", "spatial_rms_distance_mm"
)]
```
```{r check-cluster-metrics, include=FALSE}
stopifnot(
identical(rena_metrics$n_clusters, 4L),
all(is.finite(unlist(rena_metrics))),
rena_metrics$ari_truth >= -1 && rena_metrics$ari_truth <= 1,
rena_metrics$variation_of_information_truth_bits >= 0,
rena_metrics$spatial_rms_distance_mm > 0
)
```
Ground truth is available here because this is a simulation. Do not substitute
an anatomical atlas and silently call it truth; describe the atlas comparison
as agreement with a reference partition.
# How do you compare two outputs? {#compare}
Temporal coherence requires the original voxel-by-time matrix. Pairwise
partition agreement requires exactly two results. The function refuses
incompatible masks or physical geometry instead of silently comparing
misaligned labels.
```{r prepare-features}
feature_mat <- t(as.matrix(neuroim2::series(
syn$vec, which(as.array(syn$mask) > 0)
)))
```
```{r compare-results}
comparison <- compare_cluster4d(
ReNA = rena_fit, G3S = g3s_fit,
metrics = c(
"summary", "spatial_dispersion", "temporal_coherence",
"partition_agreement"
),
feature_mat = feature_mat
)
comparison
```
```{r check-comparison, include=FALSE}
numeric_comparison <- comparison[vapply(comparison, is.numeric, logical(1))]
stopifnot(
nrow(comparison) == 2L,
all(comparison$N_Clusters == 4L),
all(vapply(numeric_comparison, function(x) all(is.finite(x)), logical(1))),
length(unique(comparison$Adjusted_Rand_Index)) == 1L,
length(unique(comparison$Variation_of_Information_bits)) == 1L
)
```
The agreement columns repeat because they describe the one ReNA–G3S pair.
Adjusted Rand index corrects for chance agreement; variation of information is
an information distance in bits; pairwise Dice compares whether voxel pairs are
placed together. None is a substitute for spatial or temporal diagnostics.
```{r size-figure, echo=FALSE, fig.cap='Sorted parcel sizes reveal balance without implying that equal sizes are inherently better. Both valid outputs contain four parcels over the same 2,048 voxels.', fig.alt='Grouped dot plot of four sorted parcel sizes for ReNA and G3S. ReNA has a wider size range than G3S.'}
sizes <- rbind(
ReNA = sort(tabulate(rena_fit$labels, nbins = rena_fit$actual_k)),
G3S = sort(tabulate(g3s_fit$labels, nbins = g3s_fit$actual_k))
)
matplot(
seq_len(ncol(sizes)), t(sizes), type = "b", lty = 1, lwd = 2,
pch = c(16, 17), col = method_colors,
xlab = "Parcel size rank", ylab = "Included voxels", xaxt = "n", bty = "l"
)
axis(1, at = seq_len(ncol(sizes)), labels = seq_len(ncol(sizes)))
legend("topleft", legend = rownames(sizes), col = method_colors,
pch = c(16, 17), lty = 1, lwd = 2, bty = "n")
```
# What belongs in a report? {#report}
Record the mask and geometry, method and complete parameter set, requested and
actual K, validity outcome, parcel-size distribution, and each estimand with its
units. For simulation or external-reference studies, name the reference and
state whether it is genuine ground truth.
Next, [Visualize and export](visualize-export.html) shows how to inspect the
spatial result and round-trip it through NIfTI.