SECTION 1
Historical Context & Motivation
The art and science of statistical graphics stretches back centuries, but the computational revolution of the late twentieth century fundamentally transformed how practitioners create visualizations. Before programmable tools existed, researchers drew plots by hand or relied on expensive mainframe batch-processing systems—a workflow that made iterative exploration prohibitively slow. The emergence of R as a free, open-source statistical computing environment democratized access to publication-quality plotting, enabling researchers and engineers alike to move from raw data to insightful graphics in seconds. Understanding the lineage of these plotting paradigms helps contextualize why R's current visualization ecosystem looks the way it does and why certain plot types have become canonical representations of data.
1786 • Playfair's Statistical Charts
1976 • S Language at Bell Labs
plot() and hist() that directly influenced R's base graphics system.1993 • Birth of R
2005 • ggplot2 and the Grammar of Graphics
2020s • Modern R Visualization Ecosystem
The central question this lesson addresses is straightforward yet fundamental: given a dataset, how do you choose and implement the right plot type in R to effectively reveal patterns, distributions, and relationships? Whether you are profiling algorithm runtime complexity, exploring a machine-learning feature space, or summarizing simulation results, the five plot types covered here—scatter, line, bar, histogram, and boxplot—form the foundational visual vocabulary you will use daily.
SECTION 2
Core Principles of Statistical Plotting in R
Before diving into individual plot types, it is essential to internalize the foundational principles that govern effective data visualization in R. These principles apply regardless of whether you use base R graphics or ggplot2. They determine not only which function to call but how to structure your data, map variables to visual channels, and select the appropriate geometric representation for your analytical question. A solid grasp of these ideas will let you reason about new plot types you encounter in the future, not just the five covered here.
Data ↔ Aesthetic Mapping
aes(); in base R it is implicit in function arguments.Geometry Selection
Tidy Data Prerequisite
Layered Composition
+ operator chains layers; in base R, functions like points() and lines() add to an existing canvas.Scale & Coordinate Control
KEY TAKEAWAY
SECTION 3
Visual Overview — Five Plot Types at a Glance
The following diagram provides a visual reference for all five canonical plot types covered in this lesson. Each plot is shown with a schematic representation of its geometric elements and annotated with the type of data relationship it is best suited to reveal. Refer to this diagram throughout the lesson as a quick orientation tool.
Notice that the first two plot types (scatter and line) both place continuous variables on both axes, but they differ in a critical assumption: scatter plots make no assumption about ordering, whereas line plots assert that the x-axis represents a meaningful sequence (often time). The bar chart and histogram look superficially similar—both use rectangles—but the histogram's bars are contiguous because they represent adjacent intervals of a continuous variable, while bar chart bars are separated because their categories are discrete. The boxplot occupies a unique role as a statistical summary visualization, encoding five-number summary statistics (minimum, Q1, median, Q3, maximum) plus outliers into a compact glyph.
SECTION 4
How R Produces Plots — Base Graphics vs. ggplot2
R provides two major plotting subsystems, and understanding their architectural differences helps you choose wisely for each task. Base R graphics follows an imperative, pen-on-paper model: you open a graphics device, draw one element at a time, and the final image is the accumulated result of sequential function calls. ggplot2 follows a declarative, grammar of graphics model: you specify what you want (data, mappings, geometries, scales), and the library figures out how to render it. In a CS analogy, base R is like writing assembly instructions for a GPU, while ggplot2 is like writing a scene description for a ray tracer.
Base R Syntax Patterns
| Plot Type | Base R Function | Key Arguments |
|---|---|---|
| Scatter | plot(x, y) | pch, col, cex, xlab, ylab, main |
| Line | plot(x, y, type="l") | lty, lwd, col |
| Bar | barplot(height) | names.arg, col, beside, horiz |
| Histogram | hist(x) | breaks, freq, col, border |
| Boxplot | boxplot(x ~ group) | notch, outline, col, horizontal |
Base R functions and their most important parameters for each of the five common plot types.
ggplot2 Syntax Patterns
| Plot Type | ggplot2 Geom Layer | Typical Aesthetics |
|---|---|---|
| Scatter | geom_point() | aes(x, y, color, size, shape) |
| Line | geom_line() | aes(x, y, color, linetype, group) |
| Bar | geom_bar() / geom_col() | aes(x, y, fill) |
| Histogram | geom_histogram() | aes(x), bins, binwidth, fill |
| Boxplot | geom_boxplot() | aes(x, y, fill), outlier.shape |
ggplot2 geometry layers and typical aesthetic mappings for each plot type.
geom_bar() vs. geom_col()
geom_bar() computes counts from raw data (uses stat = "count" internally), while geom_col() expects pre-summarized data with explicit y-values (uses stat = "identity"). If you already have a summary table, use geom_col().The general ggplot2 template for any of these five plot types follows the same structure: ggplot(data, aes(...)) + geom_*(). Additional layers for themes (theme_minimal()), labels (labs()), and scale overrides (scale_x_continuous()) can be appended freely. This composability is ggplot2's principal advantage over base graphics for complex, multi-layered visualizations.
SECTION 5
Detailed Breakdown — Anatomy and Options for Each Plot Type
Each of the five plot types has unique parameters and conventions that control its appearance and statistical behavior. This section provides a deeper dive into the anatomy of each plot and the key decisions you must make when constructing them. The diagram below illustrates the internal structure of a boxplot—the most statistically rich of the five types—as a reference for the summary statistics it encodes.
Key Decisions by Plot Type
- Scatter: Choose point character (
pchorshape) and transparency (alpha) to manage overplotting. Consider adding a trend line withgeom_smooth()orabline(). - Line: Ensure data is sorted by the x-variable before plotting. Use
groupaesthetic to handle multiple series; without it, ggplot2 may connect points incorrectly. - Bar: Decide between
geom_bar()(counts raw data) andgeom_col()(uses pre-computed values). For grouped bars, setposition = "dodge"; for stacked bars, use the defaultposition = "stack". - Histogram: The
binsorbinwidthparameter profoundly affects interpretation. Too few bins over-smooth the distribution; too many create noise. Sturges' rule (breaks = "Sturges") and Freedman-Diaconis (breaks = "FD") are common heuristics. - Boxplot: Use the formula interface
y ~ groupin both base R and ggplot2. Setnotch = TRUEfor a visual test of median differences between groups.
STURGES' RULE FOR HISTOGRAM BINS
Where k is the number of bins, n is the number of observations, and ⌈·⌉ denotes the ceiling function. This is R's default when you call hist(x) without specifying breaks.
FREEDMAN-DIACONIS BIN WIDTH
Where h is the bin width, IQR(x) is the interquartile range of the data, and n is the sample size. This rule is more robust to skewed distributions than Sturges' rule.
SECTION 6
Worked Example — Visualizing Algorithm Benchmarks
Suppose you have benchmarked three sorting algorithms (bubble sort, merge sort, and quicksort) across various input sizes and stored the results in a data frame called benchmarks with columns algorithm, n (input size), and time_ms (runtime in milliseconds). We will create all five plot types using ggplot2 to extract different insights from this data.
Creating Five Plots from Benchmark Data
- 1
Step 1 — Set Up Data and Load Library
First, load ggplot2 and create the sample data frame. In practice, you would read this from a CSV or generate it programmatically. We uselibrary(ggplot2)and then construct the data frame withset.seed(42)for reproducibility.library(ggplot2) set.seed(42) n_vals <- rep(c(100, 500, 1000, 5000, 10000), each = 30) algo <- rep(rep(c("Bubble", "Merge", "Quick"), each = 10), 5) time_ms <- ifelse(algo == "Bubble", 0.001 * n_vals^2 + rnorm(150, 0, 50), ifelse(algo == "Merge", 0.05 * n_vals * log2(n_vals) + rnorm(150, 0, 20), 0.04 * n_vals * log2(n_vals) + rnorm(150, 0, 15))) benchmarks <- data.frame(algorithm = algo, n = n_vals, time_ms = abs(time_ms)) - 2
Step 2 — Scatter Plot: Runtime vs. Input Size
A scatter plot reveals the relationship between input size and runtime, colored by algorithm. Each point represents a single trial. Thealphaparameter adds transparency to reveal overlapping points.ggplot(benchmarks, aes(x = n, y = time_ms, color = algorithm)) + geom_point(alpha = 0.6, size = 2) + labs(title = "Sorting Algorithm Runtime", x = "Input Size (n)", y = "Time (ms)") + theme_minimal() - 3
Step 3 — Line Plot: Mean Runtime Trends
Aggregate the data to compute mean runtime per algorithm and input size, then connect the means with lines. This shows the growth rate trend more clearly than raw scatter points. We usestat_summary()as a convenient shortcut.ggplot(benchmarks, aes(x = n, y = time_ms, color = algorithm)) + stat_summary(fun = mean, geom = "line", linewidth = 1.2) + stat_summary(fun = mean, geom = "point", size = 3) + labs(title = "Mean Runtime by Input Size", x = "Input Size (n)", y = "Mean Time (ms)") + theme_minimal() - 4
Step 4 — Bar Chart: Total Time by Algorithm
Usegeom_col()with pre-summarized totals to compare aggregate runtime. We first compute the totals withaggregate()ordplyr::summarise().totals <- aggregate(time_ms ~ algorithm, data = benchmarks, FUN = sum) ggplot(totals, aes(x = algorithm, y = time_ms, fill = algorithm)) + geom_col(width = 0.6) + labs(title = "Total Runtime per Algorithm", x = "Algorithm", y = "Total Time (ms)") + theme_minimal() + theme(legend.position = "none") - 5
Step 5 — Histogram: Distribution of Quicksort Times
Filter to quicksort only and examine the distribution of runtimes across all input sizes. We setbins = 20to balance detail and smoothness.qs <- benchmarks[benchmarks$algorithm == "Quick", ] ggplot(qs, aes(x = time_ms)) + geom_histogram(bins = 20, fill = "#34d399", color = "white") + labs(title = "Distribution of Quicksort Runtimes", x = "Time (ms)", y = "Count") + theme_minimal() - 6
Step 6 — Boxplot: Comparing Distributions by Algorithm
A boxplot compares the distributional characteristics (median, spread, outliers) of runtime across algorithms for a fixed input size. We filter ton == 5000and usegeom_boxplot().sub <- benchmarks[benchmarks$n == 5000, ] ggplot(sub, aes(x = algorithm, y = time_ms, fill = algorithm)) + geom_boxplot(outlier.shape = 21, outlier.size = 2) + labs(title = "Runtime Distribution at n = 5000", x = "Algorithm", y = "Time (ms)") + theme_minimal() + theme(legend.position = "none")
SECTION 7
Strengths & Limitations of Each Plot Type
No single plot type is universally optimal. Each has strengths that make it the ideal choice for certain data configurations and limitations that can mislead if the plot is used inappropriately. The table below summarizes these trade-offs to guide your selection process.
| Plot Type | Strengths | Limitations |
|---|---|---|
| Scatter | Reveals correlations, clusters, and nonlinear relationships. Works well with color and size mappings for multivariate exploration. | Overplotting with large n; poor for categorical data; no built-in statistical summary. |
| Line | Clearly shows trends and temporal patterns; easy to compare multiple series with color/linetype. | Implies ordering/continuity; misleading for unordered data; sensitive to missing values creating gaps. |
| Bar | Intuitive for comparing magnitudes across categories; easily extended with stacking and faceting. | Can be misleading with truncated y-axes; not suitable for continuous distributions; cluttered with many categories. |
| Histogram | Shows distributional shape (skewness, modality); essential for assessing normality assumptions. | Bin width choice significantly affects interpretation; difficult to compare multiple distributions directly. |
| Boxplot | Compact multi-group comparison; highlights median, spread, and outliers; scales well to many groups. | Hides distributional shape (bimodality invisible); less intuitive for non-statistical audiences. |
Comparative strengths and limitations of the five common plot types.
CHOOSING THE RIGHT PLOT
SECTION 8
Connection to Advanced Visualization Techniques
The five basic plot types covered in this lesson are the building blocks from which more sophisticated visualizations are constructed. Each has advanced descendants that address specific limitations or extend functionality. Understanding this progression will help you know when to reach for a more powerful tool and how it relates to the foundational plot you already understand.
| Basic Plot | Advanced Extension | What It Adds |
|---|---|---|
| Scatter | Hexbin plot / 2D density plot | Handles millions of points by aggregating into hexagonal bins with geom_hex() or geom_density_2d(). |
| Line | Ribbon / area chart | Shows confidence intervals or ranges with geom_ribbon(); stacked areas for proportional time series. |
| Bar | Lollipop chart / Cleveland dot plot | Reduces ink-to-data ratio; cleaner when many categories exist. Uses geom_segment() + geom_point(). |
| Histogram | Density plot / ridgeline plot | Smoothed continuous estimate with geom_density(); ridgeline plots (ggridges) compare many distributions. |
| Boxplot | Violin plot / beeswarm plot | Shows full distributional shape with geom_violin(); beeswarm (ggbeeswarm) shows individual data points. |
How each basic plot type maps to its advanced descendant in the ggplot2 ecosystem.
Beyond individual plot upgrades, the broader visualization frontier in R includes interactive graphics (via plotly::ggplotly() or shiny), faceted small multiples (via facet_wrap() and facet_grid()), and animation (via gganimate). Each of these builds on the same grammar-of-graphics foundation, so the conceptual investment you make in learning the five basic geoms pays compounding dividends as you explore the R visualization ecosystem.
From Static to Interactive
plotly::ggplotly(p). This adds hover tooltips, zoom, and pan—extremely useful for exploring algorithm benchmarks or large datasets during development.SECTION 9
Practice Problems
Question 1: CONCEPTUAL
Show answer
Question 2: BASIC CALCULATION
df with columns cpu_usage (x-axis) and memory_mb (y-axis), colored by a factor column process_type. Add appropriate axis labels and a minimal theme.Show answer
ggplot(df, aes(x = cpu_usage, y = memory_mb, color = process_type)) + geom_point(alpha = 0.7) + labs(x = "CPU Usage (%)", y = "Memory (MB)", title = "CPU vs Memory by Process Type") + theme_minimal(). The aes() function maps cpu_usage to the x position, memory_mb to the y position, and process_type to the color channel. The alpha = 0.7 adds transparency to help reveal overplotted regions.Question 3: INTERMEDIATE
latency_df has columns service (factor) and latency_ms (numeric). Write the code for a boxplot that uses notches and adds the raw data points on top with jitter to show the underlying distribution.Show answer
ggplot(latency_df, aes(x = service, y = latency_ms, fill = service)) + geom_boxplot(notch = TRUE, outlier.shape = NA, alpha = 0.5) + geom_jitter(width = 0.2, alpha = 0.3, size = 1) + labs(x = "Service", y = "Latency (ms)", title = "Latency Distributions by Microservice") + theme_minimal() + theme(legend.position = "none"). Setting notch = TRUE adds confidence intervals around the medians—if notches of two boxes do not overlap, there is strong evidence the medians differ. We set outlier.shape = NA to suppress default outlier dots since the jittered points already display all data, avoiding double-plotting of extreme values.Question 4: APPLIED
training_log with columns epoch (integer), loss (numeric), and split (factor: 'train' or 'validation'). Create a line plot that shows loss curves for both splits, add a horizontal dashed reference line at loss = 0.1 representing the target loss, and use a log-scale y-axis. Explain each layer.Show answer
ggplot(training_log, aes(x = epoch, y = loss, color = split)) + geom_line(linewidth = 1) + geom_hline(yintercept = 0.1, linetype = "dashed", color = "gray50") + scale_y_log10() + labs(x = "Epoch", y = "Loss (log scale)", title = "Training & Validation Loss", color = "Split") + theme_minimal(). The geom_line() layer draws one line per factor level of split. The geom_hline() layer adds a constant reference line. The scale_y_log10() transforms the y-axis to log scale, which is standard for loss curves because early training produces large losses that would compress later improvements on a linear scale. The color aesthetic automatically differentiates the two splits and creates a legend.Question 5: CRITICAL THINKING
Show answer
geom_point() + geom_segment()) with error bars showing confidence intervals for the median. Dot plots do not encode magnitude as bar length, so a non-zero axis baseline is perceptually acceptable, and the error bars communicate statistical uncertainty.SUMMARY
Lesson Summary
Summary
This lesson covered the five foundational plot types in R: scatter plots for revealing relationships between two continuous variables, line plots for displaying trends over ordered sequences, bar charts for comparing magnitudes across categorical groups, histograms for examining the distributional shape of continuous data, and boxplots for compact multi-group distributional comparisons including median, spread, and outlier detection. We explored both base R syntax (imperative, pen-on-paper model) and ggplot2 syntax (declarative, grammar-of-graphics model) for constructing each type.
The key to effective visualization is matching the variable type and analytical question to the appropriate geometric representation. Remember that aesthetic mappings connect data columns to visual channels, geometry selection determines what is drawn, and parameters like bin width, alpha transparency, and axis scales profoundly affect interpretation. These five plot types serve as the foundation for the entire R visualization ecosystem, from interactive dashboards to publication-quality figures.