What a Distance Matrix Represents
After choosing variables and preparing their scales, a distance-based analysis compares observations in pairs. A distance matrix records those pairwise distances in a square table: observation \(i\) labels a row, observation \(j\) labels a column, and the entry in row \(i\), column \(j\) is the distance between them. It is a useful representation for inspection, but it can be large even when the original data set is modest.
For \(n\) observations with numeric measurements \(x_{i1},\ldots,x_{ip}\), the Euclidean distance between observations \(i\) and \(j\) is the square root of the sum of squared differences across the \(p\) variables. The observations must have measurements on the same variables for this calculation. The examples below use complete numeric data and focus on how the resulting distances are represented, not on how to prepare missing data.
The diagonal is zero because an observation has no difference from itself. Symmetry means that the distance from observation \(i\) to observation \(j\) does not depend on which one is named first. These properties are useful checks on a computed matrix. A nonzero diagonal or unequal values on opposite sides of it would be a reason to check the data and calculation.
Why the Matrix Is Symmetric
For Euclidean distance, write the measurements for observations \(i\) and \(j\) as vectors \(\boldsymbol{x}_i\) and \(\boldsymbol{x}_j\). Their distance is the length of the difference vector. Reversing the order changes the sign of each coordinate difference, but squaring removes that sign.
Swapping \(i\) and \(j\) gives \((x_{jk}-x_{ik})^2=(x_{ik}-x_{jk})^2\) for every variable \(k\). Therefore \(d(j,i)=d(i,j)\). Setting \(i=j\) makes every difference zero, so \(d(i,i)=0\). This justifies symmetry and the zero diagonal for Euclidean distance. The same checks apply to other standard symmetric distance measures, but not every quantity called a dissimilarity is necessarily a metric with all the same properties; check the definition being used.
Only one value is needed for each distinct pair. There are \(n\) choices for a first observation and \(n-1\) choices for a different second observation. This counts every pair twice, once in each order, so the number of unique distances is
A full \(n\)-by-\(n\) matrix contains \(n^2\) entries, including the diagonal and both copies of every off-diagonal distance. The formula above explains why R can store the distinct distances in a compact form rather than storing every matrix entry.
Worked Example: Constructing a Three-Observation Matrix
A hypothetical portfolio review represents three accounts by two already-prepared numeric features. Account A has coordinates \((0,0)\), account B has \((3,4)\), and account C has \((3,0)\). Construct the Euclidean distance matrix and check its structure.
Method. Apply the Euclidean formula to each distinct pair. There are \(3(3-1)/2=3\) distinct pairs.
For A and B, the coordinate differences are \(0-3=-3\) and \(0-4=-4\). Their squared differences sum to \(9+16=25\), so \(d(A,B)=\sqrt{25}=5\).
For A and C, the differences are \(0-3=-3\) and \(0-0=0\). Thus \(d(A,C)=\sqrt{9+0}=3\).
For B and C, the differences are \(3-3=0\) and \(4-0=4\). Thus \(d(B,C)=\sqrt{0+16}=4\).
Place these values in both corresponding off-diagonal positions, and place zero on the diagonal:
The matrix is symmetric: for example, \(D_{AB}=D_{BA}=5\). Its diagonal entries are all zero. The three computed pairwise distances account for all \(3\) unique pairs, as required. The common mistake this avoids is calculating only one triangle of the matrix and leaving the other triangle blank when a full matrix is required.
Computing and Inspecting Distances in R
In base R, `dist()` computes pairwise distances between rows of a numeric data object. With `method = "euclidean"`, it uses Euclidean distance. The returned object has class `dist`; it is a compact representation of the unique pairwise distances, not a full square matrix. Use `as.matrix()` when a square matrix is needed for display or for an operation that expects one.
The following code reproduces the first example. Row names provide readable observation labels. They do not change the distance calculation.
`X` has three rows, one for each account, and two columns, one for each feature.
`D_dist <- dist(X, method = "euclidean")` stores one distance for each distinct pair.
`D_mat <- as.matrix(D_dist)` creates the full symmetric matrix, including both copies of each off-diagonal value and the zero diagonal.
A complete R version is:
To attach the labels and compute the matrix, use:
The matrix `D_mat` contains the entries shown in the worked example. In R, `D_mat["A", "B"]` retrieves the distance between A and B, which is 5. The compact object also retains information such as its size and labels; `attr(D_dist, "Size")` gives the number of observations, and `labels(D_dist)` returns the labels when supplied. A common practical check is to compare selected matrix entries with direct calculations for the corresponding rows.
Worked Example: Reading a Compact R Distance Object
A hypothetical service team records two numeric profile features for four branches: U is \((1,1)\), V is \((1,5)\), W is \((4,1)\), and Z is \((4,5)\). The team computes `D <- dist(X, method = "euclidean")`. Determine the full matrix and interpret what R's compact output represents.
Method. Compute the six unique pairwise distances, then use symmetry and the zero diagonal to fill the full matrix. With four observations, the number of distinct pairs is \(4(4-1)/2=6\).
For U–V, the differences are \(0\) and \(-4\), so the distance is \(\sqrt{0^2+(-4)^2}=4\). For U–W, the differences are \(-3\) and \(0\), giving \(\sqrt{9}=3\). For U–Z, the differences are \(-3\) and \(-4\), giving \(\sqrt{9+16}=5\).
For V–W, the differences are \(-3\) and \(4\), giving \(\sqrt{9+16}=5\). For V–Z, the differences are \(-3\) and \(0\), giving \(\sqrt{9}=3\). For W–Z, the differences are \(0\) and \(-4\), giving \(\sqrt{16}=4\).
Therefore the full matrix, in U, V, W, Z order, is:
R's `dist` object stores the six values \(4,3,5,5,3,4\) in a compact triangular representation; it does not store a separate value for both U–V and V–U, or an explicit diagonal entry for each branch. After `M <- as.matrix(D)`, the expression `M["V", "Z"]` returns 3, while `M["Z", "V"]` also returns 3. The check is consistent with symmetry. The common mistake is to interpret the compact printout as though omitted matrix positions were missing distances; the other triangle and the diagonal are implied by the representation.
Memory and Computation Grow Quadratically
A compact `dist` object reduces storage compared with a full square matrix, but it still contains a number of values proportional to \(n^2\). If the number of observations doubles, the number of unique pairs becomes approximately four times as large for large \(n\). In R, a double-precision number typically occupies 8 bytes, so a first-order estimate for the compact numeric values is \(8\binom{n}{2}\) bytes. A full double matrix requires \(8n^2\) bytes for its numeric entries.
These are estimates for the numeric values, not exact total memory use. R objects also need metadata, and labels or copies of the original data take additional space. Converting a compact object to a full matrix can require the full matrix to exist in memory alongside the compact object and the input data. The maximum usable allocation also depends on the computer and R environment.
Worked Example: Estimating Storage Before Computing
A hypothetical segmentation exercise has \(n=12{,}000\) observations and \(p=8\) numeric features. Estimate storage for the compact distances and for a full distance matrix, and identify the main computational scale. Assume 8 bytes per double and report storage to one decimal place in MiB or GiB, as appropriate. Use \(1\text{ MiB}=1{,}048{,}576\) bytes and \(1\text{ GiB}=1{,}073{,}741{,}824\) bytes.
Method. First count unique pairs. Then multiply by 8 bytes for compact storage. For the full matrix, count all \(n^2\) entries. These estimates exclude overhead and the input data.
The number of distinct pairs is \(12{,}000 \times 11{,}999 / 2=71{,}994{,}000\). The compact numeric storage estimate is \(71{,}994{,}000 \times 8=575{,}952{,}000\) bytes. Converting to MiB gives \(575{,}952{,}000/1{,}048{,}576=549.3\) MiB, rounded to one decimal place.
A full matrix has \(12{,}000^2=144{,}000{,}000\) entries. Its numeric storage estimate is \(144{,}000{,}000 \times 8=1{,}152{,}000{,}000\) bytes. In GiB this is \(1{,}152{,}000{,}000/1{,}073{,}741{,}824=1.1\) GiB, rounded to one decimal place.
For Euclidean distances on eight features, the calculation examines each pair across eight coordinate differences: \(71{,}994{,}000 \times 8=575{,}952{,}000\) coordinate comparisons. The work therefore grows on the order of \(n^2p\), as well as storage growing on the order of \(n^2\). This does not predict an exact runtime, which depends on hardware and implementation. The common mistake is to estimate compact storage as though it were only \(n\) values; every distinct pair contributes a distance, so the correct count is \(\binom{n}{2}\).
Practical Checks and Common Traps
Before using a computed distance object, verify that the number of observations is the number of rows intended, that the feature columns are the intended numeric inputs, and that labels remain in the expected row order. When converting to a matrix, check that its dimensions are \(n\) by \(n\), its diagonal is zero, and entries are symmetric. For a small data set, recomputing one or two distances by hand is a direct validation of the chosen method and inputs.
A particularly tempting but incorrect storage calculation is to multiply the number of observations by itself and assume that this is the number of values in a compact `dist` object. That is the count for a full square matrix, not the compact object. Conversely, using only \(\binom{n}{2}\) to estimate a full matrix omits the mirrored entries. State which representation is being estimated before calculating memory.
These distinctions matter in practice because a conversion made only for convenient printing can substantially increase memory use. At larger sample sizes, estimate the pair count and storage before calling `as.matrix()`. If the estimate is close to the available memory, a full matrix may not be practical. This tutorial is aligned with the SOA Exam PA (Predictive Analytics) syllabus.
Check Your Understanding
For each problem, identify the relevant representation or calculation before giving your answer.
- A complete numeric data set has five observations. How many distinct pairwise distances are stored in a compact distance object? (Answer: 10)
- A distance matrix has 6 observations. How many entries does the full square matrix have, including its diagonal? (Answer: 36)
- For three one-dimensional observations with values 2, 6, and 8, compute the Euclidean distance between the first and third observations. (Answer: 6)
- A symmetric distance matrix has distance 7 between observations P and Q. What is the value in row Q, column P, and what is the diagonal entry for P? (Answer: row Q, column P is 7; the diagonal entry for P is 0)
- A compact distance object has 45 unique pairwise values. How many observations does it represent? Solve \(n(n-1)/2=45\). (Answer: 10 observations)
- For \(n=100\), estimate compact numeric storage at 8 bytes per distance, ignoring overhead. (Answer: 4,950 distances; \(4{,}950 \times 8=39{,}600\) bytes)