Software Tools for Data Analysis
STA 9750
Michael Weylandt
Week 7 – Tuesday 2026-10-20, Thursday 2026-10-15
Last Updated: 2026-10-15

STA 9750 Week 7

Today: Lecture #06: Plotting with ggplot2

These slides can be found online at:

https://michael-weylandt.com/STA9750/slides/slides07.html

In-class activities can be found at:

https://michael-weylandt.com/STA9750/labs/lab06.html

Upcoming TODO

Upcoming student responsibilities - Tuesday Section:

Date Time Student Responsibility
2026-10-27 6:00pm ET Mid-Semester Check-In Slides Due
2026-10-31 11:59pm ET Mini-Project Peer Feedback #01 Due
2026-11-03 6:00pm ET Pre-Assignment #07 Due
2026-11-03 11:59pm ET Mid-Semester Teammate Peer Evaluations Due
2026-11-06 11:59pm ET Mini-Project #02 Due
2026-11-10 6:00pm ET Pre-Assignment #08 Due
2026-11-16 11:59pm ET Mini-Project Peer Feedback #02 Due
2026-11-17 6:00pm ET Pre-Assignment #09 Due

Upcoming TODO

Upcoming student responsibilities - Thursday Section:

Date Time Student Responsibility
2026-10-16 11:59pm ET Mini-Project #01 Due
2026-10-22 6:00pm ET Mid-Semester Check-In Slides Due
2026-10-29 6:00pm ET Pre-Assignment #07 Due
2026-10-29 11:59pm ET Mid-Semester Teammate Peer Evaluations Due
2026-10-31 11:59pm ET Mini-Project Peer Feedback #01 Due
2026-11-05 6:00pm ET Pre-Assignment #08 Due
2026-11-06 11:59pm ET Mini-Project #02 Due
2026-11-12 6:00pm ET Pre-Assignment #09 Due

Other Important Dates

Other Important Dates:

Date Instructor Action
2026-10-15 Mini-Project Released #02
2026-10-19 Mini-Project Peer Feedback Assigned #01
2026-11-04 Estimated Return of Peer Comments #01
2026-11-05 Mini-Project Released #03
2026-11-08 Estimated Return of Meta-Review Comments #01
2026-11-09 Mini-Project Peer Feedback Assigned #02

STA 9750 Week 7

Today: Lecture #06: Plotting with ggplot2

  • Communicating Results (quarto) ✅
  • R Basics ✅
  • Data Manipulation in R ✅
  • Data Visualization in R ⬅️
    • Static Plots ⬅️
    • Interactivity, Maps, Animated Plots
  • Getting Data into R
  • Statistical Modeling in R

STA 9750 Week 7

Today: Lecture #06: Plotting with ggplot2

  • MP#01 Peer Feedback
  • Mid-Semester Check-In Advice
  • Warm-Up Exercises
  • Plotting: Principles for Visual Storytelling
  • Introduction to ggplot2
  • Plotting FAQs
  • Wrap-Up

MP#01 - Peer Feedback

Assigned via GitHub - due on 2026-10-31

  • \(\approx 4\) feedbacks each
  • Take this seriously: 20% of this assignment is “meta-review”
  • Goal: rigorous constructive critique

Use helper function to perform peer feedback assigned to you. Ask on Teams if still having trouble.

MP#01 - Peer Feedback

Submissions may not map perfectly to rubric - use your best judgement

Be generous but serious:

  • Goal is improvement, so “everything is great, no comments” is unhelpful
    • Nothing is completely right nor completely wrong
  • Remember, meta-review (instructor scores of your feedback) to follow

Learn from this! What can you adapt for MP#02?

MP#01 - Peer Feedback

Example of poor feedback:

Comments
Website looks really good, nice and clean!

Written Communication
Excellent and straight to the point.

Project Skeleton
Solid skeleton, well-organized .

Formatting & Display
Nicely formatted and balanced across the website.

Code Quality
Code runs like Forrest Gump in a Slump.

Data Preparation
Very great. 

Extra Credit
Added graphes for even better understanding.
  • Superficial comments / no sign of actually reading work

Red Flag: Repeated verbatim on several posts

Reminder: Poor feedback \(\neq\) poor work.

MP#01 - Peer Feedback

Example of medium feedback:

## Comments

Love the visuals for Press Releases.

### Written Communication

A short summary or a description of this project would be great to add.

### Project Skeleton

Code completes all instructor-provided tasks correctly.

### Formatting & Display

Tables have well-formatted column names; caption would be great.

### Code Quality

Code is clear and well written.

### Data Preparation

Automatic (10/10). Out of scope for this mini-project.

### Extra Credit

I found all the Press Releases very interesting. The visuals were a great touch.
  • Gave actionable suggestions
  • Directionally correct, but a bit vague

MP#01 - Peer Feedback

Example of great feedback:

### Written Communication

Overall, your writing is clear and easy to follow. I noticed a few small typos, 
but nothing major — using the built-in spell check in RStudio should catch those 
quickly. Everything else looks solid, and I didn’t have any major concerns based
on my review.

### Project Skeleton

All tasks were completed satisfactorily. 

### Formatting & Display

Overall, your tables and figures are well-organized and clear. There is one 
table in the "Data" section with column titles that could be formatted a little
more cleanly for easier reading. I also noticed a few small typos in some of the
table captions, but they should be easy to fix.

### Code Quality

The code quality is generally good, but there are a few minor linter issues. The
comments could be more frequent and clearer in some places. For example, the
comment `#checking dupolicate so that github will not block it` should be 
corrected to "duplicate," and the explanation could be clarified to avoid
confusion–what duplicate are you referring to, and how does it block GitHub? 
It might confuse someone else reading the code.

Also, I would recommend moving all library imports to the top of the script, 
as that is typically considered standard practice.

### Data Preparation

