---
title: "Higher-dimensional metric MDS for 3D visualization"
output:
rmarkdown::html_vignette:
toc: true
vignette: >
%\VignetteIndexEntry{Higher-dimensional metric MDS for 3D visualization}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include=FALSE}
library(grip)
knitr::opts_chunk$set(collapse=TRUE,comment='#>')
source(system.file('scripts','high-dimensional-mds.R',package='grip'),local=TRUE)
bundle <- readRDS(system.file('extdata','high-dimensional-mds','examples.rds',package='grip'))
```
Can fitting distances in more than three dimensions give a better **final 3D
drawing**? This vignette compares direct 3D metric MDS with a higher-dimensional
fit followed by principal component analysis (PCA), with or without a further
3D metric-MDS fit. Higher-dimensional fitting provides another initialization
strategy; it does not guarantee a better drawing or a faster calculation.
The small recipe below runs with grip's public interfaces. The interactive
examples and repeated-start tables use a compact subset of a saved experiment,
so rebuilding this vignette does not repeat the larger fits. Every figure is
an interactive **ivue** 3D view. Its **Layout** dropdown selects the method and
intermediate dimension while retaining the camera. Drag to rotate and scroll
to zoom. If ivue is unavailable, explanatory text replaces the viewer and the
numerical tables remain; no static projection is substituted.
This guide uses the development metric-MDS interface, introduced after grip
0.2.0. For backend and weighting choices, see [Metric MDS](metric-mds-backends.html).
## Three routes to a 3D drawing
1. **Direct 3D:** minimize metric-MDS stress with three coordinates per vertex.
2. **Higher-dimensional MDS + PCA:** fit, for example, ten coordinates per
vertex; center them and retain the first three principal components.
3. **Higher-dimensional MDS + PCA + 3D refinement:** use those three PCA
coordinates as the starting configuration for another metric-MDS fit.
Every vertex can then move freely in 3D.
PCA chooses a three-dimensional linear subspace that retains coordinate
variance. It does not minimize error against the original distance targets.
Free 3D refinement optimizes those original targets again; the result need
not remain a linear projection of the higher-dimensional coordinates. This
is different from the optional `edge.kk()` stage in the
[synthetic graph gallery](synthetic-graph-families.html#optional-edge-length-comparison),
which fits directly connected vertices rather than all finite pairs.
An optimized higher-dimensional fit can have low stress while its PCA
projection has much higher stress. Evaluate the actual three-column result,
not just the higher-dimensional fit. Also compare a **classical-MDS start
followed by 3D metric MDS**: a useful initialization need not require a
higher-dimensional iterative fit.
## A runnable 10D example
We use the bundled 200-point paraboloid with Euclidean edge lengths on a
symmetric six-nearest-neighbor graph. Its targets are shortest-path distances.
Preparing the graph once lets every route use exactly the same target matrix.
```{r recipe-input}
library(grip)
bundle <- readRDS(system.file('extdata','high-dimensional-mds','examples.rds',package='grip'))
example <- bundle$cases[['paraboloid_17_6']]
prepared <- prepare.graph.geodesic.mds(
edges=example$edges, n=example$n, edge.weights=example$weights)
D <- prepared$distance_matrix
stopifnot(isTRUE(all.equal(unname(D),unname(example$distances),tolerance=1e-10)))
```
The following is a short usage demonstration, with 20 SGD passes per fitted
stage and one start. It is not a convergence study or an equal-time benchmark.
The 10D route performs an additional fitting stage. To expose the initialization,
we supply random matrices explicitly; the direct start uses the first three
columns of the same draw. Internal scale adjustment differs between dimensions.
```{r recipe-fit}
set.seed(17)
start10 <- matrix(rnorm(example$n * 10),example$n,10)
fit <- function(dimension, initial) {
# Record schedule termination below; suppress its expected warning here.
withCallingHandlers(
metric.mds(prepared=prepared, dim=dimension, init=initial,
backend='sgd', approximation='full', pair.weights='uniform',
n.init=1, max.iter=20, seed=17, diagnostics=FALSE,
sgd.control=list(checkpoint.every=1L)),
warning=function(w) {
if (grepl('iteration_limit',conditionMessage(w),fixed=TRUE))
invokeRestart('muffleWarning')
})
}
direct <- fit(3,start10[,1:3])
high <- fit(10,start10)
pca3 <- prcomp(high$coords,center=TRUE,scale.=FALSE,rank.=3)$x[,1:3,drop=FALSE]
refined <- fit(3,pca3)
classical.start <- fit(3,'classical')
```
Use `scale.=FALSE`: rescaling the ten coordinate axes to unit variance would
change their Euclidean geometry before projection. `project.3d()` is a drawing
helper, not PCA; it uses the first three coordinate columns.
We score the returned coordinates without an additional fitted scale:
\[
S_{\mathrm{norm}}(Z)=
\frac{\sum_{i