How to display only some country names with tm_text in R - r

I have created a map of Africa in R, using tmap, showing some numerical variables, but I would like to show the name of the countries only for the countries where the numerical variable is not 0.
So I have created a colour vector containing the value 'black' when the numeric was >0 and NA when it was =0. For a lot of functions, this will allow to display only the 'black' labels and not the NA ones, but with tm_text the NA values are shown in white on the map.
Map image
I tried to use several of the tm_text options, but nothing worked. If I change the NA with a colour name it works, the labels are displayed in the colour I indicated, but NA doesn't allow to make the labels disappear.
spdf_africa <- ne_countries(continent = 'africa',type="map_units",scale = "medium", returnclass = "sf")
xx<-numeric(length=57)
xx[match(names(tole),spdf_africa$name)]<-tole
xxcol<-xx
xxcol[xx>0]<-"black"
xxcol[xx==0]<-NA
afric=spdf_africa
afric$studies<-xx
afric$studiesTF<-xxcol
tm_shape(afric)+tm_fill(col="studies",title="Nb",style="cat")+tm_text("iso_a3",col="studiesTF",size=0.8)+tm_borders()
Of course I could use the pale yellow as label colour for the countries I don't want to display but these labels would still be visible when they overlap with borders or neighbouring countries.
Is there a way to do that more elegantly?
Thank you

