This document works through the five independent exercises from the
Data Visualization Practicum. Each section calculates or
prepares the data needed, builds the requested plot with
ggplot2, and closes with a short, plain language read of
what the plot is actually telling us not just a restatement of the
numbers.
mpg)Task: Calculate the average cty for
each manufacturer, keep the top 10, build a comparison plot, and
summarize the finding.
mpg_cty <- mpg %>%
group_by(manufacturer) %>%
summarise(
average_cty = mean(cty),
n_models = n(),
.groups = "drop"
) %>%
arrange(desc(average_cty)) %>%
slice_head(n = 10)
mpg_cty
## # A tibble: 10 × 3
## manufacturer average_cty n_models
## <chr> <dbl> <int>
## 1 honda 24.4 9
## 2 volkswagen 20.9 27
## 3 subaru 19.3 14
## 4 hyundai 18.6 14
## 5 toyota 18.5 34
## 6 nissan 18.1 13
## 7 audi 17.6 18
## 8 pontiac 17 5
## 9 chevrolet 15 19
## 10 ford 14 25
ggplot(data = mpg_cty,
aes(x = reorder(manufacturer, average_cty),
y = average_cty)) +
geom_col(fill = "#2C7FB8") +
geom_text(aes(label = round(average_cty, 1)),
hjust = -0.2, size = 3.5) +
coord_flip(clip = "off") +
labs(
title = "Top 10 Manufacturers by Average City Fuel Efficiency",
subtitle = "Based on the mpg dataset",
x = NULL,
y = "Average city mileage (mpg)"
) +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"))
What this is actually telling us:
Honda’s clearly out in front here — nobody else in the top 10 gets close to 24 mpg. After that it’s a pretty steady slide down to Ford at 14. That makes sense - the top of the list is dominated by compact, economy focused lineups, and the further down you go the more trucks and SUVs start pulling the average down. One thing to keep in mind, this is just an average of whatever models each brand happens to have in the dataset, so a manufacturer with only a couple of small cars in the sample can look better than it “really” is.
diamonds)Task: Choose one numerical variable, compare its distribution across one categorical variable, improve the plot’s appearance, and interpret the pattern.
Here we look at carat (diamond weight) split by cut quality, using a ridgeline plot so the shapes of the distributions are easy to line up against each other.
ggplot(data = diamonds,
aes(x = carat,
y = cut,
fill = cut)) +
geom_density_ridges(
alpha = 0.8,
scale = 1.1,
color = "white",
show.legend = FALSE
) +
scale_fill_viridis_d(option = "magma", begin = 0.15, end = 0.9) +
scale_x_continuous(limits = c(0, 3)) +
labs(
title = "Distribution of Diamond Carat Weight by Cut Quality",
subtitle = "Heavier stones are noticeably rarer among the best cuts",
x = "Carat",
y = "Cut quality"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
panel.grid.minor = element_blank()
)
What this is actually telling us:
Ideal of cut diamonds are clearly bunched up under 1 carat sharp peak, quick drop off. As you move down toward Good and Fair, that peak spreads out and slides right, meaning more of the big stones end up with lower cut grades. That tracks with how cutting actually works, turning a big rough diamond into a perfect Ideal-cut means losing more material, so cutters working with larger stones often accept a slightly worse cut to hang onto carat weight (and value). Cut and carat aren’t really independent this plot is basically that trade off in picture form.
diamonds_sample)Task: Visualize the relationship between
carat and price, add at least one relevant
aesthetic, apply suitable customization, and explain the relationship
shown.
#creating sample
set.seed(123)
diamonds_sample <- diamonds %>%
slice_sample(n=3000)
ggplot(data = diamonds_sample,
aes(x = carat,
y = price,
color = clarity)) +
geom_point(alpha = 0.55, size = 1.6) +
geom_smooth(
aes(group = 1),
method = "loess",
color = "black",
linewidth = 0.9,
se = FALSE
) +
scale_color_viridis_d(option = "plasma") +
scale_y_continuous(labels = scales::dollar_format()) +
labs(
title = "Diamond Price Rises Sharply with Carat Weight",
subtitle = "Color shows clarity grade; black line shows the overall trend",
x = "Carat",
y = "Price (USD)",
color = "Clarity"
) +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"))
What this is actually telling us:
Price obviously climbs with carat, and it’s not linear, it accelerates, so the jump from 1.5 to 2 carats adds a lot more than the jump from 0.5 to 1. That lines up with how diamonds get priced in general, big stones are rarer, so price per carat jumps at certain size thresholds. Clarity adds some spread at any given carat size, but I wouldn’t read too much into which direction it goes from the color alone, the diamonds dataset has a known quirk where clarity, color, and carat are tangled together (bigger stones tend to get cut for weight over clarity), so a clean “better clarity = higher price at the same size” story isn’t something I’d claim without actually checking the correlation. Carat is clearly doing most of the work here; clarity is a secondary factor worth digging into further rather than something I can confidently characterize from the scatter alone.
economics)Task: Visualize psavert over time, use
clear labels and a suitable theme, highlight or annotate a noticeable
change, and interpret it.
lowest_point <- economics |>
filter(psavert == min(psavert))
ggplot(data = economics,
aes(x = date, y = psavert)) +
geom_line(color = "#2C7FB8", linewidth = 0.7) +
geom_point(data = lowest_point,
aes(x = date, y = psavert),
color = "firebrick", size = 2.5) +
annotate(
"text",
x = lowest_point$date,
y = lowest_point$psavert + 1.5,
label = paste0("Lowest point: ", round(lowest_point$psavert, 1),
"%\n(", format(lowest_point$date, "%b %Y"), ")"),
color = "firebrick",
size = 3.3,
hjust = 0.15
) +
scale_y_continuous(labels = scales::percent_format(scale = 1)) +
labs(
title = "U.S. Personal Savings Rate, 1967–2015",
subtitle = "Savings habits shifted dramatically over five decades",
x = "Year",
y = "Personal savings rate"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
panel.grid.minor = element_blank()
)
What this is actually telling us:
From the graph the whole shift in American saving habits in one line. Through the late 60s and 70s people were regularly saving 10–15% of income — sounds crazy by today’s standards. From there it just grinds downward through the 80s and 90s, bottoming out at 2.2% in mid-2005. That timing isn’t a coincidence — it’s right in the middle of the cheap-credit, rising-home-equity period leading into the 2008 crash, when people leaned on debt and home value instead of savings. After the crash there’s a real rebound as households got more cautious. So the line isn’t just noisy data — it’s basically a proxy for how nervous or confident people were about money over 50 years.
Task: Create one visualization with at least three presentation problems, then redesign it with improvements, and briefly explain the changes.
mpg_class_summary <- mpg |>
group_by(class) |>
summarise(mean_hwy = mean(hwy), .groups = "drop")
ggplot(data = mpg_class_summary,
aes(x = class, y = mean_hwy, fill = class)) +
geom_col(show.legend = TRUE) +
coord_cartesian(ylim = c(15, 30)) + # problem 1: truncated axis exaggerates differences
theme(
axis.text.x = element_text(size = 6), # problem 2: tiny, unreadable labels, no rotation
legend.position = "right" # problem 3: redundant legend duplicating the x-axis
) +
labs(x = "", y = "") # problem 4: no title, no axis labels/units
Problems with this plot:
ylim = c(15, 30)) —
this exaggerates the differences between classes, making a gap of a few
mpg look like a two- or three-fold difference.class is already labeled on the x-axis, so mapping it to
fill as well adds visual noise (and a legend) without
adding any new information.ggplot(data = mpg_class_summary,
aes(x = reorder(class, mean_hwy), y = mean_hwy)) +
geom_col(fill = "#2C7FB8", width = 0.7) +
geom_text(aes(label = round(mean_hwy, 1)), vjust = -0.6, size = 3.5) +
coord_cartesian(ylim = c(0, max(mpg_class_summary$mean_hwy) * 1.15)) +
labs(
title = "Average Highway Fuel Efficiency by Vehicle Class",
subtitle = "Full y-axis scale, from zero, for an honest comparison",
x = NULL,
y = "Average highway mileage (mpg)"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 0, size = 10),
panel.grid.major.x = element_blank()
)
What changed, and why:
fill = class
legend and replaced it with a single consistent bar color,
since the categories were already labeled on the x-axis. One less thing
competing for attention.reorder()) instead of leaving them in whatever order the
factor levels happened to be in, so the reader can immediately see the
ranking from lowest to highest efficiency.Small changes, but together they turn a chart that technically shows the data into one that actually communicates it clearly and honestly.