Data Visualization, Part 1

Histograms, boxplots, and scatter/line plots in base R, and layered graphics with ggplot2, using AI-assisted prompting

Author
JP Adjadeh
Published
July 30, 2026

1. Introduction


The previous modules covered data structures and data manipulation — identifying what you are working with, then indexing, filtering, and cleaning it. This module turns to a different kind of task: turning a data frame into a picture. Visualization uses different functions and a different logic than manipulation, but the same discipline applies. Every plot below is produced by prompting an AI model for code — using the same role, task, context, and format elements from earlier modules — and every result is checked before it is trusted, not accepted simply because it rendered.

This module works with two different plotting engines. Sections 2 through 4 use base R, where a plot is produced by a single function call with many arguments. Section 5 introduces ggplot2, which builds a plot as a stack of layers added with +. The two engines can produce similar-looking output, but they are structured differently enough that a prompt written for one will not simply drop into the other — part of the point of this module is noticing that difference.

2. Basic Histograms


Dataset: life_exp
Already imported as: life_exp

A histogram shows the distribution of a single numeric column — how its values are spread out, and where they cluster. In base R, hist() takes a numeric vector and does the rest.

Segment 1: A basic histogram

Mission Card — complete the prompt, then run it
You are a [role]. Using the life_exp data frame, write R code to [create a histogram of the Average_Life_Expectancy column, with a title and an x-axis label].
⚠ Exercise 1
Run your prompt. Then, without asking AI, add col = "lightblue" and border = "grey" to the call yourself and re-run it. Confirm the bars changed color.
Guide code
hist(life_exp$Average_Life_Expectancy,
     main = "Histogram of Average Life Expectancy",
     xlab = "Average Life Expectancy",
     col = "lightblue",
     border = "grey")

Segment 2: Adding mean and median reference lines

ⓘ Note — na.rm
If Average_Life_Expectancy has any missing values, mean() and median() will return NA unless you pass na.rm = TRUE. This is the same argument used in the data manipulation module — it does not remove missing values from the data frame, only from that one calculation.
Mission Card — complete the prompt, then run it
You are a [role]. Using the life_exp data frame, write R code to [calculate the mean and median of Average_Life_Expectancy, then add both as vertical lines on the histogram in different colors, with a legend identifying which line is which].
⚠ Exercise 2
Run your prompt. Print mean(life_exp$Average_Life_Expectancy, na.rm = TRUE) and median(...) directly, and confirm the two numbers match where the two lines fall on the plot.
Guide code
mean_le <- mean(life_exp$Average_Life_Expectancy, na.rm = TRUE)
median_le <- median(life_exp$Average_Life_Expectancy, na.rm = TRUE)

hist(life_exp$Average_Life_Expectancy,
     main = "Histogram of Average Life Expectancy with Mean and Median",
     xlab = "Average Life Expectancy",
     col = "lightblue", border = "black")
abline(v = mean_le, col = "blue", lwd = 2)
abline(v = median_le, col = "red", lwd = 2)
legend("topleft", legend = c("Mean", "Median"), col = c("blue", "red"), lwd = 2)

3. Boxplots by Category


Dataset: life_exp
Already imported as: life_exp

A boxplot summarizes a numeric column's distribution — its median, quartiles, and any outliers — and is most useful when comparing that distribution across the levels of a categorical column, using the formula syntax numeric ~ category.

Segment 1: Comparing distributions

Mission Card — complete the prompt, then run it
You are a [role]. Using the life_exp data frame, write R code to [create a boxplot comparing Average_Life_Expectancy across the Gender column].
⚠ Exercise 3
Run your prompt. Look at the plot and state which gender has the higher median. Then confirm your answer numerically with tapply(life_exp$Average_Life_Expectancy, life_exp$Gender, median, na.rm = TRUE). If tapply() is unfamiliar, ask AI to explain what it is doing before you run it — do not just paste it.
Guide code
boxplot(Average_Life_Expectancy ~ Gender, data = life_exp,
        main = "Boxplot of Life Expectancy by Gender",
        xlab = "Gender", ylab = "Average Life Expectancy")

Segment 2: Adding color and reading outliers

Mission Card — complete the prompt, then run it
You are a [role]. Using the life_exp data frame, write R code to [recreate the boxplot above with a different color for each box].
⚠ Exercise 4
Run your prompt. A boxplot marks outliers as individual points beyond the whiskers. State whether either group in your plot shows one, and explain in your own words what makes a point count as an outlier in a boxplot rather than a normal value.
Guide code
boxplot(Average_Life_Expectancy ~ Gender, data = life_exp,
        main = "Boxplot of Life Expectancy by Gender",
        xlab = "Gender", ylab = "Average Life Expectancy",
        col = c("lightblue", "lightgreen"))

