8.4 Arranging maps
8.4.1 Multiple panels of figures as a single figure
Faceting using facet_wrap()
or facet_grid()
allows for dividing the data into groups and creating a map for each group. It is particularly suitable for visualizing one variable at different facets. A good example is a collection of maps of tmax observed at different dates (see figure below). Faceting provides a single consistent color scale shared across the facets.
ggplot() +
geom_raster(data = tmax_long_df, aes(x = x, y = y, fill = tmax)) +
facet_wrap(date ~ .) +
coord_equal() +
scale_fill_viridis_c() +
theme_void() +
theme(
legend.position = "bottom"
)
However, faceting is not suitable for creating maps of different variables. To see this let’s plot tmax and precipitation on Jan 1, 2009 together. We first read precipitation data for Jan 1, 2009.
<- readRDS("Data/ppt_long_df.rds") ppt_long_df
Let’s extract tmax and precipitation on Jan 1, 2009 from their respective datasets, and combine them.
#--- get tmax on Jan 1, 2009 ---#
<- filter(tmax_long_df, date == ymd("2009-01-01")) %>%
tmax_df setnames("tmax", "value") %>%
mutate(type = "tmax")
#--- get precipitation on Jan 1, 2009 ---#
<- filter(ppt_long_df, date == ymd("2009-01-01")) %>%
ppt_df mutate(type = "ppt")
#--- combine them ---#
<- rbind(tmax_df, ppt_df) combined_df
Here is the map faceted for tmax and precipitation:
ggplot() +
geom_raster(data = combined_df, aes(x = x, y = y, fill = value)) +
facet_grid(type ~ .) +
scale_fill_viridis() +
theme_void()
As you can see, a single color scale is created for precipitation recorded in mm and temperature observed in Celsius. On this particular day, precipitation of more than 150 mm was observed on a part of the west coast. Consequently, you see almost no color differentiation on the tmax map which ranges roughly from -20 to 30. It is simply not a good idea to facet for two variables observed at different scales.
Instead, you should have an independent color scale for each of the variables and then just combine them. Now, you might ask if you really need to combine the two. Can’t you just have two figures and arrange them in the manner you would like on a pdf or WORD document? If you are still convinced that you need to have two panels of figures as one figure, then you can use the patchwork
package.
library(patchwork)
patchwork
combines ggplot
objects (maps) using simple operators: +
, /
, and |
. Let’s first create maps of tmax and precipitation separately.
#--- tmax ---#
(<- ggplot() +
g_tmax geom_raster(data = tmax_df, aes(x = x, y = y, fill = value)) +
scale_fill_viridis() +
theme_void() +
coord_equal() +
theme(legend.position = "bottom")
)
#--- ppt ---#
(<- ggplot() +
g_ppt geom_raster(data = ppt_df, aes(x = x, y = y, fill = value)) +
scale_fill_viridis() +
theme_void() +
coord_equal() +
theme(legend.position = "bottom")
)
It is best to just look at examples to get the sense of how patchwork
works. A fuller treatment of patchwork
is found here (https://patchwork.data-imaginist.com/index.html).
Example 1
+ g_ppt g_tmax
Example 2
/ g_ppt / g_tmax g_tmax
Example 3
+ g_ppt + plot_layout(nrow = 2) g_tmax
Example 4
+ g_ppt + g_tmax + g_ppt + plot_layout(nrow = 3, byrow = FALSE) g_tmax
Example 5
| (g_ppt / g_tmax) g_tmax
Sometimes figures are placed too close to each other. In such a case, you can pad a figure at the time of generating individual figures by adding the plot.margin
option to theme()
. For example, the following code creates space at the bottom of g_tmax
(5 cm), and vertically stack g_tmax
and g_ppt
.
#--- space at the bottom ---#
<- g_tmax + theme(plot.margin = unit(c(0, 0, 5, 0), "cm"))
g_tmax
#--- vertically stack ---#
/ g_ppt g_tmax
In plot.margin = unit(c(a, b, c, d), "cm")
, here is which margin a
, b
, c
, and d
refers to.
- a: top
- b: right
- c: bottom
- d: left
8.4.2 A map in a map: inset
Sometimes, it is useful to present a map that covers a larger geographical range than the area of interest in the same map. This provides a better sense of the geographic extent and location of the area of interest relative to the larger geographic extent that the readers are more familiar with. For example, suppose your work is restricted to three counties in Kansas: Cheyenne, Sherman, and Wallace. Here is the map of three counties:
<- filter(KS_county, NAME %in% c("Cheyenne", "Sherman", "Wallace"))
three_counties
(<- ggplot() +
g_three_counties geom_sf(data = three_counties) +
geom_sf_text(data = three_counties, aes(label = NAME)) +
theme_void()
)
Well, for those who are not familiar with Kansas, it might be useful to show where in Kansas they are located on the same map (or even where Kansas is in the U.S.). This can be achieved using ggplotGrob()
and annotation_custom()
. The steps are the following:
- create a map of the area of interest and turn it into a
grob
object usingggplotGrob()
- create a map of the region that includes the area of interest and turn it into a
grob
object usingggplotGrob()
- combine the two using
annotation_custom()
#--- convert the ggplot into a grob ---#
<- ggplotGrob(g_three_counties)
grob_aoi
#--- check the class ---#
class(grob_aoi)
[1] "gtable" "gTree" "grob" "gDesc"
#--- create a map of Kansas ---#
<- ggplot() +
g_region geom_sf(data = KS_county) +
geom_sf(data = three_counties, fill = "blue", color = "red", alpha = 0.5) +
theme_void()
#--- convert to a grob ---#
<- ggplotGrob(g_region) grob_region
Now that we have two maps, we can now put them on the same map using annotation_custom()
. The first task is to initiate a ggplot
with coord_equal()
as follows:
(<- ggplot() +
g_inset coord_equal(xlim = c(0, 1), ylim = c(0, 1), expand = FALSE)
)
You now have a blank canvas to put the images on. Let’s add a layer to the canvas using annotation_custom()
in which you provide the grob
object (a map) and specify the range of the canvas the map occupies. Since the extent of x and y are set to [0, 1] above with coord_equal(xlim = c(0, 1), ylim = c(0, 1), expand = FALSE)
, the following code put the grob_aoi
to cover the entire y range and up to 0.8 of x from 0.
+
g_inset annotation_custom(grob_aoi,
xmin = 0, xmax = 0.8, ymin = 0,
ymax = 1
)
Similarly, we can add grob_region
using annotation_custom()
. Let’s put it at the right lower corner of the map.
+
g_inset annotation_custom(grob_aoi,
xmin = 0, xmax = 0.8, ymin = 0,
ymax = 1
+
) annotation_custom(grob_region,
xmin = 0.6, xmax = 1, ymin = 0,
ymax = 0.3
)
Note that the resulting map still has the default theme because it does not inherit the theme of maps added by annotation_custom()
. So, you can add theme_void()
to the map to make the border disappear.
+
g_inset annotation_custom(grob_aoi,
xmin = 0, xmax = 0.8, ymin = 0,
ymax = 1
+
) annotation_custom(grob_region,
xmin = 0.6, xmax = 1, ymin = 0,
ymax = 0.3
+
) theme_void()