The data preparation looks solid overall. I like how you handle missing files 
and JSON parsing failures–it demonstrates strong defensive programming.

### Extra Credit

Additional two points for using quarto's video support!
  • Positive tone
  • Detailed comments
  • Noted issues & gave suggestions on how to fix
  • Noted unclear sections for improvement

MP#01 - Peer Feedback

Lack of prior experience is not a hinderance here:

  • If something is unclear to you, that’s a problem!
  • Nothing required super-complex code, so anything overly complex probably could have been a simpler way (except for some above-and-beyond stuff)
  • You don’t have to be definitive in comments - impressions and questions are just as helpful.

Please ask on Teams if you have any questions.

Check-In Presentations

Next Week: Mid-Semester Check-In Presentations (Official Description)

I’m looking for:

  • Finalized overarching question
  • Locking in on specific questions
  • Evaluation of relevant data sources
    • Anticipated data or statistical challenges
  • Engagement with existing literature
  • 6 minutes

Mainly, I want to see that you will be able to succeed

Data Sources

When using ‘found’ data, two important questions to ask:

  • Quality: Does the data do what it claims to?
    • Exhaustiveness, sampling error, sampling bias, missingness
  • Suitability: Does the data do what you need it to?
    • Right ‘unit of analysis’, construct alignment

Your goal is not to find perfect data sets; your goal is to understand the strengths and weaknesses of what you can access and to work with them accordingly.

Prior Art

Context and Novelty: Why is your project worth doing and what does it add to the conversation?

  • What else have people said on your topic?
    • What is missing?
  • What do you have to add to this conversation? (Novelty)
    • New data set, new way of measuring, new style of analysis

A research project is not just summarization of other work: how can you contribute something new?

Possible forms of Novelty:

  • New time period (post-COVID)
  • Applying an Analysis from LA to NYC
  • Using an alternative data source

How does your project complement and contrast work already done on the topic?

Project Advice

General Advice:

  • Work on as small a scale as possible
  • Leave room to demonstrate your coding skill: if you can’t
    demonstrate the skills of this class, your SQ may be too small
  • Plan how to integrate your findings: if you find 5 factors are all correlated with response, how can you identify which ones are most important?

After presentations: 100% optional intro to SQL

Course Support

  • Synchronous - MW Office Hours 2x / week:
    • Tuesdays 5pm: Zoom
    • Thursdays 5pm: Zoom
  • Asynchronous: Teams

See Week 02 Slides for advice on asking good questions - Good questions get faster answers

Ask early for help with MPs - fixing problems is slow and iterative

Review Exercise

GMT Anomaly

CDIAC estimates of global temperature

cdiac data in CVXR package or via my GitHub:

cdiac <- read_csv("https://michael-weylandt.com/STA9750/cdiac.csv")
cdiac
# A tibble: 166 × 14
    year    jan    feb    mar    apr    may    jun    jul    aug    sep    oct
   <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
 1  1850 -0.702 -0.284 -0.732 -0.57  -0.325 -0.213 -0.128 -0.233 -0.444 -0.452
 2  1851 -0.303 -0.362 -0.485 -0.445 -0.302 -0.189 -0.215 -0.153 -0.108 -0.063
 3  1852 -0.308 -0.477 -0.505 -0.559 -0.209 -0.038 -0.016 -0.195 -0.125 -0.216
 4  1853 -0.177 -0.33  -0.318 -0.352 -0.268 -0.179 -0.059 -0.148 -0.409 -0.359
 5  1854 -0.36  -0.28  -0.284 -0.349 -0.23  -0.215 -0.228 -0.163 -0.115 -0.188
 6  1855 -0.176 -0.4   -0.303 -0.217 -0.336 -0.16  -0.268 -0.159 -0.339 -0.211
 7  1856 -0.119 -0.373 -0.513 -0.371 -0.119 -0.288 -0.297 -0.305 -0.459 -0.384
 8  1857 -0.512 -0.344 -0.434 -0.646 -0.567 -0.31  -0.544 -0.327 -0.393 -0.467
 9  1858 -0.532 -0.707 -0.55  -0.517 -0.651 -0.58  -0.324 -0.28  -0.339 -0.2  
10  1859 -0.307 -0.192 -0.334 -0.203 -0.31  -0.25  -0.285 -0.104 -0.575 -0.255
# ℹ 156 more rows
# ℹ 3 more variables: nov <dbl>, dec <dbl>, annual <dbl>

Temperature Anomaly

Mann, Bradley, & Hughes. Geophysical Research Letters 26(6). 1999. (via Wikipedia)

GMT Anomaly

In breakout rooms:

  1. In what year was the highest annual anomaly observed? The lowest?
  2. For how many months was 2015 the highest anomaly recorded?
  3. For how many years did July have the largest anomaly of that year?

See Lab #06 for details.

Breakout Rooms

Breakout Rooms
Please join the Breakout Room for your Project Team
Breakout #01: Breathe Easy (T) + Emergency Care Bears (R) Breakout #02: Degrees of Freedom (T) + Team Nexus (R)
Breakout #03: Table for One (T) + Zipliners (R) Breakout #04: EconoMates (T) + xG Analytics (R)
Breakout #05: CUNY-naughts (T) + Data Vinci (R) Breakout #06: Beat The Odds (T) + King Philip (R)
Breakout #07: Team Rocket (T) + TeamTZ (R) Breakout #08: CitiThatNeverSleeps (T) + Collide-o-Scope (R)
Breakout #09: Crude Awakening (T) + Unemployment Hub (R) Breakout #10: Beyond the Scale (T) + NYC Housing Pulse (R)

Go to your team’s breakout room. I will adjust / merge rooms based to ensure every room has enough people.

Aside: Oak Ridge

Calutron Girls

Via Wikipedia:

Plotting

Why Visualization?

Why do we visualize data?

  • Data exploration and understanding
  • Hypothesis generation
  • Data communication
  • Humans are better at visuals than numbers
  • Allow the data to surprise you

