Introduction
To plot an SVM classification graph in R, use the plot() method from the e1071 package on an svm object. This displays the decision boundary and support vectors for two-feature classification. For more customizable plots, use ggplot2 with a grid of predicted values to visualize the decision regions. Both approaches require the data to have exactly two features (or you must select two features to plot).
Basic SVM Plot with e1071
1library(e1071)
2
3# Use the iris dataset (select 2 features and 2 classes for simplicity)
4data <- iris[iris$Species != "setosa", ]
5data$Species <- droplevels(data$Species)
6
7# Train SVM with 2 features
8model <- svm(Species ~ Sepal.Length + Sepal.Width, data = data, kernel = "radial")
9
10# Plot the decision boundary
11plot(model, data, Sepal.Length ~ Sepal.Width)
The plot() method shows:
Colored regions for each class's decision area
Data points marked by class
Support vectors highlighted with crosses
Customizing the e1071 Plot
1plot(model, data, Sepal.Length ~ Sepal.Width,
2 svSymbol = "x", # Symbol for support vectors
3 dataSymbol = "o", # Symbol for data points
4 symbolPalette = c("red", "blue"),
5 color.palette = terrain.colors,
6 main = "SVM Classification")
ggplot2 Visualization (More Control)
1library(e1071)
2library(ggplot2)
3
4# Prepare data
5data <- iris[iris$Species != "setosa", ]
6data$Species <- droplevels(data$Species)
7
8# Train SVM
9model <- svm(Species ~ Sepal.Length + Sepal.Width, data = data, kernel = "radial")
10
11# Create a grid of points covering the feature space
12x_range <- seq(min(data$Sepal.Length) - 0.5, max(data$Sepal.Length) + 0.5, length.out = 200)
13y_range <- seq(min(data$Sepal.Width) - 0.5, max(data$Sepal.Width) + 0.5, length.out = 200)
14grid <- expand.grid(Sepal.Length = x_range, Sepal.Width = y_range)
15
16# Predict class for each grid point
17grid$Predicted <- predict(model, grid)
18
19# Plot
20ggplot() +
21 geom_tile(data = grid, aes(x = Sepal.Length, y = Sepal.Width, fill = Predicted),
22 alpha = 0.3) +
23 geom_point(data = data, aes(x = Sepal.Length, y = Sepal.Width,
24 color = Species, shape = Species), size = 3) +
25 scale_fill_manual(values = c("versicolor" = "lightblue", "virginica" = "lightyellow")) +
26 scale_color_manual(values = c("versicolor" = "blue", "virginica" = "red")) +
27 labs(title = "SVM Decision Boundary", x = "Sepal Length", y = "Sepal Width") +
28 theme_minimal()
Highlighting Support Vectors
1# Get support vector indices
2sv_indices <- model$index
3
4# Add support vector flag to data
5data$is_sv <- seq_len(nrow(data)) %in% sv_indices
6
7ggplot() +
8 geom_tile(data = grid, aes(x = Sepal.Length, y = Sepal.Width, fill = Predicted),
9 alpha = 0.3) +
10 geom_point(data = data[!data$is_sv, ],
11 aes(x = Sepal.Length, y = Sepal.Width, color = Species), size = 2) +
12 geom_point(data = data[data$is_sv, ],
13 aes(x = Sepal.Length, y = Sepal.Width, color = Species),
14 size = 4, shape = 4, stroke = 2) + # Crosses for SVs
15 labs(title = "SVM with Support Vectors Highlighted") +
16 theme_minimal()
Different Kernel Comparison
1kernels <- c("linear", "radial", "polynomial", "sigmoid")
2plots <- list()
3
4for (k in kernels) {
5 model <- svm(Species ~ Sepal.Length + Sepal.Width, data = data, kernel = k)
6 grid$Predicted <- predict(model, grid)
7
8 p <- ggplot() +
9 geom_tile(data = grid, aes(x = Sepal.Length, y = Sepal.Width, fill = Predicted),
10 alpha = 0.3) +
11 geom_point(data = data, aes(x = Sepal.Length, y = Sepal.Width, color = Species),
12 size = 2) +
13 labs(title = paste("Kernel:", k)) +
14 theme_minimal() +
15 theme(legend.position = "none")
16
17 plots[[k]] <- p
18}
19
20# Display all four plots
21library(gridExtra)
22grid.arrange(grobs = plots, ncol = 2)
Multi-Class SVM Plot
1# All 3 iris classes with 2 features
2model <- svm(Species ~ Sepal.Length + Sepal.Width, data = iris, kernel = "radial")
3
4grid <- expand.grid(
5 Sepal.Length = seq(min(iris$Sepal.Length) - 0.5, max(iris$Sepal.Length) + 0.5, length.out = 200),
6 Sepal.Width = seq(min(iris$Sepal.Width) - 0.5, max(iris$Sepal.Width) + 0.5, length.out = 200)
7)
8grid$Predicted <- predict(model, grid)
9
10ggplot() +
11 geom_tile(data = grid, aes(x = Sepal.Length, y = Sepal.Width, fill = Predicted),
12 alpha = 0.3) +
13 geom_point(data = iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species),
14 size = 2) +
15 scale_fill_manual(values = c("setosa" = "lightgreen", "versicolor" = "lightblue",
16 "virginica" = "lightyellow")) +
17 labs(title = "Multi-Class SVM Decision Regions") +
18 theme_minimal()
Plotting with Decision Values (Margins)
1model <- svm(Species ~ Sepal.Length + Sepal.Width, data = data,
2 kernel = "radial", decision.values = TRUE)
3
4# Get decision values for the grid
5pred <- predict(model, grid, decision.values = TRUE)
6grid$decision_value <- as.numeric(attr(pred, "decision.values"))
7
8ggplot() +
9 geom_tile(data = grid, aes(x = Sepal.Length, y = Sepal.Width, fill = decision_value)) +
10 scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0) +
11 geom_contour(data = grid, aes(x = Sepal.Length, y = Sepal.Width, z = decision_value),
12 breaks = c(-1, 0, 1), color = "black") +
13 geom_point(data = data, aes(x = Sepal.Length, y = Sepal.Width, shape = Species),
14 size = 3) +
15 labs(title = "SVM Decision Boundary with Margins") +
16 theme_minimal()
The contour lines at z = -1, z = 0, and z = 1 show the decision boundary and the margin boundaries.
Common Pitfalls
More than 2 features: SVM decision boundaries in 2D can only be plotted for 2 features. If your model uses more features, select the 2 most important ones for visualization or use dimensionality reduction (PCA) to project to 2D before plotting.
Grid resolution too low: Using too few grid points (e.g., length.out = 50) makes the decision boundary look pixelated. Use at least 100-200 points per axis for smooth boundaries, but balance against computation time.
Factor levels not dropped: After subsetting data (e.g., removing "setosa"), unused factor levels remain. Use droplevels() to remove them, otherwise the SVM trains with phantom classes.
Kernel mismatch with data: A linear kernel cannot capture non-linear boundaries. If the classes overlap in complex ways, try kernel = "radial". If the plot shows poor separation, tune the cost and gamma parameters.
Forgetting to scale features: SVM is sensitive to feature scales. If one feature ranges 0-1 and another ranges 0-1000, the SVM is dominated by the larger feature. Use scale = TRUE (the default in e1071::svm) to standardize features before training.
Summary
Use plot(model, data, Feature1 ~ Feature2) from e1071 for quick SVM visualization
Use ggplot2 with a prediction grid for customizable decision boundary plots
Highlight support vectors by extracting model$index and plotting them differently
Compare kernels by training multiple models and arranging plots with gridExtra
Only 2 features can be visualized directly — use PCA for higher-dimensional data