Bivariate Map with TMAP 4
Dear friends,
I would like to have some reference documentation on how to create a bivariate map with tmap.
Good one. Here is an example:
https://github.com/r-tmap/tmap/issues/1068
We should write a vignette about this... (Surprised that there was none)
Could we also have an example with a raster? As for now, there is only a shapefile-based version: https://r-tmap.github.io/tmap/articles/basics_charts.html?q=biva#bivariate-charts
It would be perfect. But a function would be needed that would put the rasters on the same scale and intervals and allow defining the breaks for each one to combine the information...
I used to work with biscale package: https://cran.r-project.org/web/packages/biscale/vignettes/biscale.html
Thanks. I'll take a look and see if I can do something similar using tmap.
Is this any useful @gcamaro and @Rapsodia86 ?
tm_shape(land) +
tm_raster(
col = tm_vars(c("trees", "elevation"), multivariate = TRUE),
col.scale = tm_scale_bivariate(values = "pu_gn_bivs",
scale1 = tm_scale_intervals(breaks = c(0, 33, 66, 100)),
scale2 = tm_scale_intervals(breaks = c(-500, 1500, 4000, 6500), labels = c("L", "M", "H"))))
Let me know hat you would like to achieve else.
There are a couple of bivariate color palettes in cols4all. However, one thing to improve is changing the order of the colors. For any univariate palette, a "-" can be put in front to reserve it. However, for bivariate palettes we have two dimensions. Via cols4all::c4a two arguments should be added: mirror and flip.
Wow! Very intuitive. Thank you!! I'll try it with my SDM/ENM maps and let you know about the results ASAP.
I tried your code and it didn't work, even after I did a tmap update from the repository. The error message was:
Error: tm_scale_bivariate cannot be used for layer raster, aesthetic col
Any clues?
Thx for testing @gcamaro
That was an old bug that is already fixed. As a double-check, I've just cloned the current repo, and it works for me. Could you test it again? Does it also work for you @Rapsodia86 ?
I reinstalled the entire repository and your code worked. Thank you! Now I'm trying to put this into my workflow and I noticed that there was a change with the legends with histograms, using groups, which requires some adjustments to the code I was using. But as soon as I finish testing, I'll come back.
Yeah! Works very nicely!
One thing:
To change the title of each scale, I needed to add col.legend(), and xlab.size() & ylab.size() within.
To change labels size, I needed to use legend.text.size() in the tm_layout(), which works for both at once.
However, tm_scale_intervals() has label.format() parameter, although I could not make it work to change the labels size.
p1 <- tm_shape(ph_ttp) +
tm_raster(
col=tm_vars(c("PH", "TTP"), multivariate = TRUE),
col.scale = tm_scale_bivariate(values = "bu_br_bivs",
scale1 = tm_scale_intervals(n=4,style = "quantile", labels = c("Q1", "Q2", "Q3","Q4")),
scale2 = tm_scale_intervals(n=4,style = "quantile",labels = c("Q1", "Q2", "Q3","Q4"))),
col.legend = tm_legend_bivariate(xlab="TTPx",ylab="PHx",xlab.size=0.3,ylab.size=0.3))+
tm_layout(bg.color = "white",legend.text.size = 0.2)
It worked like a charm!
Sorry, but I couldn't find a way to rotate the variable names in the legend or to not abbreviate the values; or to be able to write the class names more completely. Maybe that's the intention, but I would like to have a way to specify the values in class intervals, also for the second variable. But it's not a problem. Easily solved in the figure's caption.
env.map <-
# Ocean
tm_shape(oceans) +
tm_polygons(fill = "lightblue",
fill_alpha = 0.7,
col = NULL) +
# Raster of environmental variables
# Bio01 = Annual Mean Temperature
# Bio12 = Annual Precipitation
tm_shape(predictors, is.main = TRUE) +
tm_raster(
col = tm_vars(c("Bio01", "Bio12"), multivariate = TRUE),
col.scale = tm_scale_bivariate(values = "pu_gn_bivs",
scale1 = tm_scale_intervals(
style = "fixed",
n = 5,
midpoint = NA,
breaks = c(-Inf, -15, 0, 15, 30, Inf)
),
scale2 = tm_scale_intervals(
style = "fixed",
n = 4,
midpoint = NA,
breaks = c(-Inf, 1000, 2000, 4000, Inf)
)),
col.legend = tm_legend_bivariate(
title = NA,
title.size = 0.6,
title.align = "left",
title.fontface = "bold",
xlab = "Temp.",
ylab = "Prec.",
xlab.size = 0.6,
ylab.size = 0.6,
text.size = 0.6,
show = TRUE,
orientation = "portrait",
position = tm_pos_in("LEFT", "BOTTOM"),
bg.color = "grey80",
bg.alpha = 0.6,
height = 13,
frame = TRUE)
) +
# Countries of the world
tm_shape(world) +
tm_polygons(col = "black",
fill = NULL,
lwd = 0.6) +
# Species occurrences
tm_shape(points.map) +
tm_symbols(size = 0.2,
lwd = 0.1,
col = "black",
fill = "sp",
fill_alpha = 1,
fill.scale = tm_scale_categorical(
n = 1,
values = "black",
labels = my.species
),
fill.legend = tm_legend(
title = "Occurrences",
title.size = 0.6,
title.align = "left",
title.fontface = "bold",
text.size = 0.6,
show = TRUE,
orientation = "portrait",
position = tm_pos_in("LEFT", "BOTTOM"),
bg.color = "grey80",
bg.alpha = 0.6,
frame = TRUE
)
) +
# Map add-ons
tm_graticules(alpha = 0.5, labels.size = 1) +
tm_compass(
type = "4star",
size = 2,
position = tm_pos_in(pos.h = "RIGHT", pos.v = "BOTTOM", align.h = "center")
) +
tm_scalebar(
position = tm_pos_in(pos.h = "RIGHT", pos.v = "BOTTOM", align.h = "center"),
width = 15
) +
tm_layout(
scale = 1
) +
tm_crs(4326)
@gcamaro you can add labels in tm_scale_intervals() to control class names. See my example above.
I did that. Thanks. But I was thinking of a way to leave the numerical ranges calculated. In the first variable, the labels are placed and in the second, due to the restricted space to maintain the format of the legend, they are not. Perhaps, rotating the name of the first variable 90º and the labels of the second, also 90º, this could be done. But I adjusted it in another way, which works elegantly.
I agree! Adding the rotation option and extending space (to at least three characters) per label would be beneficial! @mtennekes, what do you think about this? Would that be possible?
Of course @Rapsodia86
I already added the rotation arguments xlab.rot and ylab.rot. The rotation itself was trivial to implement, but it takes some time to fine-tune the alignment and placement of the labels, and the resulting resizing of the whole legend.
tm_shape(land) +
tm_raster(
col = tm_vars(c("trees", "elevation"), multivariate = TRUE),
col.scale = tm_scale_bivariate(values = "pu_gn_bivs",
scale1 = tm_scale_intervals(breaks = c(0, 33, 66, 100)),
scale2 = tm_scale_intervals(breaks = c(-500, 1500, 4000, 6500), labels = c("L", "M", "H"))),
col.legend = tm_legend_bivariate(xlab = "Scale 1",
ylab = "Scale 2",
xlab.size = 2,
ylab.size = 3,
item.r = 0,
ylab.rot = 15,
item.width = 3,
item.height = 3,
text.size = 1.5))
Some other things you may notice:
- Arguments of
tm_legendare passed on. I deliberately didn't name them explicitly, but instead used the ... (to have the argument list not too long). - However, some arguments are handy for bivariate legends, in this example:
text.size,item.width,item.height, anditem.r - Probably I would make sense to pass on the text properties not just as
text.but also asxtext.andytext..
For some other examples of bivariate maps with tmap see https://tmap.geocompx.org/scales#sec-bivariate-scales
Wrote a vignette about bivariate choropleths: https://r-tmap.github.io/tmap/articles/examples_biv_choro Let me know your thoughts @gcamaro @Rapsodia86 @Nowosad It requires the dev version of cols4all.
It turned out really well! It will also be a great help in R mapping courses. The package documentation is excellent, and the example texts are crucial, given the various options and ways to obtain different results. Thank you!
Thanks, looking really good. Is it worth mentioning in the page that you need the dev cols4all? That could easily trip some users up.
Looks good! Maybe, it would be useful to add upfront that it also works for tm_raster? But that is a really minor thing! Thank you very much @mtennekes!