## Visualization and Comparison

A visualization of a single value without some comparative context is rarely useful

Useful visualizations almost always involve comparisons.

• position of a value on an axis

• relative position of two values

• relative magnitudes of two values

A simple and common setting: Visualizing a measurement for each of a set of categories.

Some of the more common visualizations:

• Dot Charts (also called Cleveland Dot Charts)

• Bar Charts
• Grouped Bar Charts (Clustered Bar Charts)
• Stacked Bar Charts
• Diverging Bar Charts
• Heat Maps

• Bubble Charts

Some less frequently used visualizations:

• Waterfall Charts
• Polar Area Charts (Coxcomb Charts)
• Dumbbell Charts

Some questions to keep in mind:

• What comparisons and other assessments do these visualizations support?

• How do these visualizations scale to larger data sets?

## Dot Plots

### Basics

One of the simplest visualizations of a single numerical variable with a modest number of observations and lables for the observations is a dot plot, or Cleveland dot plot:

library(dplyr)
library(ggplot2)
library(gapminder)
le_am_2007 <- filter(gapminder,
year == 2007,
continent == "Americas")
thm <- theme_minimal() +
theme(text = element_text(size = 16))
ggplot(le_am_2007,
aes(y = country, x = lifeExp)) +
geom_point(color = "deepskyblue3",
size = 2) +
labs(x = "Life Expectancy (years)",
y = NULL) +
thm

This visualization

• shows the overall distribution of the data, and

• makes it easy to locate the life expectancy of a particular country.

Unless there is a natural order to the categories (e.g. months of the year or days of the week) it is usually better to reorder to make the plot increasing or decreasing:

ggplot(le_am_2007,
aes(y = reorder(country, lifeExp),
x = lifeExp)) +
geom_point(color = "deepskyblue3",
size = 2) +
labs(x = "Life Expectancy (years)",
y = NULL) +
thm

• Locating a particular country is a little more difficult.

• But the shape of the distribution is more apparent.

• Approximate median and quartiles can be read off easily.

Dot plots are particularly appropriate for interval data.

• they often do not show the origin;

• they focus the viewer’s attention on differences.

Dot plots are often very useful for group summaries like totals or averages.

For the barley data, total yield within each site, adding up across all varieties and both years, can be computed as

b_tot_site <-
group_by(barley, site) %>%
summarize(yield = sum(yield))

The totals can then be visualized in a dot plot:

ggplot(b_tot_site, aes(x = yield, y = site)) +
geom_point(color = "deepskyblue3", size = 2.5) +
labs(x = "Total Yield (bushels/acre)", y = NULL) +
thm

### Larger Data Sets

For larger data sets, like the citytemps data with 138 observations, over-plotting of labels becomes a problem:

ggplot(citytemps,
aes(x = temp, y = reorder(city, temp))) +
geom_point(color = "deepskyblue3", size = 0.5) +
labs(x = "Temperature (degrees F)", y = NULL) +
thm

Reducing to 30 or 40, e.g. by taking a sample or a meaningful subset, can help:

ct1 <- filter(citytemps, temp < 32) %>% sample_n(10)
ct2 <- filter(citytemps, temp >= 32) %>% sample_n(20)
ctsamp <- bind_rows(ct1, ct2)
ggplot(ctsamp,
aes(x = temp, y = reorder(city, temp))) +
geom_point(color = "deepskyblue3", size = 2) +
labs(x = "Temperature (degrees F)", y = NULL) +
thm

### Some Variations

The size of the dots can be used to encode an additional numeric variable.

This view uses area to encode population size:

ggplot(le_am_2007, aes(y = reorder(country, lifeExp),
x = lifeExp,
size = pop / 1000000)) +
geom_point(col = "deepskyblue3") +
labs(x = "Life Expectancy (years)", y = NULL) +
scale_size_area("Population\n(Millions)",
max_size = 8) +
thm + theme(legend.position = "top")

This is sometimes called a bubble chart.

Repeated measures, such as values for 2002 and 2007, can be shown and distinguished by color, shape, or both.

Using color:

## filter down to data for 2002 and 2007
## for the Americas
le2 <- filter(gapminder,
year >= 2002,
continent == "Americas")

## make a factor Year to get a discrete color
## palette, not a continuous one
le2 <- mutate(le2, Year = factor(year))

