Lecture 7

Data Visualization I: Distributions and Group Comparisons

Byeong-Hak Choe

SUNY Geneseo

October 21, 2026

📊 One numeric distribution

🧭 A useful chart answers a question

  • Data visualization represents data through positions, lengths, colors, and other visual features.
  • Why use it? Charts make patterns easier to see than a long table of values and help us explain findings to others.
  • A distribution describes how values occur across a variable’s range: which values are common, uncommon, or absent.
  • Variation means how much the observed values differ from one another. Values may cluster closely or spread widely.
  • Our first question: How does city fuel economy vary across vehicles in the dataset?

🚗 Each row in mpg describes a vehicle

manufacturer model year cty drv class
audi a4 1999 18 f compact
audi a4 1999 21 f compact
audi a4 2008 20 f compact
audi a4 2008 21 f compact
  • mpg comes with ggplot2. It contains data on 234 vehicles from 1999 and 2008.
  • Each row describes a particular version of a vehicle model, not an individual car sold.

📏 Variable names need meanings and units

Column Meaning Type for this comparison
cty City fuel economy in miles per gallon Numeric
drv Drive system, coded 4, f, or r Categorical
class Vehicle class, such as compact or SUV Categorical
  • A higher miles-per-gallon (mpg) value means the vehicle travels farther per gallon of fuel.
  • A numeric value has a meaningful magnitude. A category identifies a group, even if its label looks like a number.

🧰 ggplot2 is part of the tidyverse

library(tidyverse)
mpg <- mpg    # ggplot2::mpg

?mpg  # description of the `mpg` data.frame
  • library(tidyverse) loads ggplot2 along with packages such as dplyr.
  • mpg is available after loading ggplot2. This example needs no CSV import.
  • The same plotting structure also works with a data frame we import ourselves.

📊 A histogram shows a distribution of a numeric variable

ggplot(data = mpg,
       mapping = aes(x = cty)) +
  geom_histogram()

Histogram of city fuel economy for 234 vehicles in the dataset. Most values fall between roughly 12 and 22 MPG, with a longer tail toward higher MPG.

  • A histogram groups numeric values into equal-width intervals called bins, arranged along the horizontal axis.
  • The height of each bar represents the number of observations in each bin. Here, the vertical axis counts vehicles.

🧩 Data, aesthetic mappings, and geometric objects

Essential component Its job in our histogram
Data frame: data = mpg Supplies the observations in rows and variables in columns
Aesthetic mappings: mapping = aes(x = cty) Connect variables to visual features, such as position or color
Geometric object: geom_histogram() Chooses how to draw the data. This geom groups values into bins and draws bars
  • Geom functions (geom_*()) draw objects such as bars, points, or lines. We choose the geom to match our question.
  • aes() is short for aesthetics. Here, x = cty maps city MPG to horizontal position. The geom_histogram() calculates the vertical counts automatically.
  • data = and mapping = are argument names. R lets us omit these names when we supply the arguments in that order.

➕ + connects the plot’s parts

ggplot(data = mpg,
       mapping = aes(x = cty)) +
  geom_histogram()
  • Put + at the end of a line when the plotting expression continues on the next line.
  • Each + adds a plot component. We can add more layers or settings with another +, such as labs() for labels.

At the R console

A + at the R console prompt means R is waiting for the rest of an unfinished command. Do not type that prompt into your code.

🖌️ A fixed appearance belongs outside aes()

ggplot(data = mpg,
       mapping = aes(x = cty)) +
  geom_histogram(
    fill = "steelblue",
    color = "black"
  )

City MPG histogram with blue interiors and black borders separating the bins.

  • fill controls the inside of the bars. color controls their outlines.
  • Both settings stay outside aes() because they give all bars one appearance. aes(x = cty) maps a data column.

🏷️ labs() provides readable labels

ggplot(data = mpg,
       mapping = aes(x = cty)) +
  geom_histogram(fill = "steelblue",
                 color = "white") +
  labs(x = "City fuel economy (MPG)",
       y = "Number of vehicles")

City MPG histogram with readable axes labeled City fuel economy (MPG) and Number of vehicles.

  • labs() changes displayed text. The column name remains cty.
  • Recognize what it does. It is optional in classwork and homework answers, while project charts need clear labels and units.

📏 Bin width changes the visible detail

Bin width: 1 MPG

City MPG histogram with one-MPG bins, showing several local peaks and gaps.

geom_histogram(binwidth = 1)

Bin width: 5 MPG

City MPG histogram with five-MPG bins, showing less detail than the one-MPG histogram of the same observations.

geom_histogram(binwidth = 5)
  • binwidth sets each interval’s width. These bins are 1 MPG or 5 MPG wide.
  • Distribution shape is sensitive to bin width. Narrow bins reveal detail. Wide bins can hide peaks or gaps. The 234 rows stay the same.

