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.
Render all standard symbols from 0 through 25
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)
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)))
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.