-
Notifications
You must be signed in to change notification settings - Fork 48
/
Copy path05-gg_graphics.Rmd
executable file
·1663 lines (1223 loc) · 100 KB
/
05-gg_graphics.Rmd
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
# Graphics with ggplot {#graphics_r}
```{r setup-gg, echo=FALSE}
# knitr::opts_chunk$set(message=FALSE, tidy.opts=list(width.cutoff=80), tidy=TRUE)
```
For many people, using R to create informative and pretty figures is one of the more rewarding aspects of using R. These can either take the form of a rough and ready plot to get a feel for what's going on in your data, or a fancier, more complex figure to use in a publication or a report. This process is often as close as many scientists get to having a professional creative side (at least that's true for us), and it's a source of pride for some folk.
As mentioned in the Introduction, one of the many reasons for the rise in the popularity of R is its ability to produce publication quality figures. Not only can R users make figures well suited for publication, but the means in which the figures are produced also offers a wide-range of customisation. This in turn allows users to create their own particular styles and brands of figures which are well beyond the cookie-cutter styles in more traditional point and click software. Because of this inherent flexibility when producing figures, data visualisation in R and supporting packages has grown substantially over the years.
In this Chapter, we will focus on creating figures using a specialised package called `ggplot2`.
Before we get going with making some plots of the `gg` variety, how about a quick history of one of the most commonly used packages in R? `ggplot2`\index{ggplot2 package} was based on a book called *Grammar of Graphics* by Leland Wilkinson (hence the gg in `ggplot2`), yours for only [£100][leland] or so. But before you spend all that money, see [here][leland-summary] for an interesting summary of Wilkinson's book.
The *Grammar of Graphics* approach moves away from the idea that to create, for example, a scatterplot, users should click the `scatterplot` button or use the `scatterplot()` function. Instead, by breaking figures down into their various components (e.g. the underlying statistics, the geometric arrangement, the theme, see Fig. 5.1), users will be able to manipulate each of these components (i.e. layers) and produce a tailor-made figure fit for their specific needs. Contrast this approach with the one used by, for example, Microsoft Excel. The user specifies the data and then clicks the scatterplot button. This inherently locks the user into many choices made by the software developer and not the user. Think of how easily you can spot an Excel scatterplot because other than a couple of pre-set options, there's really not much you can do to change the way the plot displays the data - you are at the mercy of the [insert corporation here] Gods.
\
```{r, echo=FALSE, out.width="50%", fig.align="center", fig.cap = "framework behind ggplot2"}
library(knitr)
knitr::include_graphics(path = "images/full_connected.png")
```
\
While Wilkinson would eventually go on to become vice-president of SPSS, his (and his oft forgotten co-author's) ideas would, never-the-less, make their way into R via `ggplot2` as well as other implementations (e.g. tableau).
In 2007 `ggplot2` was released by Hadley Wickham. By 2017 the package had reportedly been downloaded 10 million times and over the last few years `ggplot2` has become the foundation for numerous other packages which expand its functionality even more. `ggplot2` is now part of the [tidyverse][tidyverse] collection of R packages.
It's important to note that `ggplot2` is not **required** to make "fancy" and informative figures in R. If you prefer using base R graphics then feel free to continue as almost all `ggplot2` type figures can be created using base R (we often use either approach depending on what we're doing). The difference between`ggplot2` and base R is how you *get* to the end product rather than any substantial differences in the end product itself. This is, never-the-less, a common belief probably due to the fact that making a moderately attractive figure is (in our opinion at least), easier to do with `ggplot2` as many aesthetic decisions are made for the user, without you necessarily even knowing that a decision was ever made!
With that in mind, let's get started making some figures.
## Beginning at the end
The approach we'll use in this Chapter will be to start off by showing you a figure which we suggest is at a standard that you could use in a poster or presentation. Using that as the aim, we will then work towards it step-by-step. You should not view this final figure as any sort of holy grail. For instance, you would be very unlikely to use this in a publication (you'd be much more likely to use some results from your hard earned-analysis). Regardless, this "final figure" is, and will only ever be, a reflection of what our personal preferences are. As with anything subjective, you may well disagree, and to some extent we hope you do. Much better that we all have slightly (or grossly) different views on what a good figure is - otherwise we may as well go back to using cookie-cutter figures.
So what's the figure we're going to make together?
\
```{r final-figure-start, echo = FALSE, out.width = "75%", fig.align = "center", warning = FALSE, message=FALSE}
flower <- read.csv("data/flower.csv", stringsAsFactors = TRUE)
library(ggplot2)
df <- flower
df$nitrogen2 <- factor(df$nitrogen, levels = c("high", "medium", "low"))
theme_deon <- function(base_size = 15, base_family = "", base_line_size = base_size / 22,
base_rect_size = base_size / 22) {
theme(
axis.title = element_text(size = 15),
axis.text.x = element_text(size = 10),
axis.text.y = element_text(size = 10),
plot.caption = element_text(size = 10, face = "italic"),
panel.background = element_rect(fill = "white"),
axis.line = element_line(size = 1, colour = "black"),
strip.background = element_rect(fill = "#cddcdd"),
panel.border = element_rect(colour = "black", fill = NA, size = 0.5),
strip.text = element_text(colour = "black"),
legend.key = element_blank()
)
}
final_figure <- ggplot(aes(x = weight, y = shootarea), data = df) +
geom_point(aes(colour = nitrogen2, shape = nitrogen2), size = 2, alpha = 0.6) +
geom_smooth(colour = "black", method = "lm", se = FALSE, linetype = 2, alpha = 0.6) +
geom_smooth(aes(colour = nitrogen2), method = "lm", se = FALSE, size = 1.2) +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot" ~ (cm^2))) +
geom_hline(aes(yintercept = 79.7833), size = 0.5, colour = "black", linetype = 3) +
labs(shape = "Nitrogen\nConcentration",
colour = "Nitrogen\nConcentration",
caption = "Note: Regression assumptions are unvalidated") +
scale_colour_manual(
values = c("#5C1AAE", "#AE5C1A", "#1AAE5C"),
labels = c("High", "Medium", "Low")
) +
scale_shape_manual(
values = c(15, 17, 19, 21),
labels = c("High", "Medium", "Low")
) +
theme_deon()
final_figure
```
\
Before we go further, let's take a second and talk about what this figure is showing. On the y axes of the four plots we have the surface area of flower shoots, and on the x axes we have the weight of the flowers. Each column of plots shows whether the tip of the flower was removed (notip) or retained (tip). Each row of plots identifies which experimental block in the greenhouse the plants were grown in, either 'block 1' or 'block 2'.
The different coloured and shaped points within each plot represents plants grown at three nitrogen concentrations. These colours are used to distinguish which points correspond to a certain nitrogen concentration. For example, data points coloured green were plants grown in low nitrogen concentrations, brown in medium nitrogen and purple in high concentrations.
We have also added four trend lines to each plot (using a linear model, see [Chapter 6](#stats_r)). The three solid coloured lines show the relationship between shoot area and weight of the flower according to which nitrogen treatment and block the plants were grown. The dashed black line in each plot represents the relationship between shoot area and flower weight whilst ignoring any nitrogen affect.
Finally, the thin grey dotted line on each plot represents the overall mean shoot area regardless of the weight of the flowers, the nitrogen concentration or the block.
For the purposes of this Chapter, we won't worry about the biology here. Do not take this as standard practice, you should absolutely care deeply about the science in your own data. It's the science that should be the driving force behind the questions you ask, which in turn determines what figures you should make.
## The start of the end
The first step in producing a plot with `ggplot()`\index{ggplot()} is the easiest! We just need to install and then make the package avaialble. Use the skills you learnt in [Chapter 1](#packages) to install and load the package. Note that although most people refer to the package as `ggplot`, it's proper name is `ggplot2`.
```{r installing-loading-ggplot, eval = FALSE, message=FALSE}
install.packages("ggplot2")
library(ggplot2)
```
With that taken care of, let's make our first `ggplot`!
### The purest of ggplots
When we run our 'in person' R courses that accompany this book, we often ask our students to name all of the functions they have either learnt during the course, have heard of previously, or have used before (we call it R bingo!). At this point in the course, the students have not yet learnt about `ggplot2`, but never-the-less one year a student suggested the function `ggplot()`. When asked what the `ggplot()` function does, they joked that it obviously makes a `ggplot.` This makes intuitive sense, so let's make a `ggplot` now:
```{r pure-ggplot, out.width = "75%", fig.align = "center", message=FALSE}
ggplot()
```
And here we have it. A fully formed, perfect `ggplot`. We may have a small issue though. Some puritan data visualisers/plotists/figurines make the claim that figures should include some form of information beyond a light grey background. As loathe as we are to agree with purists, we'll do so here. We really should include some information, but to do so, we need data.
We'll keep using the `flower` dataset that you've used in [Chapter 3](#data_r). Let's have a quick reminder of what the structure of the data looks like.
```{r str-of-flower, collapse=TRUE}
flower <- read.table("data/flower.csv", stringsAsFactors = TRUE,
header = TRUE, sep = ",")
str(flower)
```
We know from the "final figure" that we want the variable `shootarea` on the y axis (response/dependent variable) and `weight` on the x axis (explanatory/independent variable). To do this in `ggplot2` we need to make use of the `aes()`\index{aes()} function and also add a `data =` argument. `aes` is short for aesthetics, and it's the function we use to specify what we want displayed in the figure.
If we did not include the `aes()`\index{aes()} function, then the `x =` and `y =` arguments would produce an error saying that the object was not found. A good rule to keep in mind when using `ggplot2` is that the variables which we want displayed on the figure must be included in `aes()` function via the `mapping =` argument (corresponding to the mapping layer from Figure. 4.1).
All features in the figure which alter the displayed information, not based on a variable in our dataset (e.g. increasing the size of points to an arbitrary value), is included outside of the `aes()` function. Don't worry if that doesn't make sense for now, we'll come back to this later.
Let's update our code to include the 'data' and 'mapping' layers (indicated by the grey Data and mustard Mapping *layer bubbles* which will precede relevant code chunks):
```{r, echo=FALSE, out.width="15%", fig.align="center"}
knitr::include_graphics(path = "images/data_mapping.png")
```
```{r ggplot-with-xy-axis, out.width = "75%", fig.align = "center", message=FALSE}
# Including aesthetics for x and y axes as well as specifying the dataset
ggplot(mapping = aes(x = weight, y = shootarea), data = flower)
```
\
That's already much better. At least it's no longer a blank grey canvas. We've now told `ggplot2` what we want as our x and y axes as well as where to find that data. But what's missing here is where we tell `ggplot2` *how* to display that data. This is now the time to introduce you to 'geoms' or geometry layers.
Geometries are the way that `ggplot2` displays information. For instance `geom_point()`\index{geom\_point()} tells `ggplot2` that you want the information to be displayed as points (making scatterplots possible for example). Given that the "final figure" uses points, this is clearly the appropriate geom to use here.
Before we can do that, we need to talk about the coding structure used by `ggplot2`. The analogy that we and many others use is to say that making a figure in `ggplot2` is much like painting. What we've did in the above code was to make our "canvas". Now we are going to add sequential layers to that painting, increasing the complexity and detail over time. Each time we want to include a new layer we need to end a preceding layer with a `+` at the end to tell `ggplot2` that there are additional layers coming.
Let's add (`+`) a new geometry layer now:
```{r, echo=FALSE, out.width="7.5%", fig.align="center"}
knitr::include_graphics(path = "images/geoms.png")
```
```{r ggplot-with-geom_point, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea), data = flower) +
geom_point() # Adding a geom to display data as point data
```
\
When you first start using `ggplot2` there are three crucial layers that require your input. You can safely ignore the other layers initially as they all receive sensible (if sometimes ugly) defaults. The three crucial layers are:
\
```{r, echo=FALSE, out.width="7.5%", fig.align="center", fig.cap="Data - the information we want to plot"}
knitr::include_graphics(path = "images/data.png")
```
```{r, echo=FALSE, out.width="7.5%", fig.align="center", fig.cap="Mapping - which variables we want displayed and where"}
knitr::include_graphics(path = "images/mapping.png")
```
```{r, echo=FALSE, out.width="7.5%", fig.align="center", fig.cap="Geometry - how we want that data displayed"}
knitr::include_graphics(path = "images/geoms.png")
```
\
Given that 'data' only requires us to specify the dataset we want to use, it is trivially easy to complete. 'Mapping' only requires you to specify what variables in the data to use, often just the x- and y-axes (specified using `aes()`). Lastly, 'geometry' is where we choose how we want the data to be visualised.
With just these three fundamentals, you will be able to produce a large variety of plots (see later in this Chapter for a [bestiary](#bestiary) of plots).
If what we wanted was a quick and dirty figure to get a grasp of the trend in the data we can stop here. From the scatterplot that we've produced, we can see that `shootarea` looks like it's increasing with `weight` in a linear fashion. So long as this answers the question we were asking from these data, we have a figure that is fit for purpose. However, for showing to other people we might want something a bit more developed. If we glance back to our "final figure" we can see that we have lines representing the different nitrogen concentrations. We can include lines using a geom. If you have a quick look through the available geoms [here][geom], you might think that `geom_line()`\index{geom\_line()} would be appropriate. Let's try it.
```{r, echo=FALSE, out.width="7.5%", fig.align="center"}
knitr::include_graphics(path = "images/geoms.png")
```
```{r geom_line, out.width="75%", fig.align="center", message=FALSE}
ggplot(aes(x = weight, y = shootarea), data = flower) +
geom_point() +
geom_line() # Adding geom_line
```
\
Not quite what we were going for. The problem that we have is that `geom_line()` is actually just playing join-the-dots in the order they appear in the data (an alternative to `geom_path()`\index{geom\_path()}). The geom we actually want to use is called `geom_smooth()`\index{geom\_smooth()}. We can fix that very easily just by changing "line" to "smooth".
```{r, echo=FALSE, out.width="15%", fig.align='center'}
knitr::include_graphics(path = "images/geoms_statistics.png")
```
```{r geom_smooth, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea), data = flower) +
geom_point() +
geom_smooth() # Changing to geom_smooth
```
\
Better, but still not what we wanted. The challenge here is that drawing a line is actually somewhat complicated. The way our line above was drawn was by using a method called "LOESS" (locally estimated scatterplot smoothing) which gives something very close to a moving average; useful in some cases, less so in others. `ggplot2` will use LOESS as default when you have < 1000 observations, so we'll need to manually specify the method. Instead of a wiggly line, we want a nice simple 'line of best fit' to be drawn using a method called "lm" (short for linear model - see [Chapter 6](#stats_r) for more details). Try looking at the help file, using `?geom_smooth`, to see what other options are available for the `method =` argument.
While we're at it, let's get rid of the confidence interval ribbon around the line. We prefer to do this as we think it's clearer to the audience that this isn't a properly analysed line and to treat it as a visual aid only. We can do this at the same time as changing the method by setting the `se =` argument (short for standard error) to `FALSE`.
Let's update the code to use a linear model without confidence intervals.
```{r, echo=FALSE, out.width="7.5%", fig.align='center'}
knitr::include_graphics(path = "images/statistics.png")
```
```{r geom_smooth-lm, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea), data = flower) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) # method and se
```
\
We get the straight line that we wanted, though it's still not matching the "final figure". We need to alter `geom_smooth()` so that it draws lines for each level of nitrogen concentration. Getting `ggplot2` to do that is pretty straightforward. We can use the `colour =` argument within `aes()` (remember whatever we include in `aes()`\index{aes()} will be something displayed in the figure) to tell `ggplot2` to draw a different coloured lines depending on the `nitrogen` variable. Keep in mind that we have no variable in our dataset called "nitrogen_colour", so `ggplot2` is taking care of that for us here and assigning a colour to each unique nitrogen level.
An aside: `ggplot2` was written with both UK English and American English in mind, so both `colour` and `color` spellings work in `ggplot2`.
```{r, echo=FALSE, out.width="15%", fig.align='center'}
knitr::include_graphics(path = "images/mapping_scales.png")
```
```{r geom_smooth-colour-lm, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea), data = flower) +
geom_point() +
# Including colour argument in aes()
geom_smooth(aes(colour = nitrogen), method = "lm", se = FALSE)
```
\
We're getting closer, especially since `ggplot2` has automatically created a legend for us. At this point it's a good time to talk about where to include information - whether to include it within a geom or in the main call to `ggplot()`. When we include information such as `data =` and `aes()` in `ggplot()` we are setting those as the default, universal values which all subsequent geoms use. Whereas if we were to include that information within a geom, only that geom would use that specific information. In this case, we can easily move the information around and get exactly the same figure.
```{r, echo=FALSE, out.width="15%", fig.align='center'}
knitr::include_graphics(path = "images/data_mapping.png")
```
```{r universal-vrs-geom, out.width="75%", fig.align="center", message=FALSE}
ggplot() +
# Moved aes() and data into geoms
geom_point(aes(x = weight, y = shootarea), data = flower) +
geom_smooth(aes(x = weight, y = shootarea, colour = nitrogen),
data = flower, method = "lm", se = FALSE)
```
\
Doing so we get exactly the same figure. This ability to move information between the main `ggplot()`\index{ggplot()} call or in specific geoms is surprisingly powerful (although sometime confusing!). It can allow different geoms to display different (albeit similar) information (see more on this later).
For this worked example, we'll move the same information back to the universal `ggplot()`, but we'll also move `colour = nitrogen` into `ggplot()` so that we can have the points coloured according to nitrogen concentration as well.
```{r, echo=FALSE, out.width="15%", fig.align='center'}
knitr::include_graphics(path = "images/data_mapping.png")
```
```{r universal-colours, out.width = "75%", fig.align = "center", message=FALSE}
# Moved colour = nitrogen into the universal ggplot()
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point() +
geom_smooth(method = "lm", se = FALSE)
```
\
This figure is now what we would consider to be the typical `ggplot2` figure (once you know to look for it, you'll see it everywhere). We have specified some information, with only a few lines of code, yet we have something that looks quite attractive. While it's not yet the "final figure" it's perfectly suited for displaying the information we need from these data. You have now created your first "pure" `ggplot` using only the 'data', 'mapping' and 'geom' layers (as well as others indirectly).
Let's keep going as we're aiming for something a bit more "sophisticated".
### Wrapping grids
Having made our "pure" `ggplot`, the next big obstacle we're going to tackle is the grid like layout of the "final figure" where our main figure has been split according to the `treat` and `block` variables, with new trends shown for each combination.
Each of these panels (technically "multiples") are a great way to help other people understand what's going on in the data. This is especially true with large datasets which can obscure subtle trends simply because so much data is overlaid on top of each other. When we split a single figure into multiples, the same axes are used for all multiples which serve to highlight shifts in the data (data in some multiples may have inherently higher or lower values for instance).
`ggplot2` includes options for specifying the layout of plots using the 'facets' layer. We'll start off by using `facet_wrap()`\index{facet\_wrap()} to show what this does. For `facet_wrap()` to work we need to specify a formula for how the facets will be defined (see `?facet_wrap` for more details and also how to define facets without using a formula). In our example we want to use the factor `treat` to determine the layout so our formula would look like `~ treat`. You can read `~ treat` as saying "according to treatment". Let's see how it works:
```{r, echo=FALSE, out.width="7.5%", fig.align='center'}
knitr::include_graphics(path = "images/facets.png")
```
```{r facet_wrap, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) +
# Splitting the single figure into multiple depending on treatment
facet_wrap(~ treat)
```
\
That's pretty good. Notice how we can see the impact that the `tip` treatment has on shoot area now (generally lowering shoot area), where in the previous figure this was much more difficult to see?
While this looks pretty good, we are still missing information showing any potential effect of the different blocks. Given that `facet_wrap()` can use a formula, maybe we could simply include `block` in the formula? Remember that the `block` variable refers to the region in the greenhouse where the plants were grown. Let's try it and see what happens.
```{r, echo=FALSE, out.width="7.5%", fig.align='center'}
knitr::include_graphics(path = "images/facets.png")
```
```{r facet_wrap-formula, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) +
# Adding "block" to formula
facet_wrap(~ treat + block)
```
\
This facet layout is almost exactly what we want. Close but no cigar. In this case we actually want to be using `facet_grid()`\index{facet\_grid()}, an alternative to `facet_wrap()`, which should put us back on track to make the "final figure".
*Play around*: Try changing the formula to see what happens. Something like `~ treat + flowers` or even `~ treat + block + flowers`. The important thing to remember here is that `facet_wrap()` will create a new figure for each value in a variable. So when you wrap using a continuous variable like flowers, it makes a plot for every unique number of flowers counted. Be aware of what it is that you are doing, but never be scared to experiment. Mistakes are easily fixed in R - it's not like a point and click programme where you'd have to go back through all those clicks to get the same figure produced. Made a mistake? Easy, change it back and rerun the code (see [Chapter 9](#github_r) for version control which takes this to the next level).
Let's try using `facet_grid` instead of `facet_wrap` to produce the following plot.
\
```{r, echo=FALSE, out.width="7.5%", fig.align='center'}
knitr::include_graphics(path = "images/facets.png")
```
```{r facet_grid, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) +
# Changing to facet_grid
facet_grid(~ treat + block)
```
\
That's disappointing. It's pretty much the same as what we had before and is no closer to the "final figure". To fix this we need to do to rearrange our formula so that we say that it is block in relation to treatment (not in combination with).
```{r, echo=FALSE, out.width="7.5%", fig.align='center'}
knitr::include_graphics(path = "images/facets.png")
```
```{r facet-grid-formula, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) +
# Rearranging formula, block in relation to treatment
facet_grid(block ~ treat)
```
And we're there. Although the styling is not the same as the "final figure" this is showing the same core fundamental information.
### Plotting multiple ggplots
While we've made multiples of the same figure, what if we wanted to take two completely different figures and plot them together in the same frame? As a demonstration, let's plot the last figure we made and the "final figure" shown at the start of this chapter one on top of the other to see how they compare. To do this we are going to use a package called `patchwork`\index{patchwork package}. First you will need to install and make the `patchwork` package available.
```{r loading-patchwork, eval = FALSE, echo = TRUE, message=FALSE}
install.packages("patchwork")
library(patchwork)
```
```{r loading-patchwork_true, eval = TRUE, echo = FALSE, message=FALSE}
library(patchwork)
```
An important note: For those who have used base R to produce their figures and are familiar with using `par(mfrow = c(2,2))` (which allows plotting of four figures in two rows and two columns) be aware that this does not work for `ggplot2` objects. Instead you will need to use either the `patchwork` package or alternative packages such as `gridArrange` or `cowplot` or covert the `ggplot2` objects to grobs.
To plot both of the plots together we need to go back to our previous code and do something clever. We need to assign each figure to a separate object and then use these objects when we use `patchwork`. For instance, we can assigned our "final figure" plot to an object called `final_figure` (we're not very imaginative!), you haven't see the code yet so you'll just have to take our word for it! You may see this method used a lot in other textbooks or online, especially when adding addition layers. Something like this:
```{r named-ggplot-example, eval = FALSE, message=FALSE}
p <- ggplot(df, mapping = aes(x = x, y = y))
```
And later to add additional layers:
```{r named-ggplot-example-adding-layer, eval = FALSE, message=FALSE}
p + geom_point()
```
We prefer not to use this approach here, as we like to always have the code visible to you while you're reading this book. Anyway, let's remind ourselves of the final figure.
\
```{r calling-figure-name, echo=FALSE, out.width = "75%", fig.align = "center", message=FALSE}
final_figure
```
\
We'll now assign the code we wrote when creating our previous plot to an object called `rbook_figure`:
\
```{r naming-figure, out.width = "75%", fig.align = "center", message=FALSE}
# Naming our figure object
rbook_figure <- ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat)
```
\
Now when the code is run, the figure won't be shown immediately. To show the figure we need to type the name of the object. We'll do this at the same time as showing you how `patchwork` works.
An old headache when using `ggplot2` was that it could be difficult to create a nested figure (different plots, or "multiples", all part of the same dataset). `patchwork` resolves this problem very elegantly and simply. We have two immediate and simple options with patchwork; arrange figures on top of each other (specified with a `/`) or arrange figures side-by-side (specified with either a `+` or a `|`). Let's try to plot both figures, one on top of the other.
\
```{r side-by-side-patchwork, echo=TRUE, out.width = "85%", fig.align = "center", message=FALSE}
rbook_figure / final_figure
```
\
*Play around*: Try to create a side-by-side version of the above figure (hint: try the other operators [`+`, `/`, `|`]).
We can take this one step further and assign nested `patchwork` figures to an object and use this in turn to create labels for individual figures.
\
```{r labelled-nested, out.width = "75%", fig.align = "center", message=FALSE}
nested_compare <- rbook_figure / final_figure
nested_compare +
plot_annotation(tag_levels = "A", tag_suffix = ")")
```
\
This is only the basics of what the `patchwork` package can do but there are many other uses. We won't go into them in any great detail here, but later in the [tips and tricks](#tips) section we'll show you some more cool features.
### Make it your own
While we already have a great figure showing the main aspects of our data, it uses many default layer options. Whilst the default options are fine we may want to change them to get our plot looking exactly how we want it. Maybe we're going to use this figure in a presentation and we want to make sure someone in the very back of the room can easily read the figure. Maybe we want to use our own colour scheme. Maybe we want to change the grey background to a nice bright neon pink. In essence, maybe we want to decide things for ourselves. This next section will go through how to customise the appearance of our figure.
Let's start with the easier stuff, namely changing the size of the plotting symbols using the `size =` argument. Before we do, have a think about where we'd include this argument? Should it be in main call to `ggplot()`\index{ggplot()} or in the `geom_point()` geom? Does size depend on a variable in our dataset and is therefore something we want displayed on the figure (meaning we should include it within `aes()`)? Or is it merely changing the appearance of information?
Let's include it in the `geom_point` geom
\
```{r change-size, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
# Including size argument to change the size of the points
geom_point(size = 2) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat)
```
\
Pretty straight forward, we changed the size from the default of `size = 1` to a value that we decide for ourselves. What happens if you included size in `ggplot()` or within the `aes()` of `geom_point()`?
If we wanted to change the shape of the plotting symbols to reflect the different nitrogen concentrations (low, high, medium), how do you think we'd do that? We'd use the `shape =` argument, but this time we need to include an `aes()` within `geom_point()` because we want to include specific information to be displayed on the figure.
```{r, echo=FALSE, out.width="15%", fig.align='center'}
knitr::include_graphics(path = "images/scales_mapping.png")
```
```{r change-shape, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
# Including shape argument to change the shape of the points
geom_point(aes(shape = nitrogen), size = 2) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat)
```
\
Try including `shape = nitrogen` without also including `aes()`\index{aes()} and see what happens.
We're edging our way closer to our "final figure". Another thing we may want to be able to do is change the transparency of the points. While it's not actually that useful here, changing the transparency of points is really valuable when you have lots of data resulting in clusters of points obscuring information. Doing this is easily accomplished using the `alpha =` argument. Again, ask yourself where you think the `alpha =` argument should be included (hint: you should put it in the `geom_point` geom!).
\
```{r change-alpha, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
# Including alpha argument to change the transparency of the points
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat)
```
\
We can also include user defined labels for the x and y axis. There are a couple of ways to do this, but a more familiar way may be to use the same syntax as used in base R figures; using `xlab()`\index{xlab()} and `ylab()`\index{ylab()}. We'll specify that these belong to the `ggplot` by using the `+` symbol.
\
```{r axis-labels, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat) +
# Adding layers for x and y labels
xlab("Weight of flower (g)") +
ylab("Area of shoot (cm^2)")
```
\
Great. Just as we wanted, though getting the "(cm^2)" to show the square as a superscript would be ideal. Here, we're going to accomplish that using a function which is part of base R called `bquote()`\index{bquote()} which allows for special characters to be shown.
\
```{r bquote, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
# Using bquote to get mathematically correct formatting
ylab(bquote("Area of shoot"~(cm^2)))
```
\
Let's now work on the legend title while also including a caption to warn people looking at the figure to treat the trend lines with caution. We'll use a new layer called `labs()`\index{labs()}, short for labels, which we could have also used for specifying the x and y axes labels (we didn't only for demonstration purposes, but give it a shot). `labs()` is a fairly straightforward function. Have a look at the help file (using `?labs`) to see which arguments are available. We'll be using `caption =` argument for our caption, but notice that there isn't a single simple argument for `legend =`? That's because the legend actually contains multiple pieces of information; such as the colour and shape of the symbols. So instead of `legend =` we'll use `colour =` and `shape =`. Here's how we do it:
\
```{r extra-labels, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
# Adding labels for shape, colour and a caption
labs(shape = "Nitrogen Concentration", colour = "Nitrogen Concentration",
caption = "Regression assumptions are unvalidated")
```
*Play around*: Try removing `colour =` or `shape = ` from `labs()` to see what happens. The resulting legends are why we need to specify both colour and shape (and call it the same thing).
Now's a good time to introduce the `\n`. This is a base R feature that tells R that a string should be continued on a new line. We can use that with "Nitrogen Concentration" so that the legend title becomes more compact.
\
```{r compact-labels, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
# Including \n to split legend title over two lines
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",
caption = "Regression assumptions are unvalidated")
```
\
We can now move onto some more wholesale-stylistic choices using the themes layer.
### Setting the theme
Themes control the general style of a `ggplot` (things like the background colour, size of text etc.) and comes with a whole bunch of predefined themes. Let's play around with themes using some skills we've already learnt; assigning plots to an object and plotting multiple `ggplots` in a single figure using `patchwork`. We assign themes by creating a new layer with the general notation - `theme_NameOfTheme()`. For example, to use the `theme_classic`\index{theme\_classic()}, `theme_bw`\index{theme\_bw()}, `theme_minimal`\index{theme\_minimal()} and `theme_light`\index{theme\_light()} themes
```{r, echo=FALSE, out.width="7.5%", fig.align='center'}
knitr::include_graphics(path = "images/theme.png")
```
```{r testing-themes, out.width = "85%", fig.align = "center", message=FALSE}
classic <- ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",
caption = "Regression assumptions are unvalidated") +
# Classic theme
theme_classic()
bw <- ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",
caption = "Regression assumptions are unvalidated") +
# Black and white theme
theme_bw()
minimal <- ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",
caption = "Regression assumptions are unvalidated") +
# Minimal theme
theme_minimal()
light <- ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",
caption = "Regression assumptions are unvalidated") +
# Light theme
theme_light()
(classic | bw) /
(minimal | light)
```
\
In terms of finding a theme that most closely matches our "final figure", it's probably going to be `theme_classic()`\index{theme\_classic()}. There are additional themes available to you, and even more available online. `ggthemes`\index{ggthemes package} is a package which contains many more themes for you to use. The BBC even have their own `ggplot2` theme called "BBplot" which they use when making their own figures (while good, we don't like it too much for scientific figures). Indeed, you can even make your own theme which is what we'll work on next. To begin with, we'll have a look to see how `theme_classic()` was coded. We can do that easily enough by just writing the function name without the parentheses (see [Chapter 7](#prog_r) for a bit more on this).
```{r theme_classic, message=FALSE, comment="", collapse=TRUE}
theme_classic
```
Let's use this code as the basis for our own theme and modify it according to our needs. We'll call the theme, `theme_rbook`. Not all of the options will immediately make sense, but don't worry about this too much for now. Just know that the settings we're putting in place are:
\
- Font size for axis titles = 13
- Font size for x axis text = 10
- Font size for y axis text = 10
- Font for caption = 10 and italics
- Background colour = white
- Background border = black
- Axis lines = black
- Strip colour (for facets) = light blue
- Strip text colour (for facets) = black
- Legend box colours = No colour
\
This is by no means an exhaustive list of features you can specify in your own theme, but this will get you started. Of course, there's no need to use a personalised theme as the pre-built options are perfectly suitable.
```{r, echo=FALSE, out.width="7.5%", fig.align='center'}
knitr::include_graphics(path = "images/theme.png")
```
```{r rbook-theme, message=FALSE}
theme_rbook <- function(base_size = 13, base_family = "", base_line_size = base_size/22,
base_rect_size = base_size/22) {
theme(
axis.title = element_text(size = 13),
axis.text.x = element_text(size = 10),
axis.text.y = element_text(size = 10),
plot.caption = element_text(size = 10, face = "italic"),
panel.background = element_rect(fill="white"),
axis.line = element_line(size = 1, colour = "black"),
strip.background =element_rect(fill = "#cddcdd"),
panel.border = element_rect(colour = "black", fill=NA, size=0.5),
strip.text = element_text(colour = "black"),
legend.key=element_blank()
)
}
```
\
`theme_rbook()`\index{theme\_rbook()} is now available for us to use just like any other theme. Let's try remaking our figure using our new theme.
```{r, echo=FALSE, out.width="7.5%", fig.align='center'}
knitr::include_graphics(path = "images/theme.png")
```
```{r rbook-figure, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",
caption = "Regression assumptions are unvalidated") +
# Updated theme to our theme_rbook
theme_rbook() # use our new theme
```
\
### Prettification
We've pretty much replicated our "final figure". We just have a few final adjustments to make, and we'll do so in order of difficulty.
Let's remind ourselves of what that "final figure" looked like. Remember, since we've previously stored the figure as an object called `final_figure` we can just type that into the console and pull up the figure.
\
```{r final-figure-reminder, out.width = "75%", fig.align = "center", message=FALSE}
final_figure +
labs(title = "Reminder of the final figure")
```
\
Let's begin the final push by including that dashed horizontal line at the average shoot area, at about 80, on our y axis. This represents the overall mean area of a shoot, regardless of nitrogen concentration, treatment, or block. To draw a horizontal line we use a geom called `geom_hline()`\index{geom\_hline()}, and the most important thing we need to specify is the y intercept value (in this case the mean area of a shoot). We can also change the type of line using the argument `linetype =` and also the colour (as we did before). Let's see how it works.
```{r, echo=FALSE, out.width="15%", fig.align='center'}
knitr::include_graphics(path = "images/geoms_mapping.png")
```
```{r geom_hline, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",
caption = "Regression assumptions are unvalidated") +
# Added a horizontal line using geom_hline
geom_hline(aes(yintercept = mean(shootarea)), size = 0.5, colour = "black", linetype = 3) +
theme_rbook()
```
\
Notice how we included the function `mean(shootarea)` within the `geom_hline()` function? We could also do that externally to the `ggplot2` code and get the same result.
```{r shootarea-mean, message=FALSE, collapse=TRUE}
mean(flower$shootarea)
```
\
```{r geom_hline-alternative, out.width = "75%", fig.align = "center", message=F}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",
caption = "Regression assumptions are unvalidated") +
# Manually entering mean value
geom_hline(aes(yintercept = 79.8), size = 0.5, colour = "black", linetype = 3) +
theme_rbook()
```
\
Exactly the same figure but produced in a slightly different way (the point being that there are always multiple ways to get what you want).
Now let's tackle that "overall" nitrogen effect. This overall line is effectively the figure we produced much earlier when we learnt how to include a line of best fit from a linear model. However, we are already using `geom_smooth()`\index{geom\_smooth()}, surely we can't use it again? This may shock and/or surprise you so please ensure you are seated. You *can* use `geom_smooth()` again. In fact you can use it as many times as you want. You can use any layer as many times as you want! Isn't the world full of wonderful miracles? ... Anyway, here's the code...
```{r, echo=FALSE, out.width="15%", fig.align='center'}
knitr::include_graphics(path = "images/geoms_statistics.png")
```
```{r geom_smooth2, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
# Adding a SECOND geom_smooth :O
geom_smooth(method = "lm", se = FALSE, linetype = 2, alpha = 0.6, colour = "black") +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",
caption = "Regression assumptions are unvalidated") +
geom_hline(aes(yintercept = 79.8), size = 0.5, colour = "black", linetype = 3) +
theme_rbook()
```
\
That's great! But you should be asking yourself why that worked. Why when we specified the first `geom_smooth()` did it draw 3 lines, whereas the second time we used `geom_smooth()` it just drew a single line? The secret lies in a "conflict" (it isn't actually a conflict but that's what we'll call it) between the colour specified in the main call to `ggplot()`\index{ggplot()} and the colour specified in the second `geom_smooth()`. Notice how in the second we've specifically told `ggplot2` that the colour will be black, while prior to this it drew lines based on the number of groups (or colours) in nitrogen? In "overriding" the universal `ggplot()` with a geom specific argument we're able to get `ggplot2` to plot what we want.
The only things left to do are to change the colour and the shape of the points to something of our choosing and include information on the "overall" trend line in the legend. We'll begin with the former; changing colour and shape to something we specifically want. When we first started using `ggplot2` this was the thing which caused us the most difficulty. We think the reason is, that to manually change the colours actually requires an additional layer, where we assumed this would be done in either the main call to `ggplot()` or in a geom.
Instead of doing this within the specific geom, we'll use `scale_colour_manual()`\index{scale\_colour\_manual()} as well as `scale_shape_manual()`\index{scale\_shape\_manual()}. Doing it this way will allow us to do two things at once; change the shape and colour to our choosing, and assign labels to these (much like what we did with `xlab()` and `ylab()`). Doing so is not too complex but will require nesting a function (`c()`\index{c()}) within our `scale_colour_manual` and `scale_shape_manual` functions (see [Chapter 2](#basics_r) for a reminder on the concatenate function (`c()`) if you've forgotten).
Choosing colours can be fiddly. We've found using a colour wheel helps with this step. You can always use Google to find an interactive colour wheel, or use Google's Colour picker. Any decent website should give you a HEX code, something like: #5C1AAE which is a "code" representation of a colour. Alternatively, there are colour names which R and `ggplot2` will also understand (e.g. "firebrick4"). Having chosen our colours using whichever means, let's see how we can do it:
```{r, echo=FALSE, out.width="7.5%", fig.align='center'}
knitr::include_graphics(path = "images/scales.png")
```
```{r scale-manual, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
geom_smooth(method = "lm", se = FALSE, linetype = 2, alpha = 0.6, colour = "black") +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",
caption = "Regression assumptions are unvalidated") +
geom_hline(aes(yintercept = 79.8), size = 0.5, colour = "black", linetype = 3) +
# Setting colour and associated labels
scale_colour_manual(values = c("#5C1AAE", "#AE5C1A", "#1AAE5C"),
labels = c("High", "Medium", "Low")) +
# Setting shape and associated labels
scale_shape_manual(values = c(15,17,19),
labels = c("High", "Medium", "Low")) +
theme_rbook()
```
\
To make sense of that code (or any code for that matter) try running it piece by piece. For instance in the above code, if we run `c("#5C1AAE", "#AE5C1A", "#1AAE5C")` we'll get a list of those strings. That list is then passed on to `scale_colour_manual()` as the colours we wish to use. Since we only have three nitrogen concentrations, it will use these three colours. Try including an additional colour in the list and see what happens (if you place the new colour at the end of the list, nothing will happen since it will use the first three colours of the list - try adding it to the start of the list). The same is true for `scale_shape_manual()`.
But if you're paying close attention you'll notice that there's a mistake with the figure now. What should be labelled "Low" is actually labelled "Medium" (the green points and line are our low nitrogen concentration, but `ggplot2` is saying that it's purple). `ggplot2` hasn't made a mistake here, we have. Remember that code is purely logical. It will do explicitly what it is told to do, and in this case we've told it to call the labels High, Medium and Low. We could have just as easily told `ggplot2` to call them Pretoria, Tokyo, and Copenhagen. The lesson here is to always be critical of what your outputs are. Double check everything you do.
So how do we fix this? We need to do a little data manipulation to rearrange our factors so that the order goes High, Medium, Low. Let's do that:
```{r relevel-factor, message=FALSE}
flower$nitrogen <- factor(flower$nitrogen, levels = c("high", "medium", "low"))
```
With that done, we can re-run our above code to get a correct figure and assign it the name "rbook_figure".
\
```{r corrected-factor-level, out.width = "75%", fig.align = "center", message=FALSE}
rbook_figure <- ggplot(aes(x = weight, y = shootarea, colour = nitrogen), data = flower) +
geom_point(aes(shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
geom_smooth(method = "lm", se = FALSE, linetype = 2, alpha = 0.6, colour = "black") +
facet_grid(block ~ treat) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",
caption = "Regression assumptions are unvalidated") +
geom_hline(aes(yintercept = 79.8), size = 0.5, colour = "black", linetype = 3) +
scale_colour_manual(values = c("#5C1AAE", "#AE5C1A", "#1AAE5C"),
labels = c("High", "Medium", "Low")) +
scale_shape_manual(values = c(15,17,19),
labels = c("High", "Medium", "Low")) +
theme_rbook()
rbook_figure
```
\
And we've done it! Our final figure matches the "final figure" exactly. We can then save the final figure to our computer so that we can include it in a poster etc. The code for this is straightforward, but does require an understanding of file paths. Be sure to check [Chapter 1](#work-d) for an explanation if you're unsure. To save `ggplot` figures to an external file we use the function `ggsave`.
This is the point when having assigned the code to the object named `rbook_figure` comes in handy. Doing so allows us to specify which figure to save within `ggsave()`\index{ggsave()}. If we hadn't specified which plot to save, `ggsave()` would instead save the last figure produced.
Other important arguments to take note of are: `device =` which tells `ggplot2` what format we want to save the figure (in this case a pdf) though `ggplot2` is often smart enough to guess this based on the extension we give our file name, so it is often redundant; `units =` which specifies the units used in `width =` and `height =`; `width =` and `height =` specify the width and height of figure (in this case in mm as specified using `units =`); `dpi =` which controls the resolution of the saved figure; and `limitsize =` which prevents accidentally saving a massive figure of 1 km x 1 km!
```{r ggsave, eval = FALSE, message=FALSE}
ggsave(filename = "areashoot_weight_facet.pdf", plot = rbook_figure, device = "pdf",
path = "output", width = 250, height = 150, units = "mm",
dpi = 500, limitsize = TRUE)
```
This concludes the worked example to reproduce our final figure. While absolutely not an exhaustive list of what you can do with `ggplot2`, this will hopefully help when you're making your own from scratch or, perhaps likely when starting, copying `ggplots` made by other people (in which case hopefully this will help you understand what they've done).
Two sections follow this one. The first is simply a collection of tips and tricks that couldn't quite make their way into this worked example, and the second section is a collection of different geometries to give you a feel for what's possible. We would recommend only glancing through each section and coming back to them as and when you need to know how to do *that particular thing*.
Hopefully you've enjoyed reading through this Chapter, and more importantly, that it's increased your confidence in making figures. Here are two final parting *words of wisdom!*. First, as we've said before, don't fall into the trap of thinking your figures are superior to those who don't use `ggplot2`. As we've mentioned previously, equivalent figures are possible in both base R and `ggplot2`. The only difference is how you get to those figures.
Secondly, don't overcomplicate your figures. With regards to the final figure we produced here, we've gone back and forth as to whether we are guilty of this. The figure does contain a lot of information, drawing on information from five different variables, with one of those being presented in three different ways. We would be reluctant, for example, to include this in a presentation as it would likely be too much for an audience to fully appreciate in the 30 seconds to 1 minute that they'll see it. In the end we decided it was worth the risk as it served as a nice demonstration. Fortunately, or unfortunately depending on your view, there are no hard and fast rules when it comes to making figures. Much of it will be at your discretion, so please ensure you give it some thought.
## Tips and tricks {#tips}
For this section we'll use various versions of the final figure without including the `facet_grid()`\index{facet\_grid()}. We do so only to allow the changes to be more apparent. With this plot, we'll run through some tips and tricks that we wish we'd learnt when we started using `ggplot2`.
### Statistics layer
The statistics layer is often ignored in favour of working solely with the geometry layer, as we've done above. The two are largely interchangeable, though the relative rareness of online help and discussions on the statistics layer seems to have relegated it to almost a state of anonymity. There is real value in at least understanding what the statistics layer is doing, even if you don't ever need to make direct use of it. It is perhaps most clear what the statistical layer is doing in the early `geom_smooth()`\index{geom\_smooth()} figure we made.
\
```{r stat-layer}
ggplot(aes(x = weight, y = shootarea), data = flower) +
geom_point() +
geom_smooth(method = "lm", se = FALSE)
```
\
Nowhere in our dataset are there columns for either the y-intercept or the gradient needed to draw the straight line, yet we've managed to draw one. The statistics layer calculates these based on our data, without us necessarily knowing what we've done. It's also the engine behind converting your data into counts for producing a bar chart, or densities for violin plots, or summary statistics for boxplots and so on.
It's entirely possible that you'll be able to use `ggplot2` for the vast majority of you plotting without ever consulting the statistics layer in any more detail than we have here (simply by "calling" - unknowingly - to it via the geometry layer), but be aware that it exists.
If we wanted to recreate the above figure using the statistics layer we would do it like this:
```{r, echo=FALSE, out.width="7.5%", fig.align='center'}
knitr::include_graphics(path = "images/statistics.png")
```
```{r stat_smooth-lm, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea), data = flower) +
geom_point() +
# using stat_smooth instead of geom_smooth
stat_smooth(geom = "smooth", method = "lm", se = FALSE)
```
\
While in this example it doesn't make a difference which we use, in other cases we may want to use the calculated statistics in alternative ways. We won't get into it, but see `?after_stat` if you are interested.
### Axis limits and zooms
Fairly often, you may want to limit the range of your axes. Maybe you want to focus a particular part of the data to really tease apart any patterns occurring there. Whatever the reason, it's a useful skill, and with most things code related, there's a couple of ways to do this. We'll show two here; `xlim()`\index{xlim()} and `ylim()`\index{ylim()}, and `coord_cartesian()`\index{coord\_cartesian()}. Using both of these we'll set the x axis to only show data between 10 and 15 g and the y axis to only show the area of the shoot between 50 and 150 mm^2^. We'll start with limiting the axes:
```{r, echo=FALSE, out.width="7.5%", fig.align='center'}
knitr::include_graphics(path = "images/coords.png")
```
```{r axis-limits, out.width = "75%", fig.align = "center", warning=FALSE, message=FALSE}
ggplot(aes(x = weight, y = shootarea), data = flower) +
geom_point(aes(colour = nitrogen, shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(colour = "black", method = "lm", se = FALSE, linetype = 2, alpha = 0.6) +
geom_smooth(aes(colour = nitrogen), method = "lm", se = FALSE, size = 1.2) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
geom_hline(aes(yintercept = 79.7833), size = 0.5, colour = "black", linetype = 3) +
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",
caption = "Regression assumptions are unvalidated") +
scale_colour_manual(values = c("#5C1AAE", "#AE5C1A", "#1AAE5C"),
labels = c("High", "Medium", "Low")) +
scale_shape_manual(values = c(15,17,19,21),
labels = c("High", "Medium", "Low")) +
theme_rbook() +
# New x and y limits
xlim(c(10, 15)) +
ylim(c(50, 150))
```
\
If you run this yourself you'll see warning messages telling us that n rows contain either missing or non-finite values. That's because we've essentially chopped out a huge part of our data using this method (everything outside of the ranges that we specified is "removed" from the data). As a result of doing this our lines have now completely changed direction. Notice that for low nitrogen concentration, the line is being drawn using only two points? This may, or may not be a problem depending on the aim we have, but we can use an alternative method; `coord_cartesian()`.
`coord_cartesian()` works in much the same way, but instead of chopping out data, it instead zooms in. Doing so means that the entire dataset is maintained (and any trends are maintained).
```{r, echo=FALSE, out.width="7.5%", fig.align='center'}
knitr::include_graphics(path = "images/coords.png")
```
```{r coord_cartesian, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea), data = flower) +
geom_point(aes(colour = nitrogen, shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(colour = "black", method = "lm", se = FALSE, linetype = 2, alpha = 0.6) +
geom_smooth(aes(colour = nitrogen), method = "lm", se = FALSE, size = 1.2) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
geom_hline(aes(yintercept = 79.7833), size = 0.5, colour = "black", linetype = 3) +
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",
caption = "Regression assumptions are unvalidated") +
scale_colour_manual(values = c("#5C1AAE", "#AE5C1A", "#1AAE5C"),
labels = c("High", "Medium", "Low")) +
scale_shape_manual(values = c(15,17,19,21),
labels = c("High", "Medium", "Low")) +
theme_rbook() +
# Zooming in rather than chopping out
coord_cartesian(xlim = c(10, 15), ylim = c(50, 150))
```
\
Notice now that the trends are maintained (as the lines are being informed by data which are off-screen). We would generally advise using `coord_cartesian()` as it protects you against possible misinterpretations.
### Layering layers
Remember that `ggplot2` works like painting. Let's imagine we were one of the great renaissance painters. We've just finished the focal point of our painting, the half naked Duke of Toulouse looking moody or something. Now that we've finished painting the honourable Duke, we proceed to paint the background, a beautiful landscape showing the Pyrenees mountains in the distance. Unfortunately, in doing so we've painted over the Duke because the order of our layers was wrong. We get our heads chopped off, but learn a valuable lesson in the process: the order of layers matter.
The exact same is true in `ggplot2` (minus the chopping off of heads, though your situation may vary). The layers are read and "painted" in order of their appearance in the code. If `geom_point()`\index{geom\_point()} comes before `geom_col()`\index{geom\_col()}, then your points may well end up being hidden. To fix this is easy, we simply need to move the layers up or down in the code. A useful tip for those using RStudio, is that you can move lines of code especially easily. Simply click on a line of code, hold down Alt and then press either the up or down arrows, and the entire line will move up or down as well. For multiple lines of code, simply highlight all those lines you want to move and press Alt + Up/Down arrow.
We'll move both the points and the overall black dashed line down in the code so that they are superimposed over the nitrogen lines:
\
```{r layering-layers, out.width = "75%", fig.align = "center", message=FALSE}
ggplot(aes(x = weight, y = shootarea), data = flower) +
geom_smooth(aes(colour = nitrogen), method = "lm", se = FALSE, size = 1.2) +
# Move one line down
geom_point(aes(colour = nitrogen, shape = nitrogen), size = 2, alpha = 0.6) +
geom_smooth(colour = "black", method = "lm", se = FALSE, linetype = 2, alpha = 0.6) +
xlab("Weight of flower (g)") +
ylab(bquote("Area of shoot"~(cm^2))) +
geom_hline(aes(yintercept = 79.7833), size = 0.5, colour = "black", linetype = 3) +
labs(shape = "Nitrogen\nConcentration", colour = "Nitrogen\nConcentration",