H. Sherry Zhang
Department of Statistics and Data Sciences
University of Texas at Austin
library(usethis) create_from_github("SDS322E-26FALL/0402-viz4", fork = FALSE)
is better than
Make sure when you knit a pdf, the code is not cut off. You will lose marks if we can’t see your code.
Syntax:
geom_bar() and geom_col()position argument in geom_bar(): fill and dodgedata.frame vs. a tibbleforcats package to manipulate factor variables
as_factor(), fct_reorder(), and fct_recode()labs()Pie chart is almost never a good idea, because it is hard to compare the size of the slices e.g. A vs. C
It is a small subset of the questions from the 2016 General Social Survey, or GSS. The GSS is a long-running survey of American adults that asks about a range of topics of interest to social scientists.
# the package associated with the textbook
# Data Visualization A practical introduction by Kieran Healy
library(socviz)
gss_sm# A tibble: 2,867 × 32
year id ballot age childs sibs degree race sex region income16 relig
<dbl> <dbl> <labe> <dbl> <dbl> <lab> <fct> <fct> <fct> <fct> <fct> <fct>
1 2016 1 1 47 3 2 Bache… White Male New E… $170000… None
2 2016 2 2 61 0 3 High … White Male New E… $50000 … None
3 2016 3 3 72 2 3 Bache… White Male New E… $75000 … Cath…
4 2016 4 1 43 4 3 High … White Fema… New E… $170000… Cath…
# ℹ 2,863 more rows
# ℹ 20 more variables: marital <fct>, padeg <fct>, madeg <fct>, partyid <fct>,
# polviews <fct>, happy <fct>, partners <fct>, grass <fct>, zodiac <fct>,
# pres12 <labelled>, wtssall <dbl>, income_rc <fct>, agegrp <fct>,
# ageq <fct>, siblings <fct>, kids <fct>, religion <fct>, bigregion <fct>,
# partners_rc <fct>, obama <dbl>
We have created the plot that shows the count and proportion of bigregion:
There is also a geometry called geom_col() that does something similar. Read the example in the documentation with ?geom_col() and create the plot above using geom_col(). Which geom would you prefer?
geom_col() requires you to pre-compute the count or proportion:
# A tibble: 4 × 3
bigregion n prop
<fct> <int> <dbl>
1 Northeast 488 0.170
2 Midwest 695 0.242
3 South 1052 0.367
4 West 632 0.220
Show the count of (big)region and religion through the fill aesthetic
Good or bad?
Again the “pie chart issue” - it is difficult to compare middle category, e.g. Catholic.
We can use position = "fill" to expand the bars to [0, 1]
We can use position = "dodge" to make the bars within each group side-by-side:
In this plot, it is easy to compare for each (big)region, the count of each religion category.
Would you say it is easy to compare the same religion across different (big)regions?
To observe the change of count, our eyes need to trace the bars across regions.
When you arrange the aesthetics differently, it tells different stories.
This point is probably easier to justify in facets
To observe differences of religion within each (big)region group
Data frame:
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
Tibble:
# A tibble: 32 × 11
mpg cyl disp hp drat wt qsec vs am gear carb
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 21 6 160 110 3.9 2.62 16.5 0 1 4 4
2 21 6 160 110 3.9 2.88 17.0 0 1 4 4
3 22.8 4 108 93 3.85 2.32 18.6 1 1 4 1
4 21.4 6 258 110 3.08 3.22 19.4 1 0 3 1
5 18.7 8 360 175 3.15 3.44 17.0 0 0 3 2
# ℹ 27 more rows
A few things that are inconvenient with a data frame:
A data frame will print all the rows, while a tibble only prints the first 10 rows. You will need to scroll all the way up to see the column names in a data frame.
A tibble has a few prints that make it easier to know your data, e.g. 1) data dimension: 32 x 11, 2) variable type: <dbl>
For some historical reasons, a data frame allows you to specify a rowname:
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
There is a convenient function to make it as a variable in a tibble:
Then convert it to a tibble:
# A tibble: 3 × 12
model mpg cyl disp hp drat wt qsec vs am gear carb
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Mazda RX4 21 6 160 110 3.9 2.62 16.5 0 1 4 4
2 Mazda RX4 W… 21 6 160 110 3.9 2.88 17.0 0 1 4 4
3 Datsun 710 22.8 4 108 93 3.85 2.32 18.6 1 1 4 1
mtcars: mpg vs. disp colored by cyl
The variable cyl only has three values (4, 6, 8) but it is mapped to a continuous scale.
Can you spot the difference before and after?
# A tibble: 32 × 11
mpg cyl disp hp drat wt qsec vs
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 21 6 160 110 3.9 2.62 16.5 0
2 21 6 160 110 3.9 2.88 17.0 0
3 22.8 4 108 93 3.85 2.32 18.6 1
4 21.4 6 258 110 3.08 3.22 19.4 1
5 18.7 8 360 175 3.15 3.44 17.0 0
6 18.1 6 225 105 2.76 3.46 20.2 1
7 14.3 8 360 245 3.21 3.57 15.8 0
8 24.4 4 147. 62 3.69 3.19 20 1
9 22.8 4 141. 95 3.92 3.15 22.9 1
10 19.2 6 168. 123 3.92 3.44 18.3 1
# ℹ 22 more rows
# ℹ 3 more variables: am <dbl>, gear <dbl>,
# carb <dbl>
# A tibble: 32 × 11
mpg cyl disp hp drat wt qsec vs
<dbl> <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 21 6 160 110 3.9 2.62 16.5 0
2 21 6 160 110 3.9 2.88 17.0 0
3 22.8 4 108 93 3.85 2.32 18.6 1
4 21.4 6 258 110 3.08 3.22 19.4 1
5 18.7 8 360 175 3.15 3.44 17.0 0
6 18.1 6 225 105 2.76 3.46 20.2 1
7 14.3 8 360 245 3.21 3.57 15.8 0
8 24.4 4 147. 62 3.69 3.19 20 1
9 22.8 4 141. 95 3.92 3.15 22.9 1
10 19.2 6 168. 123 3.92 3.44 18.3 1
# ℹ 22 more rows
# ℹ 3 more variables: am <dbl>, gear <dbl>,
# carb <dbl>
<dbl> means double - the variable is a continuous variable <fct> means factor - the variable is discrete/ a factor variable
The most pedantic way
The legend title is now ugly - we can change it with labs():
mtcars_tbl <- rownames_to_column(
mtcars, var = "model") |>
as_tibble() |>
mutate(cyl = as.factor(cyl))
mtcars_tbl# A tibble: 32 × 12
model mpg cyl disp hp drat wt qsec
<chr> <dbl> <fct> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Mazd… 21 6 160 110 3.9 2.62 16.5
2 Mazd… 21 6 160 110 3.9 2.88 17.0
3 Dats… 22.8 4 108 93 3.85 2.32 18.6
4 Horn… 21.4 6 258 110 3.08 3.22 19.4
5 Horn… 18.7 8 360 175 3.15 3.44 17.0
6 Vali… 18.1 6 225 105 2.76 3.46 20.2
7 Dust… 14.3 8 360 245 3.21 3.57 15.8
8 Merc… 24.4 4 147. 62 3.69 3.19 20
9 Merc… 22.8 4 141. 95 3.92 3.15 22.9
10 Merc… 19.2 6 168. 123 3.92 3.44 18.3
# ℹ 22 more rows
# ℹ 4 more variables: vs <dbl>, am <dbl>,
# gear <dbl>, carb <dbl>

