
10 Overlapping bar graphs
theme_netware()
Graphs in this chapter use fmggp2::theme_netware(), built from Novell NetWare 2.0 boxes: a NetWare red background with white text, a maroon frame, maroon, red, rose, and blush marks, and a Gill Sans title.
10.1 Description
An overlapping bar graph shows more than one column of the data in one place.1 The bars cover each other instead of standing side by side. The bars are often made so we can see through them a little,2 which helps us see the bars behind them. We need a key that tells us which color goes with which group.3
In ggplot2, we can build overlapping bar graphs with fill,4 the color inside each bar, in geom_bar() or geom_col().
10.2 Set up
PACKAGES:
Bring in the code we’ll use.5
show/hide
library(ggplot2)
library(dplyr) # for tibble() and data manipulation
library(tidyr) # for uncount()DATA:
A LAN is a group of computers in one place that are joined so they can work together. NetWare, made by Novell, was the most used way to run a LAN6 in the first few years after 1990. Office LANs grew as more people at work got a computer and a card that joins it to the LAN.7 We’ll make up8 the number of computers9 on the LAN for 24 offices in 1990, 1994, and 1998. Each year gets its own average LAN size (mean_nodes), tidyr::uncount() makes one row for each office, and rnorm() picks the number of computers in each office,10 made into the closest whole number.11 We’ll also mark whether each office ran 25 computers or more (lan_size). Starting with set.seed()12 means you’ll get the same 72 offices every time you run the code below.
show/hide
set.seed(1990) # same 72 offices every time the code runs
netware_offices <- tibble(
year = c("1990", "1994", "1998"), # three survey years
mean_nodes = c(22, 24, 27) # average workstations per office (networks grow over time)
) |>
tidyr::uncount(24, .id = "office") |> # 24 offices in each year
mutate(
nodes = round(rnorm(n(), mean = mean_nodes, sd = 2.5)), # each office's workstation count
lan_size = if_else(nodes >= 25, "25 or more", "Under 25") # sort offices into two sizes
) |>
select(year, office, nodes, lan_size)
glimpse(netware_offices)
#> Rows: 72
#> Columns: 4
#> $ year <chr> "1990", "1990", "1990", "1990",…
#> $ office <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, …
#> $ nodes <dbl> 20, 23, 20, 22, 21, 26, 22, 22,…
#> $ lan_size <chr> "Under 25", "Under 25", "Under …10.3 Distribution
The bars only cover each other when groups share numbers. Three years with very different LAN sizes would stand side by side, with no bar on top of another,13 and three years with the same LAN size would all go on top of each other in the same bars. How much the bars cover each other comes from how far the years’ means are from each other, compared with how different offices in the same year are from each other. Below we’ll move from the offices, to a graph, to the rule behind it.14
COUNT IT:
Start with the first six offices from 1990.
show/hide
head(netware_offices, 6)These offices ran between 20 and 26 computers. The numbers below stand for all the offices in each year:15 the mean, how far the offices usually fall from the mean,16 and the smallest and largest numbers.17
show/hide
netware_offices |>
group_by(year) |>
summarise(
mean = mean(nodes),
sd = sd(nodes),
min = min(nodes),
max = max(nodes))In each year, the largest office has 8 to 11 more computers than the smallest, but the means are only about 2 to 5 computers from each other. So the years share many of the same numbers, which is what puts more than one color in the same bar.
DRAW IT:
The overlapping bar graph below puts the offices from all three years on top of each other at each LAN size. The broken lines mark the average LAN size we gave each year in rnorm(). Most of the 1990 offices are on the left, most of the 1998 offices are on the right, and the 1994 offices land in the middle, sharing bars with both.
show/hide
ggplot(data = netware_offices,
aes(x = nodes, fill = year)) +
geom_bar() +
geom_vline(xintercept = c(22, 24, 27),
color = c("#69100A", "#A91E28", "#B3575A"),
linetype = "dashed", linewidth = 1) +
labs(x = "Workstations", y = "Offices", fill = "Year")
NAME IT:
A hill is a line that is high in the middle and low on both sides. Each year’s LAN size comes from its own hill,18 and all three hills are just as wide as each other:19
\[ X_{y} \sim \text{Normal}(\mu_y, \sigma = 2.5), \quad \mu_{1990} = 22, \; \mu_{1994} = 24, \; \mu_{1998} = 27 \]
The 2.5 in the rule sets how wide each hill is,20 so we can say how far two means are from each other in steps of 2.5. The more steps between two means, the less the two years cover each other. The 1990 and 1998 means are \((27 - 22) / 2.5 = 2\) steps from each other, and the 1994 and 1998 means are only \((27 - 24) / 2.5 = 1.2\) steps from each other. One way to put a number on how much two years cover each other is to pick one office from each year by chance and ask how often the “smaller” year wins:21
\[ P(X_{1994} > X_{1998}) = \Phi\left(\frac{24 - 27}{2.5\sqrt{2}}\right) \approx 0.20 \]
So a 1994 office runs more computers than a 1998 office about 20 times out of 100, while a 1990 office does so only about 8 times out of 100. Try moving the three means in mean_nodes closer together, and the three colors end up in the same few bars.
10.4 Grammar
CODE:
Make the words around the graph22 with
labs()Start the graph with
ggplot()and give itdataPut
nodesonxand useyearfor thefillcolor23Add
geom_bar()on top24
show/hide
labs_bar_ovrlp <- labs(
title = "Novell NetWare",
caption = "Simulated data",
subtitle = "Workstations per office LAN in three years",
x = "Workstations",
y = "Offices",
fill = "Year")
ggp2_bar_ovrlp <- ggplot(data = netware_offices,
aes(x = nodes, fill = year)) +
geom_bar()
ggp2_bar_ovrlp +
labs_bar_ovrlpGRAPH:

10.5 More info
Overlapping bar graphs can also be built with
geom_col().geom_bar()has more ways to place overlapping bars. We can move the bars so they don’t cover each other25 by settingpositionto"dodge"or"dodge2", to show the data the way we want.
10.5.1 geom_col()
To build an overlapping bar graph with geom_col(), we need to make a column in the data that holds the number of offices with each nodes number in each year.26
- Make the
netware_coldata:
show/hide
netware_col <- netware_offices |>
count(year, nodes, name = "Offices")- Put the number of offices on
y,nodesonx, and useyearfor thefillcolor
show/hide
labs_col_ovrlp <- labs(
title = "Novell NetWare",
caption = "Simulated data",
subtitle = "built with 'geom_col()'",
x = "Workstations",
y = "Offices",
fill = "Year")
ggp2_col_ovrlp <- ggplot(data = netware_col,
mapping = aes(y = Offices,
x = nodes,
fill = year)) +
geom_col()
ggp2_col_ovrlp +
labs_col_ovrlp
Compare the two graphs below:


10.5.2 dodge
We’ll use the lan_size column, which puts offices into two groups: those with 25 computers or more and those with fewer.
show/hide
count(netware_offices, lan_size, year)Make the words around the graph with
labs()Start the graph with
ggplot()and give itdataPut
lan_sizeonx, and useyearforgroupand thefillcolorInside
geom_bar(), setpositionto"dodge"
show/hide
labs_bar_dodge <- labs(
title = "Novell NetWare",
caption = "Simulated data",
subtitle = "position = 'dodge'",
x = "LAN size",
y = "Offices",
fill = "Year")
ggp2_bar_dodge <- ggplot(data = netware_offices,
aes(x = lan_size,
group = year,
fill = year)) +
geom_bar(
position = "dodge")
ggp2_bar_dodge +
labs_bar_dodge
10.5.3 dodge2
Make the words around the graph with
labs()Start the graph with
ggplot()and give itdataPut
lan_sizeonxand useyearfor thefillcolorInside
geom_bar(), setpositionto"dodge2"
show/hide
labs_bar_dodge2 <- labs(
title = "Novell NetWare",
caption = "Simulated data",
subtitle = "position = 'dodge2'",
x = "LAN size",
y = "Offices",
fill = "Year")
ggp2_bar_dodge2 <- ggplot(data = netware_offices,
aes(x = lan_size,
fill = year)) +
geom_bar(
position = "dodge2")
ggp2_bar_dodge2 +
labs_bar_dodge2
Compare the two graphs below:


Overlapping bar graphs display multiple variables on the same graph.↩︎
Partially transparent, to aid visibility (the
alphaaesthetic).↩︎A legend, which distinguishes the groups (here, the
fillcolor for eachyear).↩︎The
fillaesthetic, mapped to a categorical variable.↩︎Install and load the packages.↩︎
The most popular network operating system (NOS) of the early 90s.↩︎
A PC and a network interface card.↩︎
Simulate.↩︎
Workstations (
nodes) on the office LAN.↩︎rnorm()draws each office’s workstation count from a normal distribution.↩︎Rounded to a whole workstation with
round().↩︎Setting the random seed.↩︎
Stacking. By default
geom_bar()usesposition = "stack", so the bars for eachfillgroup are stacked on top of each other.↩︎The formula.↩︎
Summary statistics.↩︎
The standard deviation.↩︎
The minimum and maximum (the range).↩︎
A normal distribution.↩︎
A shared standard deviation of 2.5.↩︎
The standard deviation, \(\sigma\). The distance between two means is measured in standard deviations.↩︎
The probability that a randomly drawn office from the lower mean year has more workstations than one from the higher mean year, computed with the standard normal CDF \(\Phi\).↩︎
The labels (title, subtitle, caption, axis titles, and legend title).↩︎
Map
nodestoxandyeartofillinsideaes().↩︎Add the
geom_bar()layer.↩︎Position adjustments, set with the
positionargument.↩︎A column with the counts for each combination of
nodesandyearin the dataset, made withcount().↩︎position = "dodge"preserves the vertical position of a geom while adjusting the horizontal position.↩︎The grouping variable (the
groupaesthetic), specified in the globalggplot()mapping or in thegeom_layer.↩︎Rectangles.↩︎
Geoms with variable widths.↩︎