How To Make A Scatter Plot In R

8 min read

Creating a scatter plot in R is one of the most fundamental skills for anyone working with data visualization. On top of that, whether you are exploring the relationship between two continuous variables, checking for correlations, or identifying outliers, the scatter plot remains the go-to chart for initial data investigation. R offers multiple pathways to build these visualizations, ranging from the built-in graphics package to the powerful ggplot2 system and even interactive libraries like plotly. Mastering these tools allows you to move quickly from raw data to actionable insight The details matter here..

Understanding the Basics of Scatter Plots

Before diving into the code, it helps to understand what a scatter plot actually represents. Each point represents an observation. On top of that, this chart type uses Cartesian coordinates to display values for typically two variables for a set of data. The position on the horizontal (x) axis indicates the value of one variable, while the position on the vertical (y) axis indicates the value of the other.

If the points trend upward from left to right, you have a positive correlation. If they trend downward, the correlation is negative. Think about it: a random cloud of points suggests no linear relationship. Beyond simple correlation, scatter plots reveal clusters, gaps, and outliers that summary statistics like mean or standard deviation might hide Easy to understand, harder to ignore..

Method 1: Using Base R Graphics

The fastest way to generate a scatter plot requires no external packages. In practice, the plot() function is part of R’s base installation, making it immediately available in any environment. This is ideal for quick, exploratory checks where publication-quality aesthetics are not the priority It's one of those things that adds up..

Simple Syntax for Quick Exploration

Assume you have a data frame named df with two numeric columns, height and weight. The basic syntax is remarkably intuitive:

# Basic scatter plot
plot(x = df$height, y = df$weight,
     main = "Relationship Between Height and Weight",
     xlab = "Height (cm)",
     ylab = "Weight (kg)",
     pch = 19,        # Solid circle plotting character
     col = "steelblue") # Color of the points

The pch argument controls the plotting symbol (shape), and col defines the color. Base R offers over 25 distinct symbols, allowing for quick visual differentiation if you are plotting multiple groups on the same canvas using the points() function subsequently But it adds up..

Adding a Regression Line

A common requirement is visualizing the trend. In base R, you calculate the linear model separately and add it using abline():

# Fit linear model
model <- lm(weight ~ height, data = df)

# Plot with regression line
plot(df$height, df$weight, pch = 19, col = "steelblue")
abline(model, col = "red", lwd = 2) # lwd controls line width

While functional, base graphics can become cumbersome when you need complex faceting, detailed legends, or sophisticated theming. This is where the Grammar of Graphics implementation in ggplot2 shines It's one of those things that adds up..

Method 2: Creating Publication-Quality Plots with ggplot2

The ggplot2 package, part of the tidyverse ecosystem, is the industry standard for data visualization in R. Even so, it operates on a layered grammar: you start with data, map aesthetics, add geometric objects (geoms), and refine scales, themes, and labels. This modular approach makes complex plots manageable and code readable.

The Core Syntax: ggplot() + geom_point()

First, ensure the package is installed and loaded:

install.packages("ggplot2")
library(ggplot2)

The foundational structure looks like this:

ggplot(data = df, mapping = aes(x = height, y = weight)) +
  geom_point()

Breaking down the components:

  • ggplot(): Initializes the plot object. The data argument specifies the data frame. The mapping argument (wrapped in aes()) defines aesthetic mappings—how variables map to visual properties like x-position, y-position, color, size, or shape.
  • geom_point(): Adds the layer that draws the points. Without a geom, the canvas is empty.

Enhancing Aesthetics: Color, Size, and Shape

The real power of ggplot2 emerges when you map variables to aesthetics rather than setting them to constant values. To give you an idea, if df has a categorical column gender, you can color points by group:

ggplot(df, aes(x = height, y = weight, color = gender)) +
  geom_point(size = 3, alpha = 0.7) +
  labs(title = "Height vs. Weight by Gender",
       x = "Height (cm)",
       y = "Weight (kg)",
       color = "Gender") +
  theme_minimal()
  • color = gender inside aes(): Tells R to assign a unique color to each level of gender and automatically generates a legend.
  • alpha: Controls transparency (0 to 1). This is crucial for large datasets where overplotting (points stacking on top of each other) obscures data density.
  • theme_minimal(): Applies a clean, modern theme. Other built-in options include theme_bw(), theme_classic(), and theme_light().

Adding Trend Lines with geom_smooth()

Adding a line of best fit is a single line of code. By default, geom_smooth() uses LOESS (locally estimated scatterplot smoothing) for fewer than 1,000 observations and a Generalized Additive Model (GAM) for larger datasets. For a linear regression line, specify method = "lm":

ggplot(df, aes(x = height, y = weight, color = gender)) +
  geom_point(alpha = 0.6) +
  geom_smooth(method = "lm", se = TRUE, linewidth = 1) +
  theme_minimal()
  • se = TRUE: Adds a confidence interval ribbon around the line (default 95%).
  • linewidth: Controls the thickness of the line (replaces the older size argument for lines in newer ggplot2 versions).

Faceting: Small Multiples for Comparison

If you want separate panels for each category instead of overlapping colors, use faceting. This creates "small multiples," a technique championed by Edward Tufte for comparing patterns across groups Small thing, real impact..

