# load your libraries hereLab 3: Maps
You are encouraged to work together and get started in class. Talk through the problems with the people around you. Then, you will finish the lab on your own time and submit your work individually to Blackboard.
AI use: You may use AI to ask questions or debug, but not to write your code for you. The point is to think it through yourself.
Overview
In this lab you will make two kinds of maps: a static choropleth with ggplot2 and sf, and an interactive leaflet map. We will map data relevant to Michael Ross’s argument in The Oil Curse that oil wealth undermines democracy.
Step 1: Choropleth maps (50 pts)
Step 1a: Build your data frame (10 pts)
Choose a World Bank indicator related to Ross’s argument. Some options, with the most recent year that has good coverage:
| Indicator | Code | Year to use |
|---|---|---|
| Oil rents (% of GDP) | NY.GDP.PETR.RT.ZS |
2021 |
| Total natural resource rents (% of GDP) | NY.GDP.TOTL.RT.ZS |
2021 |
| Fuel exports (% of merchandise exports) | TX.VAL.FUEL.ZS.UN |
2023 |
| Tax revenue (% of GDP) | GC.TAX.TOTL.GD.ZS |
2023 |
Use wb_data() from wbstats to download your indicator for that single year (set start_date and end_date to the same year). Then use vdem from vdemdata to download the V-Dem dataset, filter it to the same year, and select the V-Dem country ID and a democracy measure of your choice, such as electoral democracy (v2x_polyarchy) or liberal democracy (v2x_libdem). Convert the V-Dem country ID to ISO3C codes with countrycode() and join the two data frames by iso3c. glimpse() the result.
Step 1b: Map your World Bank indicator (20 pts)
Use rnaturalearth to get country shapes (filter out Antarctica) and join your data frame to them, matching iso_a3_eh to iso3c. Then map your World Bank indicator with ggplot() and geom_sf(), using theme_map() from ggthemes and a viridis color scale. Give the map clear labels. Below the map, say in a sentence or two what pattern you see.
Step 1c: Map democracy (20 pts)
Now map your democracy measure the same way. Below the map, compare it to your first map in a sentence or two: do the resource-rich countries look less democratic, as Ross would predict? Do you see any exceptions?
Step 2: A leaflet map of conflict (50 pts)
Pick an oil-rich country that has experienced internal conflict, such as Nigeria (country_id 475), Iraq (645), Syria (652), Sudan (625) or Colombia (100). Instead of the 2021 file we used in class, use UCDP’s newest event data, which covers January through September 2026. You can read it straight from the UCDP website without downloading anything:
ged_data <- read_csv("https://ucdp.uu.se/downloads/candidateged/GEDEvent_v26_01_26_09.csv")These are UCDP’s “candidate” events: they are released monthly and are preliminary until UCDP finishes checking them, but they have the same columns as the file we used in class. Following the approach from the Interactive Maps topic, filter a few months of events for your country and convert the coordinates to simple features with st_as_sf(). If your country has only a handful of events, use more months. Then build a leaflet map with markers for the conflict events: show the location name on hover and the number of deaths and date on click. Use “OpenTopoMap” as your basemap.
Bonus (extra credit)
Completing this bonus restores full credit on one quiz where you lost points. If you have not lost points on any quiz, you will get 2 points of extra credit on this lab instead.
Steps 1b and 1c use almost the same code. Write a function called create_map() that takes four arguments (your map data frame, the name of the variable to map, a title, and a legend title) and returns a choropleth map. Then call your function twice to remake both maps.
Head over to Blackboard and go to the Lab 3 assignment. Click “Create Submission” and write a brief statement saying that you have submitted the lab and note whether and in what capacity you used AI on the assignment.
Then upload a compressed (zipped) version of your project folder including the rendered HTML file. To compress your project folder, right-click it and choose Compress (Mac) or Send to → Compressed (zipped) folder (Windows).
Hints
Only look at these if you’re stuck!
Hint 1 — Downloading a single year from the World Bank:
Set start_date and end_date to the same year. If you pass wb_data() a named vector, the column comes back with your name instead of the indicator code:
wb_data(c(fuel_exports = "TX.VAL.FUEL.ZS.UN"), start_date = 2023, end_date = 2023)Hint 2 — Converting the V-Dem country ID:
Use countrycode() inside mutate(), telling it the codes come from V-Dem:
mutate(iso3c = countrycode(country_id, origin = "vdem", destination = "iso3c"))You will probably see a warning like Some values were not matched unambiguously. That is OK. A few V-Dem units (like Kosovo, Somaliland or Zanzibar) have no ISO3C code, so they get an NA and will not show up on the map.
Hint 3 — Put the country shapes first in the join:
If you get an error saying stat_sf() requires the missing aesthetic geometry, check the order of your left_join(). The country shapes have to come first so the result stays a simple features data frame:
left_join(world_map_df, your_data, join_by(iso_a3_eh == iso3c))Hint 4 — Gray countries:
Countries shaded gray have no data for your variable (Greenland, for example, is not in V-Dem). That is expected; you do not need to fix it.
Hint 5 — “Discrete value supplied to continuous scale” (or the reverse):
Your variables are continuous, so use scale_fill_viridis_c(), not scale_fill_viridis_d(). If your variable is a percentage, labels = scales::label_percent(scale = 1) inside the scale function will add % signs to the legend.
Hint 6 — Filtering the conflict data by date:
date_start is a date, so you can compare it to a date written as text:
filter(country_id == 475, date_start >= "2026-01-01", date_start < "2026-04-01")Hint 7 — Showing the deaths and the date in the popup:
Popups can display HTML, so you can use paste() to build the text and <br> to start a new line:
addMarkers(
popup = ~paste("Date:", date, "<br>Deaths:", deaths),
label = ~place
)Hint 8 — Mapping a variable inside a function (bonus):
If your function takes the variable name as a string (like "polyarchy"), aes(fill = var) will not work, because ggplot treats the string as a constant. Use .data[[var]] instead, as we did in the Functions topic:
geom_sf(aes(fill = .data[[var]]))