ggplot(le2, aes(y = reorder(country, lifeExp),
x = lifeExp,
color = Year)) +
geom_point() +
labs(x = "Life Expectancy (years)", y = NULL) +
thm + theme(legend.position = "top")
• All countries show some improvement in life expectancy.

• The small improvement for Jamaica pops out a bit.

Using shape for the barley data:

b_tot <-
group_by(barley, site, year) %>%
summarize(yield = sum(yield), .groups = "drop")
ggplot(b_tot, aes(x = yield, y = site, shape = year)) +
geom_point(color = "deepskyblue3", size = 3) +
labs(x = "Total Yield (bushels/acre)", y = NULL) +
thm + theme(legend.position = "top")

For repeated two values per class it can help to connect the two dots.

This visually emphasizes the relative sizes of differences.

The result is sometimes called a dumbbell chart.

For the barley yields data:

ggplot(b_tot, aes(x = yield, y = site)) +
geom_line(aes(group = site),
size = 2,
color = "grey") +
geom_point(aes(color = year), size = 4) +
labs(x = "Total Yield (bushels/acre)", y = NULL) +
thm + theme(legend.position = "top")

For the Gapminder life expectancy data:

ggplot(le2, aes(y = reorder(country, lifeExp),
x = lifeExp,
color = Year)) +
geom_line(aes(group = country),
size = 1.5,
color = "grey") +
geom_point(size = 2.5) +
labs(x = "Life Expectancy (years)", y = NULL) +
thm + theme(legend.position = "top")

### Variations in Appearance

The dot plots introduced by W. S. Cleveland in his 1993 book Visualizing Data use only horizontal grid lines.

This also corresponds to the dot plots provided by base and lattice graphics and to the dot plot obtained using the Wall Street Journal theme from the ggthemespackage:

ggplot(b_tot_site, aes(x = yield, y = site)) +
geom_point(size = 2) +
labs(x = "Total Yield (bushels/acre)", y = NULL) +
ggthemes::theme_wsj()

A theme to closely match the style used by Cleveland can be defined as

theme_dotplotx <- function() {
theme(
## remove the vertical grid lines
panel.grid.major.x = element_blank(),
panel.grid.minor.x = element_blank(),
## explicitly set the horizontal lines
## (or they will disappear too)
panel.grid.major.y =
element_line(color = "black",
linetype = 3),
axis.text.y = element_text(size = rel(1.2)),
## use a white backgrounsd
panel.background =
element_rect(fill = "white",
color = NA),
panel.border =
element_rect(fill = NA,
color = "grey20"),
## increase text size
text = element_text(size = 16))
}

This produces

ggplot(b_tot_site, aes(x = yield, y = site)) +
geom_point(size = 2) +
labs(x = "Total Yield (bushels/acre)",
y = NULL) +
theme_dotplotx()

## Bar Charts

### Basics

A basic bar chart:

p <- ggplot(le_am_2007) +
geom_col(aes(y = lifeExp,
x = reorder(country, lifeExp)),
fill = "deepskyblue3") +
labs(y = "Life Expectancy (years)",
x = NULL) +
scale_y_continuous(
expand = expansion(mult = c(0, .1))) +
thm
p

The labels are a mess.

• One option is to write labels at an angle, but this makes them hard to read.

• Flipping the plot is usually a better option.

To make labels readable we can flip the plot:

## Redoing the plot plot with x and y aesthetics reversed did not
## work in the past but does work in current ggplot2 versions.
## But as we already have the previous plot we can just use coord_flip(),
p + coord_flip()

### Some Notes

Bar charts seem to be used much more than dot plots in the popular media, but they are less widely applicable.

Research on visual perception has shown that viewers of bar charts subconsciously, or pre-attentively, focus on relative lengths of bars.

This means that for a bar chart to be appropriate and work well:

• Ratio comparisons have to make sense for you data.

• The relative bar lengths have to accurately reflect that data ratio.

This in turn implies that bar charts should always have a zero base line.

You may need to intervene with your software’s defaults to make this happen.

Creating a bar chart with a non-zero base line is possible in ggplot but not easy:

baseline <- 60
ticks <- c(0, 10, 20, 30)
ggplot(le_am_2007,
aes(x = lifeExp - baseline,
y = reorder(country, lifeExp))) +
geom_col(fill = "deepskyblue3") +
labs(x = "Life Expectancy (years)",
y = NULL) +
scale_x_continuous(
breaks = ticks,
labels = ticks + baseline,
expand = expansion(mult = c(0, .1))) +
thm + labs(title = "A Bad Bar Chart")

