Fix ggplot fill scale across multiple plots - r

Based on this question, I try to plot color coded maps for multiple years. Consider the following minimal example:
library(ggplot2)
states <- map_data("state")
var <- data.frame(table(states$region))
var$x1 = runif(49, 5.0, 7.5)
var$x2 = 2+runif(49, 5.0, 7.5)
states$x1 <- var$x1[match(states$region,var$Var1)]
states$x2 <- var$x2[match(states$region,var$Var1)]
## first plot
map <- ggplot(states, aes(x=long, y=lat,fill=x1,group=group))+ geom_polygon()
map = map + scale_fill_gradient(low='white', high='grey20')
print(map)
## second plot
map <- ggplot(states, aes(x=long, y=lat,fill=x2,group=group))+ geom_polygon()
map = map + scale_fill_gradient(low='white', high='grey20')
print(map)
The plots work, yet the scale is adjusted to each plot. therefore comparing color across graphs is not meaningful. I would like to have a fixed "number to color" scale across all graphs (I am aware that I might lose some details in my graphs by doing so).

Add limits to scale_fill_gradient:
map <- ggplot(states, aes(x=long, y=lat,fill=x1,group=group))+ geom_polygon()
map = map + scale_fill_gradient(low='white', high='grey20', limits=c(1, 10))
print(map)
map <- ggplot(states, aes(x=long, y=lat,fill=x2,group=group))+ geom_polygon()
map = map + scale_fill_gradient(low='white', high='grey20', limits=c(1, 10))
print(map)

Related

How can I scale the size of points in geom_sf in R/ggplot2 so that two plots will use the same scale?

I am trying to make a map of several points using ggplot2 with the geom_sf() function. I am scaling the size of these points using a variable n.
I have two maps I want to combine side by side. I cannot figure out how to get these two maps to use the same scale for the size=n, so that one legend will work for both plots.
Where:
a_sf and b_sf are sf tibbles with named points, latitutde, longitude and an explanatory variable n.
library(rnaturalearth)
library(gridExtra)
library(ggmap)
library(sf)
world <- ne_countries(scale = "medium", returnclass = "sf")
map_a <- ggplot(data = world) +
geom_sf() +
geom_sf(data = a_sf, col = "darkred", alpha=0.3, aes(size=n)) +
coord_sf(xlim=c(0,60), ylim=c(0,-40), expand = FALSE) +
labs(size="Study size")
map_b <- ggplot(data = world) +
geom_sf() +
geom_sf(data = b_sf, col = "darkred", alpha=0.3, aes(size=n), show.legend="point") +
coord_sf(xlim=c(0,60), ylim=c(0,-40), expand = FALSE) +
labs(size="Study size")
grid.arrange(a_sf, b_sf)
Output plot from above code
I've tried every combination of + scale_size_discrete(), + scale_size_binned() and guides(), I can think of, but I cannot work this out.
I want both plots to have the same size and be labelled in the legend at eg. "50", "100", "250", "1000", so one legend works for both plots. Thanks!

Ggplot2 - Map polygons aes fill in ggplot() versus geom()

I am practicing the ggplot2 grammar of graphics on World maps using the base R dataset and the ggplot2 and mapproj packages.
When building a map that colours countries by a random variable (called "CountryColour" in the following example):
world_map <- map_data("world")
country_colours <- data.frame(region = c(names(table(world_map$region))),
colour= sample(c(1:20), length(names(table(world_map$region))), replace = TRUE))
world_map <- merge(world_map, country_colours)
world_map <- world_map[order(world_map$region, world_map$order),]
I happened to include the aes(fill) argument in the ggplot component:
ggplot(world_map[abs(world_map$long) < 180,], aes(x=long, y=lat, group=group, fill=colour)) +
geom_polygon(color="black") +
coord_map(projection = "mercator")
Now, if I include it in the ggplot() component itself I get EXACTLY the same output:
ggplot(world_map[abs(world_map$long) < 180,], aes(x=long, y=lat, group=group)) +
geom_polygon(aes(fill=colour), color="black") +
coord_map(projection = "mercator")
I would like to understand the conceptual difference between placing the fill function in one place or the other.