📊 Uniform, unimodal, and bimodal

Uniform

Example uniform histogram: ten equal-width bins each contain ten observations, giving equal bar heights.

Roughly similar bar heights across bins.

Unimodal

Example unimodal histogram: counts rise to one clear peak near the middle and then fall.

One clear peak (mode).

Bimodal

Example bimodal histogram: two clear peaks near 2.5 and 7.5, separated by a dip near the middle.

Two clear peaks separated by a dip.

🧭 Skewness, mean, median, and mode

Three single-peaked distribution examples. Left-skewed is also labeled negatively skewed, with a long left tail and mean below median below mode. The symmetric example has coincident mean, median, and mode. Right-skewed is also labeled positively skewed, with a long right tail and mode below median below mean.

  • Like a histogram, a smooth curve shows distribution shape. Skewness is asymmetry: one tail extends farther than the other.
  • Left (negative) skew has a longer tail toward smaller values. Right (positive) skew has a longer tail toward larger values. Follow the longer tail, not the side with most values.

🔎 Reading the city-MPG distribution

City fuel economy histogram with most vehicles near 12 to 22 MPG and a longer tail toward the high-MPG values. Observed city MPG ranges from 9 to 35.

  • Typical values: most vehicles cluster roughly between 12 and 22 MPG in city driving.
  • Spread and shape: values range from 9 to 35 MPG, with a longer tail toward higher MPG. This is right skew.
  • Unusual values: high-MPG vehicles deserve a closer look at their rows and context. A histogram alone cannot tell us why they differ.

✍️ Classwork 10: Distributions

  • Use histograms to examine city fuel economy and distribution shapes.

🚙 Distributions across groups

🚙 drv identifies the drive system

Value of drv Meaning
4 Four-wheel drive
f Front-wheel drive
r Rear-wheel drive
  • Our next question: How does city fuel economy differ across drive systems?
  • The numeric variable remains cty. The categorical variable drv tells us which group each row belongs to.

🪟 Facets separate the distributions

ggplot(data = mpg,
       mapping = aes(x = cty)) +
  geom_histogram(binwidth = 2) +
  facet_wrap(~ drv)

Histograms of city fuel economy in separate panels for four-wheel, front-wheel, and rear-wheel drive, with two-MPG bins and shared axes.

  • facet_wrap(~ drv) means “make panels by drive system.” Each row appears in its group’s panel.

📐 Shared bins and axes support comparisons

  • Every panel uses 2-MPG bins and the same axes. The same position represents the same value across panels.
  • Front-wheel-drive vehicles tend toward higher city MPG in these observations.
  • The groups contain different numbers of vehicles. Taller count bars can reflect a larger group, as well as how values concentrate within it.

Try a different x-axis range

In the previous plot, replace facet_wrap(~ drv) with:

facet_wrap(~ drv, scales = "free_x")

Each panel now has its own x-axis range, while the y-axis stays shared. Does this make the groups easier to inspect or harder to compare?

👁️ Design with colorblindness in mind

Illustrative color-wheel simulations: typical color vision alongside deuteranopia, protanopia, and tritanopia. Some hues become harder to distinguish. These are examples, not a diagnostic test.

  • Color-vision deficiency can make some colors difficult to distinguish.
  • Among people with Northern European ancestry, inherited red–green deficiency affects about 8% of males and 0.5% of females.

🎨 Tip 1: Colorblind-friendly colors

Weekly sales. Red–green simulation below.

Without this tip

Original: Store A Store B

Illustrative weekly sales for Store A and Store B using red and green under a simulated severe red–green color-vision deficiency. The two fills appear very similar, making the store legend difficult to match to bars.

With this tip

Original: Store A Store B

The identical weekly sales data and axes, with blue and orange transformed by the same red–green color-vision simulation. The two fills remain easier to distinguish.

Red and green look similar in this simulation.

🏷️ Tip 2: Text labels or separate panels

Weekly sales, shown without color. Can you identify each store?

Without this tip

Two gray lines show illustrative weekly sales for Store A and Store B, but no direct labels connect each store name to its line. Color cannot identify the series in this grayscale view.

With this tip

The same gray lines, data, and axes, now with Store A and Store B written beside their line endpoints. Store A ends at 18 thousand dollars and Store B at 23 thousand dollars.

Text labels identify each line directly. Separate panels (e.g., facet_wrap()) identify groups with panel labels.

🔷 Tip 3: Point shapes and line types

Weekly sales, shown without color. Which line belongs to each store?

Without this tip

The two store series use the same gray color, circular points, and solid lines. Their legend keys look alike, making the series difficult to identify.

With this tip

