# Paleta: clase 1 = morado, clase 2 = azul, clase 3 = naranja
pal <- c("#C45BC4", "#2BA8E0", "#E8A33D")
# Puntaje de un SVM binario: > 0 <=> segunda clase (levels[2]).
# (e1071 devuelve el signo contrario: positivo = primera clase)
score_svm <- function(fit, newdata) {
dv <- attr(predict(fit, newdata, decision.values = TRUE), "decision.values")
-as.numeric(dv[, 1])
}
# Coeficientes de un SVM LINEAL (solo si scale = FALSE): f(x) = beta0 + beta'x
# e1071 no los entrega directamente; se reconstruyen desde los vectores de soporte.
coef_svm <- function(fit) {
stopifnot(fit$kernel == 0, !any(fit$scaled))
beta <- -colSums(fit$coefs[, 1] * fit$SV) # signo: positivo = levels[2]
list(beta0 = fit$rho, beta = beta, margen = 1 / sqrt(sum(beta^2)))
}
# Frontera de decisión (línea continua), margen (punteada) y vectores de soporte (círculos)
plot_svm <- function(fit, data, titulo = NULL, margenes = TRUE, n = 150) {
lev <- levels(data$y)
rejilla <- expand_grid(
x1 = seq(min(data$x1) - .4, max(data$x1) + .4, length.out = n),
x2 = seq(min(data$x2) - .4, max(data$x2) + .4, length.out = n))
rejilla$pred <- predict(fit, rejilla)
binario <- length(lev) == 2
if (binario) rejilla$score <- score_svm(fit, rejilla)
data$sv <- seq_len(nrow(data)) %in% fit$index
p <- ggplot() +
geom_raster(data = rejilla, aes(x1, x2, fill = pred), alpha = .15) +
geom_point(data = data, aes(x1, x2, colour = y), size = 2) +
geom_point(data = filter(data, sv), aes(x1, x2), shape = 1, size = 4.5) +
scale_fill_manual(values = pal, guide = "none") +
scale_colour_manual(values = pal) +
coord_cartesian(expand = FALSE) +
labs(title = titulo, colour = "clase", x = expression(X[1]), y = expression(X[2]))
if (binario) {
p <- p + geom_contour(data = rejilla, aes(x1, x2, z = score),
breaks = 0, colour = "black", linewidth = .8)
if (margenes)
p <- p + geom_contour(data = rejilla, aes(x1, x2, z = score),
breaks = c(-1, 1), colour = "black",
linetype = "dashed", linewidth = .4)
}
p
}
# Matriz de confusión como mapa de calor
plot_conf <- function(pred, real, titulo = NULL) {
tibble(predicho = pred, real = real) |>
count(predicho, real) |>
ggplot(aes(real, predicho, fill = n)) +
geom_tile(colour = "white") + geom_text(aes(label = n), size = 6) +
scale_fill_gradient(low = "white", high = pal[2], guide = "none") +
labs(title = titulo, x = "clase real", y = "clase predicha")
}
# Curvas ROC con pROC (lista nombrada de objetos roc)
plot_roc <- function(lst, titulo = NULL) {
nombres <- imap_chr(lst, \(r, nm) sprintf("%s (AUC = %.3f)", nm, as.numeric(auc(r))))
ggroc(setNames(lst, nombres), legacy.axes = TRUE, linewidth = 1) +
geom_abline(linetype = 3) + coord_equal() +
labs(title = titulo, x = "Tasa de falsos positivos",
y = "Tasa de verdaderos positivos", colour = NULL) +
theme(legend.position = "bottom", legend.direction = "vertical")
}