Session 1: Quarto for Spatial Reporting, Coordinates, and CRS
By the end, you will have a Quarto document that renders a map of Mexico.
Start one .qmd today. Add to it every week:
| Week | What you add |
|---|---|
| 1 | A map of Mexico |
| 2 | sf objects, your own state or municipio |
| 3 | A choropleth of an indicator |
| 4 | A spatial join and distances |
| 5 | A siting analysis |
By Session 5 that document is your reto deliverable.
Every line of code in these slides runs on your machine, provided your folders look like this:
ciencia_datos_2/
├── data/
│ ├── gadm41_MEX_shp/ <- Mexico boundaries (GADM)
│ ├── denue/ <- DENUE point data
│ └── mex_formats/ <- the same states as .geojson and .gpkg
└── session1/
└── my_notes.qmd <- you work here
Code in these slides reads ../data/... — up one level, then into data. Keep that shape and everything copies and pastes without edits.
Every .qmd file has exactly three kinds of content:
--- fencesLet’s build one together, from an empty RStudio window.
Nothing open yet — just the Console on the left and an empty Environment on the right.
Note Quarto Document and Quarto Presentation are separate entries. Today: Document.
Title — goes straight into the YAML header. You can change it later.
HTML / PDF / Word — HTML is the right default. PDF needs a LaTeX install; Word needs Word.
Engine: Knitr — the R engine. Leave it.
Use visual markdown editor — a word-processor-like view.
Tip
Leave the visual editor checked for now. You can toggle between Visual and Source at any time — they edit the same file.
RStudio gives you a template document. Headings look like headings; code chunks look like boxes.
The same file, shown as raw text. ## Quarto is a heading; ```{r} opens a chunk.
Visual
Source
Note
Everything in these slides is written in Source. When something goes wrong, Source is where you find out why.
Delete the sample text. Keep the YAML header, one heading, and one chunk.
Warning
Save before you render. An unsaved document has no folder, so relative paths like data/... have nothing to be relative to.
The tab now reads example.qmd, and the tooltip shows the Render shortcut: Shift+Cmd+K (Shift+Ctrl+K on Windows).
Plain prose between the chunks. This is the part that makes it a report and not a script.
Inside a chunk, # starts a comment — exactly as in any R script.
The green ▶ runs the chunk. Output appears inline, under the chunk, and in the Console.
The circled button inserts a new chunk — or press Option+Cmd+I (Alt+Ctrl+I on Windows). Learn this shortcut; you will use it constantly.
Prose, code, and output — one document, reproducible from source. That is the whole point.
format: html — what gets producedtoc: true — table of contentstoc-depth: 2 — how many heading levelstoc-title: — rename itYAML is whitespace-sensitive. Two spaces of indentation mean something; a tab breaks it.
toc-depth DoesThe same document, rendered three times. Only toc-depth changes.
toc-depth: 1
toc-depth: 2
toc-depth: 3
Each level deeper adds the next rank of headings — #, then ##, then ###. The default is 3.
echo and eval are independent. echo: true, eval: false shows code without running it — useful for an expensive download step you do not want repeated on every render.
This is the part that is not in a normal Quarto tutorial.
Tip
Mexico is wider than it is tall — about 32 degrees of longitude by 18 of latitude. Start at fig-width: 8, fig-height: 5 and adjust from there.
Every package announces itself. Here is dplyr in a rendered report:
Nobody wants to read “The following objects are masked from ‘package:stats’” in a policy report.
Run this once, in the Console — not in your document:
Warning
sf is the one that can fail. It needs GDAL, GEOS and PROJ underneath. On Mac and Windows the binary from CRAN includes them; on Linux install libgdal-dev, libgeos-dev and libproj-dev first.
Everything the code in these slides reads — the Mexico shapefiles, DENUE, and the .geojson and .gpkg copies — is in one Dropbox folder:
Unzip it so that data/ sits next to your session1/ folder, exactly as on the folder slide. Then ../data/gadm41_MEX_shp/... resolves and every chunk runs unedited.
sf is worse than dplyr — it reports the geometry type, the bounding box, and the full CRS on every single read.
Important
You need both. quiet = TRUE silences sf’s own reporting; the chunk options silence R’s warnings and messages. They are different channels.
Municipio-level shapefiles are large. Mexico’s level-2 file is 53 MB.
Without caching, you re-read it on every render, every time you fix a typo.
Warning
cache: true notices when the code changes — not when the data file changes. If you replace the shapefile, delete the _cache folder by hand.
embed-resources: The TrapYou build an interactive map. It works on your laptop. You email your reader the .html file — just that one file, the way you would send any attachment.
They see a blank white box.
An HTML file by default links to its JavaScript, CSS, and images, which sit in a _files folder next to it. Send the HTML alone and every link breaks.
This inlines everything into a single, larger, self-sufficient file.
Important
One rule, two halves: embed the resources so everything lives inside the file, then send that single .html file. Nothing else travels with it — no _files folder, no images. For anything you send to anyone, embed-resources: true is not optional. We build a leaflet map in Session 3 — that is exactly when this bites.
Table
| State | Municipios |
|:--------|-----------:|
| Oaxaca | 570 |
| Puebla | 217 |
| Yucatán | 106 |
| State | Municipios |
|---|---|
| Oaxaca | 570 |
| Puebla | 217 |
| Yucatán | 106 |
Callout
:::{.callout-note}
Source: INEGI, Marco
Geoestadístico 2020.
:::
Note
Source: INEGI, Marco Geoestadístico 2020.
Types: note, tip, warning, caution, important
In the course folder, one level up: template_reto.qmd — every setting from this part, already in place.
execute: at the top level applies to every chunk, so you stop repeating yourself.
Open it now. This is the document you carry to Session 5.
We have a container. Now: how does a place on Earth become a number we can plot?
This is the part people skip. Skipping it is why maps come out empty, sideways, or refusing to combine.
Meridians run north–south.
They measure longitude — east or west of Greenwich, from -180 to 180.
The prime meridian is in red. Mexico, in orange, sits west of it.
Parallels run east–west.
They measure latitude — north or south of the equator, from -90 to 90.
The equator is in red. Mexico, in orange, sits north of it.
Together they form a grid. Mexico sits roughly between -118 and -87 longitude, 14 and 33 latitude.
Warning
sf expects longitude first, then latitude — x before y. Reversing them is the single most common way to put Mexico in the Indian Ocean.
To place an object on a map you need two things:
A CRS has three components:
To reconcile a simple mathematical model with the undulating shape of the Earth, we:
A datum is how you choose to align them.
Local datums fit one region well:
Geocentric datums fit the whole world:
There are two kinds of CRS, and the difference decides what you can do:
| Geographic | Projected | |
|---|---|---|
| Units | degrees | metres |
| Shape | curved surface | flat plane |
| Example | WGS84 (EPSG:4326) | Mexico LCC (EPSG:6372) |
| Good for | storing, sharing | measuring, mapping |
Going from geographic to projected requires a mathematical transformation — and every such transformation distorts something.
The most common geographic system is the World Geodetic System of 1984.
It was established because navigation, aviation, and geography needed global maps.
Almost everything you download — GADM, INEGI, OpenStreetMap — arrives in WGS84.
EPSG:4326 is its code. EPSG codes let you name a CRS with one short integer, and you will use them constantly.
Every projected system has one too — and choosing between them is the next slide.
Search any country at epsg.io — the screenshot searches Italy; for Mexico the answer is EPSG:6372.
A projected coordinate system is a reference system for identifying locations and measuring features on a flat (map) surface
Going from a geographic system to a projected one requires mathematical transformations
To perform spatial analyses correctly, all your data has to be in the same coordinate system
There is no single correct projection. Each one preserves something and sacrifices something else.
#Step1: Load the libraries
library(sf); library(tidyverse); library(cowplot); library(ggspatial)
sf_use_s2(FALSE)
#Step2: Get world polygons from the spData package
dunia <- spData::world %>% st_as_sf()
#Step3: Draw them in one projection
ggplot() +
geom_sf(data = dunia) +
coord_sf(crs = "EPSG: 3857") +
ggtitle("World Mercator") +
theme_minimal_grid()Mercator is the one you have seen most — it is what web maps use — and it is the worst choice for a national map. It inflates everything away from the equator.
For Mexico, use EPSG:6372 (Mexico ITRF2008 / LCC):
Tip
coord_sf(crs = ...) reprojects for display only. st_transform() changes the data itself. Use st_transform when you are going to measure something.
Modern sf is good at geodesic mathematics. Ask for a distance between two points stored in degrees and it still returns a sensible answer in metres.
So CRS mistakes rarely corrupt your numbers quietly. They stop you loudly, the moment you combine two layers:
#Step1: Make a point for Mexico City, in WGS84 (degrees)
cdmx <- st_as_sf(data.frame(lon = -99.1332, lat = 19.4326),
coords = c("lon", "lat"), crs = 4326)
#Step2: Project the states to EPSG:6372 (metres) -- a DIFFERENT crs
mex_projected <- st_transform(mex1, 6372)
#Step3: Try to combine them
st_join(cdmx, mex_projected)Error in `st_geos_binop()`:
! st_crs(x) == st_crs(y) is not TRUE
You will meet this exact error in Session 4. Now you know what it means.
st_transform Is the FixPut both layers in the same CRS. That is the whole solution.
Tip
Habit worth forming: transform everything to one CRS immediately after reading it, before you do anything else.
Look at that answer again: “Distrito Federal”.
GADM still uses the name retired in 2016, when the Distrito Federal became Ciudad de México.
Boundary files carry their own vintage and their own politics:
Important
Always check when your boundary file was made, and whether its keys match the dataset you plan to join to it. This is one more reason to prefer INEGI for official Mexican work.
In these five sessions we work with vectors. Rasters are a course of their own.
A vector layer is a set of geometries attached to a table of non-spatial attributes.
Geometries are sequences of coordinates forming points, lines, or polygons.
A point is a single coordinate pair.
A hospital, a school, a DENUE establishment.
A line is an ordered sequence of points.
A road, a river, a bus route.
A polygon is a closed line enclosing an area.
A municipio, a state, a colonia.
Country and subnational boundaries for the whole world: https://gadm.org
GADM gives you nested levels: _0 country, _1 states, _2 municipios. For Mexico that is 1, 32, and 2,466 features.
Warning
A “shapefile” is not one file. It is at least .shp (geometry), .dbf (attributes), .shx (index), and .prj (the CRS). Move them together or the layer breaks — and a missing .prj means R has no idea where on Earth your data is.
GADM is convenient and global. But for Mexican policy work, INEGI’s Marco Geoestadístico is better:
CVE_ENT and CVE_MUN, the keys every other INEGI dataset joins onWe use GADM today because it is one click. Use INEGI for your reto.
sf Object Is a Dataframe[1] "sf" "data.frame"
Simple feature collection with 3 features and 2 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -118.3665 ymin: 21.62227 xmax: -101.8353 ymax: 32.71863
Geodetic CRS: WGS 84
NAME_1 TYPE_1 geometry
1 Aguascalientes Estado MULTIPOLYGON (((-102.0659 2...
2 Baja California Estado MULTIPOLYGON (((-114.1228 2...
3 Baja California Sur Estado MULTIPOLYGON (((-109.9104 2...
This is the most important idea in the next four weeks:
Everything you know from dplyr still works.
filter, mutate, group_by, summarise, left_join — all of it. There is simply an extra geometry column that travels along with every row.
Tip
st_drop_geometry() gives you the plain table back when you want to inspect the attributes without pages of coordinates.
st_simplify: Making Maps Render FastDetailed boundaries are slow to draw, and at national scale you cannot see the detail anyway.
[1] "11.9 Mb"
[1] "0.1 Mb"
11.9 MB to 0.1 MB — a hundredfold reduction, with no visible difference at this scale.
Warning
dTolerance is in the units of your CRS. In WGS84 that is degrees, so 0.02 is roughly 2 km. sf warns you about this — the warning is correct, and simplifying in a projected CRS is more defensible.
st_simplify: Before and AfterThe middle map is what the code above produces — invisible at this scale. Turn the dial further and the coastline collapses into straight lines.
#Step1: Load ggplot2
library(ggplot2)
#Step2: Read the country outline
mex_country <- st_read(
"../data/gadm41_MEX_shp/gadm41_MEX_0.shp",
quiet = TRUE)
#Step3: Map it, projected to EPSG:6372
ggplot() +
geom_sf(data = mex_country,
fill = "#dbe6f0",
colour = "#33475b",
linewidth = 0.3) +
coord_sf(crs = 6372) +
theme_bw()Two layers, same ggplot. geom_sf stacks like any other geom.
#Step1: Keep only the three peninsula states
peninsula <- mex_simple[
mex_simple$NAME_1 %in%
c("Yucatán", "Campeche",
"Quintana Roo"), ]
#Step2: Map them, coloured by state
ggplot() +
geom_sf(data = peninsula,
aes(fill = NAME_1),
colour = "white") +
scale_fill_brewer(palette = "Blues") +
labs(fill = "State") +
theme_bw()filter the sf object like a dataframe, and the map follows. No special spatial subsetting needed.
Everything from this session, as one chunk in template_reto.qmd:
```{r}
#| label: fig-mexico
#| fig-cap: "States of Mexico"
#| fig-width: 8
#| fig-height: 5
#| cache: true
library(sf); library(ggplot2)
mex <- st_read("../data/gadm41_MEX_shp/gadm41_MEX_1.shp", quiet = TRUE)
mex <- st_transform(mex, 6372) # project immediately
mex <- st_simplify(mex, dTolerance = 2000) # metres now, not degrees
ggplot() + geom_sf(data = mex, fill = "#dbe6f0") + theme_bw()
```Note dTolerance = 2000: once you have transformed to EPSG:6372 the units are metres, so the tolerance changes meaning. Same idea, different number.
fig-width, cache, execute: warning/message, and embed-resourcesst_transform is the fixsf object is a dataframe with a geometry columnNext session: sf geometries in depth — points, lines, polygons, and building maps layer by layer.
Popescu Data Science II — Session 1: Quarto, Coordinates, and CRS