Graph Points Customization with 'pch' in R

R Published Updated 4 mins read By Leon Wei
Graph Points Customization with 'pch' in R cover image

Quick summary

Summarize this blog with AI

Introduction

In base R graphics, pch selects a plotting symbol. Codes 0–25 are standard symbols: 0 is an open square, 1 an open circle, and 24 a triangle pointing up with a separately controllable fill. The chart below is generated by the exact code in this lesson, so it can serve as a working reference.

Tested with R 4.5.1. Run the code blocks in order. Install the named packages before starting; inputs are supplied below.

Use labels beside each symbol rather than relying on a remembered mapping. The last five symbols use bg for their fill.

draw_symbols <- function() {
  code <- 0:25
  x <- code %% 7; y <- 3 - code %/% 7
  plot(x, y, type = "n", axes = FALSE, xlab = "", ylab = "",
       xlim = c(-0.5, 6.5), ylim = c(-0.5, 3.5), main = "Base R plotting symbols: pch 0-25")
  points(x, y, pch = code, cex = 2.2, col = "#243746", bg = "#f4c95d")
  text(x, y - 0.28, labels = code, cex = 0.95)
}
draw_symbols()
stopifnot(length(0:25) == 26L)
All 26 standard base R point symbolsA labeled grid shows pch codes 0 through 25, including open square 0, open circle 1, solid circle 19 and border-plus-fill symbols 21 through 25. Base R plotting symbols: pch 0-25 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
The complete standard pch reference, including code 0.

The points reference distinguishes open symbols, solid symbols and symbols 21–25 with a border and fill. Device rendering and symbol size can affect apparent weight.

Map symbols to categories explicitly

Use a named mapping so row order does not accidentally choose the symbol. Include all data in the plotting range before adding overlays.

d <- data.frame(x = 1:6, y = c(2, 3, 4, 2.5, 3.5, 5),
                group = c("A", "B", "A", "B", "A", "B"))
shape <- c(A = 21, B = 24)
fill <- c(A = "#66c2a4", B = "#f4c95d")
stopifnot(all(d$group %in% names(shape)))
draw_groups <- function() {
  plot(d$x, d$y, pch = unname(shape[d$group]), bg = unname(fill[d$group]),
       col = "#243746", cex = 1.7, xlab = "Observation", ylab = "Measured value",
       main = "Use both shape and fill to identify groups")
  legend("topleft", legend = names(shape), pch = shape, pt.bg = fill,
         col = "#243746", pt.cex = 1.7, bty = "n")
}
draw_groups()
stopifnot(identical(unname(shape[d$group]), c(21, 24, 21, 24, 21, 24)))
Group mapping with circles and trianglesSix observations alternate between filled circles for group A and upward triangles for group B. The legend repeats both shape and fill. 1 2 3 4 5 6 2.0 2.5 3.0 3.5 4.0 4.5 5.0 Use both shape and fill to identify groups Observation Measured value A B
Named mappings preserve group identity regardless of row order.

For symbols 21–25, col controls the border and bg controls the fill. For solid symbols such as 19, col controls the symbol; bg is not a separate interior color. Matching legend symbols make the mapping understandable without color alone.

Use characters deliberately and preserve missing data

A one-character string such as "+" is a plotting character, not an arbitrary vector icon. Unicode support depends on the graphics device and font; an unterminated string is a syntax error.

stopifnot(identical(nchar("+"), 1L))
# Explicit empty frame and character overlay:
plot(1:3, c(1, 2, 3), type = "n", xlab = "x", ylab = "y")
points(1:3, c(1, 2, 3), pch = "+", col = "#1b6a83", cex = 1.5)
with_missing <- data.frame(x = c(1, 2, NA), y = c(2, NA, 4))
visible <- complete.cases(with_missing)
stopifnot(identical(visible, c(TRUE, FALSE, FALSE)))

A point with a missing coordinate is not drawn. A missing pch can hide a point deliberately but does not remove it from the data. If you want an empty plotting region, use type="n" and explicit finite limits instead of asking R to infer limits from empty data.

Check range, vector lengths and readability

The empty-data case needs explicit axes; graphical vectors otherwise recycle, which can hide a mismatched group mapping.

plot(numeric(0), numeric(0), xlim = c(0, 1), ylim = c(0, 1),
     xlab = "x", ylab = "y", main = "No observations")
stopifnot(length(d$x) == length(d$y), length(shape[d$group]) == nrow(d))
unknown <- c("A", "C")
stopifnot(is.na(shape[unknown][2]))

Reject unknown categories before drawing rather than silently hiding them. Inspect the actual exported image at the intended size: crowded symbols and overlapping points can obscure data. Changes to pch improve visual encoding; they do not change the underlying observations or analysis.

Primary references

Interview Prep

Begin Your SQL, Python, and R Journey

Master 230 interview-style coding questions and build the data skills needed for analyst, scientist, and engineering roles.

Related Articles

All Articles
How to Remove Outliers in R cover image
r Oct 17, 2026

How to Remove Outliers in R

Learn how to identify and remove outliers in R with this step-by-step guide, featuring detailed code samples for beginners.

How to Use 'countif' in R cover image
r Oct 17, 2026

How to Use 'countif' in R

Unlock the power of 'countif' in R with our comprehensive guide. Perfect for beginners looking to enhance their R programming skills.

How to Use 'abline' in R cover image
r Apr 30, 2024

How to Use 'abline' in R

Unlock the power of 'abline' function in R for data visualization; this guide covers everything from basics to advanced applications with exampl…