The same weekly sales data in gray, now using circles with a solid line for Store A and triangles with a dashed line for Store B. The distinct legend keys identify the series without color.

Point shapes and line types distinguish groups even when colors look alike.

🧰 ggthemes: color scales

  • The scale_*_colorblind() and scale_*_tableau() functions are provided by the ggthemes package.
install.packages("ggthemes")  # Install once
library(ggthemes)            # Load in each R session
  • Tableau is business intelligence (BI) software for exploring data and building interactive dashboards.
    • scale_fill_tableau() and scale_color_tableau() are inspired by Tableau’s color palette.
Mapped feature Tableau functions Colorblind-friendly functions
fill: interiors scale_fill_tableau() scale_fill_colorblind()
color: points or lines scale_color_tableau() scale_color_colorblind()
  • These scales apply a colorblind-friendly palette (a set of colors) to the mapped categories.

🎨 Mapping fill and choosing a palette

ggplot(data = mpg,
       mapping = aes(x = cty, fill = drv)) +
  geom_histogram(binwidth = 2, color = "white",
                 show.legend = FALSE) +  # Hide the legend (color key)
  facet_wrap(~ drv) +
  scale_fill_colorblind()   # Use a colorblind-friendly color palette for fills

City-MPG histograms in three labeled drive-system panels, with a colorblind-friendly fill color for each group. Facet labels identify the groups without color.

  • fill = drv maps groups to colors. scale_fill_colorblind() chooses the colorblind-friendly color palette.
  • Discuss: Do we need different fill colors when the facet labels already identify each drive system?

📦 Boxplots compare a numeric variable across categories

ggplot(data = mpg,
       mapping = aes(x = drv,
                     y = cty,
                     fill = drv)) +
  geom_boxplot(show.legend = FALSE) +
  scale_fill_tableau() +
  labs(x = "Drive system",
       y = "City fuel economy (MPG)")

City-MPG boxplots by drive system. Four-wheel drive has median 14 MPG, front-wheel drive median 19 MPG, and rear-wheel drive median 15 MPG.

  • We now map both axes: a category on x and its numeric values on y.
  • geom_boxplot() calculates a separate summary for each drive-system group. fill = drv inside aes() gives each group its own color.

⚖️ Histograms and boxplots provide different detail

Display Especially useful for Main limitation
Histograms Peaks, gaps, tails, and distribution shape Bin width changes visible detail
Boxplots Comparing group medians and spread compactly Can hide peaks and gaps within groups
  • Both displays summarize the same city-MPG observations.
  • The chart choice depends on which features the reader needs to compare.

✍️ Classwork 11: Distribution of a Numeric Variable Across Groups

  • Compare drive systems using faceted histograms and boxplots.

📊 Distribution of a categorical variable

🚗 geom_bar() counts categories

ggplot(data = mpg,
       mapping = aes(y = class,
                     fill = class)) +
  geom_bar(show.legend = FALSE) +
  scale_fill_tableau() +
  labs(x = "Number of vehicles",
       y = "Vehicle class")

Horizontal bar chart counting vehicles in seven vehicle classes. SUVs have the largest count and two-seaters the smallest.

  • A categorical distribution shows how many observations belong to each category. Here, our question is: Which vehicle classes are most common in mpg?
  • geom_bar() counts rows automatically. Map class to y for horizontal bars and to fill inside aes() for category colors.

🧱 Bar charts and histograms group values differently

Histogram Bar chart
Variable Numeric, such as city MPG Categorical, such as vehicle class
Grouping Numeric intervals called bins Named categories
Order Follows the numeric scale Chosen to support the comparison
  • Histograms show a numeric distribution using adjacent intervals.
  • A bar chart compares categories through bar length. Its numeric value axis should begin at zero.

🔢 count() creates a table of counts

class_counts <- mpg |>
  count(class)
class n
2seater 5
compact 47
midsize 41
minivan 11
pickup 33
subcompact 35
suv 62
  • count(class) creates one row per vehicle class.
  • The new column n gives the number of original rows in that class.
  • The seven counts add to 234, the total number of vehicles in mpg.

📊 geom_col() uses numbers already in a column

ggplot(data = class_counts,
       mapping = aes(x = n,
                     y = class,
                     fill = class)) +
  geom_col(show.legend = FALSE) +
  scale_fill_tableau() +
  labs(x = "Number of vehicles",
       y = "Vehicle class")

Horizontal bars plotting n from the seven-row class_counts table. The classes remain in alphabetical order, with SUVs at 62 vehicles and two-seaters at 5.

  • Use class_counts data.frame from the previous slide: one row per vehicle class, with its count in n.
  • x = n sets each bar’s length. geom_col() uses these supplied values, while geom_bar() counts rows automatically.