Why Visualization?

Same \(\mu_X, \mu_Y, \sigma_X, \sigma_Y, \rho_{XY}, \beta_{Y|X}, \dots\) - OLS can’t distinguish

Why Visualization?

Modeling and visualizing are not sequential:

  • Build a model, where does it fail?
  • See a pattern, does it hold up in a model / test?

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Examples from Viz.WTF:

Spot the Problem!

Spot the Problem!

From dadtawrapper.de:

Zero-Based Axes?

CDIAC Data:

Zero-Based Axes?

CDIAC Data:

Zero-Based Axes?

CDIAC Data:

Zero-Based Axes?

CDIAC Data:

Practical Advice: use the natural scale of the problem

Plotting with ggplot2

ggplot2

ggplot2:

  • Grammar of Graphics Ploting, Version 2
  • Structured Plotting
    • Plotting to express statistical visualization
    • Not raw shapes and colors (“graphics primitives”)
  • Make it easy to make good visualizations

ggplot2

ggplot2 provides a system (“grammar”) for visualizations:

  • geom_s: the actual thing to be plotted (points, lines, etc.)
  • aes (aesthetics): mapping of aspects of data
  • scale_s: control mapping from ‘data space’ to ‘graphics space’
  • theme: basic non-data-dependent plot elements
  • guides: legends
  • stat_s: transformations of data used to plot (CDF, histogram counts)

ggplot2 - Worked Example

Let’s plot the penguins data. To avoid warnings, use a no-NA version:

library(tidyverse)
penguins_ok <- penguins |> drop_na()
ggplot(penguins_ok)

ggplot2 - Worked Example

Need to map specific variables to aspects of plots: aes mapping

ggplot(penguins_ok, 
       aes(x=flipper_len, y=body_mass))

  • Map flipper_len to \(x\)-axis
  • Map body_mass to \(y\)-axis
  • Basic labels and ticks auto-generated
  • Still nothing in the plot!

ggplot2 - Worked Example

Add a geom_ to draw plot elements

ggplot(penguins_ok, 
       aes(x=flipper_len, y=body_mass)) + 
       geom_point()

  • \((x, y)\) coordinates inherited from aes
  • Default simple points
  • Plot elements are added
    • Combining elements into a single plot, not sequencing (no |>)

ggplot2 - Worked Example

Replace default labels:

ggplot(penguins_ok, 
       aes(x=flipper_len, y=body_mass)) + 
       geom_point() + 
       xlab("Flipper Length (mm)") + 
       ylab("Body Mass (g)") + 
       ggtitle("Body Mass vs Flipper Length for 333 Penguins")

  • Helper functions xlab, ylab, ggtitle

  • Can also use labs() directly with named arguments:

    + labs(x = "Flipper Length(mm)", 
           y = "Body Mass (g)", 
           title="Body Mass vs Flipper Length for 333 Penguins")

ggplot2 - Worked Example

Add color aesthetic

ggplot(penguins_ok, 
       aes(x=flipper_len, y=body_mass, color=species)) + 
       geom_point() + 
       xlab("Flipper Length (mm)") + 
       ylab("Body Mass (g)") + 
       ggtitle("Body Mass vs Flipper Length for 333 Penguins")

  • Additional aesthetic (color) inherited by geom_point
  • Automatic identification of categorical (factor) data

ggplot2 - Worked Example

Replace default color scale:

ggplot(penguins_ok, 
       aes(x=flipper_len, y=body_mass, color=species)) + 
       geom_point() + 
       xlab("Flipper Length (mm)") + 
       ylab("Body Mass (g)") + 
       ggtitle("Body Mass vs Flipper Length for 333 Penguins") + 
       scale_color_brewer(name="Species", type="qual", palette=2)

  • Override default color scale with scale_color_brewer
  • Colors taken from work of Cynthia Brewer (PSU)
  • Using a qualitative palette here because no order to species

ggplot2 - Worked Example

Change theme for non-data elements:

ggplot(penguins_ok, 
       aes(x=flipper_len, y=body_mass, color=species)) + 
       geom_point() + 
       xlab("Flipper Length (mm)") + 
       ylab("Body Mass (g)") + 
       ggtitle("Body Mass vs Flipper Length for 333 Penguins") + 
       scale_color_brewer(name="Species", type="qual", palette=2) + 
       theme_bw()

  • Default theme_grey()
  • Replace by theme_bw() (Black & White)
  • Many more themes available

ggplot2 - Worked Example

Override default aesthetic to change shape of points:

ggplot(penguins_ok, 
       aes(x=flipper_len, y=body_mass, color=species)) + 
       geom_point(shape=15) + 
       xlab("Flipper Length (mm)") + 
       ylab("Body Mass (g)") + 
       ggtitle("Body Mass vs Flipper Length for 333 Penguins") + 
       scale_color_brewer(name="Species", type="qual", palette=2) + 
       theme_bw()

  • Override default shape aesthetic
  • Provide directly to geom_point, not via aes since not data dependent
  • See ?scale_shape_discrete for table of values

ggplot2 - Worked Example

Add trend lines with stat_smooth:

ggplot(penguins_ok, 
       aes(x=flipper_len, y=body_mass, color=species)) + 
       geom_point(shape=15) + 
       stat_smooth(method="lm", se=FALSE) + 
       xlab("Flipper Length (mm)") + ylab("Body Mass (g)") + 
       ggtitle("Body Mass vs Flipper Length for 333 Penguins") + 
       scale_color_brewer(name="Species", type="qual", palette=2) + 
       theme_bw()

  • stat_s implement transformations
  • stat_smooth marks a trend
  • Specify use of OLS (lm = linear model) + disable SE shading

ggplot2 - Worked Example

Break data into subplots (“facets”) to avoid over-plotting:

ggplot(penguins_ok, 
       aes(x=flipper_len, y=body_mass, color=species)) + 
       geom_point(shape=15) + stat_smooth(method="lm", se=FALSE) + 
       xlab("Flipper Length (mm)") + ylab("Body Mass (g)") + 
       ggtitle("Body Mass vs Flipper Length for 333 Penguins") + 
       scale_color_brewer(name="Species", type="qual", palette=2) + 
       theme_bw() + facet_wrap(~species)

  • facet_wrap (split by one grouping) or facet_grid (show all pairs of groups)
  • group_by of plotting
  • Called “small multiples”

ggplot2 - Worked Example

Remove redundant legend:

ggplot(penguins_ok, 
       aes(x=flipper_len, y=body_mass, color=species)) + 
       geom_point(shape=15) + stat_smooth(method="lm", se=FALSE) + 
       xlab("Flipper Length (mm)") + ylab("Body Mass (g)") + 
       ggtitle("Body Mass vs Flipper Length for 333 Penguins") + 
       scale_color_brewer(name="Species", type="qual", palette=2) + 
       theme_bw() + facet_wrap(~species) + 
       guides(color="none")

  • guides controls legends (also via scale_*)
  • Here redundant with facet labels

ggplot2 Workflow

Take a sad plot and make it better

Start with exploratory graphics:

  • Quick and easy
  • Find the story you want to tell
  • Let the data drive you
  • More use of raw data

Iterate to publication-quality graphics:

  • Repeat to improve quality
  • Tell a story to your reader
  • More use of transformations

ggplot2 Workflow

Many ways to get the same result

Practice Exercise

ggplot2 provides diamonds data of ~54K diamonds

  • Size, quality, price
  • 4 C’s of Diamonds: Color, Cut, Clarity, Carat (Weight)

Return to breakout rooms for Practice Activity #01

Custom Themes

The theme mechanism provides extensive opportunities to customize:

library(ggplot2)
theme_grey() # Default theme
<theme> List of 144
 $ line                            : <ggplot2::element_line>
  ..@ colour       : chr "black"
  ..@ linewidth    : num 0.5
  ..@ linetype     : num 1
  ..@ lineend      : chr "butt"
  ..@ linejoin     : chr "round"
  ..@ arrow        : logi FALSE
  ..@ arrow.fill   : chr "black"
  ..@ inherit.blank: logi TRUE
 $ rect                            : <ggplot2::element_rect>
  ..@ fill         : chr "white"
  ..@ colour       : chr "black"
  ..@ linewidth    : num 0.5
  ..@ linetype     : num 1
  ..@ linejoin     : chr "round"
  ..@ inherit.blank: logi TRUE
 $ text                            : <ggplot2::element_text>
  ..@ family       : chr ""
  ..@ face         : chr "plain"
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : chr "black"
  ..@ size         : num 11
  ..@ hjust        : num 0.5
  ..@ vjust        : num 0.5
  ..@ angle        : num 0
  ..@ lineheight   : num 0.9
  ..@ margin       : <ggplot2::margin> num [1:4] 0 0 0 0
  ..@ debug        : logi FALSE
  ..@ inherit.blank: logi TRUE
 $ title                           : <ggplot2::element_text>
  ..@ family       : NULL
  ..@ face         : NULL
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : NULL
  ..@ size         : NULL
  ..@ hjust        : NULL
  ..@ vjust        : NULL
  ..@ angle        : NULL
  ..@ lineheight   : NULL
  ..@ margin       : NULL
  ..@ debug        : NULL
  ..@ inherit.blank: logi TRUE
 $ point                           : <ggplot2::element_point>
  ..@ colour       : chr "black"
  ..@ shape        : num 19
  ..@ size         : num 1.5
  ..@ fill         : chr "white"
  ..@ stroke       : num 0.5
  ..@ inherit.blank: logi TRUE
 $ polygon                         : <ggplot2::element_polygon>
  ..@ fill         : chr "white"
  ..@ colour       : chr "black"
  ..@ linewidth    : num 0.5
  ..@ linetype     : num 1
  ..@ linejoin     : chr "round"
  ..@ inherit.blank: logi TRUE
 $ geom                            : <ggplot2::element_geom>
  ..@ ink        : chr "black"
  ..@ paper      : chr "white"
  ..@ accent     : chr "#3366FF"
  ..@ linewidth  : num 0.5
  ..@ borderwidth: num 0.5
  ..@ linetype   : int 1
  ..@ bordertype : int 1
  ..@ family     : chr ""
  ..@ fontsize   : num 3.87
  ..@ pointsize  : num 1.5
  ..@ pointshape : num 19
  ..@ colour     : NULL
  ..@ fill       : NULL
 $ spacing                         : 'simpleUnit' num 5.5points
  ..- attr(*, "unit")= int 8
 $ margins                         : <ggplot2::margin> num [1:4] 5.5 5.5 5.5 5.5
 $ aspect.ratio                    : NULL
 $ axis.title                      : NULL
 $ axis.title.x                    : <ggplot2::element_text>
  ..@ family       : NULL
  ..@ face         : NULL
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : NULL
  ..@ size         : NULL
  ..@ hjust        : NULL
  ..@ vjust        : num 1
  ..@ angle        : NULL
  ..@ lineheight   : NULL
  ..@ margin       : <ggplot2::margin> num [1:4] 2.75 0 0 0
  ..@ debug        : NULL
  ..@ inherit.blank: logi TRUE
 $ axis.title.x.top                : <ggplot2::element_text>
  ..@ family       : NULL
  ..@ face         : NULL
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : NULL
  ..@ size         : NULL
  ..@ hjust        : NULL
  ..@ vjust        : num 0
  ..@ angle        : NULL
  ..@ lineheight   : NULL
  ..@ margin       : <ggplot2::margin> num [1:4] 0 0 2.75 0
  ..@ debug        : NULL
  ..@ inherit.blank: logi TRUE
 $ axis.title.x.bottom             : NULL
 $ axis.title.y                    : <ggplot2::element_text>
  ..@ family       : NULL
  ..@ face         : NULL
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : NULL
  ..@ size         : NULL
  ..@ hjust        : NULL
  ..@ vjust        : num 1
  ..@ angle        : num 90
  ..@ lineheight   : NULL
  ..@ margin       : <ggplot2::margin> num [1:4] 0 2.75 0 0
  ..@ debug        : NULL
  ..@ inherit.blank: logi TRUE
 $ axis.title.y.left               : NULL
 $ axis.title.y.right              : <ggplot2::element_text>
  ..@ family       : NULL
  ..@ face         : NULL
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : NULL
  ..@ size         : NULL
  ..@ hjust        : NULL
  ..@ vjust        : num 1
  ..@ angle        : num -90
  ..@ lineheight   : NULL
  ..@ margin       : <ggplot2::margin> num [1:4] 0 0 0 2.75
  ..@ debug        : NULL
  ..@ inherit.blank: logi TRUE
 $ axis.text                       : <ggplot2::element_text>
  ..@ family       : NULL
  ..@ face         : NULL
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : chr "#4D4D4DFF"
  ..@ size         : 'rel' num 0.8
  ..@ hjust        : NULL
  ..@ vjust        : NULL
  ..@ angle        : NULL
  ..@ lineheight   : NULL
  ..@ margin       : NULL
  ..@ debug        : NULL
  ..@ inherit.blank: logi TRUE
 $ axis.text.x                     : <ggplot2::element_text>
  ..@ family       : NULL
  ..@ face         : NULL
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : NULL
  ..@ size         : NULL
  ..@ hjust        : NULL
  ..@ vjust        : num 1
  ..@ angle        : NULL
  ..@ lineheight   : NULL
  ..@ margin       : <ggplot2::margin> num [1:4] 2.2 0 0 0
  ..@ debug        : NULL
  ..@ inherit.blank: logi TRUE
 $ axis.text.x.top                 : <ggplot2::element_text>
  ..@ family       : NULL
  ..@ face         : NULL
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : NULL
  ..@ size         : NULL
  ..@ hjust        : NULL
  ..@ vjust        : num 0
  ..@ angle        : NULL
  ..@ lineheight   : NULL
  ..@ margin       : <ggplot2::margin> num [1:4] 0 0 2.2 0
  ..@ debug        : NULL
  ..@ inherit.blank: logi TRUE
 $ axis.text.x.bottom              : NULL
 $ axis.text.y                     : <ggplot2::element_text>
  ..@ family       : NULL
  ..@ face         : NULL
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : NULL
  ..@ size         : NULL
  ..@ hjust        : num 1
  ..@ vjust        : NULL
  ..@ angle        : NULL
  ..@ lineheight   : NULL
  ..@ margin       : <ggplot2::margin> num [1:4] 0 2.2 0 0
  ..@ debug        : NULL
  ..@ inherit.blank: logi TRUE
 $ axis.text.y.left                : NULL
 $ axis.text.y.right               : <ggplot2::element_text>
  ..@ family       : NULL
  ..@ face         : NULL
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : NULL
  ..@ size         : NULL
  ..@ hjust        : num 0
  ..@ vjust        : NULL
  ..@ angle        : NULL
  ..@ lineheight   : NULL
  ..@ margin       : <ggplot2::margin> num [1:4] 0 0 0 2.2
  ..@ debug        : NULL
  ..@ inherit.blank: logi TRUE
 $ axis.text.theta                 : NULL
 $ axis.text.r                     : <ggplot2::element_text>
  ..@ family       : NULL
  ..@ face         : NULL
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : NULL
  ..@ size         : NULL
  ..@ hjust        : num 0.5
  ..@ vjust        : NULL
  ..@ angle        : NULL
  ..@ lineheight   : NULL
  ..@ margin       : <ggplot2::margin> num [1:4] 0 2.2 0 2.2
  ..@ debug        : NULL
  ..@ inherit.blank: logi TRUE
 $ axis.ticks                      : <ggplot2::element_line>
  ..@ colour       : chr "#333333FF"
  ..@ linewidth    : NULL
  ..@ linetype     : NULL
  ..@ lineend      : NULL
  ..@ linejoin     : NULL
  ..@ arrow        : logi FALSE
  ..@ arrow.fill   : chr "#333333FF"
  ..@ inherit.blank: logi TRUE
 $ axis.ticks.x                    : NULL
 $ axis.ticks.x.top                : NULL
 $ axis.ticks.x.bottom             : NULL
 $ axis.ticks.y                    : NULL
 $ axis.ticks.y.left               : NULL
 $ axis.ticks.y.right              : NULL
 $ axis.ticks.theta                : NULL
 $ axis.ticks.r                    : NULL
 $ axis.minor.ticks.x.top          : NULL
 $ axis.minor.ticks.x.bottom       : NULL
 $ axis.minor.ticks.y.left         : NULL
 $ axis.minor.ticks.y.right        : NULL
 $ axis.minor.ticks.theta          : NULL
 $ axis.minor.ticks.r              : NULL
 $ axis.ticks.length               : 'rel' num 0.5
 $ axis.ticks.length.x             : NULL
 $ axis.ticks.length.x.top         : NULL
 $ axis.ticks.length.x.bottom      : NULL
 $ axis.ticks.length.y             : NULL
 $ axis.ticks.length.y.left        : NULL
 $ axis.ticks.length.y.right       : NULL
 $ axis.ticks.length.theta         : NULL
 $ axis.ticks.length.r             : NULL
 $ axis.minor.ticks.length         : 'rel' num 0.75
 $ axis.minor.ticks.length.x       : NULL
 $ axis.minor.ticks.length.x.top   : NULL
 $ axis.minor.ticks.length.x.bottom: NULL
 $ axis.minor.ticks.length.y       : NULL
 $ axis.minor.ticks.length.y.left  : NULL
 $ axis.minor.ticks.length.y.right : NULL
 $ axis.minor.ticks.length.theta   : NULL
 $ axis.minor.ticks.length.r       : NULL
 $ axis.line                       : <ggplot2::element_blank>
 $ axis.line.x                     : NULL
 $ axis.line.x.top                 : NULL
 $ axis.line.x.bottom              : NULL
 $ axis.line.y                     : NULL
 $ axis.line.y.left                : NULL
 $ axis.line.y.right               : NULL
 $ axis.line.theta                 : NULL
 $ axis.line.r                     : NULL
 $ legend.background               : <ggplot2::element_rect>
  ..@ fill         : NULL
  ..@ colour       : logi NA
  ..@ linewidth    : NULL
  ..@ linetype     : NULL
  ..@ linejoin     : NULL
  ..@ inherit.blank: logi TRUE
 $ legend.margin                   : NULL
 $ legend.spacing                  : 'rel' num 2
 $ legend.spacing.x                : NULL
 $ legend.spacing.y                : NULL
 $ legend.key                      : NULL
 $ legend.key.size                 : 'simpleUnit' num 1.2lines
  ..- attr(*, "unit")= int 3
 $ legend.key.height               : NULL
 $ legend.key.width                : NULL
 $ legend.key.spacing              : NULL
 $ legend.key.spacing.x            : NULL
 $ legend.key.spacing.y            : NULL
 $ legend.key.justification        : NULL
 $ legend.frame                    : NULL
 $ legend.ticks                    : NULL
 $ legend.ticks.length             : 'rel' num 0.2
 $ legend.axis.line                : NULL
 $ legend.text                     : <ggplot2::element_text>
  ..@ family       : NULL
  ..@ face         : NULL
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : NULL
  ..@ size         : 'rel' num 0.8
  ..@ hjust        : NULL
  ..@ vjust        : NULL
  ..@ angle        : NULL
  ..@ lineheight   : NULL
  ..@ margin       : NULL
  ..@ debug        : NULL
  ..@ inherit.blank: logi TRUE
 $ legend.text.position            : NULL
 $ legend.title                    : <ggplot2::element_text>
  ..@ family       : NULL
  ..@ face         : NULL
  ..@ italic       : chr NA
  ..@ fontweight   : num NA
  ..@ fontwidth    : num NA
  ..@ colour       : NULL
  ..@ size         : NULL
  ..@ hjust        : num 0
  ..@ vjust        : NULL
  ..@ angle        : NULL
  ..@ lineheight   : NULL
  ..@ margin       : NULL
  ..@ debug        : NULL
  ..@ inherit.blank: logi TRUE
 $ legend.title.position           : NULL
 $ legend.position                 : chr "right"
 $ legend.position.inside          : NULL
 $ legend.direction                : NULL
 $ legend.byrow                    : NULL
 $ legend.justification            : chr "center"
 $ legend.justification.top        : NULL
 $ legend.justification.bottom     : NULL
 $ legend.justification.left       : NULL
 $ legend.justification.right      : NULL
 $ legend.justification.inside     : NULL
  [list output truncated]
 @ complete: logi TRUE
 @ validate: logi TRUE