ggplot(df, aes(x = height, y = weight)) +
  geom_point(alpha = 0.6, color = "steelblue") +
  geom_smooth(method = "lm", color = "red") +
  facet_wrap(~ gender) + # Creates a panel for each gender
  theme_bw()

facet_wrap() arranges panels in a ribbon; facet_grid(rows ~ cols) creates a matrix layout if you have two categorical variables.

Method 3: Handling Overplotting in Large Datasets

With large datasets (tens of thousands of points), standard scatter plots turn into solid blocks of ink. Several strategies exist within R to handle this Most people skip this — try not to..

1. Transparency and Jitter

As covered, alpha helps. For discrete data (e.g., integer scores), jittering adds a small amount of random noise to separate overlapping points:

ggplot(df, aes(x = factor(education_level), y = income)) +
  geom_jitter(width = 0.2, alpha = 0.3, color = "darkgreen")

2. 2D Density and Hexbin Plots

For continuous variables, geom_hex() or geom_density_2d() visualizes density rather than individual points. This requires the hexbin package for hexagons.

#

```r
# Hexbin plot (requires hexbin package)
ggplot(large_df, aes(x = height, y = weight)) +
  geom_hex(bins = 50) +
  scale_fill_viridis_c(option = "plasma") +
  theme_minimal()

# 2D Density contours
ggplot(large_df, aes(x = height, y = weight)) +
  geom_density_2d_filled(alpha = 0.5) +
  geom_density_2d(color = "white", linewidth = 0.3) +
  theme_minimal()
  • geom_hex(): Divides the plane into hexagonal bins; color intensity represents the count of observations per bin. bins controls resolution.
  • geom_density_2d_filled(): Creates filled contour bands representing density estimates, offering a smoother, "topographic map" aesthetic.
  • scale_fill_viridis_c(): Applies a perceptually uniform, colorblind-friendly palette (from the viridis package) ideal for continuous density scales.

Method 4: Interactive Exploration with plotly

Static plots are perfect for reports, but exploration demands interactivity. The plotly package renders ggplot objects into interactive HTML widgets (zoom, pan, hover tooltips) with a single function call Worth knowing..

library(plotly)

p <- ggplot(df, aes(x = height, y = weight, color = gender,
                    text = paste("ID:", id, "
Height:", height, "
Weight:", weight))) + geom_point(alpha = 0.6) + geom_smooth(method = "lm", se = FALSE) + theme_minimal() ggplotly(p, tooltip = "text")
  • tooltip = "text": Maps the custom text aesthetic defined in aes() to the hover tooltip, allowing you to display specific metadata (IDs, categories, precise values) not shown on axes.
  • Output: An interactive HTML widget embeddable in R Markdown, Quarto, or Shiny apps.

Polishing for Publication: Themes and Labels

A plot is not finished until it communicates clearly without a legend key. The labs() function and theme() system handle the final 20% of work that takes 80% of the polish And that's really what it comes down to. And it works..

final_plot <- ggplot(df, aes(x = height, y = weight, color = gender)) +
  geom_point(alpha = 0.6, size = 2) +
  geom_smooth(method = "lm", se = TRUE, linewidth = 1) +
  scale_color_manual(values = c("Female" = "#D55E00", "Male" = "#0072B2"),
                     labels = c("Female", "Male")) +
  labs(
    title = "Relationship Between Height and Weight by Gender",
    subtitle = "Linear regression fit with 95% confidence intervals",
    caption = "Source: NHANES 2017-2018 | Analysis: R ggplot2",
    x = "Height (cm)",
    y = "Weight (kg)",
    color = "Gender"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    legend.position = "bottom",
    plot.title = element_text(face = "bold", size = 16),
    plot.subtitle = element_text(color = "grey30"),
    plot.caption = element_text(color = "grey50", hjust = 0),
    panel.grid.minor = element_blank()
  )

# Save high-resolution output
ggsave("height_weight_scatter.png", plot = final_plot, width = 8, height = 6, dpi = 300, bg = "white")
  • scale_color_manual(): Overrides default hues for brand compliance or colorblind safety (Okabe-Ito palette shown).
  • labs(): Centralizes all text annotations; caption is critical for reproducibility.
  • theme(): Granular control over non-data ink. panel.grid.minor = element_blank() reduces chartjunk.
  • ggsave(): The reliable way to export; specify dpi = 300 (print) or dpi = 72 (web), and explicit width/height in inches to control aspect ratio.

Conclusion

R’s visualization ecosystem offers a graduated path from rapid exploration to polished communication. Base R remains unmatched for zero-dependency, instantaneous sanity checks. ggplot2 transforms plotting into a declarative, reproducible grammar—separating data, aesthetics, and geometry—making complex multi-layered graphics (facets, density estimates, custom smooths) maintainable rather than fragile scripts. For high-volume data, hexbinning and 2D density prevent the "ink blob" anti-pattern, while plotly bridges the gap between static publication and dynamic dashboarding.

Mastering these tools

Just Made It Online

Latest Additions

On a Similar Note

Good Reads Nearby

Thank you for reading about How To Make A Scatter Plot In R. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
⌂ Back to Home