Skip to main content
AI in Production 2026 is now open for talk proposals.
Share insights that help teams build, scale, and maintain stronger AI systems.
items
Menu
  • About
    • Overview 
    • Join Us  
    • Community 
    • Contact 
  • Training
    • Overview 
    • Course Catalogue 
    • Public Courses 
  • Posit
    • Overview 
    • License Resale 
    • Managed Services 
    • Health Check 
  • Data Science
    • Overview 
    • Visualisation & Dashboards 
    • Open-source Data Science 
    • Data Science as a Service 
    • Gallery 
  • Engineering
    • Overview 
    • Cloud Solutions 
    • Enterprise Applications 
  • Our Work
    • Blog 
    • Case Studies 
    • R Package Validation 
    • diffify  

Styling Base R Graphics

Author: Colin Gillespie

Published: January 25, 2018

tags: r, graphics, base
  • Publication quality base R graphics
  • Fixing the problem
  • Why not use {ggplot2} (or something else)?

Publication quality base R graphics

Base R graphics get a bad press (although to be fair, they could have chosen their default values better). In general, they are viewed as a throw back to the dawn of the R era. I think that most people would agree that, in general, there are better graphics techniques in R (e.g. {ggplot2}). However it is occasionally worthwhile making a plot using base R graphics. For example, if you have a publication and you want to make sure the graphics are reproducible in five years.

In this post we’ll discuss methods for dramatically altering the look and feel of a base R plot. With a bit (ok, a lot) of effort, it is possible to change all aspects of the plot to your liking.

Typically I detest the iris the data set. It’s perhaps the most over used dataset in the entire R world. For this very reason, we’ll use it in this post to show what’s possible ;)

The standard base R scatter plot is

plot(iris$Sepal.Length, iris$Sepal.Width, col = iris$Species)
legend("topright", legend = levels(iris$Species), col = 1:3, pch = 21)

This gives a simple scatter plot with associated legend using the default colour scheme. The list of things wrong with the this plot is fairly lengthy, but not limited to

  • Colours
  • Margins
  • Axis labels
  • Overlapping points
  • Wasted space

However with base R graphics we can fix all of these faults!

Do you use Professional Posit Products? If so, check out our managed Posit services

Fixing the problem

What’s not clear in the scatter plot above is that some points lie on top of each other. So the first step is to wiggle the points using the jitter() function to avoid points sitting on top of each other.

## Same as geom_jitter
iris$Sepal.Length = jitter(iris$Sepal.Length)
iris$Sepal.Width = jitter(iris$Sepal.Width)

Next we select nicer colours (I’ve taken this palette from the great I want hue website). The palette() function allows you to globally change the colour palette used by base R plots

alpha = 150 # Transparent points
palette(c(rgb(200, 79, 178, alpha = alpha, maxColorValue = 255),
          rgb(105, 147, 45, alpha = alpha, maxColorValue = 255),
          rgb(85, 130, 169, alpha = alpha, maxColorValue = 255)))

Next we alter a few plot characteristics with the par() function

par(mar = c(3, 3, 2, 1), # Dist' from plot to side of page
    mgp = c(2, 0.4, 0), # Dist' plot to label
    las = 1, # Rotate y-axis text
    tck = -.01, # Reduce tick length
    xaxs = "i", yaxs = "i") # Remove plot padding

Then it comes to the plot() function itself. This has now become a lot more complicated. We create the plot using the plot() function, with a number of arguments

plot(iris$Sepal.Length, iris$Sepal.Width,
     bg = iris$Species, # Fill colour
     pch = 21, # Shape: circles that can filed
     xlab = "Sepal Length", ylab = "Sepal Width", # Labels
     axes = FALSE, # Don't plot the axes
     frame.plot = FALSE, # Remove the frame
     xlim = c(4, 8), ylim = c(2, 4.5), # Limits
     panel.first = abline(h = seq(2, 4.5, 0.5), col = "grey80"))

then add in the x-axis tick marks

