R Walkthrough: Create a Pixel Map

Cross-posted: taraskaduk.com


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.

Setup

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 mapspackage.

# Library ---------------------------------------------------------
library(tidyverse)
library(googlesheets)
library(maps)
library(readxl)

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 ggmappackage. 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=xlsx"
destfile <- "data.xlsx"
curl::curl_download(url, destfile)
locations <- read_xlsx(destfile, sheet = 'locations')
tail(locations)
## # A tibble: 6 x 4
## city status lon lat
## <chr> <chr> <dbl> <dbl>
## 1 Ocala, FL been -82.14009 29.18720
## 2 St. Augustine, FL been -81.31243 29.90124
## 3 Denver, CO been -104.99025 39.73924
## 4 Atlanta, GA been -84.38798 33.74900
## 5 New York, NY been -74.00597 40.71278
## 6 Key West, FL been -81.77999 24.55506

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 %>% select(-status))
## # A tibble: 6 x 5
## city lon lat long_round lat_round
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 Ocala, FL -82.14009 29.18720 -82 29
## 2 St. Augustine, FL -81.31243 29.90124 -81 30
## 3 Denver, CO -104.99025 39.73924 -105 40
## 4 Atlanta, GA -84.38798 33.74900 -84 34
## 5 New York, NY -74.00597 40.71278 -74 41
## 6 Key West, FL -81.77999 24.55506 -82 25

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

Plot

And now the easy part. Plotting.

theme <- theme_void() +
theme(panel.background = element_rect(fill="grey20"),
plot.background = element_rect(fill="grey20"),
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 = "grey30",
size = 0.4) +
#plot all the places I've been to
geom_point(data = locations,
aes(x=long_round, y=lat_round),
color="grey80",
size=0.5) +
#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.5) +
#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)) +
ggsave('map_wide.png', 
device = 'png',
path = getwd(),
width = 360,
height = 90,
units = 'mm',
dpi = 300)

Outro

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!