Custom Themes

From the Tidyverse Blog:

Custom Themes

Best practice:

  • Pick a starting theme you like and customize
  • theme_STARTER() + theme(things.i.change="to.new.values")

Custom Themes

ggplot2 has 8 built-in themes. The ggthemes and ThemePark packages has many more!

Let’s define a basic theme and see how it is rendered in different themes:

my_plot <- ggplot(penguins_ok, 
       aes(x=flipper_len, y=body_mass, color=species)) + 
       geom_point(shape=15) + stat_smooth(method="lm", se=FALSE) + 
       xlab("Flipper Length (mm)") + ylab("Body Mass (g)") + 
       ggtitle("Body Mass vs Flipper Length for 333 Penguins") + 
       facet_wrap(~species) + guides(color="none")

Reminder: ggplot2 only displays plot when printed so assign to variable if you want to keep modifying

Custom Themes

Default theme (theme_grey())

my_plot + theme_grey() # Default: can omit

Custom Themes

Black-and-White theme (ggplot2::theme_bw()) - MW’s favorite

my_plot + theme_bw()

Custom Themes

Minimal theme (ggplot2::theme_minimal())

my_plot + theme_minimal()

Custom Themes

Light theme (ggplot2::theme_light())

my_plot + theme_light()

Custom Themes

Dark theme (ggplot2::theme_dark())

my_plot + theme_dark()

Custom Themes

Old-school MS Excel theme (ggthemes::theme_excel())

library(ggthemes)
my_plot + theme_excel()

Custom Themes

Google Docs theme (ggthemes::theme_gdocs())

library(ggthemes)
my_plot + theme_gdocs()

Custom Themes

Economist theme (ggthemes::theme_economist())

library(ggthemes)
my_plot + theme_economist()

Custom Themes

Wall St Journal theme (ggthemes::theme_wsj())

library(ggthemes)
my_plot + theme_wsj()

Custom Themes

Edward Tufte theme (ggthemes::theme_tufte())

library(ggthemes)
my_plot + theme_tufte()

Custom Themes

Barbie theme (ThemePark::theme_barbie())

library(ThemePark)
my_plot + theme_barbie()

Custom Themes

Oppenheimer theme (ThemePark::theme_oppenheimer())

library(ThemePark)
my_plot + theme_oppenheimer()

Custom Themes

Simpsons theme (ThemePark::theme_simpsons())

library(ThemePark)
my_plot + theme_simpsons()

Custom Themes

Spiderman theme (ThemePark::theme_spiderman())

library(ThemePark)
my_plot + theme_spiderman()

Custom Themes

Game of Thrones theme (ThemePark::theme_gameofthrones())

