Creating Maps in R

Introduction

This tutorial will guide you through creating both basic and slightly advanced maps in R using the ggplot2 and sf libraries. Our example will focus on creating a map for Latin America and the Caribbean with custom features such as color gradients, annotations, and thematic mapping.

Overview

  • Load and filter global data for the Latin America and Caribbean region.

  • Create a basic map.

  • Merge external data (WEO) for enhanced thematic mapping.

  • Add color gradients, labels, and custom styling.

First, let’s load all the necessary libraries.

Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.3.1; sf_use_s2() is TRUE

Attaching package: 'rnaturalearthdata'
The following object is masked from 'package:rnaturalearth':

    countries110

Attaching package: 'dplyr'
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
here() starts at C:/IMF-R-Book

Load World Map Data

To begin, we’ll use world map data from rnaturalearth and filter it to display only Latin America and the Caribbean.

# Load world map data from rnaturalearth
world <- ne_countries(scale = "medium", returnclass = "sf")

# Filter for Latin America and the Caribbean
latam_carribean <- world %>%
  filter(region_un == "Americas" & subregion %in% c("Central America", "Caribbean", "South America"))

We now have a spatial dataset, latam_carribean, containing only the Latin America and Caribbean countries.

Create a Basic Map

Using this data we loaded, let’s create a basic map highlighting the countries in Latin America and the Caribbean.

ggplot(data = latam_carribean) +
  geom_sf(fill = "lightblue", color = "black", size = 0.3) +  # Use a thinner border for countries
  labs(
    title = "Latin America and the Caribbean",
    caption = "Source: Natural Earth"
  ) +
  theme_void() +  # A completely clean theme
  theme(
    plot.title = element_text(size = 16, face = "bold", hjust = 0.5),  # Center-align and style title
    plot.caption = element_text(size = 10, hjust = 0),                # Align caption to the left
    panel.background = element_rect(fill = "white", color = NA)       # Optional: white background
  )

That was easy! We quickly created a clean, simple map for Latin America and the Caribbean. Now, let’s explore how to add more customized features to it.

Add Custom Features to the Map

We will run you through how to create an advanced map with the following features:

  • Color gradients to represent a ratio (e.g., GDP per capita).

  • Country labels for easy identification.

  • IMF theme for enhanced visualization.

Add in WEO Population and GDP Data

To have our map display an indicator we want, we can add in WEO data and have it diplay a color gradient representing GDP per capita. We would have to follow these steps in order to complete this:

  1. Read the .dta file.

  2. Select the relevant indicators.

  3. Filter for 2023.

  4. Convert IMF numeric codes to ISO3 codes.

  5. Join with our spatial data.

Let’s load the previously used WEO database here.

# Read your WEO .dta file
weo <- read_dta(here("databases/WEOApr2023Pub.dta"))

Now, let’s filter to ensure we are only displaying the relevant indicators that we want and ensure we are using the year 2023.

weo <- weo %>%
  select(country, ifscode, year, ngdp, ngdp_r_ppp_pc, lp, lur) 

weo <- weo %>%
  filter(year == 2023)

As we learned earlier in the book, WEO uses numeric ifscode that we need to convert to iso3 codes to merge into our current dataset. Let’s do that now.

weo <- weo %>%
  mutate(iso3 = countrycode(
    sourcevar   = ifscode,
    origin      = "imf",
    destination = "iso3c"
  ))

Finally, let’s merge the WEO data into the Latam_Carribean data, preserving the current rows we have. We can ensure that the dataset is merged correctly by listing all the columns and seeing our added WEO columns at the end.

latam_carribean_weo <- latam_carribean %>%
  left_join(weo, by = c("iso_a3" = "iso3"))

