Getting Started with gghinton

library(gghinton)
library(ggplot2)
#> Warning: package 'ggplot2' was built under R version 4.4.3

What is a Hinton diagram?

A Hinton diagram is a compact visualization of a numerical matrix. Each cell is represented by a square whose area is proportional to the magnitude of the value, so larger values are immediately distinguishable from smaller ones, something that colour shading alone (as in a heatmap) does not achieve as directly.

For signed data, the convention established by Geoffrey Hinton for visualizing neural network weights is:

For non-negative data, the grey background is omitted and all squares are drawn in black.

When to use a Hinton diagram

A Hinton diagram excels when:

Typical use cases include neural network weight matrices, correlation matrices, factor loadings from PCA or factor analysis, and transition matrices from Markov models.

Quick start

# A 10x10 signed matrix
set.seed(99)
m <- matrix(rnorm(100), nrow = 10)
rownames(m) <- paste0("r", 1:10)
colnames(m) <- paste0("c", 1:10)

df <- matrix_to_hinton(m)

ggplot(df, aes(x = col, y = row, weight = weight)) +
  geom_hinton() +
  scale_fill_hinton() +
  coord_fixed() +
  theme_hinton() +
  labs(title = "A signed Hinton diagram")

The five-function recipe is the standard gghinton workflow:

  1. matrix_to_hinton(): reshape the matrix to a tidy data frame
  2. geom_hinton(): draw the squares
  3. scale_fill_hinton(): apply the conventional white/black colour scheme
  4. coord_fixed(): ensure squares appear as squares (not rectangles)
  5. theme_hinton(): remove grid lines that would compete visually with the squares

Unsigned data

When all values are non-negative, geom_hinton() detects this automatically and omits the grey background.

m_pos <- abs(m)
df_pos <- matrix_to_hinton(m_pos)

ggplot(df_pos, aes(x = col, y = row, weight = weight)) +
  geom_hinton() +
  scale_fill_hinton() +
  coord_fixed() +
  theme_hinton() +
  labs(title = "An unsigned Hinton diagram")

Converting different data structures

as_hinton_df() provides a generic interface that dispatches on the class of its input.

# matrix
as_hinton_df(matrix(c(1, -2, 3, -4), 2, 2))
#>   row col weight
#> 1   2   1      1
#> 2   1   1     -2
#> 3   2   2      3
#> 4   1   2     -4

# base R table
t2 <- table(
  group   = c("A", "A", "B", "B"),
  outcome = c("yes", "no", "yes", "no")
)
as_hinton_df(t2)
#>   row col weight row_label col_label
#> 1   2   1      1         A        no
#> 2   1   1      1         B        no
#> 3   2   2      1         A       yes
#> 4   1   2      1         B       yes

If you already have a tidy data frame with the right column names, pass it directly:

tidy <- data.frame(row = c(1, 1, 2, 2), col = c(1, 2, 1, 2),
                   weight = c(0.5, -0.3, 0.8, -0.1))
as_hinton_df(tidy)
#>   row col weight
#> 1   1   1    0.5
#> 2   1   2   -0.3
#> 3   2   1    0.8
#> 4   2   2   -0.1

The scale_by parameter

By default, normalization is per-panel: the largest value in each facet fills its cell. When you want to compare magnitudes across facets, use scale_by = "global" so that all panels share the same scale.

set.seed(1)
df_a <- cbind(matrix_to_hinton(matrix(runif(9, -1,  1), 3, 3)),
              panel = "A (range +/-1)")
df_b <- cbind(matrix_to_hinton(matrix(runif(9, -5,  5), 3, 3)),
              panel = "B (range +/-5)")
df_ab <- rbind(df_a, df_b)

# Per-panel scaling: each panel's largest value fills its cell
ggplot(df_ab, aes(x = col, y = row, weight = weight)) +
  geom_hinton(scale_by = "panel") +
  scale_fill_hinton() +
  coord_fixed() +
  theme_hinton() +
  facet_wrap(~panel) +
  labs(title = 'scale_by = "panel" (default)')


# Global scaling: panel A appears much smaller
ggplot(df_ab, aes(x = col, y = row, weight = weight)) +
  geom_hinton(scale_by = "global") +
  scale_fill_hinton() +
  coord_fixed() +
  theme_hinton() +
  facet_wrap(~panel) +
  labs(title = 'scale_by = "global"')

Customization

Custom colours

Pass a named values vector to scale_fill_hinton() to override individual colours while keeping the defaults for the rest:

df <- matrix_to_hinton(m)

ggplot(df, aes(x = col, y = row, weight = weight)) +
  geom_hinton(background = FALSE) +
  scale_fill_hinton(values = c(positive = "darkblue", negative = "darkred")) +
  coord_fixed() +
  theme_hinton() +
  labs(title = "Custom colours")

Axis labels from matrix names

When matrix_to_hinton() detects row or column names it adds row_label and col_label columns that you can use with scale_* breaks and labels.

# m has rownames/colnames set above
df_named <- matrix_to_hinton(m)

ggplot(df_named, aes(x = col, y = row, weight = weight)) +
  geom_hinton() +
  scale_fill_hinton() +
  scale_x_continuous(
    breaks = seq_len(ncol(m)),
    labels = colnames(m)
  ) +
  scale_y_continuous(
    breaks = seq_len(nrow(m)),
    labels = rev(rownames(m))  # reversed because row 1 is at the top
  ) +
  coord_fixed() +
  theme_hinton() +
  labs(title = "Named axes")

Removing the background

Set background = FALSE to suppress the grey background even for signed data:

ggplot(df, aes(x = col, y = row, weight = weight)) +
  geom_hinton(background = FALSE) +
  scale_fill_hinton(values = c(positive = "grey70")) +
  coord_fixed() +
  theme_hinton() +
  labs(title = "Signed data without background")

Correlation matrix example

df_cor <- as_hinton_df(cor(mtcars))
vars <- colnames(mtcars)

ggplot(df_cor, aes(x = col, y = row, weight = weight)) +
  geom_hinton() +
  scale_fill_hinton() +
  scale_x_continuous(breaks = seq_along(vars), labels = vars) +
  scale_y_continuous(breaks = seq_along(vars), labels = rev(vars)) +
  coord_fixed() +
  theme_hinton() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  labs(title = "cor(mtcars)",
       subtitle = "White = positive, black = negative correlation")