ggplot(something)+
plotfunction(aes(x=category,y=quantitative))+
#(add more such as theme, color by simply using +)Lect 3 Note
Principles of graphing
Principles:
Business Challenge: Understanding price sensitivity in Beer Sales
The role of visualization: “The greatest value of a picture is when it forces us to notice what we never expected to see.” — John Tukey
Key question: How do price changes affect sales across different beer brands? And: Do imported and domestic beers respond differently to price changes?
Required data: - The beer brand, - Price per unit, - Number of units sold, - Whether the beer is imported or domestic, - The store identifier, - The week of sale, and - Whether the product was under promotion that week
library(tidyverse) # for plotting, includes ggplot── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.5
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ ggplot2 3.5.2 ✔ tibble 3.3.0
✔ lubridate 1.9.4 ✔ tidyr 1.3.1
✔ purrr 1.1.0
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(patchwork) # for combining multiple plots into subfigures
library(scales) # for formatting axis scales
Attaching package: 'scales'
The following object is masked from 'package:purrr':
discard
The following object is masked from 'package:readr':
col_factor
library(ggokabeito) # color blind friendly color palette -- this course's defaultAs you look at the glimpse() output, notice the data types in each column (e.g.
Visualizing the data
Principles:
Choose the right visual for the question you’re asking
Don’t overload the viewer—clarity beats complexity
Colour and scale should help highlight structure, not distract from it
geom_boxplot() is box plot usegul for: - Compare medians and variability accross group - Spot outliers or unusually hihg/low values - See how spread out a variable is
Thinking of box plot like a normal distribution but in another form
- The box captures the middle 50% of values (from the 1st to 3rd quartile)
- The line inside the box shows the median
- The “whiskers” extend to typical lower and upper values
- Any points outside the whiskers are considered outliers
1. Box Plot: Exploring the distribution of price
ggplot: grammar graphic plot
The data
beer <- read.csv("data/beer.csv")
glimpse(beer)Rows: 4,033
Columns: 17
$ store <int> 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86…
$ week <int> 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94…
$ brand <chr> "Budweiser", "Corona", "Lowenbrau", "Miller", "Budweis…
$ upc <dbl> 1820000016, 8066095605, 3410021505, 3410000554, 182000…
$ qty <int> 23, 13, 13, 15, 46, 24, 21, 117, 47, 23, 34, 118, 46, …
$ price <dbl> 3.49, 5.79, 3.99, 3.69, 3.49, 5.79, 3.99, 2.99, 3.49, …
$ sales_indicator <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE…
$ city <chr> "Chicago", "Chicago", "Chicago", "Chicago", "Chicago",…
$ price_tier <chr> "medium", "medium", "medium", "medium", "medium", "med…
$ zone <int> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
$ zip <int> 60618, 60618, 60618, 60618, 60618, 60618, 60618, 60618…
$ address <chr> "3350 Western Ave", "3350 Western Ave", "3350 Western …
$ latitude <dbl> 41.94235, 41.94235, 41.94235, 41.94235, 41.94235, 41.9…
$ longtitude <dbl> -87.68999, -87.68999, -87.68999, -87.68999, -87.68999,…
$ start_of_week <chr> "1991-06-06", "1991-06-06", "1991-06-06", "1991-06-06"…
$ is_holiday_week <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE…
$ imported <chr> "domestic", "imported", "imported", "domestic", "domes…
Blank canvas:
ggplot(beer)Plotting
ggplot(beer) +
geom_boxplot((aes(y=price)))# upc: bar codeTo separate by imported:
price_plot <-
ggplot(beer) +
geom_boxplot(aes(y = price, x = imported))
price_plotggsave("price_box_plot.png",plot=price_plot)Saving 7 x 5 in image
To separated by brand:
ggplot(beer) +
geom_boxplot(aes(y = price, x = brand))To save the image outside R
price_dist <-
ggplot(beer) +
geom_boxplot(aes(y = price, x = imported))
price_distggsave("price_box_plot.png",plot=price_dist)Saving 7 x 5 in image
Assign the plot to an object: price_box <- ggplot(beer) + geom_boxplot(aes(y = price, x = imported))
Save the plot as an image to use outside of R: ggsave(“price_box.png”, plot = price_box, width = 5, height = 4 )
2. Histogram and density plot: Exploring the quantity sold
geom_histogram() for histogram
ggplot(beer) +
geom_histogram(aes(x=qty))`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
# -> this uses bin=30Change bin width
ggplot(beer) +
geom_histogram(aes(x = qty),
binwidth = 5)Categorize with imported
ggplot(beer) +
geom_histogram(aes(x = qty,
fill = imported
),
binwidth = 5)ggplot(beer) +
geom_histogram(aes(x = qty, fill = brand), alpha = 0.35)`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
geom_density() for Density Plot
- Density plot turns the bars into curve that’s easier to compare
ggplot(beer) +
geom_density(aes(x = qty))Detour: adding style to our plots
Styling in ggplot is about guiding the reader’s eye to the important parts of the story—it’s not just decoration. ### Color and Fill fill changes the inside colour of shapes (e.g., bars in a bar chart, the interior of a box in a boxplot, or the area under a density curve). color (spelled without the “u” in code) changes the outline or border colour of shapes, or the colour of points and lines when there is no interior to fill.
Key is consistency When deciding what colors to use, consistency is key: if “imported” is blue in one plot, it should be blue in every plot in the report. -> Use Okabe-Ito colour pallete
ggplot(beer) +
geom_boxplot(aes(y = price,
x = imported,
fill = imported)) +
scale_fill_okabe_ito()###Complete version
sales_plot <-
ggplot(beer) +
geom_density(aes(x = qty, fill = imported),
alpha = 0.35
) +
# Natural log scale for x-axis with meaningful breaks
scale_x_continuous(
trans = "log",
breaks = c(1, 2, 5, 10, 20, 50, 100, 200)
) +
labs(y = "Density", x = "Quantity Sold") +
scale_fill_okabe_ito() +
theme_minimal() 3. Scatter plot: Explore price and Quantity relationship
geom_point() for scatter plot
ggplot(beer) +
geom_point(aes(y = price,
x = qty,
color = imported),
alpha = 0.25
) +
scale_color_okabe_ito()log-log relationship: in a log–log plot, straight-line patterns often suggest a constant percentage relationship between the two variables.
ggplot(beer) +
geom_point(aes(y = log(price),
x = log(qty),
color = imported),
alpha = 0.25
) +
scale_color_okabe_ito()A more advanced way is instead of wrapping log() around price and qty inside aes(), we can use scale_x_continuous(trans = “log”) and scale_y_continuous(trans = “log”). This means we are applying a logarithmic transformation to the axes.
ggplot(beer) +
geom_point(aes(y = price,
x = qty,
color = imported),
alpha = 0.25
) +
scale_color_okabe_ito() +
# Natural log scale for x-axis with meaningful breaks
scale_x_continuous(
trans = "log",
breaks = c(1, 2, 5, 10, 20, 50, 100, 200),
#labels = scales::comma
) +
# Natural log scale for y-axis with meaningful breaks
scale_y_continuous(
trans = "log",
breaks = c(3, 4, 5, 6),
labels = scales::dollar
) +
labs(x = "Quantity", y = "Price")two big advantages:
- Cleaner code – you keep the plotting aesthetics simple while still getting the transformation you want.
- More control over axis appearance – you can set breaks and labels that are easy to interpret, even when using a logarithmic scale. ### Creating Subplots with facet_wrap()
ggplot(beer) +
geom_point(aes(y = price, x = qty,
color = imported),
alpha = 0.25
) +
facet_wrap(~ imported) +
# Natural log scale for x-axis with meaningful breaks
scale_x_continuous(
trans = "log",
breaks = c(1, 2, 5, 10, 20, 50, 100, 200)#,
#labels = scales::comma
) +
# Natural log scale for y-axis with meaningful breaks
scale_y_continuous(
trans = "log",
breaks = c(3, 4, 5, 6),
labels = scales::dollar
) +
labs(x = "Quantity", y = "Price") +
scale_color_okabe_ito() +
theme_minimal() +
theme(legend.position = "none")Adding a statistical transformation
price_qty <-
ggplot(beer) +
geom_point(aes(y = price, x = qty,
color = imported),
alpha = 0.25
) +
# This is the addition of the linear model
geom_smooth(aes(y = price, x = qty),
method = "lm",
# add a complementary okabe_ito color
color = palette_okabe_ito()[7],
se = TRUE) +
facet_wrap(~ imported) +
# Natural log scale for x-axis with meaningful breaks
scale_x_continuous(
trans = "log",
breaks = c(1, 2, 5, 10, 20, 50, 100, 200)#,
#labels = scales::comma
) +
# Natural log scale for y-axis with meaningful breaks
scale_y_continuous(
trans = "log",
breaks = c(3, 4, 5, 6),
labels = scales::dollar
) +
labs(x = "Quantity", y = "Price") +
scale_color_okabe_ito() +
theme_minimal() +
theme(legend.position = "none")4. Theme: control the non-data elements of your plot
Such as background colour, grid lines, axis text, titles, legend placement, and more. ### Example
ggplot(beer) +
geom_boxplot(aes(y = price,
x = imported,
fill = imported)) +
scale_fill_okabe_ito() +
theme_minimal() +
theme(legend.position = "none")Add more title for the graph
ggplot(beer) +
geom_boxplot(aes(y = price,
x = imported,
fill = imported)) +
scale_fill_okabe_ito() +
labs(
title = "Weekly Beer Prices by Import Status",
subtitle = "Dominicks Finer Foods Stores",
caption = "Source: Univeristy of Chicago Booth School of Business Kilt's Center for Marketing Research"
) +
theme_minimal() +
theme(legend.position = "none")Axis labels
ggplot(beer) +
geom_boxplot(aes(y = price,
x = imported,
fill = imported)) +
scale_fill_okabe_ito() +
labs(
title = "Weekly Beer Prices by Import Status",
x = "Production Location",
y = "Price (USD)"
) +
theme_minimal() +
theme(legend.position = "none")labs() is for content — it changes the text that appears (the wording of titles, subtitles, captions, and axis labels).
theme() is for appearance — it controls how that text (and other plot elements) is drawn: font size, colour, position, rotation, margins, and so on.
hjust argument in the element_text() function. hjust controls the horizontal justification of text: How it works:
0 = left aligned 0.5 = centered 1 = right aligned
Axis scales
ggplot(beer) +
geom_boxplot(aes(y = price, x = imported, fill = imported)) +
scale_y_continuous(
labels = scales::dollar
) +
scale_fill_okabe_ito() +
theme(legend.position = "none") +
labs(y = "Price", x = "Production Location") +
theme_minimal()Scale to each 50 cents
ggplot(beer) +
geom_boxplot(aes(y = price, x = imported, fill = imported)) +
scale_y_continuous(
breaks = c(3, 3.5, 4, 4.5, 5, 5.5, 6),
labels = scales::dollar
) +
scale_fill_okabe_ito() +
theme(legend.position = "none") +
labs(y = "Price", x = "Production Location") +
theme_minimal()Understanding scale__() naming The first * refers to the axis:
x for the x-axis y for the y-axis The second * refers to the type of data on that axis:
continuous for numeric/quantitative data discrete for categories (factors or character variables) Example:
scale_x_continuous() → Adjusts breaks/labels for a numeric x-axis scale_y_discrete() → Adjusts breaks/labels for a categorical y-axis ### Adjusting transparency with Alpha (trial and error process)
ggplot(beer) +
geom_histogram(
aes(x = qty, fill = imported),
position = "identity",
alpha = 0.6,
binwidth = 5
)5. Bringing it all together
Think of | as saying “put this next to that.”
price_qty | price_plot | sales_plot`geom_smooth()` using formula = 'y ~ x'
# Better arrangement below:
price_qty /
(price_plot | sales_plot)`geom_smooth()` using formula = 'y ~ x'
Conclusion
First, the process of building a ggplot is consistent no matter what you’re visualising:
Initialize the plot with ggplot() – set your dataset and define the aesthetic mappings (aes()). Add geometric layers (geom_*) – choose the right geometry for the data and question at hand. Customize the appearance – adjust labels, themes, colours, and axes so the plot tells its story clearly. (Optionally) Add statistical transformations to make the main insight easier to spot. Second, we paired each plot with one principle of effective data visualisation:
Price boxplot: Choose visualisations that match your question. Histograms & Density plots: Colour enhances categorical comparisons. Price-Quantity Relationship: Transformations (like log scales) can reveal patterns. # Practice space
ggplot(beer)+
geom_point(aes(x=qty,
y=price,
color=imported
),
alpha=0.15
) +
geom_smooth(aes(x=qty,
y=price),
method = "lm"
) +
facet_wrap(~imported)+
labs(
x="Quantity",
y="Price"
) +
scale_y_continuous(
labels=dollar
)`geom_smooth()` using formula = 'y ~ x'
lm is for linerization