Menu

The plot() Function in R: Types, Colors, pch and Legends

How R's plot() function works - plot types, titles and axis labels, colors, point symbols (pch), layering with lines() and abline(), and legends.

This page includes runnable editors - edit, run, and see output instantly.

plot() Is R's One-Stop Charting Function

To plot in R, call plot(x, y) with two numeric vectors of the same length. R draws one point for each pair - the first vector along the horizontal axis, the second along the vertical axis - and labels the axes with the variable names automatically:

x <- 1:12
y <- c(2, 5, 4, 8, 9, 12, 11, 15, 14, 18, 17, 21)

plot(x, y)

The result is a scatter of twelve hollow circles climbing steadily as x grows. That's the whole entry fee: no packages to install, no setup. plot() ships with base R and it's the function behind most quick, exploratory graphics.

Because the code runner on this page shows text output only, plotting calls in this article are static snippets - paste them into RStudio or any local R session to see the pictures. What you can run here is the data prep. It's a good habit anyway: look at the numbers before you chart them.

If you pass only one vector, plot(y) uses the index positions 1, 2, 3, ... as the horizontal axis - handy for a quick look at a sequence of measurements.

Choosing the Plot Type

The type argument decides how the pairs are drawn:

plot(x, y, type = "p")   # points (the default)
plot(x, y, type = "l")   # a connected line
plot(x, y, type = "b")   # both: points joined by line segments
plot(x, y, type = "h")   # vertical bars from zero, like a spike chart
plot(x, y, type = "s")   # a step function

type = "l" turns the scatter into a line chart, which is what you want for anything measured over time. type = "b" keeps the individual observations visible while still showing the trend. type = "h" draws a thin vertical segment from zero up to each value, and type = "s" draws a staircase that holds each value until the next one - useful for quantities that change in jumps, like a price or an inventory count.

Titles, Labels, and Colors

A plot without labels is a private note to yourself. Three arguments fix that: main for the title, xlab and ylab for the axis labels. col sets the color:

plot(x, y,
     type = "b",
     main = "Monthly signups",
     xlab = "Month",
     ylab = "Signups (thousands)",
     col  = "steelblue")

col accepts three kinds of values:

  • Named colors - "red", "steelblue", "tomato", "darkgreen". R knows 657 of them; run colors() to list every name.
  • Hex codes - col = "#2C7FB8", exactly as in CSS.
  • A vector of colors - one per point, which is how you color points by group: col = c("tomato", "steelblue")[group] where group is a factor.

Point Symbols with pch, Sizes with cex and lwd

The hollow default circle is fine for exploration but muddy in anything you show others. pch (plotting character) picks the symbol, numbered 0 through 25:

plot(x, y, pch = 19)                     # solid circle - the usual choice
plot(x, y, pch = 17)                     # solid triangle
plot(x, y, pch = 21, col = "black",
     bg = "gold")                        # fillable circle: border + fill

Two numbers are worth memorizing. pch = 19 is the solid circle that most finished plots use. pch = 21 through 25 are the fillable symbols: col colors the border and bg colors the inside, so you get two-tone points.

Size and line style have their own dials:

  • cex scales point size: cex = 1.5 makes points half again as big.
  • lwd sets line width for type = "l" or "b": lwd = 2 is a solid, readable line.
  • lty sets the line pattern: lty = 1 solid, 2 dashed, 3 dotted.

Layering: points(), lines(), and abline()

plot() starts a fresh plot. To draw on top of an existing one, use its companions - they add to the current plot instead of replacing it:

plot(x, y, type = "l", lwd = 2, col = "steelblue",
     main = "Actual vs. target")

points(x, y, pch = 19, col = "steelblue")   # add the observations
lines(x, x * 1.5, lty = 2, col = "gray40")  # add a second series, dashed
abline(h = 10, col = "tomato")              # horizontal reference line at y = 10

abline() is the reference-line tool and it has three modes:

  • abline(h = 10) - a horizontal line at a given vertical-axis value.
  • abline(v = 6) - a vertical line at a given horizontal-axis value.
  • abline(a = 0, b = 1.5) - a line by intercept a and slope b.

Its best trick: pass it a fitted model and it draws the regression line for you - abline(lm(y ~ x)). That one line turns a scatter into an argument; the scatter plot guide builds on it.

One rule to respect: the axis limits are fixed by the first plot() call. If a layered series extends beyond them, it gets clipped - set xlim = and ylim = in the original plot() generously enough for everything you plan to add.

Adding a Legend

Once a plot has more than one series, it needs a legend. legend() takes a position keyword as its first argument - "topright", "topleft", "bottomright", "bottomleft", "top", "bottom", or "center" (these are literal R strings, so they stay in English in your code) - then the labels and the matching styles:

legend("topright",
       legend = c("Actual", "Target"),
       col    = c("steelblue", "gray40"),
       lty    = c(1, 2),
       pch    = c(19, NA),
       lwd    = 2)

The col, lty, and pch vectors must line up with legend entry by entry - the first label gets the first color, and so on. Use NA for a style that doesn't apply to a series (here, the target line has no points). The keyword pins the box to a corner of the plot region; pick whichever corner your data leaves empty.

Multiple Panels and Saving to a File

To show several plots in one figure, set the plotting grid before you draw: par(mfrow = c(1, 2)) splits the device into one row and two columns, and the next two plot() calls fill the panels in order. Reset with par(mfrow = c(1, 1)) when you're done, or subsequent plots stay tiny.

Saving to a file works by redirecting the drawing to a file device instead of the screen:

png("signups.png", width = 800, height = 600)

plot(x, y, type = "b", pch = 19, col = "steelblue",
     main = "Monthly signups", xlab = "Month", ylab = "Signups")

dev.off()

Everything between png() and dev.off() lands in the file; nothing appears on screen. Forgetting dev.off() is the classic bug - the file stays locked and empty until you close the device. pdf(), jpeg(), and svg() work the same way.

What You Take Away

  • plot(x, y) draws immediately; type = switches between points ("p"), lines ("l"), and both ("b").
  • main, xlab, ylab label the plot; col takes color names (657 of them in colors()) or hex codes.
  • pch = 19 is the solid circle; pch = 2125 split border (col) from fill (bg); cex, lwd, lty control size and line style.
  • points(), lines(), and abline() layer onto the existing plot - abline(lm(y ~ x)) adds a regression line in one call.
  • legend("topright", ...) labels the series; png(...) + dev.off() saves to a file.

Next up: the histogram - the one-variable plot you'll reach for first when meeting a new dataset.

Frequently Asked Questions

How do you plot in R?

Call plot(x, y) with two numeric vectors of the same length. R opens a graphics window and draws one point per pair. Add type = "l" for a line chart, main, xlab, and ylab for titles, and col for color.

What does pch mean in R?

pch (plotting character) picks the point symbol, numbered 0 to 25. pch = 19 is the solid circle most people want. Symbols 21 to 25 have a separate fill: col sets the border and bg sets the inside.

How do you add a legend to a plot in R?

Call legend() after the plot, with a position keyword like "topright" as the first argument, a legend = vector of labels, and the matching col, pch, or lty values you used in the plot.

How do you save an R plot to a file?

Open a file device before plotting and close it after: png("myplot.png", width = 800, height = 600), then your plot code, then dev.off(). Nothing appears on screen - the plot goes straight into the file.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED