Today, I’m going to show you how to make pixel maps in R. Why pixel maps? Because they look awesome!
I was searching on the web for a while, but couldn’t find a good tutorial. Being stubborn as I am, I eventually figured out a way to get what I want. You know, if you torture your code enough, it might give you what you need.
First, of course, loading required packages. These days, I don’t bother with discrete packages and get the entire
tidyverse right away. Aside from that, you may need the
ggmap package, which I used in the earlier iterations of this script (more on that later). You’ll also need the
# Library ----------------------------------------------------------------- library(tidyverse) library(googlesheets) library(maps) library(here)
Next, we’ll need our data points. You can do anything you want here: load a google sheet with data, reach to you Google Maps data, import a csv file, whatever your heart desires.
Initially, I created a data frame with places I’ve been to, and then grabbed their coordinates with
mutate_geocode() function from a
ggmap package. That piece of code takes a while to run, and the list doesn’t really change that much, so I ended up saving it as a separate Google sheet, and now I simply import it. But you do as you wish.
You’ll obviously need to replace this chunk with your own data. I include
tail of my tibble to give you an idea about the data structure
# Get data ------------------------------------------------------------- url <- 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQoRxmeOvIAQSqOtr2DMOBW_P4idYKzRmVtT7lpqwoH7ZWAonRwOcKR2GqE-yqUOhb5Ac_RUs4MBICe/pub?output=csv' destfile <- "locations.csv" curl::curl_download(url, destfile) locations <- read_csv(destfile) tail(locations)
## # A tibble: 6 x 5 ## city status lon lat family ## <chr> <chr> <dbl> <dbl> <int> ## 1 Vancouver, BC been -123. 49.3 3 ## 2 Khmelnitskiy, Ukraine been 27.0 49.4 1 ## 3 Lviv, Ukraine been 24.0 49.8 2 ## 4 Kyiv, Ukraine lived 30.5 50.5 3 ## 5 Warsaw, Poland been 21.0 52.2 2 ## 6 Moscow, Russia been 37.6 55.8 1
Rounding the coordinates
As I’m creating a pixel map - I need dots in the right places. I’m going to plot a dot for each degree, and therefore I need my coordinates rounded to the nearest degree
locations <- locations %>% mutate(long_round = round(lon, 0), lat_round = round(lat,0)) tail(locations)
## # A tibble: 6 x 7 ## city status lon lat family long_round lat_round ## <chr> <chr> <dbl> <dbl> <int> <dbl> <dbl> ## 1 Vancouver, BC been -123. 49.3 3 -123. 49. ## 2 Khmelnitskiy, Ukraine been 27.0 49.4 1 27. 49. ## 3 Lviv, Ukraine been 24.0 49.8 2 24. 50. ## 4 Kyiv, Ukraine lived 30.5 50.5 3 31. 50. ## 5 Warsaw, Poland been 21.0 52.2 2 21. 52. ## 6 Moscow, Russia been 37.6 55.8 1 38. 56.
Generate a pixel grid
The next step is the key to getting a pixel map. We’ll fill the entire plot with a grid of dots - 180 dots from south to north, and 360 dots from east to west, but then only keep the dots that are on land. Simple!
# Generate a data frame with all dots ----------------------------------------------- lat <- data_frame(lat = seq(-90, 90, by = 1)) long <- data_frame(long = seq(-180, 180, by = 1)) dots <- lat %>% merge(long, all = TRUE) ## Only include dots that are within borders. Also, exclude lakes. dots <- dots %>% mutate(country = map.where('world', long, lat), lakes = map.where('lakes', long, lat)) %>% filter(!is.na(country) & is.na(lakes)) %>% select(-lakes) head(dots)
## lat long country ## 1 -83 -173 Antarctica ## 2 -83 -172 Antarctica ## 3 -83 -171 Antarctica ## 4 60 -167 USA:Alaska:Nunivak Island ## 5 60 -166 USA:Alaska:Nunivak Island ## 6 65 -166 USA:Alaska
And now the easy part. Plotting.
Please note that this post, just like this entire site, runs on
blogdown, and the post is created via Rmarkdown. When the plots render here - they look ugly-ish due to the fact that geom_point doesn’t scale down along with the plot. The output on your machine will look better. Take a look at the head image to understand how your output may look like
theme <- theme_void() + theme(panel.background = element_rect(fill="#212121"), plot.background = element_rect(fill="#212121"), plot.title=element_text(face="bold", colour="#3C3C3C",size=16), plot.subtitle=element_text(colour="#3C3C3C",size=12), plot.caption = element_text(colour="#3C3C3C",size=10), plot.margin = unit(c(0, 0, 0, 0), "cm")) plot <- ggplot() + #base layer of map dots geom_point(data = dots, aes(x=long, y = lat), col = "grey45", size = 0.7) + #plot all the places I've been to geom_point(data = locations, aes(x=long_round, y=lat_round), color="grey80", size=0.8) + #plot all the places I lived in, using red geom_point(data = locations %>% filter(status == 'lived'), aes(x=long_round, y=lat_round), color="red", size=0.8) + #an extra layer of halo around the places I lived in geom_point(data = locations %>% filter(status == 'lived'), aes(x=long_round, y=lat_round), color="red", size=6, alpha = 0.4) + #adding my theme theme plot
You probably want to save the map, too.
ggsave('map_full.jpg', device = 'jpg', path = getwd(), width = 360, height = 180, units = 'mm', dpi = 250)
Looking at the map of the entire world can be overwhelming and sad, especially if you, just like me, are not much of a traveler. Look at it! There aren’t many dots! WTF?! Sad!
You can zoom in on an area you did cover (e.g. include USA only), either computationally (calculate you westernmost, easternmost, southernmost and northernmost points and pass them as xlim and ylim), or excluding continents from the map with
dplyr (excluding Antarctica at least would be a good idea). You can also use a different map to start with - World map may not be necessary for some tasks. I used it because I was fortunate enough to live on 2 continents, but your mileage may vary.
In all of these cases, you may want to reconsider the grain of the map: if you zoom in on USA only, you may want to choose to plot a dot for every 0.5 degrees, and then would need to adjust your coordinate rounding respectively (round to the nearest half of degree). Why do it? The finer your grain - the more details you’ll get. For instance, with a grain of 1 degree, San Francisco, San Mateo, San Rafael and Oakland are all be one same dot.
I could definitely program my way though this scaling issue and create a parameter, and make other variables depend on it… I don’t find this exercise to be particularly useful in this case. If you get it done - awesome!
For my case, I wanted a wide banner, so I chose some specific arbitrary limits that looked good to me.
plot + scale_y_continuous(limits = c(10, 70), expand = c(0,0)) + scale_x_continuous(limits = c(-150,90), expand = c(0,0))
plot + scale_y_continuous(limits = c(10, 60), expand = c(0,0)) + scale_x_continuous(limits = c(-250,-40), expand = c(0,0))
ggsave('header2.png', device = 'png', path = "../../../static/img", width = 1710/6, height = 570/6, units = 'mm', dpi = 250)
Obviously, there is so much more to do with this. The possibilities are endless. The basic idea is pretty simple - generate a point grid and plot rounded coordinates on top of the grid.
Let me know if you find new implementations of this code!
As this blog is rendered with
blogdown, all the source code is on Github for your pleasure. taraskaduk.com repo is at https://github.com/taraskaduk/taraskaduk/ and the Rmd file for this post should be located at https://github.com/taraskaduk/taraskaduk/tree/master/content/post (unless I mess it all up and relocate it. I’m really not good at your whole github and blogdown thing. But I’m learning)