🪜 Sorted bars make rankings easier to read

ggplot(data = class_counts,
  mapping = aes(
    x = n,
    y = fct_reorder(class, n),
    fill = class)) +
  geom_col(show.legend = FALSE) +
  scale_fill_tableau() +
  labs(x = "Number of vehicles",
       y = "Vehicle class")

Vehicle-class counts sorted with SUVs at the top at 62 vehicles and two-seaters at the bottom at 5.

  • Using the same table and counts, fct_reorder(class, n) orders the categories using their corresponding counts.
  • Here, the largest count appears at the top. Horizontal bars leave room for the category names.

✍️ Classwork 12: Distribution of a Categorical Variable

  • Compare category counts and plot a prepared summary table.

✈️ Categorical distributions across groups

✈️ Airlines across three NYC-area airports

flights <- nycflights13::flights

airline_flights <- flights |>
  filter(carrier == "AA" |
         carrier == "DL" |
         carrier == "UA")
Column Categories used
origin EWR: Newark
JFK: John F. Kennedy
LGA: LaGuardia
carrier AA: American
DL: Delta
UA: United
  • Each row is one 2013 flight record. We keep only these three airlines at the three NYC-area airports.
  • Our question: How do airline counts and shares differ across airports? These records do not count passengers.

🔢 count(origin, carrier) counts combinations

airport_airline_counts <- airline_flights |>
  count(origin, carrier)
origin carrier n
EWR AA 3487
EWR DL 4342
EWR UA 46087
JFK AA 13783
JFK DL 20701
JFK UA 4534
LGA AA 15459
LGA DL 23067
LGA UA 8044
  • Each row is one airport-and-airline combination. n counts its flight records.

📊 Stacked bars: totals and their breakdown

ggplot(data = airline_flights,
       mapping = aes(x = origin,
                     fill = carrier)) +
  geom_bar()

Stacked flight-record counts for American, Delta, and United at EWR, JFK, and LGA in 2013. Airport totals for the selected airlines are 53,916, 39,018, and 46,570.

  • fill = carrier gives each airline a color. geom_bar() stacks the airline counts within each airport.
  • The total bar height counts records for AA, DL, and UA combined at that airport.
  • Use this chart for airport totals and their airline breakdown. Some segments start at different heights, making their counts harder to compare.

📊 position = "dodge": grouped counts

ggplot(data = airline_flights,
       mapping = aes(x = origin,
                     fill = carrier)) +
  geom_bar(position = "dodge")

Nine equal-width bars compare American, Delta, and United flight-record counts within EWR, JFK, and LGA. United has 46,087 records at EWR, much more than either of the other selected airlines there.

  • Our question: Which airline has the most flight records at each airport?
  • position = "dodge" puts the airline bars side by side. All bars start at zero, and their heights are flight-record counts.

💯 position = "fill": within-group proportions

ggplot(data = airline_flights,
       mapping = aes(x = origin,
                     fill = carrier)) +
  geom_bar(position = "fill") +
  labs(y = "Proportion")

Each airport bar has a total height of one. Segment heights show American, Delta, and United shares of the selected-airline flight records at that airport.

  • fill = carrier assigns colors. position = "fill" rescales each airport’s selected-airline total to 1 (100%).
  • Each segment shows an airline’s share within that airport. Its denominator is AA + DL + UA records at that airport.
  • Compare the airline mix here. Equal total heights hide differences in the number of flight records.

🧭 Bar-chart choice depends on the question

Stacked counts

Stacked selected-airline counts for EWR, JFK, and LGA, totaling 53,916, 39,018, and 46,570 flight records.

Use: totals and breakdown.

Watch out: unaligned segments.

Side by side

Nine equal-width airline-count bars within EWR, JFK, and LGA. All bars start at zero and use the same count scale as the stacked panel.

Use: subgroup counts.

Watch out: crowded bars with many categories.

100% stacked

Within-airport proportions for American, Delta, and United. Each airport bar has height one, hiding differences in selected-airline totals.

Use: within-group proportions.

Watch out: hidden totals.

🧩 The same grammar supports different questions

Question Mapping Drawing layer
How does city MPG vary? aes(x = cty) geom_histogram()
How does city MPG differ by drive system? aes(x = drv, y = cty, fill = drv) geom_boxplot()
How many vehicles are in each class? aes(y = class, fill = class) geom_bar()
How do airline counts differ across airports? aes(x = origin, fill = carrier) geom_bar(position = "dodge")
What counts are in our class-count table? aes(x = n, y = class, fill = class) geom_col()
  • We choose the data and mappings, then add the layer suited to our question.

✍️ Classwork 13: Distribution of a Categorical Variable Across Groups

  • Compare side-by-side counts with within-group proportions, then check a two-variable count table.