Directional data, data collected on the surface of a sphere or circle, is rather common in certain lines of work. There are several approaches to handling this data, but a most satisfying approach is to adopt a directional statistics mindset, which respects the topology of the sphere or circle.
However, visualizing spherical data in R is challenging. R is exceptional for
making two-dimensional Cartesian visualizations, but these can distort the
geometry of the sphere. The post will show you how to use ggplot
to make some
cool visuals on a sphere, without messing up the geometry.
First, we take some fake data. The library Directional is great for this.
library(data.table)
library(ggplot2); theme_set(theme_bw(base_size = 15))
library(colorspace)
library(Directional)
dat <- rvmf(200, mu = c(1, 2, 0) / sqrt(5), k = 3)
dat <- setDT(data.frame(dat)); colnames(dat) <- c("x", "y", "z")
Looks like Directional is giving back data in Cartesian coordinates.
plot(dat,
xlim = c(-1, 1),
ylim = c(-1, 1),
asp = 1)
Convert to Lat/Long coordinates, which is the expected format for a lot of packages.
dat[, long := atan2(y, x) * (180 / pi) + 180][, lat := acos(z) * (180 / pi)]
Fit a kernel density estimate to the data, using Directional
package.
dens <- vmf.kerncontour(dat[, .(lat, long)], ngrid = 300, full = TRUE, thumb = "rot", den.ret = TRUE)
Here is a 2D version of the density estimate.
with(dens, image(x = long, y = lat, z = den,
main = "Spherical Density Estimate",
xlab = "Longitude",
ylab = "Latitude"))
While the image is a nice way to show the contours, I would personally rather see the data on the surface of a sphere, and even better if I can do it with ggplot.
The main ggplot trick is to use coord_map()
. The secondary trick is to avoid
contour type plotting functions, and instead go for a dense grid of
geom_point
s. The final trick is to bring the panel grid to the foreground,
which makes things look just a bit nicer.
First, convert the dens
matrix to a data.frame that is amenable to ggplotting.
pdat <- data.table(d = c(dens$den),
lat = rep(dens$lat, each = 300) - 90,
long = rep(dens$long, 300) - 180)
And now the final ggplot. Here is one angle:
ggplot(pdat, aes(x = long, y = lat, color = d)) +
geom_point() +
scale_color_continuous_sequential(palette = "YlOrRd") +
coord_map("orthographic", orientation = c(20, 0, 0)) +
scale_x_continuous(breaks = seq(-180, 180, 20)) +
scale_y_continuous(breaks = seq(-90, 90, 45)) +
ggtitle("Orthographic Projection of Spherical Density", "Top / Front View") +
xlab("") +
ylab("") +
theme(axis.ticks = element_blank(),
axis.text = element_blank(),
panel.ontop = TRUE,
legend.position = "none",
plot.title = element_text(hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5),
panel.grid = element_line(color = "black"),
panel.background = element_rect(fill = NA))
And another view from a different angle:
ggplot(pdat, aes(x = long, y = lat, color = d)) +
geom_point() +
scale_color_continuous_sequential(palette = "YlOrRd") +
coord_map("orthographic", orientation = c(-20, 180, 0)) +
scale_x_continuous(breaks = seq(-180, 180, 20)) +
scale_y_continuous(breaks = seq(-90, 90, 45)) +
ggtitle("Orthographic Projection of Spherical Density", "Bottom / Back View") +
xlab("") +
ylab("") +
theme(axis.ticks = element_blank(),
axis.text = element_blank(),
panel.ontop = TRUE,
legend.position = "none",
plot.title = element_text(hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5),
panel.grid = element_line(color = "black"),
panel.background = element_rect(fill = NA))
Many other map projections are available in mapproj
package.