library(tidyverse)
# Simplify the Motor Trends data to two predictors legible at aspect ratio 1.
mtcars |>
transform(hp00 = hp/100) |>
subset(select = c(mpg, hp00, wt)) ->
subcars
head(subcars)
## mpg hp00 wt
## Mazda RX4 21.0 1.10 2.620
## Mazda RX4 Wag 21.0 1.10 2.875
## Datsun 710 22.8 0.93 2.320
## Hornet 4 Drive 21.4 1.10 3.215
## Hornet Sportabout 18.7 1.75 3.440
## Valiant 18.1 1.05 3.460
# Here's the setup; i want to project the data points onto the gradient axis.
ggplot(subcars) +
aes(x = hp00, y = wt) +
coord_equal() +
geom_point()

compute_group_proj <- function(data, scales, na.rm = FALSE) {
# arbitrary values of computed aesthetics
res <- transform(
data,
xend = NA_real_,
yend = NA_real_
)
# empty initialized output
res <- data[c(), , drop = FALSE]
lm(outcome ~ x + y, data) |>
coefficients() |>
as.list() |> as.data.frame() ->
gradient
# no referent means no projection
if (is.null(gradient) || ! is.data.frame(gradient)) return(res)
# compute and collect projections of `data` onto `referent` rows
inertias <- gradient$x^2 + gradient$y^2
for (i in seq(nrow(gradient))) {
data$dots <- data$x * gradient$x[i] + data$y * gradient$y[i]
res_i <- transform(
data,
xend = dots / inertias[i] * gradient$x[i],
yend = dots / inertias[i] * gradient$y[i]
)
res <- rbind(res, res_i)
}
res
}
subcars %>%
rename(x = hp00, y = wt, outcome = mpg) %>%
compute_group_proj() %>%
head()
## outcome x y dots xend yend
## Mazda RX4 21.0 1.10 2.620 -13.65494 1.726263 2.106873
## Mazda RX4 Wag 21.0 1.10 2.875 -14.64379 1.851273 2.259445
## Datsun 710 22.8 0.93 2.320 -11.95145 1.510907 1.844035
## Hornet 4 Drive 21.4 1.10 3.215 -15.96225 2.017954 2.462876
## Hornet Sportabout 18.7 1.75 3.440 -18.90000 2.389346 2.916153
## Valiant 18.1 1.05 3.460 -16.75345 2.117978 2.584954
StatProj <- ggproto("StatProj", Stat,
required_aes = c("x", "y", "outcome"),
compute_group = compute_group_proj
)
last_plot() +
geom_segment(stat = StatProj,
aes(outcome = mpg))