4. Scatter and Line Plots


Dataset: life_exp
Already imported as: life_exp

Scatter plots and line plots both use plot(), differentiated by the type argument. Both are well suited to a numeric column tracked over time, such as Year.

Segment 1: A basic scatter plot

Mission Card — complete the prompt, then run it
You are a [role]. Using the life_exp data frame, write R code to [create a scatter plot with Year on the x-axis and Average_Life_Expectancy on the y-axis, using solid filled points].
⚠ Exercise 5
Run your prompt. Then, without asking AI, change the pch value to a different number and re-run it. Describe what changed about the points visually.
Guide code
plot(life_exp$Year, life_exp$Average_Life_Expectancy,
     xlab = "Year", ylab = "Average Life Expectancy",
     main = "Scatter Plot of Life Expectancy Over Time",
     pch = 19)

Segment 2: Coloring points by a category

Mission Card — complete the prompt, then run it
You are a [role]. Using the life_exp data frame, write R code to [recreate the scatter plot above, coloring each point blue if Gender is "Male" and red otherwise].
⚠ Exercise 6
Run your prompt. Identify which function assigns the color conditionally, and state what would happen to points from a third category if the Gender column had one — would they show as blue, red, or something else? Explain why.
Guide code
plot(life_exp$Year, life_exp$Average_Life_Expectancy,
     xlab = "Year", ylab = "Average Life Expectancy",
     main = "Scatter Plot of Life Expectancy Over Time by Gender",
     pch = 19, col = ifelse(life_exp$Gender == "Male", "blue", "red"))

Segment 3: Two line plots, a legend, and an annotation

Mission Card — complete the prompt, then run it
You are a [role]. Using the life_exp data frame, write R code to [create separate data frames for Male and Female rows, then plot Average_Life_Expectancy over Year as two lines on the same plot in different colors, sharing the same axis limits, with a legend identifying each line, a vertical dotted line at the year 1918, and a text label reading "End of World War I" near that line].
⚠ Exercise 7
Run your prompt. Find the xlim and ylim arguments in the code and explain, in your own words, what would go wrong visually if they were removed and the male and female subsets covered different year ranges.
Guide code
male_data <- subset(life_exp, Gender == "Male")
female_data <- subset(life_exp, Gender == "Female")

plot(male_data$Year, male_data$Average_Life_Expectancy,
     type = "l", col = "blue", lwd = 2,
     xlab = "Year", ylab = "Average Life Expectancy (years)",
     main = "Life Expectancy by Gender Over Time",
     lty = 1,
     xlim = c(min(life_exp$Year), max(life_exp$Year)),
     ylim = c(min(life_exp$Average_Life_Expectancy, na.rm = TRUE),
              max(life_exp$Average_Life_Expectancy, na.rm = TRUE)))
lines(female_data$Year, female_data$Average_Life_Expectancy, col = "red", lwd = 2, lty = 2)

legend("bottomright", legend = c("Male", "Female"), col = c("blue", "red"), lwd = 2, lty = c(1, 2))
text(1918, 75, "End of World War I", col = "black")
abline(v = 1918, col = "black", lwd = 1, lty = 3)
ⓘ Note
AI can generate a working plot() call in one pass. It will not tell you whether a line chart, scatter plot, or boxplot is the right choice for what you are trying to show — that decision depends on the shape of your data and the question you are asking, both of which are yours to judge.

5. Layered Graphics with ggplot2


Dataset: insurance
Already imported as: insurance

ggplot2 follows the Grammar of Graphics: instead of one function with many arguments, a plot is built by stacking layers with + — a base canvas, a geometry (points, lines, bars), and optional layers for scales, facets, labels, and themes. The same visual ideas from Sections 2–4 reappear here, but expressed as layers rather than arguments.

Segment 1: A blank canvas, then points

Mission Card — complete the prompt, then run it
You are a [role]. Using the insurance data frame and the ggplot2 package, write R code to [create a scatterplot of age on the x-axis and expenses on the y-axis, using cornflowerblue points with size 2 and moderate transparency].
⚠ Exercise 8
Run your prompt. Then run ggplot(data = insurance, aes(x = age, y = expenses)) on its own, with no geom layer added. Describe what appears, and explain in your own words why nothing is plotted without a geom.
Guide code
library(ggplot2)

