Ggplot and sf for overlaying two layers of polygons (.shp) - r

I have two polygon '.shp' files. I need one to appear on the map by filling in one variable and the other to appear only on borders, overlapping the first.
I have already used 'ggplot2' and 'sf'.
I plotted a map ('map1'), which is layered with polygons, using 'ggplot' and 'geom_sf'.
I use a variable ('var1') contained in 'map1' as a 'fill'.
Now, I need to add (overlay) another layer of polygons on top ('map2'). This will have to be 'transparent fill' or 'no fill'. Only appearing the outline of the borders.
library(ggplot2); library(sf)
map1 <- st_read("m1.shp") #reading polygon layer map 1
map2 <- st_read("m2.shp")#reading polygon layer map 2
g <- ggplot(map1, aes(fill = var1)) +
geom_sf()
How can i add 'map2' for overlay this map?
The idea would be:
g <- ggplot(map1, aes(fill = var1)) +
geom_sf() +
ggplot(map2, aes()) +
geom_sf()
#Error: Don't know how to add ggplot(map2, aes()) to a plot

Every geom_SOMETHING() function has a data argument where you can configure the data you are using. This argument plays the same role as the data argument in the ggplot() function. When you specify data in ggplot, all the other geom_SOMETHING() function inherit the argument. The same happens with the arguments in aes()
So the first recommendation is remove the data = map1 and aes arguments from ggplot and add it to the geom_sf function.
g <- ggplot() + geom_sf(map1, aes(fill = var1)) + geom_sf(map2)

Related

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.

Mapping different states with geom_sf using facet wrap and scales free

First, I am aware of this answer : Mapping different states in R using facet wrap
But I work with object of library sf.
It seems that facet_wrap(scales = "free") is not available for objects plotted with geom_sf in ggplot2. I get this message:
Erreur : Free scales are only supported with coord_cartesian() and
coord_flip()
Is there any option I have missed ?
Anyone has solve the problem without being forced to use cowplot (or any other gridarrange)?
Indeed, here is an example. I would like to show the different French regions separately in facets but with their own x/y limits.
The result without scales = "free"
Scales are calculated with the extent of the entire map.
FRA <- raster::getData(name = "GADM", country = "FRA", level = 1)
FRA_sf <- st_as_sf(FRA)
g <- ggplot(FRA_sf) +
geom_sf() +
facet_wrap(~NAME_1)
The result using cowplot
I need to use a list of ggplots and can then combine them.
This is the targeted output. It is cleaner. But I also want a clean way to add a legend. (I know may have a common legend like in this other SO question :
facet wrap distorts state maps in R)
g <- purrr::map(FRA_sf$NAME_1,
function(x) {
ggplot() +
geom_sf(data = filter(FRA_sf, NAME_1 == x)) +
guides(fill = FALSE) +
ggtitle(x)
})
g2 <- cowplot::plot_grid(plotlist = g)
I know you are looking for a solution using ggplot2, but I found the tmap package could be a choice depends on your need. The syntax of tmap is similar to ggplot2, it can also take sf object. Take your FRA_sf as an example, we can do something like this.
library(tmap)
tm_shape(FRA_sf) +
tm_borders() +
tm_facets(by = "NAME_1")
Or we can use geom_spatial from the ggspatial package, but geom_spatial only takes Spatial* object.
library(ggplot2)
library(ggspatial)
ggplot() +
geom_spatial(FRA) + # FRA is a SpatialPolygonsDataFrame object
facet_wrap(~NAME_1, scales = "free")

Incorrect polygon for New York State in ggplot2 maps

I am trying to plot data on New York state map. I am using map_data code. But If you look at polygon, It shows extra piece which is actually not part of New York state? Any ideas how can I apply filter on map data to remove that?
ny <- map_data("state", region="new york")
s1 <- ggplot() + geom_polygon(data=ny, aes(x=long, y=lat))
s2 <- ggplot() + geom_point(data=ny, aes(x=long, y=lat))
grid.arrange(s1, s2, ncol=2)
Output:
geom_point shows correct boundary, but not polygon
The state is actually composed of multiple polygons which are not connected. You just need to tell ggplot which points go with which groups. This is done by mapping your data to the group argument of aes(). See the documentation here, although it would be nicer if they had a map example.
So how do you know which points go with which groups? The data frame returned by map_data() contains a group column. See:
head(ny)
ny$group
To plot the map correctly, use:
ggplot() + geom_polygon(data = ny, aes(x = long, y = lat, group = group))

ggplot2; Adding points from a different dataset and a raster/matrix data to a plot of a spatialpolygonsdataframe

I tried this but i get Error in eval(expr, envir, enclos) : object 'group' not found. pj4s is an object containing the projection in latlong.
my code
fx.ggplot<-function(ctry,aesfill="id",scalefill="Country",pathcol="white"){
#ctry is a shapefile of countries
ctry#data$id = rownames(ctry#data)
ctry.points = fortify(ctry, region="id")
ctry.df = join(ctry.points, ctry#data, by="id")
(txt<-paste("p<-ggplot(ctry.df) + aes(long,lat,group=group,fill=",aesfill,")"))
eval(parse(text=txt))
p<-p+ geom_polygon() +
geom_path(color=pathcol) +
coord_equal() +
scale_fill_brewer(scalefill)
return(p)
}
#generate a grid of points
xo<-seq(25,45,0.5)
yo<-seq(-15,5,0.5)
head(xy<-cbind(expand.grid(xo,yo)));names(xy)<-c("lon","lat")
head(xy.sp <- SpatialPoints(xy,proj4string=pj4s))
Overlay<-over(xy.sp,ctry)
xy<-xy[!apply(Overlay, 1, function(x) any(is.na(x))),]
#plot
(p<-fx.ggplot(ctry))
(P<-p+geom_point(data=xy, aes(x=lon, y=lat))) #addpoints returns Error
ggplot()+geom_point(data=xy, aes(x=lon, y=lat)) #This plots the points (as below) without error but in a new plot.
#matrix
mat.ctry<-fx.polygon2raster2array(shp=ctry,xo,yo,cTim=1)
mat<-cbind(expand.grid(xo,yo),c(mat.ctry));names(mat)<-c(names(xy),"Z")
mat<-mat[!apply(mat, 1, function(x) any(is.na(x))),]
p+geom_raster(data=mat,aes(x=lon,y=lat,colour="red"))
#returns Error
yet ggplot()+geom_raster(data=mat,aes(x=lon,y=lat,colour="red")) returns what is expected.
So where am i failing?
In case anyone else can't get the accepted answer work for them: a key is what #aosmith noted in a comment to the original question.
Adding the geom_point(data=xy) creates a new layer on the plot. ggplot2 expects this new layer to match all the aesthetics of the original layer, so you have to specifically define any differences. In this case, the original geom_polygon layer uses group and fill aesthetics but the geom_point layer does not. Therefore, you must specify group=NULL and fill=NULL for geom_point, like so:
(myplot + geom_point(data=xy, aes(group=NULL, fill=NULL)))
Let's break down what is going on and fix some things. First, load the necessary libraries for the code/example.
library("rgeos")
library("sp")
library("ggplot2")
library("plyr")
Looking at your example map, I figured out which east African countries you had. I pulled shape files for them from the rworldmap package. These may not be as nice as your shape files, but they will do for now.
library("rworldmap")
data(countriesLow)
ctry <- countriesLow[countriesLow$ISO3.1 %in% c("TZA", "KEN", "UGA", "RWA", "BDI"),]
Create the grid of points you had. I tidied up the code some, dropping some calls to head and simplifying the subsetting code.
#generate a grid of points
xy<-expand.grid(long=seq(25,45,0.5),lat=seq(-15,5,0.5))
xy.sp <- SpatialPoints(xy, proj4string=CRS(proj4string(ctry)))
Overlay<-over(xy.sp,ctry)
xy<-xy[!is.na(Overlay$ISO3.1),]
Now your fx.ggplot call can be re-written as
fx.ggplot<-function(ctry, aesfill="id", scalefill="Country", pathcol="white") {
##ctry is a shapefile of countries
ctry#data$id = rownames(ctry#data)
ctry.points = fortify(ctry, region="id")
ctry.df = join(ctry.points, ctry#data, by="id")
ggplot(ctry.df, aes(long, lat)) +
geom_polygon(aes_string(group="group", fill=aesfill)) +
geom_path(colour = pathcol) +
scale_fill_brewer(scalefill)
}
The overall aes is just long and lat; geom_polygon needs the additional aesthetics group and fill, and those are set using aes_string since the name of the variable for fill is being passed into the function.
(p <- fx.ggplot(ctry))
There are some artifacts in this version but that is (likely) do the shape file I was using; I didn't see them in the plot you showed, so you should not have a problem with that.
p + geom_point(data=xy)

Specifying the colour scale for maps in ggplot

Having difficulty setting the colour scales for maps in ggplot. I need greyscale. Very grateful for an idea where I'm going wrong. I also wonder if there is a more efficient way of getting the colour variable into ggplot (i.e. than by attaching it to 'fortified' data)?
library(ggplot2)
states <- map_data("state")
var <- data.frame(table(states$region)) # using rows as a dummy variable
states$variable <- var$Freq[match(states$region,var$Var1)]
map <- ggplot(states, aes(x=long, y=lat)) +
geom_polygon(aes(group=group, fill=variable), col=NA,lwd=0)
map + scale_colour_gradient(low='white', high='grey20')
map + scale_colour_grey()
You need to use scale_fill_* and not scale_color_*. For the polygon geometry the fill color of the polygons is coupled to the fill aesthetic, not to the color aesthetic. In general, the function used to change the details of a particular scale is scale_aesthetic_name_type_of_scale, e.g. scale_fill_gradient.
This code works for me.
library(ggplot2)
states <- map_data("state")
var <- data.frame(table(states$region))
states$variable <- var$Freq[match(states$region,var$Var1)]
map <- ggplot(states, aes(x=long, y=lat,fill=variable,group=group)) + geom_polygon()
map + scale_fill_gradient(low='white', high='grey20')
An easy way to handle problems with discrete variables is to create a "fake" continuous palette using the color palette function. See the below example.
Define color palette, here I used the hex codes for black and white but you can use any colors
gs.pal <- colorRampPalette(c("#FFFFFF","#000000"),bias=.1,space="rgb")
Now create some fake data
x <- rnorm(100)
dat <- data.frame(cbind(x))
dat$fac <- as.factor(sort(rep(1:5,20)))
dat$y <- x * as.numeric(dat$fac)
Next plot it with the ggplot function scale_*type _manual in this case since its color
in you'd use scale_colour_manual but above you'd use scale_fill_manual
ggplot(dat,aes(x=x,y=y,colour=fac))+geom_point()+scale_colour_manual(values=gs.pal(5))

Resources