library(ThemePark)
my_plot + theme_gameofthrones()

Custom Themes

Avatar theme (ThemePark::theme_avatar())

library(ThemePark)
my_plot + theme_avatar()

Custom Themes

Most of these are silly

Adapt and extend!

Color Palettes

Three types of color palettes:

  • Sequential: ordered from 0 to “high”
    • Example: rain forecast in different areas
  • Diverging: ordered from -X to +Y with meaningful 0 in the middle
    • Example: political leaning
  • Qualitative: no ordering
    • Example: penguin species

Two ways to make a color scale for quantitative variables:

  • Binned: \([0, 1)\) light green, \([1, 3)\) medium green; \([3, 5]\) dark green
  • Continuous

Color Palettes

I often rely on the work of Cynthia Brewer

https://colorbrewer2.org

  • Officially for cartography, but generally useful
  • Different (punny) ggplot2 names for different derived scales

Color Palettes

scale_color_brewer() for discrete scales

library(ggplot2)
ggplot(penguins_ok, aes(x=bill_len, y=bill_dep, color=species)) + 
    geom_point() + theme_bw() + 
    scale_color_brewer(type="qual") # Qualitative

Color Palettes

scale_color_distiller() for continuous scales

library(ggplot2)
ggplot(penguins_ok, aes(x=bill_len, y=bill_dep, color=body_mass)) + 
    geom_point() + theme_bw() + 
    scale_color_distiller(type="seq") # Continuous

Color Palettes

scale_color_fermenter() for binned scales

library(ggplot2)
ggplot(penguins_ok, aes(x=bill_len, y=bill_dep, color=body_mass)) + 
    geom_point() + theme_bw() + 
    scale_color_fermenter(type="seq") # Binned

Color Palettes

library(ggplot2)
ggplot(penguins_ok, aes(x=bill_len, y=bill_dep, color=body_mass)) + 
    geom_point() + theme_bw() + 
    scale_color_fermenter(type="seq") # Binned + Sequential

Color Palettes

library(ggplot2)
ggplot(penguins_ok, aes(x=bill_len, y=bill_dep, color=body_mass)) + 
    geom_point() + theme_bw() +
    scale_color_fermenter(type="qual") # Binned + Qualitative

Color Palettes

library(ggplot2)
ggplot(penguins_ok, aes(x=bill_len, y=bill_dep, color=body_mass)) + 
    geom_point() + theme_bw() + 
    scale_color_fermenter(type="div") # Binned + Diverging

“Hard-coding” Colors

scale_color_identity will take color names from a column:

penguins_ok |>
    mutate(color_column = if_else(species == "Adelie", "blue", "red")) |>
    ggplot(aes(x=bill_len, y=bill_dep, color=color_column)) + 
    geom_point() + theme_bw() + 
    scale_color_identity(name="Species")

Practice Exercise

Return to the cdiac data from the Warm-Up

  • Visualize trends in time series
  • geom_line to create line plots

Return to breakout rooms for Practice Activity #02

Simpson’s Paradox

Data Visualization can be used to find counterintuitive trends in data:

ggplot(simpsons_paradox, aes(x=x, y=y)) + 
    geom_point() + stat_smooth(method="lm")

Simpson’s Paradox

Overall trend does not need to match trend within groups

ggplot(simpsons_paradox, aes(x=x, y=y, color=group)) + 
    geom_point() + stat_smooth(method="lm") + facet_grid(~group)

Modeling: ANCOVA or Mixed-Effects Regression

UCB Graduate Admissions

1973: UC Berkeley was concerned about bias in Grad School Admissions

  • Higher fraction of men admitted than women
  • Bickel, Hammell, O’Connell asked to study
    • When they try to find the source of this bias, there is none!
    • Each department admits women at a higher rate than men
    • Women applied to more selective programs at a higher rate

This phenomenon occurs throughout the social sciences: the best doctors have the worst patient outcomes

BHO note:

Women are shunted by their socialization and education toward fields of graduate study that are generally more crowded, less productive of completed degrees, and less well funded, and that frequently offer poorer professional employment prospects.

Red State Blue State

Red State Blue State

Facts about US politics (c. 2008):

  1. Rich people vote Republican at higher rates
  2. Rich states vote Republican at lower rates
  3. Rich states are rich because they have richer people

How can we reconcile this?

Red State Blue State

Figure from Gelman et al, Q.J.Poli.Sci. 2007.

For more, see this presentation.

Additional Resources

Textbook: R for Data Science, Part II: Visualize

General advice on data science projects:

Course Administration

Plotting FAQs

ggplot2 vs Tableau

  • Tableau
    • $$$
    • IT department automatically integrates with data sources
    • Easy, if it does what you want
  • ggplot2
    • Free
    • Can use arbitrary data sources, with effort
    • Flexible / customizable

ggplot2 vs matplotlib

  • ggplot2
    • Data visualizations
    • Enforces “good practice” via gg
  • matplotlib
    • Scientific visualizations
    • More flexible for good or for ill
    • Inspired by Matlab plotting

Closest Python analogue to ggplot2 is seaborn

Why use + instead of |>

  • ggplot2 is older than |>
  • Per H. Wickham: if ggplot3 ever gets made, will use |>
  • Unlikely to change: too much code depends on it

Performance

I tried an interactive plot with \(n=132,000\) points, but it brought my computer to a halt. [Ed. Paraphrased]

That’s a lot of plots!!

ggplot2 is itself pretty fast, but it depends on (possibly slow) graphics backends

  • Different file types implement graphics differently.
  • You should also think about overplotting / pre-processing

We’ll talk more about interactivity next week

Overplotting

Large data sets can lead to overplotting:

  • Points “on top of” each other
  • Can also occur with “designed” experiments / rounded data

Ways to address:

  • geom_jitter
  • geom_hex

Overplotting

Jitter: add a bit of random noise so points don’t step on each other