Mapping sampling sites on a map in R + creating a zoom in of a region in the same map

for my PhD project, I'd like to show my sampling sites (coordinates) on a map showing them first on a map of NZ and then building a zoom in of the region (coordinates that I pick myself) to show the sampling sites in that specific region. I am very new to R and I am finding a bit frustrating.
I managed to build a map of NZ (code follows) but how can I add the data points on it and how can I create a zoom in of a certain region and adding data points on it as well??
NZ <- map_data("nz",xlim = c(166, 179), ylim = c(-48, -34))
ggplot() +
geom_path(aes(long, lat, group=group), data=NZ, color="black") +
coord_equal() +
scalebar(NZ, dist = 100, dist_unit = "km", st.size=3, height=0.01, model = 'WGS84', transform = TRUE)
Thanks to whoever will help me!!
For example:
library(tidyverse)
dunedin <- tibble(X=170.5, Y=-45 - 52/60, Text="Dunedin")
NZ <- map_data("nz",xlim = c(166, 179), ylim = c(-48, -34))
ggplot() +
geom_path(aes(long, lat, group=group), data=NZ, color="black") +
geom_point(data=dunedin, aes(x=X, y=Y), colour="blue") +
geom_label(data=dunedin, aes(x=X, y=Y, label=Text), colour="blue", nudge_x=1) +
coord_equal()
Incidentally, scalebar isn't part of ggplot2, so your example isn't self-contained. That's not a major issue here, but could be in another situation.

Continuous colour gradient that applies to a single geom_polygon element with ggplot2 in R

I am creating a choropleth map for CO2 emissions in Europe using ggplot2 and ggmap in R. The fill colour for each country corresponds to its CO2 emissions. The colours and continuous legend are implemented with scale_fill_gradient. However, I would also like for a single country to have a colour that is not from the continuous legend (for later use in a Shiny application). I have not worked out how to have scale_fill_gradient only apply to one of multiple geom_polygon layers.
Here is my initial map:
To reproduce this map, download the map data from http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_0_countries.zip
Here is the code:
library(maptools)
library(ggplot2)
library(ggmap)
# read in a world map
WorldMap <- readShapePoly(fn="ne_50m_admin_0_countries/ne_50m_admin_0_countries")
# reduce shape file to a filtered data frame
WorldMapDf <- fortify(WorldMap,region='iso_a2')
# read in CO2 emissions
GEO <- c('FR','AT','BE','DE','DK','FI','GB','IE','NL','NO','SE')
CO2 <- c(59.5,86.9,137.4,425.8,303.9,353.2,380.3,427.0,476.4,1.8,47.6)
CO2_df <- data.frame(GEO,CO2)
# the range of values that the colour scale should cover
colorbar_range <- range(CO2_df$CO2)
mean_price <- mean(colorbar_range)
# merge map polygons with electricity CO2 emissions
CO2Map <- merge(WorldMapDf, CO2_df, by.x="id", by.y="GEO", all.x = T)
CO2Map <- CO2Map[order(CO2Map$order),]
#limit data to main Europe
europe.limits <- data.frame(matrix(c(35.50,-11.43,70,31.11),nrow=2,ncol=2,byrow = T))
names(europe.limits) <- c('lat','long')
CO2MapSubset <- subset(CO2Map, long > min(europe.limits$lon) & long < max(europe.limits$lon) & lat > min(europe.limits$lat) & lat < max(europe.limits$lat))
# create x and y limits
xrange <- c(min(CO2MapSubset$long),max(CO2MapSubset$long))
yrange <- c(min(CO2MapSubset$lat),max(CO2MapSubset$lat))
initial_map <- ggplot(data=CO2MapSubset) + # data layer
geom_polygon(aes(x=long, y=lat, group=group, fill=CO2)) +
coord_map(projection = "mercator",xlim=xrange,ylim=yrange) +
scale_fill_gradient2(low='gold',mid = "white",high='firebrick2',na.value = "lightgrey",midpoint=mean_price,limits=colorbar_range, name='CO2 (g/kWh)') +
geom_path(aes(x=long, y=lat, group=group), color='black',size=0.2)
# display map
initial_map
Now, I would like to make one country a different colour (for example, blue) that is not on the continuous colour scale as shown in the legend.
I thought I could do this by adding an additional geom_polygon layer to the initial map. To make Denmark blue, I tried this:
map_with_selected_country <- initial_map +
geom_polygon(data = CO2MapSubset[CO2MapSubset$id == 'DK',], aes(x=long, y=lat, group=group, fill='blue'))
but I get the error message: 'Error: Discrete value supplied to continuous scale' because the fill 'blue' conflicts with scale_fill_gradient2. Is there a way to make scale_fill_gradient2 only point to one dataset? Or is there another way to tackle this problem?
Here's an example:
library(ggplot2)
map <- map_data("world")
map$value <- setNames(sample(1:50, 252, T), unique(map$region))[map$region]
p <- ggplot(map, aes(long, lat, group=group, fill=value)) +
geom_polygon() +
coord_quickmap(xlim = c(-50,50), ylim=c(25,75))
p + geom_polygon(data = subset(map, region=="Germany"), fill = "red")
Germany is overplotted using a red fill color:
You can adapt this example to fit your needs.