#Inspect the result
names(latam_carribean_weo)
  [1] "featurecla"    "scalerank"     "labelrank"     "sovereignt"   
  [5] "sov_a3"        "adm0_dif"      "level"         "type"         
  [9] "tlc"           "admin"         "adm0_a3"       "geou_dif"     
 [13] "geounit"       "gu_a3"         "su_dif"        "subunit"      
 [17] "su_a3"         "brk_diff"      "name"          "name_long"    
 [21] "brk_a3"        "brk_name"      "brk_group"     "abbrev"       
 [25] "postal"        "formal_en"     "formal_fr"     "name_ciawf"   
 [29] "note_adm0"     "note_brk"      "name_sort"     "name_alt"     
 [33] "mapcolor7"     "mapcolor8"     "mapcolor9"     "mapcolor13"   
 [37] "pop_est"       "pop_rank"      "pop_year"      "gdp_md"       
 [41] "gdp_year"      "economy"       "income_grp"    "fips_10"      
 [45] "iso_a2"        "iso_a2_eh"     "iso_a3"        "iso_a3_eh"    
 [49] "iso_n3"        "iso_n3_eh"     "un_a3"         "wb_a2"        
 [53] "wb_a3"         "woe_id"        "woe_id_eh"     "woe_note"     
 [57] "adm0_iso"      "adm0_diff"     "adm0_tlc"      "adm0_a3_us"   
 [61] "adm0_a3_fr"    "adm0_a3_ru"    "adm0_a3_es"    "adm0_a3_cn"   
 [65] "adm0_a3_tw"    "adm0_a3_in"    "adm0_a3_np"    "adm0_a3_pk"   
 [69] "adm0_a3_de"    "adm0_a3_gb"    "adm0_a3_br"    "adm0_a3_il"   
 [73] "adm0_a3_ps"    "adm0_a3_sa"    "adm0_a3_eg"    "adm0_a3_ma"   
 [77] "adm0_a3_pt"    "adm0_a3_ar"    "adm0_a3_jp"    "adm0_a3_ko"   
 [81] "adm0_a3_vn"    "adm0_a3_tr"    "adm0_a3_id"    "adm0_a3_pl"   
 [85] "adm0_a3_gr"    "adm0_a3_it"    "adm0_a3_nl"    "adm0_a3_se"   
 [89] "adm0_a3_bd"    "adm0_a3_ua"    "adm0_a3_un"    "adm0_a3_wb"   
 [93] "continent"     "region_un"     "subregion"     "region_wb"    
 [97] "name_len"      "long_len"      "abbrev_len"    "tiny"         
[101] "homepart"      "min_zoom"      "min_label"     "max_label"    
[105] "label_x"       "label_y"       "ne_id"         "wikidataid"   
[109] "name_ar"       "name_bn"       "name_de"       "name_en"      
[113] "name_es"       "name_fa"       "name_fr"       "name_el"      
[117] "name_he"       "name_hi"       "name_hu"       "name_id"      
[121] "name_it"       "name_ja"       "name_ko"       "name_nl"      
[125] "name_pl"       "name_pt"       "name_ru"       "name_sv"      
[129] "name_tr"       "name_uk"       "name_ur"       "name_vi"      
[133] "name_zh"       "name_zht"      "fclass_iso"    "tlc_diff"     
[137] "fclass_tlc"    "fclass_us"     "fclass_fr"     "fclass_ru"    
[141] "fclass_es"     "fclass_cn"     "fclass_tw"     "fclass_in"    
[145] "fclass_np"     "fclass_pk"     "fclass_de"     "fclass_gb"    
[149] "fclass_br"     "fclass_il"     "fclass_ps"     "fclass_sa"    
[153] "fclass_eg"     "fclass_ma"     "fclass_pt"     "fclass_ar"    
[157] "fclass_jp"     "fclass_ko"     "fclass_vn"     "fclass_tr"    
[161] "fclass_id"     "fclass_pl"     "fclass_gr"     "fclass_it"    
[165] "fclass_nl"     "fclass_se"     "fclass_bd"     "fclass_ua"    
[169] "country"       "ifscode"       "year"          "ngdp"         
[173] "ngdp_r_ppp_pc" "lp"            "lur"           "geometry"     

More Advanced Mapping

Now let’s map PPP GDP per capita as a color gradient, also adding in country labels.

# Advanced map showing GDP per capita from the WEO dataset 
ggplot(data = latam_carribean_weo) +
  # Add countries with color gradient representing GDP per capita (ngdppc)
  geom_sf(aes(fill = ngdp_r_ppp_pc), color = "white", size = 0.2) +
  
  # Define the color gradient for GDP per capita
  scale_fill_gradient(
    low = "lightblue", high = "darkblue",  # Gradient colors
    name = "GDP per capita"                # Legend title
  ) +
  
  # Add country labels using ISO codes
  geom_text(
    data = latam_carribean_weo, 
    aes(label = iso_a3, geometry = geometry),  # ISO country codes as labels
    stat = "sf_coordinates",                   # Extract coordinates for labels
    size = 2.5,                                # Label size
    color = "black",                           # Label color
    check_overlap = TRUE                       # Avoid overlapping labels
  ) +
  
  # Add title, subtitle, and caption
  labs(
    title = "Real GDP per Capita in Latin America and the Caribbean",   # Main title
    subtitle = "Per capita, in PPP international currency  (WEO 2023)",                         # Subtitle for context
    caption = "Source: IMF WEO (2023), Natural Earth"              # Updated data source
  ) +
  
  # Apply a clean theme for a minimalist design
  theme_void() +  # Removes gridlines, axis labels, and ticks
  
  # Customize the theme for titles, caption, and legend
  theme(
    plot.title = element_text(size = 16, face = "bold", hjust = 0.5),  # Center-align and style title
    plot.subtitle = element_text(size = 12, hjust = 0.5),              # Center-align and style subtitle
    plot.caption = element_text(size = 10, hjust = 0),                 # Align caption to the left
    legend.position = "bottom",                                        # Position legend at the bottom
    legend.title = element_text(size = 10, face = "bold"),             # Bold legend title for emphasis
    legend.text = element_text(size = 9),                              # Adjust legend text size for readability
    panel.background = element_rect(fill = "white", color = NA)        # White background for a clean look
  )

Formatting and adding the IMF Style

Our map is almost complete, but there is some additional formatting we should do. Let’s expand the legend so that it is more visible, and load and apply the IMF theme.

# Load IMF theme (gives us 'blue' and other style helpers)
source(here::here("utils/theme_and_colors_IMF.R"))

# Advanced map showing GDP per capita from the WEO dataset 
ggplot(data = latam_carribean_weo) +
  # Add countries with color gradient representing GDP per capita (ngdp_r_ppp_pc)
  geom_sf(aes(fill = ngdp_r_ppp_pc), color = "white", size = 0.2) +
  
  # Define the color gradient for GDP per capita, with a custom guide
  scale_fill_gradient(
    low = "lightblue", 
    high = "darkblue",  
    name = "GDP per capita", 
    guide = guide_colorbar(
      barwidth = 15,     # Increase width to stretch out the colorbar
      barheight = 0.5    # Decrease height for a slimmer bar
    )
  ) +
  
  # Add country labels using ISO codes
  geom_text(
    data = latam_carribean_weo, 
    aes(label = iso_a3, geometry = geometry),  
    stat = "sf_coordinates",                   
    size = 2.5,                                
    color = "black",                           
    check_overlap = TRUE                       
  ) +
  
  # Add title, subtitle, and caption
  labs(
    title = "Real GDP per Capita in Latin America and the Caribbean",  
    subtitle = "Per capita, in PPP international currency (WEO 2023)",
    caption = "Source: IMF WEO (2023), Natural Earth"
  ) +
  
  # Remove gridlines, axis labels, and ticks
  theme_void() +
  
  # Override the title/subtitle with IMF styling
  theme(
    plot.title = element_text(size = 16, face = "bold", color = blue, hjust = 0.5),
    plot.subtitle = element_text(size = 12, color = blue, hjust = 0.5),
    
    plot.caption  = element_text(size = 10, hjust = 0),  
    
    # Legend formatting
    legend.position = "bottom",
    legend.title = element_text(size = 9, face = "plain"),  # Slightly smaller, no bold
    legend.text  = element_text(size = 8),                  # Smaller text for numbers
    legend.key.size = unit(0.4, "cm"),                      # Control key box size
    
    # White background
    panel.background = element_rect(fill = "white", color = NA)
  )

You can now save the map as a high-quality image or PDF for presentation or reports. Check out the images you save to see your map in more detail than is shown on the website.

# Save the map using the here library
ggsave(here("Figures/latam_population_density.png"), dpi = 300, width = 10, height = 8)
ggsave(here("Figures/latam_population_density.pdf"), device = cairo_pdf, dpi = 300, width = 10, height = 8)

You now know how to load and filter spatial data, create basic maps, and add thematic features like gradients and annotations.