library(ggplot2); library(patchwork)
p <- ggplot(mpg, aes(cyl, hwy))
p1 <- p + geom_point() + ggtitle("geom_point")
p2 <- p + geom_jitter() + ggtitle("geom_jitter")
p1 + p2 # Patchwork lets us "add" plots

Hexagonal Binning

Little “heatmaps” of counts. Hexagons to avoid weird rounding artifacts

library(ggplot2); library(patchwork)
p <- ggplot(diamonds, aes(carat, price))
p1 <- p + geom_point() + ggtitle("geom_point")
p2 <- p + geom_hex() + ggtitle("geom_hex")
p1 + p2 # Patchwork lets us 'add' plots

Inside vs. Outside aes()

aes maps data to values. Outside of aes, set constant value

library(ggplot2)
ggplot(penguins_ok, 
       aes(x=bill_len, y=bill_dep, color=species)) +
    geom_point()

Inside vs. Outside aes()

aes maps data to values. Outside of aes, set constant value

library(ggplot2)
ggplot(penguins_ok, 
       aes(x=bill_len, y=bill_dep))+ geom_point(color="blue")

Global vs geom_ specific aes()

  • Elements set in ggplot() apply to entire plot
  • Elements set in specific geom apply there only
    • Override globals
library(ggplot2)
ggplot(penguins_ok, 
       aes(x=bill_len, y=bill_dep, color=species))+
    stat_smooth() + geom_point()

Global vs geom_ specific aes()

  • Elements set in ggplot() apply to entire plot
  • Elements set in specific geom apply there only
    • Override globals
library(ggplot2)
ggplot(penguins_ok, 
       aes(x=bill_len, y=bill_dep)) +
    stat_smooth() + geom_point(aes(color=species))

How to Select geoms

Two “modes”

  • Exploratory data analysis: Quick, rapid iteration, for your eyes only
    • Let the data tell you a story
    • Low pre-processing: scatter plots, lines, histograms
  • “Publication quality”: Polished, for someone else
    • You tell the reader a story
    • More processing, more modeling: trends, line segments, ribbons

Order of Layers

Order of layers technically matters, but the effect is small

p1 <- ggplot(penguins_ok, aes(x=bill_len, y=flipper_len)) +
        geom_point(color="black") + 
        stat_smooth(color="blue", method="lm") + ggtitle("Line on points")
p2 <- ggplot(penguins_ok, aes(x=bill_len, y=flipper_len)) +
        stat_smooth(color="blue", method="lm") + 
        geom_point(color="black") + ggtitle("Points on line")
p1 + p2

Order of Layers

Order matters more with theme. Adding a theme_*() will override any theme() customization you did:

p1 <- p1 + theme_bw() + theme(legend.position="bottom")
p2 <- p2 + theme(legend.position="bottom") + theme_bw() 
p1 + p2

stat_poly_line vs stat_smooth

By default stat_smooth fits a generalized additive model (GAM)

ggpmisc::stat_poly_line and stat_poly_eq fit linear models, so they can expose more machinery.

What is a GAM?

  • Take 9890 with me (typically Spring semester) to find out!
  • Free Course: “GAMs in R” from Noam Ross

Titles and Captions

ggplot() + 
    labs(title="Title", subtitle="Subtitle", caption="Caption",
         tag="Tag", alt="Alt-Text", alt_insight="Alt-Insight")

+ggtitle("text") is just shorthand for +labs(title="text")

Importance of Aesthetics

Perceptually:

  • Location > Color > Size > Shape

Humans are better at:

  • Length > Area > Volume

When to Facet?

Facets are group_by for plots. Useful for

  • Distinguishing intra- vs inter-group trends
  • Avoiding overplotting

Twin Axes Plots

How can I implement a dual (twin) axis plot in ggplot2?

Disfavored. But if you must …

sec.axis

Doesn’t allow arbitrary secondary axes; allows transformed axes (e.g., Celsius and Fahrenheit)

Embedding Images

See the ggimage or ggflags package for images as “points”:

if(!require("ggflags", quiet=TRUE)){
    devtools::install_github("jimjam-slam/ggflags"); 
}
library(ggflags)
d <- data.frame(x=rnorm(50), y=rnorm(50), 
                country=sample(c("ar","fr", "nz", "gb", "es", "ca"), 50, TRUE), 
                stringsAsFactors = FALSE)
ggplot(d, aes(x=x, y=y, country=country, size=x)) + 
  geom_flag() + scale_country()

Embedding Images

See cowplot::draw_image() for image background:

library(cowplot)
p <- ggplot(iris, aes(x = Sepal.Length, fill = Species)) +
  geom_density(alpha = 0.7) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) +
  theme_half_open(12)

logo_file <- system.file("extdata", "logo.png", package = "cowplot")
ggdraw() +
  draw_image(
    logo_file, scale = .7
  ) +
  draw_plot(p)

Wrap-Up

Review

ggplot2:

  • Structured graphics for statistical visualization
  • Grammar of Graphics for structured plotting
    • geom, stat, scale, aes, etc.
  • Integrates with dplyr, tidyr to get into suitable format

Orientation

  • Communicating Results (quarto) ✅
  • R Basics ✅
  • Data Manipulation in R ✅
  • Data Visualization in R ⬅️
    • Static Plots ⬅️
    • Interactivity, Maps, Animated Plots
  • Getting Data into R
  • Statistical Modeling in R

Next Time

  • Team Formation ✅
  • Team Contracts ✅
  • Proposal Presentations ✅
  • Mid-Semester Check-Ins ⬅️
  • Mid-Semester Peer Evaluations
  • Final Presentations
  • Final Reports
  • Final Peer Evaluations

No Pre-Assignment.

Next Time

Life Tip of the Week

Get Inspired!

The tools of this course are powerful and flexible

To learn more ways to apply them, check out ‘Galleries’:

Musical Treat