We would like the variable model to be ordered according to disp.
# A tibble: 3 × 2
group values
<chr> <dbl>
1 A 3
2 B 1
3 C 2
Two main arguments:
.f: the factor variable you want to reorderx: the variable you want to order by# A tibble: 3 × 2
group values
<fct> <dbl>
1 A 3
2 B 1
3 C 2
Now we see <fct> instead of <chr> for group.
By default .desc = FALSE
Argument .fun = median means we order by the median of values for each group.
The medians are A: 4, B: 6, C: 5
The order (from small to large) is A - C - B.
[1] A A A B B B C C C
Levels: A C B
Previously we only have one value for each group, so the order is the same as the value itself.
We can change this .fun argument to make it order by min, max, mean, or others:
e.g. Let’s order by the minimum of the each group
The minimum are A: 3, B: 1, C: 2.
The order (from small to large) is B - C - A.
Use fct_reorder() to reorder a factor by another variable
# A tibble: 3 × 12
model mpg cyl disp hp drat wt qsec
<chr> <dbl> <fct> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Mazda… 21 6 160 110 3.9 2.62 16.5
2 Mazda… 21 6 160 110 3.9 2.88 17.0
3 Datsu… 22.8 4 108 93 3.85 2.32 18.6
# ℹ 4 more variables: vs <dbl>, am <dbl>,
# gear <dbl>, carb <dbl>