• The visual impression is that the value for Bolivia is five times higher than the value for Haiti.

• This is not an accurate representation of the data.

Bar charts always push the viewer to ratio comparisons, whether they are meaningful or not.

Using a non-zero baseline can therefore mislead the viewer.

Some news organizations seem particularly prone to taking advantage of/falling prey to this issue.

A recent blog post discusses a court case in New Zealand about a misleading bar chart with a non-zero baseline.

Another recent blog post may be helpful.

### Data With Both Positive and Negative Values

Bar charts can be used for data containing both positive and negative values:

ctsampC <- mutate(ctsamp, temp = round((temp - 32) / 1.8))
p1 <- ggplot(ctsampC, aes(y = city, x = temp)) +
geom_point(color = "deepskyblue3") +
geom_vline(xintercept = 0, lty = 2) +
labs(x = "Temperature (degrees C)", y = NULL) +
thm
p2 <- ggplot(ctsampC, aes(x = temp, y = city)) +
geom_col(fill = "deepskyblue3") +
labs(x = "Temperature (degrees C)", y = NULL) +
thm
library(patchwork)
p1 + p2

This puts a strong emphasis on the base line

This can be useful if the baseline is meaningful, such as

Zero degrees Fahrenheit is not meaningful:

p1 <- ggplot(ctsamp, aes(y = city, x = temp)) +
geom_point(color = "deepskyblue3") +
geom_vline(xintercept = 0, lty = 2) +
labs(x = "Temperature (degrees F)", y = NULL) +
thm
p2 <- ggplot(ctsamp, aes(x = temp, y = city)) +
geom_col(fill = "deepskyblue3") +
labs(x = "Temperature (degrees F)", y = NULL) +
thm
p1 + p2

### Grouped Bar Charts

A grouped bar chart can be used to show two measurements, e.g. two times.

ggplot(le2) +
geom_col(aes(x = lifeExp,
y = reorder(country, lifeExp),
fill = Year),
position = "dodge") +
labs(x = "Life Expectancy (years)",
y = NULL) +
scale_x_continuous(
expand = expansion(mult = c(0, .1))) +
thm

This visualization would be more effective for

• fewer countries and more years

• with more variation in ratios within and between categories.

In this case the dot plot is a much better choice.

For the barley data looking at total yields for each of the sites and the two years:

ggplot(b_tot) +
geom_col(aes(x = yield, y = site, fill = year),
position = "dodge") +
labs(x = "Total Yield (bushels/acre)",
y = NULL) +
scale_x_continuous(
expand = expansion(mult = c(0, .1))) +
thm

### Stacked Bar Charts

A stacked bar chart is appropriate when adding the values within a category to form a total makes sense.

For the barley data:

ggplot(b_tot) +
geom_col(aes(x = yield, y = site, fill = year),
position = "stack") +
labs(x = "Total Yield (bushels/acre)",
y = NULL) +
scale_x_continuous(
expand = expansion(mult = c(0, .1))) +
thm

• The combined bars show the totals.

• The bar segments show the contribution of each year within the sites.

• Comparing 1931 yields across sites is easy; comparing 1932 values is harder.

A stacked bar chart would make no sense for the two-year life expectancy data.

### Category Order

Ordering of categories can change the visual effectiveness of bar charts.

#### Supermarket Sales

A small data set on multi-national supermarket chain sales:

chains <- read.csv(textConnection("Chain, Total, Foreign
Walmart, 22, 5
Costco, 4.5, 2
Tesco, 3, 0.5
Carrefour, 2.5, 2"))
chains <- mutate(chains, Home = Total - Foreign)
tbl <- select(chains, Chain, Home, Foreign, Total)
kbl <- knitr::kable(tbl, format = "html")
kableExtra::kable_styling(kbl, full_width = FALSE)
Chain Home Foreign Total
Walmart 17.0 5.0 22.0
Costco 2.5 2.0 4.5
Tesco 2.5 0.5 3.0
Carrefour 0.5 2.0 2.5

The default alphabetical ordering of the chains in a bar chart is not ideal:

library(tidyr)
chains <- mutate(chains, Total = NULL)
lchains <- pivot_longer(chains,
-Chain,
names_to = "Type",
values_to = "Sales")
p <- ggplot(lchains,
aes(x = Sales, y = Chain, fill = Type)) +
geom_col() +
ylab(NULL) +
scale_x_continuous(
expand = expansion(mult = c(0, .1))) +
scale_fill_brewer(
palette = "Paired",
guide = guide_legend(title = NULL,
reverse = TRUE)) +
thm + theme(legend.position = "top")
p

Reordering by total sales produces a better result:

lchain_s <-
mutate(lchains,
Chain = reorder(Chain, Sales, FUN = sum))
p %+% lchain_s

With this ordering, comparing the first category of sales, Home, among chains is easier than comparing the Foreign sales as the Home values have a common baseline.

If the goal of the visualization is to emphasize the foreign sales then reversing the order would be better.

lchain_st <-
mutate(lchain_s,
Type = factor(Type,
levels = c("Home", "Foreign")))
p %+% lchain_st +
scale_fill_brewer(palette = "Paired",
guide = guide_legend(title = NULL,
reverse = TRUE),
direction = -1)

This also makes it easy to see that Walmart’s international sales alone exceed the total sales of each of the other chains.

A filled bar chart can be used to help compare the proportions of total sales from foreign stores.

levs <-
levels(with(chains,
reorder(Chain, Foreign / (Foreign + Home))))
mutate(lchain_st, Chain = factor(Chain, levs)) %>%
ggplot(aes(x = Sales, y = Chain, fill = Type)) +
geom_col(position = "fill") +
labs(x = "Proportion of Sales", y = NULL) +
scale_x_continuous(
expand = expansion(mult = c(0, .1))) +
scale_fill_brewer(palette = "Paired",
guide = guide_legend(title = NULL,
reverse = TRUE),
direction = -1) +
thm + theme(legend.position = "top")

#### 2016 Election Results

Another example for filled bar charts is provided by 2016 presidential election results by state.

This is produced by default settings.

The values for Clinton are easy to compare, as they are on a common baseline.

The values for Other are also on a common baseline, but the numbers for Trump are not.

p <- ggplot(geofacet::election,
y = state,
fill = candidate)) +
geom_col(position = "fill") +
scale_x_continuous(expand = c(0, 0)) +
xlab(NULL) +
geom_vline(xintercept = 0.5, linetype = 2)
p

Reordering the categories puts both Clinton and Trump on common baselines and also matches the common color use:

## move 'Other' to middle, align Clinton/Trump with colors
elect <-
mutate(geofacet::election,
candidate = factor(candidate,
c("Trump", "Other", "Clinton")))
p %+% elect

Different orderings of the states can be used to achieve different goals.

Ordering by Clinton’s percentage makes it easier to see where she had an outright majority.

elect_wide <- pivot_wider(select(elect, -votes),
names_from = candidate,
values_from = pct)

## ordered by Clinton pct
slevs <- arrange(elect_wide, Clinton) %>%
pull(state)
p %+% mutate(elect, state = factor(state, slevs))

Another possibility is to order by winning margin between the two major candidates.

## ordered by winning margin
slevs <- arrange(elect_wide, Clinton - Trump) %>%
pull(state)
p %+% mutate(elect, state = factor(state, slevs))

## Faceting

Faceting can be used with dot plots and bar charts as well:

ggplot(barley) +
geom_point(aes(x = yield,
y = variety,
color = year)) +
facet_wrap(~ site) +
theme(legend.position = "top")

These plots show lower yields for 1932 than for 1931 for all sites except Morris.

• Cleveland suggest this may indicate a data entry error for Morris.

• A paper from 2014 suggests there may be no error.

## Heat Maps

Heat maps encode a numeric variable as the color of a tile placed at a particular position in a grid.

Heat maps are also called matrix charts or image plots.

Heat maps can be effective in cases where a line plot contains too much over-potting, such as

gma <- filter(gapminder, continent == "Americas")
ggplot(gma) +
geom_line(aes(x = year,
y = lifeExp,
color = country)) +
theme(legend.position = "top",
legend.title = element_blank())

A heat map of these data:

ggplot(gma) +
geom_tile(aes(x = year,
y = reorder(country, lifeExp),
fill = lifeExp)) +
scale_x_continuous(expand = c(0, 0)) +
scale_fill_viridis_c() +
labs(y = NULL,
fill = "Life Expectancy (years)") +
theme(legend.position = "top")

Again it is useful to consider reordering of categories when there is no natural order.

This heat map orders the countries by average life expectancy.

## Bubble Charts

A form of chart often seen in the popular press is the bubble chart.

The bubble chart uses ares of circles to represent magnitudes.

Position in the plane is usually not fully used or not used at all for mapping attributes.

In on-line publications further information on each of the bubbles is often provided through interactions, such as a mouse-over tooltip.

Other charts forms are almost always better for encoding the magnitude information.

It is also easy to get the encoding wrong (Corrected version):

ggplot bubble charts for the average yield values from the barley data and for the 2007 population sizes for the gapminder data:

## Waterfall Charts

These usually show the decomposition of a total into positive and negative contributions from various sources.

New Zealand net immigration by region:

Internet subscribers by month (blog post with ggplot code).

## Polar Area Charts

Florence Nightingale’s famous chart showing the effect of the sanitation improvements in March/April 1855:

This chart has been called a polar area diagrams or a Coxcomb Chart.

It can be viewed as

• a bar chart with the bars positioned in front of each other,

• transformed to polar coordinates,

• with magnitude mapped to area (i.e. square root of magnitude mapped to radius).

The data are available in the HistData package as data frame Nightingale.

Some rearrangement of the data is needed to get it into a form amenable for plotting.

library(forcats)
data(Nightingale, package = "HistData")
Night <-
mutate(Nightingale,
Period =
ifelse(Date < as.Date("1855-04-01"),
"Before",
"After"),
Period =
factor(Period, c("Before", "After")),
Month = factor(Month, month.abb)) %>%
select(Date, Month, Year, Period,
Disease, Wounds, Other) %>%
pivot_longer(5 : 7,
names_to = "Cause",
values_to = "Deaths")

Night3 <- mutate(Night, Month = fct_shift(Month, 3))

A standard stacked bar chart of the data:

ggplot(Night3,
aes(x = Month, y = Deaths, fill = Cause)) +
geom_col() +
facet_wrap(~ Period) +
theme(legend.position = "top")

A grouped bar chart of the data:

ggplot(Night3,
aes(x = Month, y = Deaths, fill = Cause)) +
geom_col(position = "dodge") +
facet_wrap(~ Period) +
theme(legend.position = "top")

Using position = "identity" the bars will be placed in front of each other rather than side by side or stacked.

p <- ggplot(Night3,
aes(x = Month, y = Deaths, fill = Cause)) +
geom_col(position = "identity", ## bars in front of each other
width = 1,             ## remove space between bars
color = "black",       ## bar border color
size = 0.1) +          ## bar border thickness
facet_wrap(~ Period) +
theme(legend.position = "top")
p

Problem: Larger bars may be placed in front of smaller ones.

Reordering the Deaths values from largest to smallest ensures that larger bars do not cover smaller ones.

p <- ggplot(arrange(Night3, desc(Deaths)),
aes(x = Month, y = Deaths, fill = Cause)) +
geom_col(position = "identity", ## bars in front of each other
width = 1,             ## remove space between bars
color = "black",       ## bar border color
size = 0.1) +          ## bar border thickness
facet_wrap(~ Period) +
theme(legend.position = "top")
p

Changing to polar coordinates and mapping square roots of Deaths to radius produces the basic polar area chart:

p %+% (arrange(Night3, desc(Deaths)) %>%
mutate(Deaths = sqrt(Deaths))) +
coord_polar()

Some adjustments bring the result closer to the original:

p %+%
(arrange(Night, desc(Deaths)) %>%
mutate(Month = fct_shift(Month, 6),
Period = factor(Period,
c("After", "Before")),
Deaths = sqrt(Deaths))) +
coord_polar() +
scale_fill_manual(
values = c(Wounds = "pink",
Other = "darkgray",
Disease = "lightblue")) +
theme(axis.title = element_blank(),
axis.text.y = element_blank(),
axis.ticks = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank())

## Base and Lattice Graphics

Base graphics provides the dotchart() function for creating dot plots:

with(le_am_2007,
dotchart(sort(lifeExp),
labels = country[order(lifeExp)]))

The lattice package provides dotplot():

library(lattice)
dotplot(reorder(country, lifeExp) ~ lifeExp,
data = le_am_2007)

Most lattice plots support a group argument that is usually mapped to color:

dotplot(reorder(country, lifeExp) ~ lifeExp,
group = year, data = le2, auto.key = TRUE)

Creating a basic bar chart in lattice uses the barchart() function.

## By default lattice creates a bad bar chart with a
## non-zero base line for these data. Fix that by
## specifying xlim = c(0, 85).
barchart(reorder(country, lifeExp) ~ lifeExp,
data = le_am_2007, xlim = c(0, 85))

Base graphics also provides a bar chart with the barplot() function.

par(mar = c(5, 5, 4, 2) + 0.1)
with(le_am_2007,
barplot(sort(lifeExp),
horiz = TRUE,
names.arg = country[order(lifeExp)],
las = 1,
cex.names = 0.7,
cex.axis = 0.7))

## Interactive Tutorial

An interactive learnr tutorial for these notes is available.

You can run the tutorial with

STAT4580::runTutorial("amounts")

You can install the current version of the STAT4580 package with

remotes::install_gitlab("luke-tierney/STAT4580")

You may need to install the remotes package from CRAN first.

## Exercises

1. A plot similar to this was featured in a CNN news story several years ago:

Which of the following is approximately correct:

• About the same number of democrats as republicans agreed with the court.
• About 15% more democrats than republicans agreed with the court.
• About tree times as many democrats than republicans agreed with the court.
• About two times as many democrats than republicans agreed with the court.
2. Consider the stacked bar chart produced by the following code:

library(tidyverse)
mpg2 <- mutate(mpg,
class = fct_rev(fct_infreq(class)),
cyl = factor(cyl))
p <- ggplot(mpg2, aes(y = class, fill = cyl)) +
geom_bar()

Which of these modifications makes it easiest to compare the count of 4-cylinder models within the different classes?

1. p %+% mutate(mpg2, cyl = factor(cyl, c(4, 5, 6, 8)))
2. p %+% mutate(mpg2, cyl = factor(cyl, c(8, 6, 5, 4)))
3. p %+% mutate(mpg2, cyl = factor(cyl, c(5, 6, 4, 8)))
4. p %+% mutate(mpg2, cyl = factor(cyl, c(4, 6, 8, 5)))
3. The bar chart produced by the following code has x axis labels that could be improved:

    library(gapminder)
library(dplyr)
library(ggplot2)
library(scales)
p <- filter(gapminder, year == 2007) %>%
group_by(continent) %>%
summarize(avgGdpPercap = mean(gdpPercap)) %>%
ggplot(aes(x = avgGdpPercap, y = continent)) +
geom_col() +
labs(x = "Average GDP Per Capita", y = NULL) +
theme_minimal() + theme(text = element_text(size = 16))

There are a number of different options. Which of the following does not provide improved x axis labels?

1. p + scale_x_continuous(labels = label_comma())
2. p + scale_x_continuous(labels = label_dollar())
3. p + scale_x_continuous(labels = unit_format(scale = 1/1000, unit = "K", prefix = "$")) 4. p + scale_x_continuous(labels = c("$10,000", "$20,000", "$30,000"))
4. A stacked bar chart is appropriate if the combined bar heights of the stacked bars have a reasonable interpretation. Consider the following two plots:

library(gapminder)
library(dplyr)
library(ggplot2)
library(patchwork)
p1 <- filter(gapminder, year >= 2000) %>%
group_by(continent, year) %>%
summarize(avgLifeExp = mean(lifeExp), .groups = "drop") %>%
ggplot(aes(x = avgLifeExp, y = continent, fill = factor(year))) +
geom_col() +
theme_minimal() +
theme(text = element_text(size = 12)) +
scale_x_continuous(expand = expansion(mult = c(0, .1))) +
labs(x = "Average Life Expectancy",
y = NULL,
fill = "Year",
title = "Average Life Expectancy\nby Continent for Two Years",
tag = "P1:")
p2 <- count(mpg, class, cyl) %>%
ggplot(aes(x = n, y = class, fill = factor(cyl))) +
geom_col() +
theme_minimal() +
theme(text = element_text(size = 12)) +
scale_x_continuous(expand = expansion(mult = c(0, .1))) +
labs(x = "Number of Models",
y = NULL,
fill = "Cylinders",
title = "Number of Car Models\nby Class and Cylinder Count",
tag = "P2:")
p1 + p2

Which of the following statements is true:

1. P1 is an appropriate use of a stacked bar chart but P2 is not.
2. P2 is an appropriate use of a stacked bar chart but P1 is not.
3. Neither P1 nor P2 is an appropriate use of a stacked bar chart.
4. Both P1 and P2 are appropriate uses of a stacked bar chart.