forked from hadley/r4ds
-
Notifications
You must be signed in to change notification settings - Fork 0
/
data-visualize.qmd
1296 lines (1013 loc) · 53.9 KB
/
data-visualize.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Data visualization {#sec-data-visualisation}
```{r}
#| results: "asis"
#| echo: false
source("_common.R")
```
## Introduction
> "The simple graph has brought more information to the data analyst's mind than any other device." --- John Tukey
This chapter will teach you how to visualize your data using ggplot2.
R has several systems for making graphs, but ggplot2 is one of the most elegant and most versatile.
ggplot2 implements the **grammar of graphics**, a coherent system for describing and building graphs.
With ggplot2, you can do more and faster by learning one system and applying it in many places.
If you'd like to learn more about the theoretical underpinnings of ggplot2, you might enjoy reading "The Layered Grammar of Graphics", <https://vita.had.co.nz/papers/layered-grammar.pdf>, the scientific paper that discusses the theoretical underpinnings..
### Prerequisites
This chapter focuses on ggplot2, one of the core packages in the tidyverse.
To access the datasets, help pages, and functions used in this chapter, load the tidyverse by running this code:
```{r}
#| label: setup
library(tidyverse)
```
That one line of code loads the core tidyverse; packages which you will use in almost every data analysis.
It also tells you which functions from the tidyverse conflict with functions in base R (or from other packages you might have loaded).
If you run this code and get the error message "there is no package called 'tidyverse'", you'll need to first install it, then run `library()` once again.
```{r}
#| eval: false
install.packages("tidyverse")
library(tidyverse)
```
You only need to install a package once, but you need to reload it every time you start a new session.
## First steps
Let's use our first graph to answer a question: Do cars with big engines use more fuel than cars with small engines?
You probably already have an answer, but try to make your answer precise.
What does the relationship between engine size and fuel efficiency look like?
Is it positive?
Negative?
Linear?
Nonlinear?
### The `mpg` data frame
You can test your answer with the `mpg` **data frame** found in ggplot2 (a.k.a. `ggplot2::mpg`).
A data frame is a rectangular collection of variables (in the columns) and observations (in the rows).
`mpg` contains observations collected by the US Environmental Protection Agency on 38 car models.
```{r}
mpg
```
Among the variables in `mpg` are:
1. `displ`, a car's engine size, in liters.
2. `hwy`, a car's fuel efficiency on the highway, in miles per gallon (mpg).
A car with a low fuel efficiency consumes more fuel than a car with a high fuel efficiency when they travel the same distance.
To learn more about `mpg`, open its help page by running `?mpg`.
### Creating a ggplot
To plot `mpg`, run this code to put `displ` on the x-axis and `hwy` on the y-axis:
```{r}
#| fig-alt: >
#| Scatterplot of highway fuel efficiency versus engine size of cars that
#| shows a negative association.
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy))
```
The plot shows a negative relationship between engine size (`displ`) and fuel efficiency (`hwy`).
In other words, cars with smaller engine sizes have higher fuel efficiency and, in general, as engine size increases, fuel efficiency decreases.
Does this confirm or refute your hypothesis about fuel efficiency and engine size?
With ggplot2, you begin a plot with the function `ggplot()`.
`ggplot()` creates a coordinate system that you can add layers to.
The first argument of `ggplot()` is the dataset to use in the graph.
So `ggplot(data = mpg)` creates an empty graph, but it's not very interesting so we won't show it here.
You complete your graph by adding one or more layers to `ggplot()`.
The function `geom_point()` adds a layer of points to your plot, which creates a scatterplot.
ggplot2 comes with many geom functions that each adds a different type of layer to a plot.
You'll learn a whole bunch of them throughout this chapter.
Each geom function in ggplot2 takes a `mapping` argument.
This defines how variables in your dataset are mapped to visual properties of your plot.
The `mapping` argument is always paired with `aes()`, and the `x` and `y` arguments of `aes()` specify which variables to map to the x and y axes.
ggplot2 looks for the mapped variables in the `data` argument, in this case, `mpg`.
### A graphing template
Let's turn this code into a reusable template for making graphs with ggplot2.
To make a graph, replace the bracketed sections in the code below with a dataset, a geom function, or a collection of mappings.
```{r}
#| eval: false
ggplot(data = <DATA>) +
<GEOM_FUNCTION>(mapping = aes(<MAPPINGS>))
```
The rest of this chapter will show you how to complete and extend this template to make different types of graphs.
We will begin with the `<MAPPINGS>` component.
### Exercises
1. Run `ggplot(data = mpg)`.
What do you see?
2. How many rows are in `mpg`?
How many columns?
3. What does the `drv` variable describe?
Read the help for `?mpg` to find out.
4. Make a scatterplot of `hwy` vs `cyl`.
5. What happens if you make a scatterplot of `class` vs `drv`?
Why is the plot not useful?
## Aesthetic mappings
> "The greatest value of a picture is when it forces us to notice what we never expected to see." --- John Tukey
In the plot below, one group of points (highlighted in red) seems to fall outside of the linear trend.
These cars have a higher mileage than you might expect.
How can you explain these cars?
```{r}
#| echo: false
#| fig-alt: >
#| Scatterplot of highway fuel efficiency versus engine size of cars that
#| shows a negative association. Cars with engine size greater than 5 litres
#| and highway fuel efficiency greater than 20 miles per gallon stand out from
#| the rest of the data and are highlighted in red.
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_point() +
geom_point(data = dplyr::filter(mpg, displ > 5, hwy > 20), color = "red", size = 2.2)
```
Let's hypothesize that the cars are hybrids.
One way to test this hypothesis is to look at the `class` value for each car.
The `class` variable of the `mpg` dataset classifies cars into groups such as compact, midsize, and SUV.
If the outlying points are hybrids, they should be classified as compact cars or, perhaps, subcompact cars (keep in mind that this data was collected before hybrid trucks and SUVs became popular).
You can add a third variable, like `class`, to a two dimensional scatterplot by mapping it to an **aesthetic**.
An aesthetic is a visual property of the objects in your plot.
Aesthetics include things like the size, the shape, or the color of your points.
You can display a point (like the one below) in different ways by changing the values of its aesthetic properties.
Since we already use the word "value" to describe data, let's use the word "level" to describe aesthetic properties.
Here we change the levels of a point's size, shape, and color to make the point small, triangular, or blue:
```{r}
#| echo: false
#| fig.asp: 0.25
#| fig-width: 8
#| fig-alt: >
#| Diagram that shows four plotting characters next to each other. The first
#| is a large circle, the second is a small circle, the third is a triangle,
#| and the fourth is a blue circle.
ggplot() +
geom_point(aes(1, 1), size = 20) +
geom_point(aes(2, 1), size = 10) +
geom_point(aes(3, 1), size = 20, shape = 17) +
geom_point(aes(4, 1), size = 20, color = "blue") +
scale_x_continuous(NULL, limits = c(0.5, 4.5), labels = NULL) +
scale_y_continuous(NULL, limits = c(0.9, 1.1), labels = NULL) +
theme(aspect.ratio = 1/3)
```
You can convey information about your data by mapping the aesthetics in your plot to the variables in your dataset.
For example, you can map the colors of your points to the `class` variable to reveal the class of each car.
```{r}
#| fig-alt: >
#| Scatterplot of highway fuel efficiency versus engine size of cars that
#| shows a negative association. The points representing each car are colored
#| according to the class of the car. The legend on the right of the plot
#| shows the mapping between colors and levels of the class variable:
#| 2seater, compact, midsize, minivan, pickup, or suv.
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy, color = class))
```
(If you prefer British English, like Hadley, you can use `colour` instead of `color`.)
To map an aesthetic to a variable, associate the name of the aesthetic with the name of the variable inside `aes()`.
ggplot2 will automatically assign a unique level of the aesthetic (here a unique color) to each unique value of the variable, a process known as **scaling**.
ggplot2 will also add a legend that explains which levels correspond to which values.
The colors reveal that many of the unusual points (with engine size greater than 5 liters and highway fuel efficiency greater than 20 miles per gallon) are two-seater cars.
These cars don't seem like hybrids, and are, in fact, sports cars!
Sports cars have large engines like SUVs and pickup trucks, but small bodies like midsize and compact cars, which improves their gas mileage.
In hindsight, these cars were unlikely to be hybrids since they have large engines.
In the above example, we mapped `class` to the color aesthetic, but we could have mapped `class` to the size aesthetic in the same way.
In this case, the exact size of each point would reveal its class affiliation.
We get a *warning* here: mapping an unordered variable (`class`) to an ordered aesthetic (`size`) is generally not a good idea because it implies a ranking that does not in fact exist.
```{r}
#| fig-alt: >
#| Scatterplot of highway fuel efficiency versus engine size of cars that
#| shows a negative association. The points representing each car are sized
#| according to the class of the car. The legend on the right of the plot
#| shows the mapping between colors and levels of the class variable -- going
#| from small to large: 2seater, compact, midsize, minivan, pickup, or suv.
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy, size = class))
```
Similarly, we could have mapped `class` to the *alpha* aesthetic, which controls the transparency of the points, or to the *shape* aesthetic, which controls the shape of the points.
```{r}
#| layout-ncol: 2
#| fig-width: 4
#| fig-height: 2
#| warning: false
#| fig-alt: >
#| Two scatterplots next to each other, both visualizing highway fuel
#| efficiency versus engine size of cars and showing a negative association.
#| In the plot on the left class is mapped to the alpha aesthetic, resulting
#| in different transparency levels for each level of class. In the plot on
#| the right class is mapped the shape aesthetic, resulting in different
#| plotting character shapes for each level of class. Each plot comes with a
#| legend that shows the mapping between alpha level or shape and levels of
#| the class variable.
# Left
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy, alpha = class))
# Right
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy, shape = class))
```
What happened to the SUVs?
ggplot2 will only use six shapes at a time.
By default, additional groups will go unplotted when you use the shape aesthetic.
For each aesthetic, you use `aes()` to associate the name of the aesthetic with a variable to display.
The `aes()` function gathers together each of the aesthetic mappings used by a layer and passes them to the layer's mapping argument.
The syntax highlights a useful insight about `x` and `y`: the x and y locations of a point are themselves aesthetics, visual properties that you can map to variables to display information about the data.
Once you map an aesthetic, ggplot2 takes care of the rest.
It selects a reasonable scale to use with the aesthetic, and it constructs a legend that explains the mapping between levels and values.
For x and y aesthetics, ggplot2 does not create a legend, but it creates an axis line with tick marks and a label.
The axis line acts as a legend; it explains the mapping between locations and values.
You can also *set* the aesthetic properties of your geom manually.
For example, we can make all of the points in our plot blue:
```{r}
#| fig-alt: >
#| Scatterplot of highway fuel efficiency versus engine size of cars that
#| shows a negative association. All points are blue.
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy), color = "blue")
```
Here, the color doesn't convey information about a variable, but only changes the appearance of the plot.
To set an aesthetic manually, set the aesthetic by name as an argument of your geom function.
In other words, it goes *outside* of `aes()`.
You'll need to pick a value that makes sense for that aesthetic:
- The name of a color as a character string.
- The size of a point in mm.
- The shape of a point as a number, as shown in @fig-shapes.
```{r}
#| label: fig-shapes
#| echo: false
#| warning: false
#| fig.asp: 0.364
#| fig-align: "center"
#| fig-cap: >
#| R has 25 built in shapes that are identified by numbers. There are some
#| seeming duplicates: for example, 0, 15, and 22 are all squares. The
#| difference comes from the interaction of the `color` and `fill`
#| aesthetics. The hollow shapes (0--14) have a border determined by `color`;
#| the solid shapes (15--20) are filled with `color`; the filled shapes
#| (21--24) have a border of `color` and are filled with `fill`.
#| fig-alt: >
#| Mapping between shapes and the numbers that represent them: 0 - square,
#| 1 - circle, 2 - triangle point up, 3 - plus, 4 - cross, 5 - diamond,
#| 6 - triangle point down, 7 - square cross, 8 - star, 9 - diamond plus,
#| 10 - circle plus, 11 - triangles up and down, 12 - square plus,
#| 13 - circle cross, 14 - square and triangle down, 15 - filled square,
#| 16 - filled circle, 17 - filled triangle point-up, 18 - filled diamond,
#| 19 - solid circle, 20 - bullet (smaller circle), 21 - filled circle blue,
#| 22 - filled square blue, 23 - filled diamond blue, 24 - filled triangle
#| point-up blue, 25 - filled triangle point down blue.
shapes <- tibble(
shape = c(0, 1, 2, 5, 3, 4, 6:19, 22, 21, 24, 23, 20),
x = (0:24 %/% 5) / 2,
y = (-(0:24 %% 5)) / 4
)
ggplot(shapes, aes(x, y)) +
geom_point(aes(shape = shape), size = 5, fill = "red") +
geom_text(aes(label = shape), hjust = 0, nudge_x = 0.15) +
scale_shape_identity() +
expand_limits(x = 4.1) +
scale_x_continuous(NULL, breaks = NULL) +
scale_y_continuous(NULL, breaks = NULL, limits = c(-1.2, 0.2)) +
theme_minimal() +
theme(aspect.ratio = 1/2.75)
```
### Exercises
1. What's gone wrong with this code?
Why are the points not blue?
```{r}
#| fig-alt: >
#| Scatterplot of highway fuel efficiency versus engine size of cars
#| that shows a negative association. All points are red and
#| the legend shows a red point that is mapped to the word blue.
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy, color = "blue"))
```
2. Which variables in `mpg` are categorical?
Which variables are continuous?
(Hint: type `?mpg` to read the documentation for the dataset).
How can you see this information when you run `mpg`?
3. Map a continuous variable to `color`, `size`, and `shape`.
How do these aesthetics behave differently for categorical vs. continuous variables?
4. What happens if you map the same variable to multiple aesthetics?
5. What does the `stroke` aesthetic do?
What shapes does it work with?
(Hint: use `?geom_point`)
6. What happens if you map an aesthetic to something other than a variable name, like `aes(color = displ < 5)`?
Note, you'll also need to specify x and y.
## Common problems
As you start to run R code, you're likely to run into problems.
Don't worry --- it happens to everyone.
We have all been writing R code for years, but every day we still write code that doesn't work!
Start by carefully comparing the code that you're running to the code in the book.
R is extremely picky, and a misplaced character can make all the difference.
Make sure that every `(` is matched with a `)` and every `"` is paired with another `"`.
Sometimes you'll run the code and nothing happens.
Check the left-hand of your console: if it's a `+`, it means that R doesn't think you've typed a complete expression and it's waiting for you to finish it.
In this case, it's usually easy to start from scratch again by pressing ESCAPE to abort processing the current command.
One common problem when creating ggplot2 graphics is to put the `+` in the wrong place: it has to come at the end of the line, not the start.
In other words, make sure you haven't accidentally written code like this:
``` r
ggplot(data = mpg)
+ geom_point(mapping = aes(x = displ, y = hwy))
```
If you're still stuck, try the help.
You can get help about any R function by running `?function_name` in the console, or selecting the function name and pressing F1 in RStudio.
Don't worry if the help doesn't seem that helpful - instead skip down to the examples and look for code that matches what you're trying to do.
If that doesn't help, carefully read the error message.
Sometimes the answer will be buried there!
But when you're new to R, the answer might be in the error message but you don't yet know how to understand it.
Another great tool is Google: try googling the error message, as it's likely someone else has had the same problem, and has gotten help online.
## Facets
One way to add additional variables to a plot is by mapping them to an aesthetic.
Another way, which is particularly useful for categorical variables, is to split your plot into **facets**, subplots that each display one subset of the data.
To facet your plot by a single variable, use `facet_wrap()`.
The first argument of `facet_wrap()` is a formula, which you create with `~` followed by a variable name (here, "formula" is the bane if a data structure in R, not a synonym for "equation").
The variable that you pass to `facet_wrap()` should be discrete.
```{r}
#| fig-alt: >
#| Scatterplot of highway fuel efficiency versus engine size of cars,
#| faceted by class, with facets spanning two rows.
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy)) +
facet_wrap(~cyl)
```
To facet your plot on the combination of two variables, add `facet_grid()` to your plot call.
The first argument of `facet_grid()` is also a formula.
This time the formula should contain two variable names separated by a `~`.
```{r}
#| fig-alt: >
#| Scatterplot of highway fuel efficiency versus engine size of cars, faceted
#| by number of cylinders across rows and by type of drive train across
#| columns. This results in a 4x3 grid of 12 facets. Some of these facets have
#| no observations: 5 cylinders and 4 wheel drive, 4 or 5 cylinders and front
#| wheel drive.
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy)) +
facet_grid(drv ~ cyl)
```
If you prefer to not facet in the rows or columns dimension, use a `.` instead of a variable name, e.g. `+ facet_grid(. ~ cyl)`.
### Exercises
1. What happens if you facet on a continuous variable?
2. What do the empty cells in plot with `facet_grid(drv ~ cyl)` mean?
How do they relate to this plot?
```{r}
#| fig-alt: >
#| Scatterplot of number of cycles versus type of drive train of cars.
#| The plot shows that there are no cars with 5 cylinders that are 4
#| wheel drive or with 4 or 5 cylinders that are front wheel drive.
ggplot(data = mpg) +
geom_point(mapping = aes(x = drv, y = cyl))
```
3. What plots does the following code make?
What does `.` do?
```{r}
#| eval: false
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy)) +
facet_grid(drv ~ .)
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy)) +
facet_grid(. ~ cyl)
```
4. Take the first faceted plot in this section:
```{r}
#| eval: false
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy)) +
facet_wrap(~ class, nrow = 2)
```
What are the advantages to using faceting instead of the color aesthetic?
What are the disadvantages?
How might the balance change if you had a larger dataset?
5. Read `?facet_wrap`.
What does `nrow` do?
What does `ncol` do?
What other options control the layout of the individual panels?
Why doesn't `facet_grid()` have `nrow` and `ncol` arguments?
6. Which of the following two plots makes it easier to compare engine size (`displ`) across cars with different drive trains?
What does this say about when to place a faceting variable across rows or columns?
```{r}
#| fig-alt: >
#| Two faceted plots, both visualizing highway fuel efficiency versus
#| engine size of cars, faceted by drive train. In the top plot, facet
#| are organized across rows and in the second, across columns.
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy)) +
facet_grid(drv ~ .)
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy)) +
facet_grid(. ~ drv)
```
7. Recreate this plot using `facet_wrap()` instead of `facet_grid()`.
How do the positions of the facet labels change?
```{r}
#| fig-alt: >
#| Scatterplot of highway fuel efficiency versus engine size of cars,
#| faceted by type of drive train across rows.
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy)) +
facet_grid(drv ~ .)
```
## Geometric objects
How are these two plots similar?
```{r}
#| echo: false
#| message: false
#| layout-ncol: 2
#| fig-width: 4
#| fig-height: 2
#| fig-alt: >
#| There are two plots. The plot on the left is a scatterplot of highway fuel
#| efficiency versus engine size of cars and the plot on the right shows a
#| smooth curve that follows the trajectory of the relationship between these
#| variables. A confidence interval around the smooth curve is also displayed.
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy))
ggplot(data = mpg) +
geom_smooth(mapping = aes(x = displ, y = hwy))
```
Both plots contain the same x variable, the same y variable, and both describe the same data.
But the plots are not identical.
Each plot uses a different visual object to represent the data.
In ggplot2 syntax, we say that they use different **geoms**.
A **geom** is the geometrical object that a plot uses to represent data.
People often describe plots by the type of geom that the plot uses.
For example, bar charts use bar geoms, line charts use line geoms, boxplots use boxplot geoms, and so on.
Scatterplots break the trend; they use the point geom.
As we see above, you can use different geoms to plot the same data.
The plot on the left uses the point geom, and the plot on the right uses the smooth geom, a smooth line fitted to the data.
To change the geom in your plot, change the geom function that you add to `ggplot()`.
For instance, to make the plots above, you can use this code:
```{r}
#| eval: false
# Left
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy))
# Right
ggplot(data = mpg) +
geom_smooth(mapping = aes(x = displ, y = hwy))
```
Every geom function in ggplot2 takes a `mapping` argument.
However, not every aesthetic works with every geom.
You could set the shape of a point, but you couldn't set the "shape" of a line.
On the other hand, you *could* set the linetype of a line.
`geom_smooth()` will draw a different line, with a different linetype, for each unique value of the variable that you map to linetype.
```{r}
#| message: false
#| fig-alt: >
#| A plot of highway fuel efficiency versus engine size of cars. The data are
#| represented with smooth curves, which use a different line type (solid,
#| dashed, or long dashed) for each type of drive train. Confidence intervals
#| around the smooth curves are also displayed.
ggplot(data = mpg) +
geom_smooth(mapping = aes(x = displ, y = hwy, linetype = drv))
```
Here, `geom_smooth()` separates the cars into three lines based on their `drv` value, which describes a car's drive train.
One line describes all of the points that have a `4` value, one line describes all of the points that have an `f` value, and one line describes all of the points that have an `r` value.
Here, `4` stands for four-wheel drive, `f` for front-wheel drive, and `r` for rear-wheel drive.
If this sounds strange, we can make it more clear by overlaying the lines on top of the raw data and then coloring everything according to `drv`.
```{r}
#| echo: false
#| message: false
#| fig-alt: >
#| A plot of highway fuel efficiency versus engine size of cars. The data
#| are represented with points (colored by drive train) as well as smooth
#| curves (where line type is determined based on drive train as well).
#| Confidence intervals around the smooth curves are also displayed.
ggplot(data = mpg, mapping = aes(x = displ, y = hwy, color = drv)) +
geom_point() +
geom_smooth(mapping = aes(linetype = drv))
```
Notice that this plot contains two geoms in the same graph!
If this makes you excited, buckle up.
You will learn how to place multiple geoms in the same plot very soon.
ggplot2 provides more than 40 geoms, and extension packages provide even more (see <https://exts.ggplot2.tidyverse.org/gallery/> for a sampling).
The best way to get a comprehensive overview is the ggplot2 cheatsheet, which you can find at <https://rstudio.com/resources/cheatsheets>.
To learn more about any single geom, use the help (e.g. `?geom_smooth`).
Many geoms, like `geom_smooth()`, use a single geometric object to display multiple rows of data.
For these geoms, you can set the `group` aesthetic to a categorical variable to draw multiple objects.
ggplot2 will draw a separate object for each unique value of the grouping variable.
In practice, ggplot2 will automatically group the data for these geoms whenever you map an aesthetic to a discrete variable (as in the `linetype` example).
It is convenient to rely on this feature because the group aesthetic by itself does not add a legend or distinguishing features to the geoms.
```{r}
#| layout-ncol: 3
#| fig-width: 3
#| fig-height: 3
#| message: false
#| fig-alt: >
#| Three plots, each with highway fuel efficiency on the y-axis and engine
#| size of cars, where data are represented by a smooth curve. The first plot
#| only has these two variables, the center plot has three separate smooth
#| curves for each level of drive train, and the right plot not only has the
#| same three separate smooth curves for each level of drive train but these
#| curves are plotted in different colors, without a legend explaining which
#| color maps to which level. Confidence intervals around the smooth curves
#| are also displayed.
ggplot(data = mpg) +
geom_smooth(mapping = aes(x = displ, y = hwy))
ggplot(data = mpg) +
geom_smooth(mapping = aes(x = displ, y = hwy, group = drv))
ggplot(data = mpg) +
geom_smooth(
mapping = aes(x = displ, y = hwy, color = drv),
show.legend = FALSE
)
```
To display multiple geoms in the same plot, add multiple geom functions to `ggplot()`:
```{r}
#| message: false
#| fig-alt: >
#| Scatterplot of highway fuel efficiency versus engine size of cars with a
#| smooth curve overlaid. A confidence interval around the smooth curves is
#| also displayed.
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy)) +
geom_smooth(mapping = aes(x = displ, y = hwy))
```
This, however, introduces some duplication in our code.
Imagine if you wanted to change the y-axis to display `cty` instead of `hwy`.
You'd need to change the variable in two places, and you might forget to update one.
You can avoid this type of repetition by passing a set of mappings to `ggplot()`.
ggplot2 will treat these mappings as global mappings that apply to each geom in the graph.
In other words, this code will produce the same plot as the previous code:
```{r}
#| eval: false
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_point() +
geom_smooth()
```
If you place mappings in a geom function, ggplot2 will treat them as local mappings for the layer.
It will use these mappings to extend or overwrite the global mappings *for that layer only*.
This makes it possible to display different aesthetics in different layers.
```{r}
#| message: false
#| fig-alt: >
#| Scatterplot of highway fuel efficiency versus engine size of cars, where
#| points are colored according to the car class. A smooth curve following
#| the trajectory of the relationship between highway fuel efficiency versus
#| engine size of cars is overlaid along with a confidence interval around it.
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_point(mapping = aes(color = class)) +
geom_smooth()
```
You can use the same idea to specify different `data` for each layer.
Here, our smooth line displays just a subset of the `mpg` dataset, the subcompact cars.
The local data argument in `geom_smooth()` overrides the global data argument in `ggplot()` for that layer only.
```{r}
#| message: false
#| fig-alt: >
#| Scatterplot of highway fuel efficiency versus engine size of cars, where
#| points are colored according to the car class. A smooth curve following
#| the trajectory of the relationship between highway fuel efficiency versus
#| engine size of subcompact cars is overlaid along with a confidence interval
#| around it.
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_point(mapping = aes(color = class)) +
geom_smooth(data = filter(mpg, class == "subcompact"), se = FALSE)
```
(You'll learn how `filter()` works in the chapter on data transformations: for now, just know that this command selects only the subcompact cars.)
### Exercises
1. What geom would you use to draw a line chart?
A boxplot?
A histogram?
An area chart?
2. Run this code in your head and predict what the output will look like.
Then, run the code in R and check your predictions.
```{r}
#| eval: false
ggplot(data = mpg, mapping = aes(x = displ, y = hwy, color = drv)) +
geom_point() +
geom_smooth(se = FALSE)
```
3. What does `show.legend = FALSE` do?
What happens if you remove it?\
Why do you think we used it earlier in the chapter?
4. What does the `se` argument to `geom_smooth()` do?
5. Will these two graphs look different?
Why/why not?
```{r}
#| eval: false
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_point() +
geom_smooth()
ggplot() +
geom_point(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_smooth(data = mpg, mapping = aes(x = displ, y = hwy))
```
6. Recreate the R code necessary to generate the following graphs.
Note that wherever a categorical variable is used in the plot, it's `drv`.
```{r}
#| echo: false
#| message: false
#| layout-ncol: 2
#| fig-width: 4
#| fig-height: 2
#| fig-alt: >
#| There are six scatterplots in this figure, arranged in a 3x2 grid.
#| In all plots highway fuel efficiency of cars are on the y-axis and
#| engine size is on the x-axis. The first plot shows all points in black
#| with a smooth curve overlaid on them. In the second plot points are
#| also all black, with separate smooth curves overlaid for each level of
#| drive train. On the third plot, points and the smooth curves are
#| represented in different colors for each level of drive train. In the
#| fourth plot the points are represented in different colors for each
#| level of drive train but there is only a single smooth line fitted to
#| the whole data. In the fifth plot, points are represented in different
#| colors for each level of drive train, and a separate smooth curve with
#| different line types are fitted to each level of drive train. And
#| finally in the sixth plot points are represented in different colors
#| for each level of drive train and they have a thick white border.
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_point() +
geom_smooth(se = FALSE)
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_smooth(aes(group = drv), se = FALSE) +
geom_point()
ggplot(data = mpg, mapping = aes(x = displ, y = hwy, color = drv)) +
geom_point() +
geom_smooth(se = FALSE)
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_point(aes(color = drv)) +
geom_smooth(se = FALSE)
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_point(aes(color = drv)) +
geom_smooth(aes(linetype = drv), se = FALSE)
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_point(size = 4, color = "white") +
geom_point(aes(color = drv))
```
## Statistical transformations
Next, let's take a look at a bar chart.
Bar charts seem simple, but they are interesting because they reveal something subtle about plots.
Consider a basic bar chart, as drawn with `geom_bar()` or `geom_col()`.
The following chart displays the total number of diamonds in the `diamonds` dataset, grouped by `cut`.
The `diamonds` dataset is in the ggplot2 package and contains information on \~54,000 diamonds, including the `price`, `carat`, `color`, `clarity`, and `cut` of each diamond.
The chart shows that more diamonds are available with high quality cuts than with low quality cuts.
```{r}
#| fig-alt: >
#| Bar chart of number of each cut of diamond. There are roughly 1500
#| Fair, 5000 Good, 12000 Very Good, 14000 Premium, and 22000 Ideal cut
#| diamonds.
ggplot(data = diamonds) +
geom_bar(mapping = aes(x = cut))
```
On the x-axis, the chart displays `cut`, a variable from `diamonds`.
On the y-axis, it displays count, but count is not a variable in `diamonds`!
Where does count come from?
Many graphs, like scatterplots, plot the raw values of your dataset.
Other graphs, like bar charts, calculate new values to plot:
- bar charts, histograms, and frequency polygons bin your data and then plot bin counts, the number of points that fall in each bin.
- smoothers fit a model to your data and then plot predictions from the model.
- boxplots compute a robust summary of the distribution and then display that summary as a specially formatted box.
The algorithm used to calculate new values for a graph is called a **stat**, short for statistical transformation.
@fig-vis-stat-bar shows how this process works with `geom_bar()`.
```{r}
#| label: fig-vis-stat-bar
#| echo: false
#| out-width: "100%"
#| fig-cap: >
#| When create a bar chart we first start with the raw data, then
#| aggregate it to count the number of observations in each bar,
#| and finally map those computed variables to plot aesthetics.
#| fig-alt: >
#| A figure demonstrating three steps of creating a bar chart.
#| Step 1. geom_bar() begins with the diamonds data set. Step 2. geom_bar()
#| transforms the data with the count stat, which returns a data set of
#| cut values and counts. Step 3. geom_bar() uses the transformed data to
#| build the plot. cut is mapped to the x-axis, count is mapped to the y-axis.
knitr::include_graphics("images/visualization-stat-bar.png")
```
You can learn which stat a geom uses by inspecting the default value for the `stat` argument.
For example, `?geom_bar` shows that the default value for `stat` is "count", which means that `geom_bar()` uses `stat_count()`.
`stat_count()` is documented on the same page as `geom_bar()`.
If you scroll down, the section called "Computed variables" explains that it computes two new variables: `count` and `prop`.
You can generally use geoms and stats interchangeably.
For example, you can recreate the previous plot using `stat_count()` instead of `geom_bar()`:
```{r}
#| fig-alt: >
#| Bar chart of number of each cut of diamond. There are roughly 1500
#| Fair, 5000 Good, 12000 Very Good, 14000 Premium, and 22000 Ideal cut
#| diamonds.
ggplot(data = diamonds) +
stat_count(mapping = aes(x = cut))
```
This works because every geom has a default stat; and every stat has a default geom.
This means that you can typically use geoms without worrying about the underlying statistical transformation.
However, there are three reasons why you might need to use a stat explicitly:
1. You might want to override the default stat.
In the code below, we change the stat of `geom_bar()` from count (the default) to identity.
This lets me map the height of the bars to the raw values of a $y$ variable.
Unfortunately when people talk about bar charts casually, they might be referring to this type of bar chart, where the height of the bar is already present in the data, or the previous bar chart where the height of the bar is generated by counting rows.
```{r}
#| warning: false
#| fig-alt: >
#| Bar chart of number of each cut of diamond. There are roughly 1500
#| Fair, 5000 Good, 12000 Very Good, 14000 Premium, and 22000 Ideal cut
#| diamonds.
demo <- tribble(
~cut, ~freq,
"Fair", 1610,
"Good", 4906,
"Very Good", 12082,
"Premium", 13791,
"Ideal", 21551
)
ggplot(data = demo) +
geom_bar(mapping = aes(x = cut, y = freq), stat = "identity")
```
(Don't worry that you haven't seen `<-` or `tribble()` before.
You might be able to guess their meaning from the context, and you'll learn exactly what they do soon!)
2. You might want to override the default mapping from transformed variables to aesthetics.
For example, you might want to display a bar chart of proportions, rather than counts:
```{r}
#| fig-alt: >
#| Bar chart of proportion of each cut of diamond. Roughly, Fair
#| diamonds make up 0.03, Good 0.09, Very Good 0.22, Premium 26, and
#| Ideal 0.40.
ggplot(data = diamonds) +
geom_bar(mapping = aes(x = cut, y = after_stat(prop), group = 1))
```
To find the variables computed by the stat, look for the section titled "computed variables" in the help for `geom_bar()`.
3. You might want to draw greater attention to the statistical transformation in your code.
For example, you might use `stat_summary()`, which summarizes the y values for each unique x value, to draw attention to the summary that you're computing:
```{r}
#| fig-alt: >
#| A plot with depth on the y-axis and cut on the x-axis (with levels
#| fair, good, very good, premium, and ideal) of diamonds. For each level
#| of cut, vertical lines extend from minimum to maximum depth for diamonds
#| in that cut category, and the median depth is indicated on the line
#| with a point.
ggplot(data = diamonds) +
stat_summary(
mapping = aes(x = cut, y = depth),
fun.min = min,
fun.max = max,
fun = median
)
```
ggplot2 provides more than 20 stats for you to use.
Each stat is a function, so you can get help in the usual way, e.g. `?stat_bin`.
To see a complete list of stats, try the [ggplot2 cheatsheet](https://rstudio.com/resources/cheatsheets).
### Exercises
1. What is the default geom associated with `stat_summary()`?
How could you rewrite the previous plot to use that geom function instead of the stat function?
2. What does `geom_col()` do?
How is it different from `geom_bar()`?
3. Most geoms and stats come in pairs that are almost always used in concert.
Read through the documentation and make a list of all the pairs.
What do they have in common?
4. What variables does `stat_smooth()` compute?
What parameters control its behaviour?
5. In our proportion bar chart, we need to set `group = 1`.
Why?
In other words, what is the problem with these two graphs?
```{r}
#| eval: false
ggplot(data = diamonds) +
geom_bar(mapping = aes(x = cut, y = after_stat(prop)))
ggplot(data = diamonds) +
geom_bar(mapping = aes(x = cut, fill = color, y = after_stat(prop)))
```
## Position adjustments
There's one more piece of magic associated with bar charts.
You can color a bar chart using either the `color` aesthetic, or, more usefully, `fill`:
```{r}
#| layout-ncol: 2
#| fig-width: 4
#| fig-height: 2
#| fig-alt: >
#| Two bar charts of cut of diamonds. In the first plot, the bars have colored
#| borders. In the second plot, they're filled with colors. Heights of the
#| bars correspond to the number of diamonds in each cut category.
ggplot(data = diamonds) +
geom_bar(mapping = aes(x = cut, color = cut))
ggplot(data = diamonds) +
geom_bar(mapping = aes(x = cut, fill = cut))
```
Note what happens if you map the fill aesthetic to another variable, like `clarity`: the bars are automatically stacked.
Each colored rectangle represents a combination of `cut` and `clarity`.
```{r}
#| fig-alt: >
#| Segmented bar chart of cut of diamonds, where each bar is filled with
#| colors for the levels of clarity. Heights of the bars correspond to the
#| number of diamonds in each cut category, and heights of the colored
#| segments are proportional to the number of diamonds with a given clarity
#| level within a given cut level.
ggplot(data = diamonds) +
geom_bar(mapping = aes(x = cut, fill = clarity))
```
The stacking is performed automatically using the **position adjustment** specified by the `position` argument.
If you don't want a stacked bar chart, you can use one of three other options: `"identity"`, `"dodge"` or `"fill"`.