Lect 3 Note

Author

Tony

Principles of graphing

Principles:

  ggplot(something)+
    plotfunction(aes(x=category,y=quantitative))+
       #(add more such as theme, color by simply using +)

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 default

As you look at the glimpse() output, notice the data types in each column (e.g. , , , ). Why might it matter whether a column is stored as a number, category, or date? To visualize later where number on one axis and category on the other axis Numeric variables like price and qty can be plotted, summarised, or used in calculations Categorical variables like brand or imported can be used to group or color plots Logical (TRUE/FALSE) variables like sales_indicator are useful for filtering Date variables help with time-based analysis

Visualizing the data

Principles:

  1. Choose the right visual for the question you’re asking

  2. Don’t overload the viewer—clarity beats complexity

  3. 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 code

To separate by imported:

price_plot <- 
ggplot(beer) +
    geom_boxplot(aes(y = price, x = imported))

price_plot

ggsave("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_dist

ggsave("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=30

Change 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