ggplot(data = insurance, aes(x = age, y = expenses)) +
  geom_point(color = "cornflowerblue", alpha = .5, size = 2)

Segment 2: Adding a trend line

Mission Card — complete the prompt, then run it
You are a [role]. Using the insurance data frame, write R code to [add a linear trend line to the scatterplot above].
⚠ Exercise 9
Before running your prompt, predict whether the trend line will slope upward or downward. Run it and confirm. Then state what method = "lm" tells R to fit.
Guide code
ggplot(data = insurance, aes(x = age, y = expenses)) +
  geom_point(color = "cornflowerblue", alpha = .5, size = 2) +
  geom_smooth(method = "lm")

Segment 3: Grouping by a categorical column

ⓘ Note — Global vs. Local Mapping
An aes() mapping placed inside ggplot() applies to every layer that follows. Placed inside a specific geom_ instead, it applies only to that layer. This matters below: mapping color = smoker at the top level colors both the points and the trend line; mapping it only inside geom_point() would color the points alone.
Mission Card — complete the prompt, then run it
You are a [role]. Using the insurance data frame, write R code to [recreate the plot above, coloring both the points and the trend line by the smoker column, and removing the shaded confidence band around the trend line].
⚠ Exercise 10
Run your prompt. Identify the single argument inside aes() responsible for both the point color and the legend that appears automatically. Then set se = TRUE and describe what the shaded band around each line represents.
Guide code
ggplot(data = insurance, aes(x = age, y = expenses, color = smoker)) +
  geom_point(alpha = .5, size = 2) +
  geom_smooth(method = "lm", se = FALSE, linewidth = 1.5)

Segment 4: Faceting and adding labels

Mission Card — complete the prompt, then run it
You are a [role]. Using the insurance data frame, write R code to [create a new column called obese that is "obese" when bmi is 30 or higher and "not obese" otherwise, then split the plot above into two side-by-side panels based on that column, and add a title, subtitle, x-axis label, and y-axis label].
⚠ Exercise 11
Run your prompt. Identify which function splits the plot into panels and which function adds the title and axis labels. Then state, in one sentence, one pattern you can see in the faceted plot that was harder to notice in the single combined plot from Segment 3.
Guide code
insurance$obese <- ifelse(insurance$bmi >= 30, "obese", "not obese")

ggplot(data = insurance, aes(x = age, y = expenses, color = smoker)) +
  geom_point(alpha = .5) +
  geom_smooth(method = "lm", se = FALSE) +
  facet_wrap(~obese) +
  labs(title = "Relationship between age and medical expenses",
       subtitle = "By smoking and obesity status",
       x = "Age (years)",
       y = "Annual expenses",
       color = "Smoker?")

Segment 5: Applying a theme

Mission Card — complete the prompt, then run it
You are a [role]. Using the plot from the previous segment, write R code to [apply a clean, minimal theme with no gray background or default grid styling].
⚠ Exercise 12
Run your prompt. Compare the plot before and after the theme is applied, and name one specific visual element that changed — for example, the background color, the gridlines, or the panel borders.
Guide code
insurance$obese <- ifelse(insurance$bmi >= 30, "obese", "not obese")

ggplot(data = insurance, aes(x = age, y = expenses, color = smoker)) +
  geom_point(alpha = .5) +
  geom_smooth(method = "lm", se = FALSE) +
  facet_wrap(~obese) +
  labs(title = "Relationship between age and medical expenses",
       subtitle = "By smoking and obesity status",
       x = "Age (years)",
       y = "Annual expenses",
       color = "Smoker?") +
  theme_minimal()
ⓘ Note
AI can generate a working ggplot2 call quickly, and it is easy to keep adding layers — a color mapping, a facet, a theme — without stopping to check whether each one is adding clarity or just complexity. Before accepting a plot, ask whether a reader unfamiliar with the data could state its main point in one sentence. If not, the plot needs fewer layers, not more code.

Conclusion


This module covered visualization in two engines. In base R, hist(), boxplot(), and plot() each produce a specific chart type through arguments passed to a single function call, with abline(), legend(), and text() layering on reference lines, legends, and annotations. In ggplot2, the same ideas are expressed as layers added with + — a base mapping, one or more geoms, and optional layers for grouping, scales, faceting, labels, and themes. Each plot was produced by prompting an AI model for code and verifying the result — predicting an outcome before running it, confirming a visual read against a computed value, or explaining what a specific argument does before relying on it.

A separate module extends this ggplot2 grammar with interactive and animated graphics.