spatial network plot using ggplot - r

I would like to reproduce plot of spatial dependency of regions in ggplot2 rather then using basic plot in R
I provided reproduceble example in code below:
I followed example: Plotting neighborhoods network to a ggplot maps
library(leaflet)
library(ggplot2)
library(sf)
library(spdep)
URL <- "https://biogeo.ucdavis.edu/data/gadm3.6/Rsp/gadm36_CZE_1_sp.rds"
data <- readRDS(url(URL))
ggplot() +
geom_polygon(data = data, aes(x=long, y = lat, group = group), color = "black", fill = F)
cns <- knearneigh(coordinates(data), k = 3, longlat=T)
scnsn <- knn2nb(cns, row.names = NULL, sym = T)
cns
scnsn
cS <- nb2listw(scnsn)
summary(cS)
# Plot of regions and k-nn neighthorhours matrix
plot(data)
plot(cS, coordinates(data), add = T)
I am asking how to reproduce Plot of regions and k-nn neighthorhours matrix using ggplot.
I know we have to retrive each point input and then use geom_segment, however I dont know how to retrive it from cS object.

The other SO post you are refering contains all steps you need to follow to get your plot (thanks to the great answer from #StupidWolf).
Basically, you need to extract the different segment using:
1) Transform coordinates of data in a dataframe, it will facilitate its use later:
data_df <- data.frame(coordinates(data))
colnames(data_df) <- c("long", "lat")
This data_df contains now all x,y values for plotting points.
2) Now, we can retrieve segments informations from the cS object using:
n = length(attributes(cS$neighbours)$region.id)
DA = data.frame(
from = rep(1:n,sapply(cS$neighbours,length)),
to = unlist(cS$neighbours),
weight = unlist(cS$weights)
)
DA = cbind(DA, data_df[DA$from,], data_df[DA$to,])
colnames(DA)[4:7] = c("long","lat","long_to","lat_to")
In the DA dataframe, you have all informations required to draw each segments
3) Finally, you can put plot every parts:
ggplot(data, aes(x = long, y =lat))+
geom_polygon(aes(group = group), color = "black", fill = FALSE)+
geom_point(data = data_df, aes(x= long, y = lat), size = 1)+
geom_segment(data = DA, aes(xend = long_to, yend = lat_to), size=0.5)
Again, the solution provided by #StupidWolf was pretty well written and understandable, so I don't know why you were not able to reproduce it.

Related

Coloring polygons (made with concaveman) in ggplot by column

I'm adapting code for a map made by someone else that uses a package concaveman to generate concave hulls from points. The goal is to plot a number of different polygons in the oceans, and to color-code them by a grouping variable. The code works great to make a map of all the polygons and color-code them by identity:
library(sf)
library(concaveman)
library(data.table)
library(ggplot2)
dat <- data.table(longitude = c(-131.319783, -131.141266, -131.08165, -131.079066, -130.894966,
-131.063783, -131.10855, -131.215533, -131.189816, -131.14565,
-131.200866, -131.046466, -130.94055, -130.928983, -130.7513,
-130.8406, -130.833433, -130.830666, -130.82205, -130.89, -63.3666666666667,
-63.3666666666667, -63.1666666666667, -64.1833333333333, -63.3166666666667,
-63.3, -63.85, -63.9333333333333, -63.9333333333333, -63.5833333333333,
-63.5833333333333, -63.7, -63.7, -63.2833333333333, -63.5833333333333,
-63.95, -64.1833333333333, -63.8833333333333, -63.8, -63.2166666666667,
-5.6788, -5.4408, -5.6835, -5.424, -5.6475, -5.4371, -5.6181,
-5.4446, -5.6753, -5.4366, -5.6746, -5.4448, -5.6642, -5.4411,
-5.666, -5.4408, -5.624, -5.4321, -5.6806, -5.4473),
latitude = c(52.646633, 52.589683, 52.556516, 52.559816, 52.402916, 52.5983,
52.554216, 52.550883, 52.539166, 52.658216, 52.627966, 52.481733,
52.486033, 52.469033, 52.469166, 52.261833, 52.292133, 52.301066,
52.3523, 52.366966, 48.4666666666667, 48.4666666666667, 48.65,
49.0166666666667, 48.8166666666667, 48.8166666666667, 49.1, 48.8666666666667,
48.8666666666667, 48.8, 48.8166666666667, 48.4833333333333, 48.4833333333333,
48.8, 48.8166666666667, 48.8833333333333, 49.05, 49.0833333333333,
48.7166666666667, 48.6666666666667, 54.7201, 54.6033, 54.7191,
54.5733, 54.7225, 54.5923, 54.7261, 54.6076, 54.719, 54.5978,
54.7195, 54.6108, 54.7204, 54.6062, 54.7214, 54.5923, 54.7275,
54.592, 54.7207, 54.6188),
group = c(rep('NEPac',20),rep('NWAtl',20),rep('NEAtl',20))
)
split <- split(dat, dat$group)
split.sf <- lapply(split, st_as_sf, coords = c("longitude", "latitude"))
concave <- lapply(split.sf, concaveman, concavity = 3, length_threshold = 2)
concave.binded <- do.call('rbind', concave)
concave.spdf <- as_Spatial(concave.binded)
ggplot() +
geom_polygon(data = concave.spdf,
aes(x = long, y = lat, group = group, fill = group, color = group))
However, I can't figure out how to fill the polygons by anything other than whatever group is. Here is my attempt:
concave.spdf$ocean <- c('P','A','A')
ggplot() +
geom_polygon(data = concave.spdf,
aes(x = long, y = lat, group = group, fill = ocean, color = ocean))
Which throws this error: Error in FUN(X[[i]], ...) : object 'ocean' not found
I think the issue is that split groups the polygons by identity when passed to concaveman, but if I change that, they won't plot correctly (because the points of different polygons will be merged). How do I keep the polygons plotted individually but color them by a grouping variable? (If it's possible I'd prefer to stick with concaveman for aesthetic reasons in the true plot [which is much more complicated than this reprex] -- I know that if I use a different approach to plotting the polygons this would be easier.)
The simplest way to do this is by adding a scale_fill_manual:
ggplot() +
geom_polygon(data = concave.spdf,
aes(x = long, y = lat, group = group, fill = group)) +
scale_fill_manual(values = c("red", "green", "blue"),
labels = c("Ocean 1", "Ocean 2", "Ocean 3"))
Arguably, a better method is to convert to a simple features collection, to which you can add any columns you like, and automatically plot with geom_sf
concave.spdf <- st_as_sf(concave.spdf)
concave.spdf$ocean <- c("Ocean 1", "Ocean 2", "Ocean 3")
ggplot(concave.spdf) +
geom_sf(aes(fill = ocean))
Note that this automatically gives the correct co-ordinate proportions too.

Specify number bins when passing sf object to ggmap R

I have a dataframe object, created by reading in a shape file with sf::read_sf and merged with some pre-existing data with a common geography column:
boundaries <- sf::read_sf('./shapefile')
map <- merge(boundaries, data, by.x = "InterZone",
by.y = "IntermediateZone2011Code", all.x = FALSE, duplicateGeoms = TRUE)
This is then overlaid using ggmap on top of a provider tile obtained with the sf get_map function:
myMap <- get_map(location = c(lon = -2.27, lat = 57.1), zoom = 6,
maptype="toner", crop=FALSE, source = 'stamen')
ggmap(myMap) +
geom_sf(data = map, aes(fill=as.factor(column1)), inherit.aes = FALSE) +
scale_fill_brewer(palette = "OrRd") +
coord_sf(crs = st_crs(4326)) +
labs(x = 'Longitude', y = 'Latitude', fill = 'column1') +
ggtitle('column1')
The issue is that this auto creates hundreds of bins.
I have been looking through the documentation but cannot find an additional argument to specify the number of bins. How can I make it clear to breakdown the column by a fixed number of bins and then map this?
Without a reproducible example it is hard to say exactly what is going on, but it looks like you might be converting a continuous variable into a factor with fill=as.factor(column1).
One option is you remove as.factor and use scale_fill_continuous or some other continuous color scale of your choice.
Another option is to look into cut, where you bin continuous data by specifying the number of bins, or the specific start and end points of your bins.
# Make n bins
map$data_bin <- cut(map$column, breaks = n )
# Or make specific start and end points for bins
map$data_bin <- cut(map$column, breaks = c(-Inf,50,100,Inf) )

Spatial network based on maximum distance in ggplot2

I would like to plot network matrix of regions in ggplot - I know that for ggplot we need data.frame in tidy format in order to plot it.
I am able to plot network based on number of neighbours in ggplot however when I need spatial network based on maximum distance I get an error when creating data frame for ggplot.
I provided example down bellow:
library(ggplot2)
library(sf)
library(spdep)
# Polygon data
URL <- "https://biogeo.ucdavis.edu/data/gadm3.6/Rsp/gadm36_DEU_1_sp.rds"
data <- readRDS(url(URL))
CORD <- rbind(
coordinates(data)
)
rownames(CORD) <- NULL
# Spatial Network based on number of neighbours
cns <- knearneigh(CORD, k = 5, longlat=T)
scnsn <- knn2nb(cns, row.names = NULL, sym = T)
cS <- nb2listw(scnsn)
data_df <- data.frame(CORD)
colnames(data_df) <- c("long", "lat")
# Creating dataframe from spatail network (neiresth neighbours) for ggplot plot
n = length(attributes(cS$neighbours)$region.id)
DA = data.frame(
from = rep(1:n,sapply(cS$neighbours,length)),
to = unlist(cS$neighbours),
weight = unlist(cS$weights)
)
DA = cbind(DA, data_df[DA$from,], data_df[DA$to,])
colnames(DA)[4:7] = c("long","lat","long_to","lat_to")
# ggplot of spatial network
ggplot(data, aes(x = long, y =lat))+
geom_polygon(aes(group = group), color = "red", fill = FALSE) +
geom_segment(data = DA, aes(xend = long_to, yend = lat_to), size=0.5, color = "royalblue") +
coord_map()
### Another type of network matrix - Maximum distance
nb200km <- dnearneigh(CORD, d1=0, d2=100, longlat=T)
summary(nb200km)
cS_distance <- nb2listw(nb200km, zero.policy = T)
# I need to recreate this plot in ggplot
plot(data)
plot(W, coordinates(data), add = T)
data_df <- data.frame(CORD)
colnames(data_df) <- c("long", "lat")
n = length(attributes(cS_distance$neighbours)$region.id)
DA = data.frame(
from = rep(1:n,sapply(cS_distance$neighboaurs,length)),
to = unlist(cS_distance$neighbours),
weight = unlist(cS_distance$weights)
)
DA = cbind(DA, data_df[DA$from,], data_df[DA$to,])
colnames(DA)[4:7] = c("long","lat","long_to","lat_to")
creating dataframe from cS object works, however creating a dataframe from cS_distance object returns an error.
I would like to ask how to solve the error and plot distance spatial network in ggplot.
I'm not sure if this is what you're looking for, but the problem seems to be that you have some regions with no neighbours in cS_distance, so DA$to contains some zero values. This means when you do data_df[DA$from,] it has more rows than data_df[DA$to,], and your code throws an error when you try to cbind them.
If you filter out the rows where DA$to is zero, you get this:
n = length(attributes(cS_distance$neighbours)$region.id)
from <- rep(1:n,sapply(cS_distance$neighbours,length))
to <- unlist(cS_distance$neighbours)[]
weight <- numeric(length(to))
weight[which(to != 0)] <- unlist(cS_distance$weights)
DA = data.frame(from = from, to = to, weight = weight)
DA <- DA[DA$to != 0,]
DA = cbind(DA, data_df[DA$from,], data_df[DA$to,])
colnames(DA)[4:7] = c("long","lat","long_to","lat_to")
# ggplot of spatial network
ggplot(data, aes(x = long, y =lat))+
geom_polygon(aes(group = group), color = "red", fill = NA) +
geom_segment(data = DA, aes(xend = long_to, yend = lat_to), size=0.5, color = "royalblue") +
coord_map()

Create shaded polygons around points with ggplot2

I saw yesterday this beautiful map of McDonalds restaurants in USA. I wanted to replicate it for France (I found some data that can be downloaded here).
I have no problem plotting the dots:
library(readxl)
library(ggplot2)
library(raster)
#open data
mac_do_FR <- read_excel("./mcdo_france.xlsx")
mac_do_FR_df <- as.data.frame(mac_do_FR)
#get a map of France
mapaFR <- getData("GADM", country="France", level=0)
#plot dots on the map
ggplot() +
geom_polygon(data = mapaFR, aes(x = long, y = lat, group = group),
fill = "transparent", size = 0.1, color="black") +
geom_point(data = mac_do_FR_df, aes(x = lon, y = lat),
colour = "orange", size = 1)
I tried several methods (Thiessen polygons, heat maps, buffers), but the results I get are very poor. I can't figure out how the shaded polygons were plotted on the American map. Any pointers?
Here's my result, but it did take some manual data wrangling.
Step 1: Get geospatial data.
library(sp)
# generate a map of France, along with a fortified dataframe version for ease of
# referencing lat / long ranges
mapaFR <- raster::getData("GADM", country="France", level=0)
map.FR <- fortify(mapaFR)
# generate a spatial point version of the same map, defining your own grid size
# (a smaller size yields a higher resolution heatmap in the final product, but will
# take longer to calculate)
grid.size = 0.01
points.FR <- expand.grid(
x = seq(min(map.FR$long), max(map.FR$long), by = grid.size),
y = seq(min(map.FR$lat), max(map.FR$lat), by = grid.size)
)
points.FR <- SpatialPoints(coords = points.FR, proj4string = mapaFR#proj4string)
Step 2: Generate a voronoi diagram based on store locations, & obtain the corresponding polygons as a SpatialPolygonsDataFrame object.
library(deldir)
library(dplyr)
voronoi.tiles <- deldir(mac_do_FR_df$lon, mac_do_FR_df$lat,
rw = c(min(map.FR$long), max(map.FR$long),
min(map.FR$lat), max(map.FR$lat)))
voronoi.tiles <- tile.list(voronoi.tiles)
voronoi.center <- lapply(voronoi.tiles,
function(l) data.frame(x.center = l$pt[1],
y.center = l$pt[2],
ptNum = l$ptNum)) %>%
data.table::rbindlist()
voronoi.polygons <- lapply(voronoi.tiles,
function(l) Polygon(coords = matrix(c(l$x, l$y),
ncol = 2),
hole = FALSE) %>%
list() %>%
Polygons(ID = l$ptNum)) %>%
SpatialPolygons(proj4string = mapaFR#proj4string) %>%
SpatialPolygonsDataFrame(data = voronoi.center,
match.ID = "ptNum")
rm(voronoi.tiles, voronoi.center)
Step 3. Check which voronoi polygon each point on the map overlaps with, & calculate its distance to the corresponding nearest store.
which.voronoi <- over(points.FR, voronoi.polygons)
points.FR <- cbind(as.data.frame(points.FR), which.voronoi)
rm(which.voronoi)
points.FR <- points.FR %>%
rowwise() %>%
mutate(dist = geosphere::distm(x = c(x, y), y = c(x.center, y.center))) %>%
ungroup() %>%
mutate(dist = ifelse(is.na(dist), max(dist, na.rm = TRUE), dist)) %>%
mutate(dist = dist / 1000) # convert from m to km for easier reading
Step 4. Plot, adjusting the fill gradient parameters as needed. I felt the result of a square root transformation looks quite good for emphasizing distances close to a store, while a log transformation is rather too exaggerated, but your mileage may vary.
ggplot() +
geom_raster(data = points.FR %>%
mutate(dist = pmin(dist, 100)),
aes(x = x, y = y, fill = dist)) +
# optional. shows outline of France for reference
geom_polygon(data = map.FR,
aes(x = long, y = lat, group = group),
fill = NA, colour = "white") +
# define colour range, mid point, & transformation (if desired) for fill
scale_fill_gradient2(low = "yellow", mid = "red", high = "black",
midpoint = 4, trans = "sqrt") +
labs(x = "longitude",
y = "latitude",
fill = "Distance in km") +
coord_quickmap()

plotting points with geom_point() and gmap()

I have 20 UTM locations included below.
EDIT I have modified the included data to include the locations in Lat Long and added additional code below. I get the same result with either UTMs or Lat, Long. I have double checked the points in google earth and they are certainly within the extent of the BaseMap I define below. Any other suggestions...
Data <- structure(list(Latitude = c(43.383819, 43.383787, 43.383838,
43.384088, 43.392086, 43.393099, 43.388453, 43.384829, 43.399706,
43.40308, 43.408739, 43.40765, 43.407522, 43.413508, 43.418288,
43.416157, 43.417822, 43.417221, 43.417209, 43.417603), Longitude = c(-111.130989,
-111.130988, -111.130996, -111.129578, -111.122884, -111.12143,
-111.126514, -111.12809, -111.125333, -111.126616, -111.139745,
-111.140401, -111.140614, -111.161305, -111.158135, -111.153607,
-111.141158, -111.13867, -111.138528, -111.138884), UTM_E = c(489389.998429055,
489390.073847615, 489389.434748439, 489504.334690555, 490047.849470232,
490165.770080405, 489753.250369502, 489624.98731782, 489850.781221576,
489747.455360571, 488685.407063201, 488632.089708587, 488614.819667178,
486940.804672798, 487198.453753294, 487564.574155778, 488572.710048877,
488774.012335271, 488785.505688775, 488756.758707237), UTM_N = c(4803447.00888757,
4803443.45497495, 4803449.11983808, 4803476.70438902, 4804364.10875695,
4804476.43597084, 4803961.08216192, 4803558.81058659, 4805210.65084223,
4805585.51119635, 4806215.67635871, 4806094.82531047, 4806080.6391722,
4806748.45554565, 4807278.81358453, 4807041.46688704, 4807224.59410279,
4807157.51112311, 4807156.15933319, 4807199.96352795)), .Names = c("Latitude",
"Longitude", "UTM_E", "UTM_N"), row.names = c(NA, 20L), class = "data.frame")
And I can plot them in using ggplot
library(ggplot2)
ggplot(aes(x = UTM_E, y = UTM_N), data = Data )+ geom_point()
ggplot(aes(x = Latitude, y = Longitude), data = Data )+ geom_point()
However I want to plot them over a map base layer using qmap and have specified the object below.
library(ggmap)
Area <- "palisades wyoming"
BaseMap <- qmap(Area , zoom = 10)
BaseMap
Following the helpful ggmap site linked here, I am trying to plot the points over the base map with the following code
BaseMap + geom_point(aes(x = UTM_E, y = UTM_N), data = Data )
BaseMap + geom_point(aes(x = Latitude, y = Longitude), data = Data )
but get the following warning
Removed 20 rows containing missing values (geom_point).
Why are the values missing when not mapped with ggplot(), but only included with geom_point()
Mapping the points within ggplot() return the following error incompatability between ggplot() and qmap()
BaseMap + ggplot(aes(x = UTM_E, y = UTM_N), data = Data )+ geom_point()
Error in p + o : non-numeric argument to binary operator
In addition: Warning message:
Incompatible methods ("+.gg", "Ops.data.frame") for "+"
Thanks in advance.
You can combine them. Don't add your map to an object, just call it with the geom_point code:
qmap(Area , zoom = 10) + geom_point(aes(x = UTM_E, y = UTM_N), data = Data )
And you'll get your map printed.
Good Luck!

Resources