There is also a fct_reorder2() that allows you to order a factor by two variables
Start from this code, how would you change it into the plot on the right?
Take the gapminder data and focus on all the European countries. Plot the lifeExp for each country and order the country by their max lifExp. Reproduce this plot:

x-axis: country
y-axis: lifeExp
geometry: points
We are only using the data in Europe
We order the country by the maximum life expectancy
You can play around with order by mean, median, min, etc if you have extra time.
A deeper dive into geom_bar() and geom_col()
ggplot2 component: statgeom_bar(), stat = "count" is used, so the geometry first calculate the count of x or y from the data before plotting.function (mapping = NULL, data = NULL, stat = "count", position = "stack",
..., just = 0.5, lineend = "butt", linejoin = "mitre", na.rm = FALSE,
show.legend = NA, inherit.aes = TRUE)
{
layer(mapping = mapping, data = data, geom = "bar", stat = stat,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list2(na.rm = na.rm, just = just, lineend = lineend,
linejoin = linejoin, ...))
}
<bytecode: 0x113624358>
<environment: 0x1136218a0>

geom_point() and geom_bar(), stat = "identity" is used, so there is no statistical transformationBased on your specification, ggplot2 will generate the tibble below internally:
# A tibble: 4 × 4
x y group PANEL
<dbl> <dbl> <int> <fct>
1 1 1 1 1
2 1 10 2 1
3 2 3 1 1
4 2 20 2 1
The internally data object for geom_bar() with a default stat = "count" is more complex:
# A tibble: 4 × 7
count prop x width flipped_aes PANEL group
<dbl> <dbl> <mppd> <dbl> <lgl> <fct> <int>
1 488 1 1 0.9 FALSE 1 1
2 695 1 2 0.9 FALSE 1 2
3 1052 1 3 0.9 FALSE 1 3
4 632 1 4 0.9 FALSE 1 4
The column count and prop are the statistics calculated from the data.
It will then plot x on the x-axis, the generated variable count on the y-axis, each bar is a group, there is only one panel (no facet), and bar is its own entity (group differs for each).
You can check the computed variables for each geom in the documentation
You need to use after_stat() to tell ggplot2 prop is not an original variable in the dataset, but something accessible only “after stat”.

This is bad - what has happened?
The proportion is calculated for each individual group, hence 1 for all prop:
# A tibble: 4 × 7
count prop x width flipped_aes PANEL group
<dbl> <dbl> <mppd> <dbl> <lgl> <fct> <int>
1 488 1 1 0.9 FALSE 1 1
2 695 1 2 0.9 FALSE 1 2
3 1052 1 3 0.9 FALSE 1 3
4 632 1 4 0.9 FALSE 1 4
We can force the group to be 1:
This is the internal data now:
# A tibble: 4 × 7
count prop x width flipped_aes group PANEL
<dbl> <dbl> <mppd> <dbl> <lgl> <int> <fct>
1 488 0.170 1 0.9 FALSE 1 1
2 695 0.242 2 0.9 FALSE 1 1
3 1052 0.367 3 0.9 FALSE 1 1
4 632 0.220 4 0.9 FALSE 1 1