What a Scatter Plot Shows
A scatter plot displays the relationship between two numeric variables: each observation becomes one point, positioned by its first value along the horizontal axis and its second value along the vertical axis. If the two variables move together, the points form a pattern; if they don't, you get a shapeless cloud. It's the standard first look before fitting any model - and R draws one with the same plot() function covered in the plot() guide.
We'll use mtcars, a built-in dataset of 32 cars, and ask a physical question: do heavier cars have more powerful engines?
Making the Plot
wt is weight in thousands of pounds, hp is horsepower:
plot(mtcars$wt, mtcars$hp,
main = "Horsepower vs. weight",
xlab = "Weight (1000 lbs)",
ylab = "Horsepower",
pch = 19,
col = "steelblue")
The picture: thirty-two solid dots rising from the low corner of the plot (light cars, around 1.5 on the weight scale, near 60–90 horsepower) toward heavy cars above 5 that push past 200 horsepower. The climb is unmistakable but not tidy - at any given weight the points spread over a fair band of horsepower.
The styling is the usual base-plot toolkit: pch = 19 for solid circles (the hollow default disappears in screenshots), col for color, cex = 1.3 if the points need to be bigger. To color points by a third, categorical variable, index a color vector with a factor - col = c("tomato", "steelblue", "darkgreen")[factor(mtcars$cyl)] gives each cylinder count its own color.
Reading It: Direction, Strength, Shape
Three questions, in order, every time you look at a scatter plot:
- Direction. Do the points rise (positive relationship) or fall (negative) as you scan along the horizontal axis? Here they rise: heavier means more powerful. Plot
mpgagainstwtinstead and the cloud falls - heavier means thirstier. - Strength. How tightly do the points hug a single path? A pencil-thin band is a strong relationship; a loose spray is a weak one. This cloud is moderately tight.
- Shape and surprises. Is the path straight or curved? Are there clusters, or points far from everything else? In
mtcars, the Maserati Bora sits conspicuously above the pack - 335 horsepower at a middling weight. One such point can drag a fitted line noticeably, which is exactly why you look before you fit.
Adding the Trend Line
A scatter plot states a relationship; a line through it summarizes the claim. Fit a linear model and hand it straight to abline():
plot(mtcars$wt, mtcars$hp,
pch = 19, col = "steelblue",
xlab = "Weight (1000 lbs)", ylab = "Horsepower")
abline(lm(hp ~ wt, data = mtcars), col = "tomato", lwd = 2)
lm(hp ~ wt) fits the least-squares line - read the formula as "hp explained by wt", with the vertical-axis variable before the ~ - and abline() draws it across the plot. The line climbs at about 46 horsepower per thousand pounds. What that model means, and how to read its summary, is the subject of linear regression.
If you don't want to assume a straight line, lowess() draws a smooth curve that follows the data wherever it goes:
lines(lowess(mtcars$wt, mtcars$hp), col = "darkgreen", lwd = 2, lty = 2)
When the lowess curve and the straight line roughly agree, a linear summary is fair. When the curve bends away, the relationship is nonlinear and a straight line would misrepresent it.
Checking the Numbers with cor()
The plot gives you the shape; cor() gives you the strength as one number. This step is pure text output, so run it here:
Weight and horsepower correlate at about 0.66 - the moderately tight rising cloud, as a number. The matrix adds that mpg correlates strongly negatively with both (about −0.87 with weight). Keep the order of operations, though: the plot first, the coefficient second. A single r-value can hide a curve or be inflated by one outlier - see correlation for the classic ways it misleads.
The Scatter Plot Matrix: pairs()
With several numeric columns, drawing every pairing by hand gets old. pairs() does it in one call:
pairs(mtcars[, c("mpg", "wt", "hp")],
pch = 19, col = "steelblue")
The result is a 3 × 3 grid: variable names run along the diagonal, and each off-diagonal panel is the scatter plot of one pair - mpg against wt, mpg against hp, wt against hp, each appearing twice with the axes swapped. It's the fastest way to triage a new dataset: one glance shows which pairs are related, which relationships curve, and where the outliers hide. Subset the columns first, as here - beyond six or seven variables the panels shrink past legibility.
The ggplot2 Version
In ggplot2, the scatter plot plus a fitted line is two layers:
library(ggplot2)
ggplot(mtcars, aes(x = wt, y = hp)) +
geom_point(color = "steelblue", size = 2) +
geom_smooth(method = "lm", color = "tomato") +
labs(title = "Horsepower vs. weight",
x = "Weight (1000 lbs)", y = "Horsepower")
geom_smooth(method = "lm") is abline(lm(...)) with a bonus: a shaded confidence band around the line. Leave method unset and it fits a loess curve instead - the ggplot2 counterpart of lowess(). Base R wins on typing speed for a quick look; ggplot2 wins the moment you want the points colored by group with an automatic legend.
What You Take Away
plot(x, y)with two numeric vectors is a scatter plot;pch = 19and labeled axes make it presentable.- Read direction, strength, and shape - and spot outliers - before computing anything.
abline(lm(y ~ x, data = df))adds the regression line;lines(lowess(x, y))adds a curve that doesn't assume straightness.cor()quantifies what the plot shows; the plot keeps the number honest.pairs(df[, cols])draws every pairwise scatter plot at once - the fastest triage of a new dataset.
Next up: the bar chart - leaving numeric pairs behind to compare counts across categories.
Frequently Asked Questions
How do you make a scatter plot in R?
Call plot(x, y) with two numeric vectors - for example plot(mtcars$wt, mtcars$hp). Each observation becomes one point. Add pch = 19 for solid dots and main, xlab, ylab for labels.
How do you add a regression line to a scatter plot in R?
Fit the model and hand it to abline(): abline(lm(hp ~ wt, data = mtcars)) draws the least-squares line over the existing plot. Note the formula order - the vertical-axis variable comes before the ~.
How do you plot many variable pairs at once in R?
pairs(df) draws a scatter plot matrix: one small panel for every pair of columns. Subset first - pairs(mtcars[, c("mpg", "wt", "hp")]) - because beyond six or seven columns the panels get too small to read.
What does a scatter plot tell you that correlation doesn't?
The shape. A correlation coefficient is one number and can be identical for a clean line, a curve, or a cloud with one extreme outlier. The scatter plot shows curvature, clusters, and outliers directly - which is why you plot first and compute cor() second.