There are, as usual, many different solutions.
You could just create another africa data frame (sfc), only with the features that you need, i.e. x>0 and go
tm_shape(afric) +
tm_fill(col="studies",title="Nb",style="cat") +
tm_borders() +
tm_shape(afric_selection) +
tm_text("iso_a3)
Reproducible Example
# load library
library(tmap)
# get world data set (sf)
data(World)
# create two shapes, one with border, one with selection (life expectancy bigger then 80 years) and only text
tm_shape(World) + tm_borders() +
tm_shape(World[World$life_exp > 80, ]) + tm_text("iso_a3")
Or the tidyverse way
tm_shape(World) +
tm_borders() +
tm_shape(World %>% filter(life_exp > 80)) +
tm_text("iso_a3")

Related

ggplot - How to conditional color points in a map

So I have a data frame that has all the information from climate_extremes.RData along with the city and country name, no. population, latitude , longitude (from packages 'maps', data (world.cities) ) All of it it's joined, everything good.
In the first bit of code I wanted to have a world map with all the cities as dots. It worked.
My problem comes on the second part where I had tried to have every city dot coloured according by the wsdi values. At the same time the city has to be grouped based on scenario and year.
I do not really understand why I get this error when everything is specified in the code? I am quite new to R and trying to learn it through a platform but ... yeah.
Error in check_aesthetics():
! Aesthetics must be either length 1 or the same as the data (939): x and y
`
map <- ggplot() + borders("world",fill="white",colour="gray80") + geom_point(aes(x=data_frame1$lon, y=data_frame1$lat), colour ="blue", size = 0.003)
data_frame1 %>% group_by(city,wsdi) %>% filter(scenario == 'historical' & year == '1997') %>%
select(city) %>%
ggplot() + borders("world",fill="white",colour="gray80") +
geom_point(aes(x=lon, y=lat), fill = wsdi , size = 0.03)
`
Thank you in advance!
I've also tried with color rather than fill, and tried to specify from where it is (like data_frame1&wsdi), same error.
Also wsdi is a column with numerical values and it has a corespondent to every city.
The praise goes to #Isaiah for pointing out the answer. You need to understand how {ggplot2} uses layers and maps variable to aesthetics.
Please also note that {ggplot2} works nicely with data frames. So you do not need to assign the vectors to the x- and y-aesthetics.
data
We simulate your wsdi (climate data) by assigning random integers to your data frame.
0. data frame operations
Working with data frames: You group and filter, then pull the variable city.
Please note that with ... %>% select(city) you truncate your data frame to this "single" column.
Note2: think about "ungrouping" data frames once you have done your calculations. This might hurt you with other operations.
library(dplyr) # data crunching
set.seed(666) # set seed to replicate random draws
df1 <- maps::world.cities %>%
mutate(wsdi = sample(1:10 #simulated values of climate
, nrow(maps::world.cities) #length of vector to input
, replace = TRUE) #identical values are possible
)
head(df1)
name country.etc pop lat long capital wsdi
1 'Abasan al-Jadidah Palestine 5629 31.31 34.34 0 5
2 'Abasan al-Kabirah Palestine 18999 31.32 34.35 0 9
3 'Abdul Hakim Pakistan 47788 30.55 72.11 0 1
4 'Abdullah-as-Salam Kuwait 21817 29.36 47.98 0 2
5 'Abud Palestine 2456 32.03 35.07 0 3
6 'Abwein Palestine 3434 32.03 35.20 0 5
1. Fixed colors - your first example that worked
ggplot() +
borders("world", fill = "white", colour = "grey80") + # specific ggplot layer for world maps
geom_point( data = df1 # our data frame for this layer
#-------- with aesthetic mapping you assign "varying" values to graph properties
, aes(x = long, y = lat) # mapping of variables to aesthetics (here: x- and y-position on our graph)
#-------- then you can assign "fixed" (aka static) properties
, colour = "blue" # make all points "blue"
, size = 0.003 # plot all points with size 0.003
)
2. Have color varying with value of wsdi variable
As mentioned by #Isaiah, you want to "map" the point color to a "changing" variable. Thus, we can no longer use a "fixed" assignment.
This is one of the characteristics of the grammar or graphics.
We created the wsdi variable in our data frame mutate above.
As a reminder ggplot works well with data frames. The simulated df1 contains the desired wsdi variable/column. Thus we can work with this data frame.
ggplot() +
borders("world", fill = "white", colour = "grey80") +
geom_point(data = df1
#-------- with aesthetic mappings ... now we add varying color! -------
, aes(x = long, y = lat
, colour = wsdi # color property mapped to variable
)
#------- any fixed properties of our graph -----------------------------
, size = 0.003)
This yields:
Note that wsdi is a continuous variable. Thus, ggplot uses a spectral palette. If you want to have dicrete colors (i.e. you group certain ranges of wsdi or factorise the values we simulated) you get a more colorful representation.
You can then move on to beautify the plot, e.g. add titles, change theme, etc.

How to combine sf elements (layers) in R

despite having some experience with R, I am much less experienced using R for GIS-like tasks.
I have a shapefile of all communities within Germany and created a new object that only shows the borders of the 16 states of Germany.
gem <- readOGR(path/to/shapefile.shp) # reading shapefile
gemsf <- st_read(path/to/shapefile.shp) # reading shapefile as sf object
f00 <- gUnaryUnion(gem, id = gem#data$SN_L) # SN_L is the column of the various states - this line creates a new sp object with only the states instead of all communities
f002 <- sf::st_as_sf(f00, coords = c("x","y")) # turning the object into an sf object, so graphing with ggplot is easier
To check my work so far I plotted the base data (communities) using
gemsf %>%
ggplot(data = .,) + geom_sf( aes(fill = SN_L)) # fill by state
as well as plot(f002) which creates a plot of the 16 states, while the ggplot-code provides a nice map of Germany by community, with each state filled in a different color.
Now I'd like to overlay this with a second layer that indicates the borders of the states (so if you e.g. plot population density you can still distinguish states easily).
My attempt to do so, I used "standard procedure" and added another layer
ggplot() +
geom_sf(data = gemsf, aes(fill = SN_L)) + # fill by state
geom_sf(data = f002) # since the f002 data frame/sf object ONLY has a geometry column, there is no aes()
results in the following output: https://i.ibb.co/qk9zWRY/ggplot-map-layer.png
So how do I get to add a second layer that only provides the borders and does not cover the actual layer of interest below? In QGIS or ArcGIS, this is common procedure and not a problem, and I'd like to be able to recreate this in R, too.
Thank you very much for your help!
I found a solution which I want to share with everyone.
ggplot() +
geom_sf(data = gemsf_data, aes(fill = log(je_km2))) + # fill by state
geom_sf(data = f002, alpha = 0, color = "black") + # since the f002 data frame/sf object ONLY has a geometry column, there is no aes()
theme_minimal()
The trick was adding "alpha" not in the aes() part, but rather just as shown above.

(tmap package in R) Filtering values within tm_text

I want to display tm_text options based on a filter of a data. Take the following example:
tm_shape(World) +
tm_polygons("HPI") +
tm_text("name", size = "AREA")
This displays text for every feature. But let's say I only wanted to display text for countries with a value of, say, HPI greater than 30. I hoped there could be an argument within tm_text, something like tm_text(..., filter = "HPI" > 30), but I can't see any like that. The only way I can think to do this is by creating another layer:
tm_shape(World) +
tm_polygons("HPI") +
tm_shape(World %>% filter(HPI > 30)) +
tm_text("name", size = "AREA")
The syntax is nice and intuitive and the text is what I want apart from colour; notice how now the colour of the text is all black. In the first example it displayed white font on darker polygons. My only reasoning for this is that the second layer isn't "aware" of the colour of the polygons since they are in an other layer.
One other way I thought might work was to include a variable within the data based on the filter.
World <- World %>% mutate(FLAG = if_else(HPI > 30, 1, 0)
tm_shape(World) +
tm_polygons("HPI") +
tm_text("name", size = "FLAG")
The placement and colour is right here, but ignoring the fact that I don't want flag variable all over my data, the size is now incorrect since I'm using it as a hacky way of filtering. Please also ignore the fact some text is not visible when white; obviously I would change the background color, this is an example and in my map the text will bve visible over the bg).
So in summary, i'm asking if there is a way I can achieve the text size + placement from the second map, but colour from the first map (and if possible, without editing the data).
In my opinion, the easiest solution is to modify the name variable creating another variable which is equal to name if HPI >= 30 and empty character vector otherwise. For example:
# packages
library(tmap)
library(dplyr)
# data
data("World")
# modify name column according to HPI
World <- World %>%
mutate(new_name = ifelse(HPI >= 30, as.character(name), ""))
# plot
tm_shape(World) +
tm_polygons("HPI") +
tm_text("new_name", size = "AREA")
Created on 2020-05-14 by the reprex package (v0.3.0)

How to add legend to plot with data from multiple data frames

I have scripted a ggplot compiled from two separate data frames, but as it stands there is no legend as the colours aren't included in aes. I'd prefer to keep the two datasets separate if possible, but can't figure out how to add the legend. Any thoughts?
I've tried adding the colours directly to the aes function, but then colours are just added as variables and listed in the legend instead of colouring the actual data.
Plotting this with base r, after creating the plot I would've used:
legend("top",c("Delta 18O","Delta 13C"),fill=c("red","blue")
and gotten what I needed, but I'm not sure how to replicate this in ggplot.
The following code currently plots exactly what I want, it's just missing the legend... which ideally should match what the above line would produce, except the "18" and "13" need superscripted.
Examples of an old plot using base r (with a correct legend, except lacking superscripted 13 and 18) and the current plot missing the legend can be found here:
Old: https://imgur.com/xgd9e9C
New, missing legend: https://imgur.com/eGRhUzf
Background data
head(avar.data.x)
time av error
1 1.015223 0.030233604 0.003726832
2 2.030445 0.014819145 0.005270609
3 3.045668 0.010054801 0.006455241
4 4.060891 0.007477541 0.007453974
5 5.076113 0.006178282 0.008333912
6 6.091336 0.004949045 0.009129470
head(avar.data.y)
time av error
1 1.015223 0.06810001 0.003726832
2 2.030445 0.03408136 0.005270609
3 3.045668 0.02313839 0.006455241
4 4.060891 0.01737148 0.007453974
5 5.076113 0.01405144 0.008333912
6 6.091336 0.01172788 0.009129470
The following avarn function produces a data frame with three columns and several thousand rows (see header above). These are then graphed over time on a log/log plot.
avar.data.x <- avarn(data3$"d Intl. Std:d 13C VPDB - Value",frequency)
avar.data.y <- avarn(data3$"d Intl. Std:d 18O VPDB-CO2 - Value",frequency)
Create allan deviation plot
ggplot()+
geom_line(data=avar.data.y,aes(x=time,y=sqrt(av)),color="red")+
geom_line(data=avar.data.x,aes(x=time,y=sqrt(av)),color="blue")+
scale_x_log10()+
scale_y_log10()+
labs(x=expression(paste("Averaging Time ",tau," (seconds)")),y="Allan Deviation (per mil)")
The above plot is only missing a legend to show the name of the two plotted datasets and their respective colours. I would like the legend in the top centre of the graph.
How to superscript legend titles?:
ggplot()+
geom_line(data=avar.data.y,aes(x=time,y=sqrt(av),
color =expression(paste("Delta ",18^,"O"))))+
geom_line(data=avar.data.xmod,aes(x=time,y=sqrt(av),
color=expression(paste("Delta ",13^,"C"))))+
scale_color_manual(values = c("blue", "red"),name=NULL) +
scale_x_log10()+
scale_y_log10()+
labs(
x=expression(paste("Averaging Time ",tau," (seconds)")),
y="Allan Deviation (per mil)") +
theme(legend.position = c(0.5, 0.9))
Set color inside the aes and add a scale_color_ function to your plot should do the trick.
ggplot()+
geom_line(data=avar.data.y,aes(x=time,y=sqrt(av), color = "a"))+
geom_line(data=avar.data.x,aes(x=time,y=sqrt(av), color="b"))+
scale_color_manual(
values = c("red", "blue"),
labels = expression(avar.data.x^2, "b")
) +
scale_x_log10()+
scale_y_log10()+
labs(
x=expression(paste("Averaging^2 Time ",tau," (seconds)")),
y="Allan Deviation (per mil)") +
theme(legend.position = c(0.5, 0.9))
You can make better use of ggplot's aesthetics by combining both data sets into one. This is particularly easy when your data frames have the same structure. Here, you could then for example use color.
This way you only need one call to geom_line and it is easier to control the legend(s). You could even make some fancy function to automate your labels. etc.
Also note that white spaces in column names are not great (you're making your own life very difficult) and that you may want to think about automating your avarn calls, e.g. with lapply, which would result in a list of data frames and makes the binding of the data frames even easier.
avar.data.x <- readr::read_table("0 time av error
1 1.015223 0.030233604 0.003726832
2 2.030445 0.014819145 0.005270609
3 3.045668 0.010054801 0.006455241
4 4.060891 0.007477541 0.007453974
5 5.076113 0.006178282 0.008333912
6 6.091336 0.004949045 0.009129470")
avar.data.y <- readr::read_table("0 time av error
1 1.015223 0.06810001 0.003726832
2 2.030445 0.03408136 0.005270609
3 3.045668 0.02313839 0.006455241
4 4.060891 0.01737148 0.007453974
5 5.076113 0.01405144 0.008333912
6 6.091336 0.01172788 0.009129470")
library(tidyverse)
combine_df <- bind_rows(list(a = avar.data.x, b = avar.data.y), .id = 'ID')
ggplot(combine_df)+
geom_line(aes(x = time, y = sqrt(av), color = ID))+
scale_color_manual(values = c("red", "blue"),
labels = c(expression("Delta 18"^"O"), expression("Delta 13"^"C")))
Created on 2019-11-11 by the reprex package (v0.2.1)

ggplot scale_fill_discrete(breaks = user_countries) creates a second, undesired legend

I am trying to change the factor level ordering of a data frame column to control the legend ordering and ggplot coloring of factor levels specified by country name. Here is my dataframe country_hours:
countries hours
1 Brazil 17
2 Mexico 13
3 Poland 20
4 Indonesia 2
5 Norway 20
6 Poland 20
Here is how I try to plot subsets of the data frame depending on a list of selected countries, user_countries:
make_country_plot<-function(user_countries, country_hours_pre)
{
country_hours = country_hours_pre[which(country_hours_pre$countries %in% user_countries) ,]
country_hours$countries = factor(country_hours$countries, levels = c(user_countries))
p = ggplot(data=country_hours, aes(x=hours, color=countries))
for(name in user_countries){
p = p + geom_bar( data=subset(country_hours, countries==name), aes(y = (..count..)/sum(..count..), fill=countries), binwidth = 1, alpha = .3)
}
p = p + scale_y_continuous(labels = percent) + geom_density(size = 1, aes(color=countries), adjust=1) +
ggtitle("Baltic countries") + theme(plot.title = element_text(lineheight=.8, face="bold")) + scale_fill_discrete(breaks = user_countries)
}
This works great in that the coloring goes according to my desired order as does the top legend, but a second legend appears and shows a different order. Without scale_fill_discrete(breaks = user_countries) I do not get my desired order, but I also do not get two legends. In the plot shown below, the desired order, given by user_countries was
user_countries = c("Lithuania", "Latvia", "Estonia")
I'd like to get rid of this second legend. How can I do it?
I also have another problem, which is that the plotting/coloring is inconsistent between different plots. I'd like the "first" country to always be blue, but it's not always blue. Also the 'real' legend (darker/solid colors) is not always in the same position - sometimes it's below the incorrect/black legend. Why does this happen and how can I make this consistent across plots?
Also, different plots have different numbers of factor groups, sometimes more than 9, so I'd rather stick with standard ggplot coloring as most of the solutions for defining your own colors seem limited in the number of colors you can do (How to assign colors to categorical variables in ggplot2 that have stable mapping?)
You are mapping to two different aesthetics (color and fill) but you changed the scale specifications for only one of them. Doing this will always split a previously combined legend. There is a nice example of this on this page
To keep your legends combined, you'll want to add scale_color_discrete(breaks = user_countries) in addition to scale_fill_discrete(breaks = user_countries).
I don't have enough reputation to comment, but this previous question has a comprehensive answer.
Short answer is to change geom_density so that it doesn't map countries to color. That means just taking everything inside the aes() and putting it outside.
geom_density(size = 1, color=countries, adjust=1)
(This should work. Don't have an example to confirm).

Resources