Choropleth map in ggplot with polygons that have holes

I'm trying to draw a choropleth map of Germany showing poverty rate by state (inspired by this question).
The problem is that some of the states (Berlin, for example) are completely surrounded by other states (Brandenburg), and I'm having trouble getting ggplot to recognize the "hole" in Brandenburg.
The data for this example is here.
library(rgdal)
library(ggplot2)
library(RColorBrewer)
map <- readOGR(dsn=".", layer="germany3")
pov <- read.csv("gerpoverty.csv")
mrg.df <- data.frame(id=rownames(map#data),ID_1=map#data$ID_1)
mrg.df <- merge(mrg.df,pov, by="ID_1")
map.df <- fortify(map)
map.df <- merge(map.df,mrg.df[,c("id","poverty")], by="id")
ggplot(map.df, aes(x=long, y=lat, group=group)) +
geom_polygon(aes(fill=poverty))+
geom_path(colour="grey50")+
scale_fill_gradientn(colours=brewer.pal(5,"OrRd"))+
labs(x="",y="")+ theme_bw()+
coord_fixed()
Notice how the colors for Berlin and Brandenburg (in the northeast) are identical. They shouldn't be - Berlin's poverty rate is much lower than Brandenburg. It appears that ggplot is rendering the Berlin polygon and then rendering the Brandenburg polygon over it, without the hole.
If I change the call to geom_polygon(...) as suggested here, I can fix the Berlin/Brandenburg problem, but now the three northernmost states are rendered incorrectly.
ggplot(map.df, aes(x=long, y=lat, group=group)) +
geom_polygon(aes(group=poverty, fill=poverty))+
geom_path(colour="grey50")+
scale_fill_gradientn(colours=brewer.pal(5,"OrRd"))+
labs(x="",y="")+ theme_bw()+
coord_fixed()
What am I doing wrong??
This is just an expansion on #Ista's answer, which does not require that one knows which states (Berlin, Bremen) need to be rendered last.
This approach takes advantage of the fact that fortify(...) generates a column, hole which identifies whether a group of coordinates are a hole. So this renders all regions (id's) with any holes before (e.g. underneath) the regions without holes.
Many thanks to #Ista, without whose answer I could not have come up with this (believe me, I spent many hours trying...)
ggplot(map.df, aes(x=long, y=lat, group=group)) +
geom_polygon(data=map.df[map.df$id %in% map.df[map.df$hole,]$id,],aes(fill=poverty))+
geom_polygon(data=map.df[!map.df$id %in% map.df[map.df$hole,]$id,],aes(fill=poverty))+
geom_path(colour="grey50")+
scale_fill_gradientn(colours=brewer.pal(5,"OrRd"))+
labs(x="",y="")+ theme_bw()+
coord_fixed()
You can plot the island polygons in a separate layer, following the example on the ggplot2 wiki. I've modified your merging steps to make this easier:
mrg.df <- data.frame(id=rownames(map#data),ID_1=map#data$ID_1)
mrg.df <- merge(mrg.df,pov, by="ID_1")
map.df <- fortify(map)
map.df <- merge(map.df,mrg.df, by="id")
ggplot(map.df, aes(x=long, y=lat, group=group)) +
geom_polygon(aes(fill=poverty), color = "grey50", data =subset(map.df, !Id1 %in% c("Berlin", "Bremen")))+
geom_polygon(aes(fill=poverty), color = "grey50", data =subset(map.df, Id1 %in% c("Berlin", "Bremen")))+
scale_fill_gradientn(colours=brewer.pal(5,"OrRd"))+
labs(x="",y="")+ theme_bw()+
coord_fixed()
As an unsolicited act of evangelism, I encourage you to consider something like
library(ggmap)
qmap("germany", zoom = 6) +
geom_polygon(aes(x=long, y=lat, group=group, fill=poverty),
color = "grey50", alpha = .7,
data =subset(map.df, !Id1 %in% c("Berlin", "Bremen")))+
geom_polygon(aes(x=long, y=lat, group=group, fill=poverty),
color = "grey50", alpha= .7,
data =subset(map.df, Id1 %in% c("Berlin", "Bremen")))+
scale_fill_gradientn(colours=brewer.pal(5,"OrRd"))
to provide context and familiar reference points.
Just to add another small improvement to #Ista's and #jhoward's answers (thanks a lot for your help!).
The modification of #jhoward could be easily wrapped in a small function like this
gghole <- function(fort){
poly <- fort[fort$id %in% fort[fort$hole,]$id,]
hole <- fort[!fort$id %in% fort[fort$hole,]$id,]
out <- list(poly,hole)
names(out) <- c('poly','hole')
return(out)
}
# input has to be a fortified data.frame
Then, one doesn't need to recall every time how to extract holes info. The code would look like
ggplot(map.df, aes(x=long, y=lat, group=group)) +
geom_polygon(data=gghole(map.df)[[1]],aes(fill=poverty),colour="grey50")+
geom_polygon(data=gghole(map.df)[[2]],aes(fill=poverty),colour="grey50")+
# (optionally). Call by name
# geom_polygon(data=gghole(map.df)$poly,aes(fill=poverty),colour="grey50")+
# geom_polygon(data=gghole(map.df)$hole,aes(fill=poverty),colour="grey50")+
scale_fill_gradientn(colours=brewer.pal(5,"OrRd"))+
labs(x="",y="")+ theme_bw()+
coord_fixed()
Alternatively you could create that map using rworldmap.
library(rworldmap)
library(RColorBrewer)
library(rgdal)
map <- readOGR(dsn=".", layer="germany3")
pov <- read.csv("gerpoverty.csv")
#join data to the map
sPDF <- joinData2Map(pov,nameMap='map',nameJoinIDMap='VARNAME_1',nameJoinColumnData='Id1')
#default map
#mapPolys(sPDF,nameColumnToPlot='poverty')
colours=brewer.pal(5,"OrRd")
mapParams <- mapPolys( sPDF
,nameColumnToPlot='poverty'
,catMethod="pretty"
,numCats=5
,colourPalette=colours
,addLegend=FALSE )
do.call( addMapLegend, c( mapParams
, legendLabels="all"
, legendWidth=0.5
))
#to test state names
#text(pov$x,pov$y,labels=pov$Id1)

Resources