R programming
data visualization
machine learning
decision boundaries
R tutorials

Drawing decision boundaries in R

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

Decision-boundary plots help you see how a classifier splits feature space into predicted classes. In R, the standard workflow is to train a model on two numeric features, generate a dense grid of points across that plane, predict the class or probability for every grid point, and then plot the result with the original observations on top.

A Simple Logistic Regression Example

To keep the visualization readable, start with two features from the iris dataset:

r
1library(ggplot2)
2
3df <- subset(iris, Species != "virginica")
4df$Species <- droplevels(df$Species)
5
6model <- glm(
7  Species ~ Sepal.Length + Sepal.Width,
8  data = df,
9  family = binomial
10)

This gives a binary classifier. The decision boundary is where the predicted probability is 0.5.

Build A Prediction Grid

Create a grid that spans the feature space:

r
1x_seq <- seq(
2  min(df$Sepal.Length) - 0.5,
3  max(df$Sepal.Length) + 0.5,
4  length.out = 200
5)
6
7y_seq <- seq(
8  min(df$Sepal.Width) - 0.5,
9  max(df$Sepal.Width) + 0.5,
10  length.out = 200
11)
12
13grid <- expand.grid(
14  Sepal.Length = x_seq,
15  Sepal.Width = y_seq
16)
17
18grid$prob <- predict(model, newdata = grid, type = "response")
19grid$predicted <- ifelse(grid$prob >= 0.5, "setosa", "versicolor")

The expand.grid call creates a dense set of points, and the predict call assigns model output to each one.

Plot The Boundary With ggplot2

Now draw the class regions and the separating line:

r
1ggplot() +
2  geom_tile(
3    data = grid,
4    aes(x = Sepal.Length, y = Sepal.Width, fill = predicted),
5    alpha = 0.25
6  ) +
7  geom_contour(
8    data = grid,
9    aes(x = Sepal.Length, y = Sepal.Width, z = prob),
10    breaks = 0.5,
11    color = "black"
12  ) +
13  geom_point(
14    data = df,
15    aes(x = Sepal.Length, y = Sepal.Width, color = Species),
16    size = 2
17  ) +
18  labs(
19    title = "Logistic Regression Decision Boundary",
20    x = "Sepal Length",
21    y = "Sepal Width"
22  ) +
23  theme_minimal()

geom_tile colors the regions by predicted class, and geom_contour draws the actual boundary where the probability crosses 0.5.

Non-Linear Boundaries With SVM

For curved boundaries, use a non-linear model such as an SVM with a radial kernel:

r
1library(e1071)
2library(ggplot2)
3
4svm_model <- svm(
5  Species ~ Sepal.Length + Sepal.Width,
6  data = df,
7  kernel = "radial",
8  cost = 1,
9  gamma = 0.5
10)
11
12grid$predicted <- predict(svm_model, newdata = grid)
13
14ggplot() +
15  geom_tile(
16    data = grid,
17    aes(x = Sepal.Length, y = Sepal.Width, fill = predicted),
18    alpha = 0.25
19  ) +
20  geom_point(
21    data = df,
22    aes(x = Sepal.Length, y = Sepal.Width, color = Species),
23    size = 2
24  ) +
25  labs(title = "SVM Decision Regions") +
26  theme_minimal()

The overall plotting pattern is the same. Only the model and prediction step change.

Keep The Problem Two-Dimensional

Decision boundaries are easiest to interpret in two dimensions. If your original model uses many features, choose two representative features for a visualization or reduce the data to two components before plotting. Otherwise, the plot becomes a projection rather than a literal picture of the model.

This is an important conceptual limit. A boundary plot is excellent for teaching, debugging, and exploratory work, but it does not always capture the full behavior of a high-dimensional model.

Common Pitfalls

The most common mistake is trying to plot boundaries for more than two predictors without deciding how to project the feature space. The resulting graphic often looks precise but hides too much of the model.

Another pitfall is forgetting to use the same preprocessing on the grid that you used during training. If you scaled or transformed the training data, apply that same transformation before predicting on grid points.

Grid resolution also matters. A coarse grid can make a smooth boundary look jagged or inaccurate. Increase length.out if the boundary appears blocky.

Finally, keep the class labels consistent. For binary models, be clear about which class corresponds to probability values above 0.5.

Summary

  • Train a classifier on two numeric features when you want a clean decision-boundary plot.
  • Use expand.grid to create a dense mesh of input values.
  • Predict on the grid, then plot regions with geom_tile and boundaries with geom_contour.
  • The same approach works for linear and non-linear models.
  • Be careful with preprocessing, grid resolution, and high-dimensional feature sets.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.