The Grammar of Graphics
ggplot2 implements the grammar of graphics, building a plot as layers rather than choosing a single chart type up front: you start with ggplot(data, aes(x = ..., y = ...)) to declare the data and the aesthetic mappings (which columns map to x, y, color, size, etc.), then add one or more geom_*() layers such as geom_point() or geom_bar() with +, each of which draws a specific kind of mark using those mappings.
Cricket analogy: Building a ggplot layer by layer is like building a scorecard graphic: first you set up the pitch (ggplot() + aes()), then you add the batting dots (geom_point()), then maybe a run-rate line on top (geom_line()).
Building Plots Layer by Layer
Different geoms interpret the same aesthetic mappings differently: geom_point() draws a scatterplot of individual x/y pairs, geom_line() connects ordered x/y pairs with a line (useful for time series), geom_bar() counts occurrences of a categorical x by default (or uses geom_col() when you already have the y value to plot), and geom_boxplot() summarizes a numeric variable's distribution across categories. Aesthetics set inside aes() are mapped from data and change per row (like color = species), while arguments set outside aes() (like color = 'blue') apply a fixed value to the whole layer.
Cricket analogy: geom_point() plotting runs vs balls faced for every batter, colored by team via aes(color = team), is like a scatter of individual batting performances, whereas color = 'blue' outside aes() would color every dot the same regardless of team.
library(ggplot2)
ggplot(mpg, aes(x = displ, y = hwy, color = class)) +
geom_point(alpha = 0.7, size = 2) +
geom_smooth(method = 'lm', se = FALSE, color = 'black') +
facet_wrap(~ drv) +
labs(
title = 'Engine Displacement vs Highway MPG',
x = 'Displacement (L)',
y = 'Highway MPG',
color = 'Class'
) +
theme_minimal()Facets, Themes, and Labels
facet_wrap() and facet_grid() split a single plot into a grid of small multiples, one panel per level of a categorical variable, so facet_wrap(~ drv) draws a separate scatterplot for each drivetrain while keeping axes comparable, an efficient way to reveal how a relationship differs across groups without cramming everything into one panel with overlapping colors.
Cricket analogy: facet_wrap(~ format) splitting a batting-average scatterplot into separate panels for Test, ODI, and T20 is like a broadcaster showing three separate screens comparing a player's form across formats side by side.
facet_grid(rows ~ cols) differs from facet_wrap() by arranging panels in a strict matrix defined by two variables (e.g. facet_grid(drv ~ cyl)), which is useful when you want every combination of two categorical variables shown, even if some panels end up empty.
Customizing Appearance with Themes and Labels
labs() sets human-readable titles, axis labels, and legend titles in one call (labs(title = ..., x = ..., y = ..., color = ...)), while theme_*() functions (theme_minimal(), theme_bw(), theme_classic()) swap out the overall visual style—background, gridlines, fonts—in one line; fine-grained tweaks like rotating axis text or removing the legend go through theme(axis.text.x = element_text(angle = 45), legend.position = 'none').
Cricket analogy: labs(title = 'Run Rate by Over') is like a broadcaster captioning a graphic clearly, while theme_minimal() is choosing a clean broadcast graphics package over a cluttered one full of sponsor logos and gridlines.
Statistical Transformations and Common Pitfalls
Some geoms compute a statistical transformation of the data before drawing, rather than plotting raw values directly: geom_smooth() fits a trend line (linear with method = 'lm', or a loess curve by default) with a shaded confidence ribbon, geom_histogram() bins a continuous variable and counts observations per bin, and geom_density() estimates a smoothed distribution curve, each a convenient shortcut for a stat_*() layer working behind the scenes.
Cricket analogy: geom_smooth(method = 'lm') fitting a trend line through a batter's strike rate over a career is like a pundit drawing a form-line on a graphic to show whether the player is improving or declining.
Aesthetics set inside aes() (e.g. aes(color = class)) map data values to visual properties and vary per row, while the same argument set outside aes() (e.g. geom_point(color = 'blue')) applies one fixed value to the whole layer; mixing these up is one of the most common ggplot2 beginner mistakes, producing a single-color plot with an unwanted colour legend or vice versa.
- ggplot2 builds a plot in layers: ggplot(data, aes(...)) sets up mappings, and geom_*() layers added with + draw the marks.
- Aesthetics inside aes() map data columns to visual properties and vary per row; the same argument outside aes() sets one fixed value.
- Different geoms suit different data shapes: geom_point() for scatter, geom_line() for ordered sequences, geom_bar()/geom_col() for counts or totals, geom_boxplot() for distributions.
- facet_wrap() and facet_grid() split one plot into small multiples by a categorical variable, keeping axes comparable.
- labs() sets titles and axis/legend labels; theme_*() functions and theme() control overall and fine-grained visual style.
- Some geoms like geom_smooth(), geom_histogram(), and geom_density() apply a statistical transformation before drawing.
- Confusing aes()-mapped and fixed arguments is a very common source of unexpected colors, sizes, or legends.
Practice what you learned
1. In ggplot(df, aes(x = displ, y = hwy, color = class)) + geom_point(), what does color = class do?
2. Which geom would you use to visualize a time series where x is date and y is a continuous measurement, connecting points in order?
3. What does facet_wrap(~ drv) do to a ggplot2 plot?
4. If you write geom_point(color = 'blue') with color outside aes(), what happens?
5. Which function would you use to fit and display a trend line with a confidence ribbon over a scatterplot?
Was this page helpful?
You May Also Like
dplyr Basics
Learn the core dplyr verbs—filter, select, mutate, arrange, summarise, and group_by—for fast, readable data manipulation in R.
tidyr and Reshaping Data
Learn how to reshape messy or wide data into tidy form using pivot_longer(), pivot_wider(), separate_wider_delim(), and complete().
String Manipulation in R
Learn to detect, extract, split, join, and clean text in R using base functions and the consistent stringr package.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics