geom_contour_filled()geom_contour_filled() and ggmapThe question was asked about whether ggplot2’s new geom_contour_filled() can be used with ggmap. In this brief article I illustrate a basic example that shows the answer is “yes”.
library("tidyverse"); theme_set(theme_minimal())
## ── Attaching packages ────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.0 ✓ purrr 0.3.3
## ✓ tibble 2.1.3 ✓ dplyr 0.8.4
## ✓ tidyr 1.0.2 ✓ stringr 1.4.0
## ✓ readr 1.3.1 ✓ forcats 0.4.0
## ── Conflicts ───────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag() masks stats::lag()
library("ggmap")
## Google's Terms of Service: https://cloud.google.com/maps-platform/terms/.
## Please cite ggmap if you use it! See citation("ggmap") for details.
The first thing I do is create a clean dataset to illustrate the point on:
`%notin%` <- function(lhs, rhs) !(lhs %in% rhs)
violent_crime <- crime %>%
filter(
offense %notin% c("auto theft", "theft", "burglary"),
-95.39681 <= lon & lon <= -95.34188,
29.73631 <= lat & lat <= 29.78400
) %>%
as_tibble()
violent_crime contains this data, among others:
geom_contour_filled() requires x, y, and z aesthetics, which works for ggplot2::faithfuld since it already has a pre-computed density column, but most datasets don’t have this, including violent_crime. Of course, we can compute this manually:
kdeout <- violent_crime %>%
with(
MASS::kde2d(lon, lat, n = 101,
lims = c(
scales::expand_range(range(lon), .20),
scales::expand_range(range(lat), .20)
)
)
)
kde_df <- kdeout %>%
.[c("x", "y")] %>%
cross_df() %>%
rename("lon" = "x", "lat" = "y") %>%
mutate(density = as.vector(kdeout$z))
We can use this 2d KDE to make a density surface plot:
ggplot() +
geom_raster(aes(lon, lat, fill = density), kde_df)
And the same data frame can be used with geom_contour_filled():
ggplot() +
geom_contour_filled(aes(lon, lat, z = density), kde_df)
This can be used as an overlay of a map with ggmap:
(bbox <- make_bbox(lon, lat, data = violent_crime))
## left bottom right top
## -95.39886 29.73408 -95.33919 29.78628
map <- get_stamenmap(bbox, zoom = 14, maptype = "toner-lite")
## Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.
ggmap(map, extent = "device") +
geom_contour_filled(aes(lon, lat, z = density), kde_df, alpha = .65)
## Warning: Removed 3960 rows containing non-finite values (stat_contour_filled).
stat_density_2d()It is likely that something like the above is also possible using stat_density_2d(). Outside of ggmap, something like this would work:
ggmap(map, extent = "device") +
stat_density_2d(
mapping = aes(lon, lat, fill = stat(level)),
data = violent_crime, geom = "raster",
alpha = .5, contour = FALSE
)
However, this doesn’t work here because ggplot2’s geom_raster() demands a Cartesian coordinate system, and ggmap() uses ggmap:::coord_map2(), like ggplot2::coord_map(). Of course, the request is not for a raster overlay, but one made by geom_contour_filled() using isoband. It is not immediately clear to me how to do that. geom_polygon() works, but the semi-transparent polygons stack to become near opaque:
ggmap(map, extent = "device") +
stat_density_2d(
mapping = aes(lon, lat, fill = stat(level)),
data = violent_crime, geom = "polygon", alpha = .5
)