4.8. Reproducible Spatial Workflows#
4.8.1. Learning objectives#
By the end of this section, you should be able to:
explain what can change between two runs of the same analysis and how to control each cause;
separate configuration from analysis;
preserve raw inputs and keep raw, intermediate, and final outputs apart;
choose an export format and explain its trade-offs;
export results with a provenance record and a methods note;
test sensitivity to thresholds before drawing a conclusion; and
write an analysis plan that another person can review and rerun.
Section goal
Turn the exploratory skills of S01–S07 into a result that another person can regenerate and check. A small synthetic layer shows each practice working: configuration, raw input, export, provenance, and a sensitivity sweep.
4.8.2. From exploration to an auditable result#
Interactive notebooks are useful for discovery, but a defensible result should be regenerable by another person [Sandve et al., 2013, University of Southern California et al., 2021]. Keep the source, decisions, transformations, checks, and outputs visible rather than relying on hidden notebook state.
Two runs of the same analysis can differ for several reasons, and each has its own remedy. Table 4.29 lists them.
Cause |
What goes wrong |
Control |
|---|---|---|
Hidden notebook state |
Cells run out of order, or variables survive from deleted cells |
Restart the kernel and run all cells from the top |
Changing data |
Open-data services update or remove features |
Cache raw responses, record the query and date, store a checksum |
Software versions |
Libraries change CRS, predicate, or overlay behaviour |
Record the Python and library versions |
Machine-specific paths |
A personal directory, or a source file that gets overwritten |
Relative paths, parameters, and a read-only raw folder |
Randomness |
Samples that differ on every run |
Fix the seed, or sample deterministically |
Unstated choices |
Thresholds and predicates buried in code |
Name them as parameters at the top |
4.8.3. A reusable workflow#
Define the question and unit of analysis. Decide whether the unit is a facility, polygon, pixel, edge, or route.
Record provenance. Save the provider, URL or query, retrieval date, license, data vintage, and software versions.
Preserve raw inputs. Do not overwrite the source while cleaning or reprojecting.
Inspect and validate. Check geometry types, CRS, missing values, identifiers, bounds, and expected ranges.
Transform deliberately. Use a documented CRS, predicate, overlay, threshold, or raster mask.
Validate intermediate results. Check row counts, area or length totals, and the number of unmatched records.
Export data and methods. Store results with metadata and a short methods note.
Review sensitivity. Test thresholds, boundary rules, source dates, and alternative assumptions that could change the conclusion.
4.8.4. Separate configuration from analysis#
Stable configuration makes examples easier to rerun. Put every choice that could change the answer, such as the study area, data date, CRS, thresholds, and output location, in one place at the top, and have later cells read it. A threshold buried in the middle of an analysis is easy to miss and hard to vary, whereas a named constant can be reviewed and swept, as the sensitivity section below does:
Keep paths relative to the project or pass them as parameters. Avoid embedding a personal home directory, writing over source files, or depending on variables created in an earlier interactive cell.
For downloaded data, cache raw responses when the license permits it and record the query parameters. Open-data services change over time, so a rerun may not return the same features even when the code is unchanged. A checksum, a short fingerprint computed from the bytes of the raw file, proves later that two runs used identical input.
4.8.5. Export spatial results with metadata#
GeoPackage [Open Geospatial Consortium, 2023] is a useful default for derived vector layers because it keeps geometry and attributes together. Table 4.30 compares it with the alternatives.
Format |
Strengths |
Limitations |
|---|---|---|
GeoPackage ( |
One file, several layers, stored CRS, long column names |
Less common in web tools than GeoJSON |
GeoJSON |
Plain text and web friendly |
Expects longitude and latitude, and is bulky for large layers |
Shapefile |
Read by almost every tool |
Several files per layer, column names cut to 10 characters, one geometry type, 2 GB limit |
GeoParquet |
Compact and fast for large tables |
Needs recent tools |
CSV with WKT |
Simplest to inspect |
No CRS, no data types, and geometry stored as text |
GeoTIFF |
Standard for rasters, with CRS, transform, and no-data stored |
Cells only, with no vector features |
The cell below runs the whole pattern on a synthetic layer. A stand-in “download” is saved as the raw input and fingerprinted. The analysis then reads it, measures areas in a metric CRS, and exports a result that keeps the original CRS, because delivery often requires it. Raw and final outputs live in separate folders.
asset_cd site_name area_ha
P-001 Central Green 1.0
P-002 Riverside 2.0
P-003 Bowness Park 3.0
raw input fingerprint: 6bdc2dda186b
The export round-trips: three rows in and three out, with the CRS and geometry unchanged, and the areas come back as exactly 1, 2, and 3 ha even though the stored coordinates are degrees, because the measuring was done in the metric CRS. The 12-character fingerprint is the start of the full SHA-256 hash that the next step stores.
The export is only half of the deliverable. A provenance record states where the data came from and how it was processed [Wilkinson et al., 2016], and a short methods note turns that record into text a reader can check. Software versions belong in the record because library behavior changes between releases; the versions printed below depend on the environment that ran the cell.
{
"study_area": "Calgary, Alberta, Canada",
"source": "synthetic park layer (stand-in for a downloaded response)",
"source_date": "2026-07-26",
"input_sha256": "6bdc2dda186bce907f2d2f54478b82af786e85ce089769826324fb556ce7b62c",
"crs": "EPSG:4326",
"analysis_crs": "EPSG:32611",
"rows": 3,
"geometry_types": {
"Polygon": 3
},
"software": {
"python": "3.13.15",
"geopandas": "1.2.0",
"shapely": "2.2.0",
"pyproj": "3.8.0"
}
}
The record holds what a rerun must match: the input hash, CRS, row count, geometry types, and the versions of Python (3.13.15), GeoPandas (1.2.0), Shapely (2.2.0), and pyproj (3.8.0). If a later run gives different numbers, compare these fields first to see whether the input or the software changed.
# Methods note: park areas
- **Question:** How large is each park site, in hectares?
- **Unit of analysis:** one park site per row.
- **Source:** synthetic park layer (stand-in for a downloaded response), retrieved 2026-07-26 (SHA-256 6bdc2dda186b).
- **Processing:** areas measured in EPSG:32611; layer delivered in EPSG:4326.
- **Checks:** 3 rows in and out, no missing geometry, areas non-negative.
- **Limitation:** areas are planar and depend on the chosen CRS.
output/chapter_04/.DS_Store
output/chapter_04/final/metadata.json
output/chapter_04/final/methods.md
output/chapter_04/final/parks.gpkg
output/chapter_04/metadata.json
output/chapter_04/parks.gpkg
output/chapter_04/raw/parks_raw.geojson
The listing shows raw input and final outputs in separate folders, with the metadata and the methods note beside the data they describe. The GeoPackage default also has a practical reason. The same layer written as a shapefile keeps its columns only if their names are 10 characters or fewer [ESRI, 1998, GDAL/OGR contributors, 2026]:
shapefile parts : ['.cpg', '.dbf', '.prj', '.shp', '.shx']
shapefile columns : ['asset_cd', 'community_', 'area_ha', 'geometry']
GeoPackage columns: ['asset_cd', 'community_district_name', 'area_ha', 'geometry']
/var/folders/fd/9ctr8dys3tv3jyfrxn6_8y5m0000gp/T/ipykernel_18008/1658088252.py:6: UserWarning: Column names longer than 10 characters will be truncated when saved to ESRI Shapefile.
wide.to_file(folder / "layer.shp")
/Users/hdtg3/miniconda/lib/python3.13/site-packages/pyogrio/raw.py:733: RuntimeWarning: Normalized/laundered field name: 'community_district_name' to 'community_'
ogr_write(
The shapefile needs five files and cuts community_district_name down to community_,
whereas the GeoPackage is one file with the full name. GDAL warns about the truncation, but
the warning is easy to miss.
The same export on the repository layer [Open Calgary: The City of Calgary Open Data Portal, 2026] follows. It reads a downloaded layer, writes it to a GeoPackage, and records the study area, date, CRS, row count, and geometry types:
167
Keep raw, intermediate, and final outputs separate. If a result is reprojected for measurement, retain the original CRS for delivery when that is part of the data contract.
4.8.6. Make maps and tables reviewable#
Every result should state the study area, date, CRS, source, operation, threshold, and unit. A plausible map can still contain a degree-based distance error, an accidental one-to-many join, or a raster no-data value interpreted as zero.
Useful review outputs include:
a source and transformation summary;
input and output row counts;
geometry validity and missing-value counts;
unique identifiers before and after joins;
total area or length in metric units; and
a map of a representative sample and any unmatched features.
For graph-specific provenance, apply the same principles to Chapter 5.
4.8.7. Test sensitivity before concluding#
A threshold is a choice, so a conclusion is only as firm as its stability when the choice
changes. The efficient pattern is to compute the underlying quantity once, here the
distance from each district’s representative point to its nearest library, and then
compare it with several thresholds. The toy example below has three libraries on a line
and four districts, and SERVICE_RADIUS_M from the configuration is one of the tested radii.
| nearest_m | |
|---|---|
| district | |
| D1 | 224.0 |
| D2 | 300.0 |
| D3 | 447.0 |
| D4 | 1900.0 |
| share_covered | covered | |
|---|---|---|
| radius_m | ||
| 250 | 25% | D1 |
| 500 | 75% | D1, D2, D3 |
| 1000 | 75% | D1, D2, D3 |
| 2000 | 100% | D1, D2, D3, D4 |
At the 500 m default, three of four districts are covered, and that headline holds at
1,000 m. Full coverage appears only at 2,000 m because of the outlying district D4, and
at 250 m only one district qualifies. A report that gave only the 500 m figure would hide that spread, so the sweep belongs in the methods note. The distances above also show how near the 500 m result is to a threshold: D3 is only 53 m inside the radius (447 m), so a 400 m radius would cover just half of the districts. The same approach applies to
boundary rules (within compared with intersects), source dates, and resolution.
4.8.8. Capstone prompt#
Choose a local question such as “Which community districts contain a library?” Write a one-page analysis plan before coding. Include:
the source layers and data types;
the unit of analysis and expected geometry;
the CRS and threshold units;
the operation sequence;
at least three validation checks;
the output data and map; and
one limitation and one sensitivity test.
Table 4.31 lists what each part must state, and a gap that each one commonly hides.
Plan item |
It must state |
A common gap |
|---|---|---|
Source layers and data types |
Provider, geometry type, and date of each layer |
Not saying which layer is the left side of a join |
Unit of analysis and expected geometry |
What one output row describes, and its geometry type |
Counting joined rows instead of features |
CRS and threshold units |
The storage CRS, the measurement CRS, and the units |
A threshold in metres applied to degrees |
Operation sequence |
Each step in order, with its predicate or overlay |
Leaving out repair and reprojection |
Validation checks |
Expected row counts, totals, and unmatched records |
Checks that could never fail |
Output data and map |
Format, fields, and what the map shows |
A map with no source, date, or units |
Limitation and sensitivity test |
One thing the data cannot show, and one parameter varied |
Reporting a single threshold as the answer |
4.8.9. Summary#
Reproducibility connects spatial reasoning to trustworthy data engineering. Preserve sources, make choices explicit, validate every stage, export useful representations with a provenance record and a methods note, test how conclusions change with their thresholds, and communicate what the data cannot establish.