# HTML formats get an interactive plotly figure (hover for the values, click the
# legend to hide a line); the handout and other formats get the same figure
# drawn with ggplot2.
# The ten cars as points, with the support vectors of the max-margin fit flagged.
cars_df <- tibble::tibble(
weight = X_sep[, 1],
horsepower = X_sep[, 2],
mpg_group = ifelse(y_sep > 0, "mpg at least 30", "mpg at most 14"),
support = is_sv_mm
)
# The set w.x + b = c is a line, so write it as horsepower = intercept + slope * weight.
# The margin lines sit at c = -m and c = m, where m = margin * ||w||.
line_at <- function(w, b, c, kind) {
tibble::tibble(intercept = (c - b) / w[2], slope = -w[1] / w[2], kind = kind)
}
m_mm <- margin_mm * sqrt(sum(w_mm^2))
lines_df <- rbind(
line_at(w_mm, b_mm, 0, "max-margin hyperplane"),
line_at(w_mm, b_mm, -m_mm, "margin"),
line_at(w_mm, b_mm, m_mm, "margin"),
line_at(w_ls, b_ls, 0, "bisector of the class means")
)
x_range <- range(cars_df$weight) + c(-0.2, 0.2)
y_range <- range(cars_df$horsepower) + c(-0.2, 0.2)
if (knitr::is_html_output()) {
library(plotly)
# Draw line i of lines_df across the plotted weight range.
add_line <- function(widget, i, name, dash, color, legend = TRUE) {
widget |>
add_trace(
x = x_range, y = lines_df$intercept[i] + lines_df$slope[i] * x_range,
type = "scatter", mode = "lines", name = name, showlegend = legend,
line = list(color = color, dash = dash), hoverinfo = "skip"
)
}
widget <- plot_ly() |>
add_line(1, "max-margin hyperplane", "solid", "#222222") |>
add_line(2, "margin", "dash", "#222222") |>
add_line(3, "margin", "dash", "#222222", legend = FALSE) |>
add_line(4, "bisector of the class means", "dot", "#7570b3")
# One trace per class, with a different symbol as well as a different color.
for (group in unique(cars_df$mpg_group)) {
high_mpg <- group == "mpg at least 30"
widget <- widget |>
add_trace(
data = cars_df[cars_df$mpg_group == group, ],
x = ~weight, y = ~horsepower, type = "scatter", mode = "markers",
name = group,
marker = list(
size = 9, color = if (high_mpg) "#1f77b4" else "#d95f02",
symbol = if (high_mpg) "circle" else "triangle-up"
)
)
}
# An open circle around each support vector.
widget <- widget |>
add_trace(
data = cars_df[cars_df$support, ], x = ~weight, y = ~horsepower,
type = "scatter", mode = "markers", name = "support vector",
marker = list(
size = 18, symbol = "circle-open", color = "#222222",
line = list(width = 2)
)
) |>
layout(
xaxis = list(title = "weight (thousands of pounds)", range = x_range),
yaxis = list(title = "horsepower (hundreds)", range = y_range),
legend = list(orientation = "h", y = -0.25),
margin = list(t = 10)
) |>
config(displaylogo = FALSE, responsive = TRUE)
# The alt text: a plotly widget has none of its own.
htmltools::div(
role = "img",
`aria-label` = paste(
"Interactive scatter plot of horsepower against weight for ten cars in",
"two groups, with the max-margin hyperplane, its two margin lines, the",
"circled support vectors, and the differently tilted bisector of the",
"class means."
),
widget
)
} else {
library(ggplot2)
ggplot(cars_df, aes(weight, horsepower)) +
geom_abline(aes(intercept = intercept, slope = slope, linetype = kind),
data = lines_df, show.legend = FALSE) +
geom_point(aes(shape = mpg_group), size = 2) +
# A larger open circle around each support vector.
geom_point(data = cars_df[cars_df$support, ], shape = 1, size = 5) +
scale_shape_manual(
values = c("mpg at least 30" = 16, "mpg at most 14" = 17)
) +
scale_linetype_manual(values = c(
"max-margin hyperplane" = "solid",
"margin" = "dashed",
"bisector of the class means" = "dotted"
)) +
coord_cartesian(xlim = x_range, ylim = y_range) +
labs(x = "weight (thousands of pounds)", y = "horsepower (hundreds)",
shape = NULL) +
theme(legend.position = "bottom")
}