at = pretty(iris$Sepal.Length)
mtext(side = 1, text = at, at = at,
      col = "grey20", line = 1, cex = 0.9)

and the y-axis

at = pretty(iris$Sepal.Width)
mtext(side = 2, text = at, at = at, col = "grey20", line = 1, cex = 0.9)

This just leaves the legend. Instead of using the legend() function, we’ll place the names next to the points via the text() function

text(5, 4.2, "setosa", col = rgb(200, 79, 178, maxColorValue = 255))
text(5.3, 2.1, "versicolor", col = rgb(105, 147, 45, maxColorValue = 255))
text(7, 3.7, "virginica", col = rgb(85, 130, 169, maxColorValue = 255))

Finally, we have the plot title

title("The infamous IRIS data", adj = 1,
      cex.main = 0.8, font.main = 2, col.main = "black")

Putting it all together gives

A much better job.

Why not use ggplot2 (or something else)?

This seems like a lot of work to create a simple scatter plot. Why not use X, Y, or {ggplot2}? We even have a course on {ggplot2} so we’re not biased. The purpose of this article isn’t to get into a religious visualisation war on base R vs … However if you want such a war, have a look at the blog posts by Flowing Data, Jeff Leek and David Robinson.

One point that is worth making is that since we are only using base R functions, our plot will almost certainly be reproducible for all future versions of R! Not something to quickly dismiss.


Jumping Rivers Logo

Recent Posts

  • Start 2026 Ahead of the Curve: Boost Your Career with Jumping Rivers Training 
  • Should I Use Figma Design for Dashboard Prototyping? 
  • Announcing AI in Production 2026: A New Conference for AI and ML Practitioners 
  • Elevate Your Skills and Boost Your Career – Free Jumping Rivers Webinar on 20th November! 
  • Get Involved in the Data Science Community at our Free Meetups 
  • Polars and Pandas - Working with the Data-Frame 
  • Highlights from Shiny in Production (2025) 
  • Elevate Your Data Skills with Jumping Rivers Training 
  • Creating a Python Package with Poetry for Beginners Part2 
  • What's new for Python in 2025? 

Top Tags

  • R (236) 
  • Rbloggers (182) 
  • Pybloggers (89) 
  • Python (89) 
  • Shiny (63) 
  • Events (26) 
  • Training (23) 
  • Machine Learning (22) 
  • Conferences (20) 
  • Tidyverse (17) 
  • Statistics (14) 
  • Packages (13) 

Authors

  • Amieroh Abrahams 
  • Aida Gjoka 
  • Shane Halloran 
  • Russ Hyde 
  • Gigi Kenneth 
  • Osheen MacOscar 
  • Sebastian Mellor 
  • Keith Newman 
  • Tim Brock 
  • Myles Mitchell 
  • Theo Roe 
  • Pedro Silva 
  • Colin Gillespie 

Keep Updated

Like data science? R? Python? Stan? Then you’ll love the Jumping Rivers newsletter. The perks of being part of the Jumping Rivers family are:

  • Be the first to know about our latest courses and conferences.
  • Get discounts on the latest courses.
  • Read news on the latest techniques with the Jumping Rivers blog.

We keep your data secure and will never share your details. By subscribing, you agree to our privacy policy.

Follow Us

  • GitHub
  • Bluesky
  • LinkedIn
  • YouTube
  • Eventbrite

Find Us

The Catalyst Newcastle Helix Newcastle, NE4 5TG
Get directions

Contact Us

  • hello@jumpingrivers.com
  • + 44(0) 191 432 4340

Newsletter

Sign up

Events

  • North East Data Scientists Meetup
  • Leeds Data Science Meetup
  • Shiny in Production
British Assessment Bureau, UKAS Certified logo for ISO 9001 - Quality management British Assessment Bureau, UKAS Certified logo for ISO 27001 - Information security management Cyber Essentials Certified Plus badge
  • Privacy Notice
  • |
  • Booking Terms

©2016 - present. Jumping Rivers Ltd