--- title: "Why Hinton Diagrams?" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Why Hinton Diagrams?} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 5, fig.height = 5 ) ``` ```{r setup, message = FALSE} library(gghinton) library(ggplot2) ``` Hinton diagrams were introduced by Geoffrey Hinton, one of the founders of deep learning, as a practical debugging tool for neural network weights in the 1980s. The diagram appeared in textbooks on neural networks and connectionist models and became a standard visualization in that literature. Despite their long history, they remain underused in modern data analysis toolkits, in part because no convenient, ggplot2-native implementation existed. `gghinton` aims to fix that. ## The problem with heatmaps for signed data Suppose you are training a neural network and you want to inspect a weight matrix: to understand which connections are large, which are small, and which are inhibitory versus excitatory. The standard tool is a heatmap: ```{r heatmap-example} set.seed(7) nr <- 10 nc <- 18 W <- matrix(rnorm(nr*nc, sd = 0.4), nrow = nr, ncol = nc) rownames(W) <- paste0("neuron_", 1:nr) colnames(W) <- paste0("input_", 1:nc) # The standard heatmap approach df <- as.data.frame(as.table(W)) names(df) <- c("row", "col", "value") ggplot(df, aes(x = col, y = row, fill = value)) + geom_tile() + scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0) + coord_fixed() + theme_minimal() + theme(panel.grid = element_blank(), axis.text.x = element_text(angle = 45, vjust = 1, hjust=1)) + labs(title = "Weight matrix as a heatmap") ``` This works, but it has weaknesses: 1. **Colour choice matters a lot.** Blue/white/red is readable; many diverging palettes are not (especially for colourblind readers). 2. **Small differences are hard to judge.** Is `0.35` more than twice `0.16`? With colour, you can't easily tell. 3. **Near-zero entries look similar** to each other and to slightly positive/negative entries. Now the same data as a Hinton diagram: ```{r hinton-example} df_h <- matrix_to_hinton(W, rowname_col = "row", colname_col = "col", value_col = "weight") ggplot(df_h, aes(x = col, y = row, weight = weight)) + geom_hinton() + scale_fill_hinton() + scale_x_continuous(breaks = seq_along(colnames(W)), labels = colnames(W)) + scale_y_continuous(breaks = seq_along(rownames(W)), labels = rev(rownames(W))) + coord_fixed() + theme_hinton() + theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+ labs(title = "Weight matrix as a Hinton diagram") ``` The key differences: - **Dominant weights are immediately visible**: large squares catch the eye. - **Near-zero weights are nearly invisible**: the background shows through. - **Sign is black-and-white**: no colour palette decisions, no colourblind concerns. - **Magnitude comparisons are accurate**: area comparisons are pre-attentive and well-calibrated in human vision. ## Why area beats colour for magnitude A large body of research in visual perception (Mackinlay 1986; Cleveland & McGill 1984) ranks visual encoding channels by how accurately humans can decode quantitative information. The consensus ranking for magnitude: 1. Position on a common scale (best) 2. Length 3. **Area** 4. Angle / slope 5. Colour saturation (worst for quantitative comparison) Heatmaps use **colour saturation** (the worst channel for magnitude). Hinton diagrams use **area** (a dramatically better channel). The improvement is most pronounced when: - Values span a wide range (e.g., `0.01` to `0.99`): tiny vs large squares are unmistakable; pale vs saturated blue is not. - You need to compare non-adjacent entries: spatial position makes area comparisons easy across the matrix. ## The signed data advantage For correlation matrices or weight matrices where sign matters, Hinton diagrams have an additional advantage. A heat map must choose a diverging colour scheme, map its midpoint correctly to zero, and hope that readers can distinguish near-zero from slightly-positive from slightly-negative. A Hinton diagram encodes sign with the most basic visual distinction possible: **black vs white**. There is no perceptual ambiguity. ```{r correlation-example} set.seed(3) # Simulate a correlation matrix S <- matrix(c( 1.00, 0.72, -0.35, 0.15, 0.72, 1.00, -0.21, 0.08, -0.35, -0.21, 1.00, -0.58, 0.15, 0.08, -0.58, 1.00 ), 4, 4) vars <- c("IQ", "Memory", "Anxiety", "Stress") rownames(S) <- colnames(S) <- vars df_cor <- matrix_to_hinton(S) ggplot(df_cor, aes(x = col, y = row, weight = weight)) + geom_hinton() + scale_fill_hinton() + scale_x_continuous(breaks = 1:4, labels = vars) + scale_y_continuous(breaks = 1:4, labels = rev(vars)) + coord_fixed() + theme_hinton() + labs(title = "Correlation matrix", subtitle = "White = positive, black = negative") ``` Notice how the `Anxiety-Stress` negative correlation is immediately visible as a large black square, while the small positive `IQ-Stress` correlation is nearly absent. ## When other visualisations are better Hinton diagrams are not universally superior. Other visualisations may be better when: - **The matrix is large** (say, > 50x50). Hinton squares become tiny and the visual advantage becomes less clear. - **You need to communicate exact values**: Hinton diagrams prioritize relative visual impression. Other representations with numeric labels may be a better choice if precision is important. - **Continuous gradients** matter more than individual entries (e.g., a spatial field like temperature over a map).