Assignment 7#

Due: Sunday Nov 15th at 11:59 pm ET

This is the final assignment. You will carry out a scalable analysis of machine learning derived agricultural field boundaries, comparing two very different farming regimes, France and Kenya, using Dask-GeoPandas and cloud-native data on Source Cooperative. You will write the core by hand, use an AI coding agent for the heavier comparison and spatial join, and take responsibility for the result.

What you are practicing

  • Scalable vector analysis with Dask-GeoPandas: lazy loading, partitions, and compute.

  • Cloud-native GeoParquet on Source Cooperative.

  • GeoPandas spatial joins and per-region aggregation.

  • Reading a machine learning product critically: model confidence and where to trust it.

  • Agentic coding with GitHub Copilot in Agent mode, guided by an AGENTS.md.

Notebooks are for visualization, not logic

As in the previous assignments, your analysis functions live in src/utils.py and are pure. The notebook imports them and uses them to explore, compare, and plot. No analysis logic in notebook cells, and no plotting inside the functions.

All work lives in a new directory named assignment-7/ in your geog313-assignments repository. Follow the same git discipline as before: stage files explicitly by name (never git add .), commit in small focused steps, and push as you go. Read the whole assignment before you start.


Background: fields, and a tale of two regimes#

Fields of The World (FTW) is a global, 10 m map of agricultural field boundaries produced by running a deep learning model (a U-Net) on Sentinel-2 imagery. It is not a cadastral or legal layer. A field here is a remote-sensing unit, so one real parcel may map to many polygons or to none, and every field comes with a model confidence score.

That makes the France versus Kenya comparison the heart of this assignment. France is dominated by large, mechanized, geometrically regular fields, the kind of landscape the model sees often and predicts with high confidence. Kenya is dominated by smallholder agriculture with small, irregular plots, a regime where the FTW authors themselves note that confidence is lower and more conservative. So when you compare the two countries you are reading two things at once: a real difference in how the two landscapes are farmed, and a difference in how certain an ML model is about what it is seeing. Keeping those two apart is the analytical point.


The data#

FTW Global is hosted on Source Cooperative as GeoParquet, partitioned one file per country. The data is anonymous read over HTTPS, no account needed.

Vector base path:

https://data.source.coop/ftw/global-data/predictions/vectors/alpha/results-by-admin-conf/

The two country files for this assignment:

.../results-by-admin-conf/admin:country_code=FR/France.parquet
.../results-by-admin-conf/admin:country_code=KE/Kenya.parquet

Note

The folder name contains a colon (admin:country_code=FR). If your HTTP client objects to it, URL-encode the colon as %3A. Confirm the exact file names and paths against the dataset’s own README and llms.txt at https://data.source.coop/ftw/global-data/ before you hard-code them, since cloud datasets do get reorganized.

Columns include: id, geometry, bbox, metrics:area, metrics:perimeter, determination:datetime, determination:method, admin:country_code, admin:subdivision_code, and confidence.

Two things to internalize before you compute anything. First, the geometry CRS is EPSG:4326, so coordinates are in degrees, but metrics:area is provided in square meters. You will use that mismatch in Part 1. Second, determination:datetime carries the prediction year (2024 and 2025 exist); pick a single year and use it for both countries so the comparison is fair.


Project layout#

assignment-7/
  pixi.toml
  pixi.lock
  .gitignore
  src/
    __init__.py
    utils.py           all analysis functions
  notebooks/
    analysis.ipynb     imports from src.utils; explores, compares, and plots only
  tests/
    test_utils.py
  results/             exported tables, figures, and the comparison map
  AGENTS.md
  README.md
  reflection.md

Tip

Run JupyterLab from the assignment-7/ directory so that from src.utils import ... works, and start a local Dask cluster (LocalCluster and Client) so you can watch the dashboard while your computations run.


Part 0: Reproducible environment (10 pts)#

  1. From the root of your geog313-assignments repository, create the project assignment-7 and initialize it with pixi.

  2. Set the platforms in pixi.toml to ["osx-arm64", "osx-64", "linux-64"].

  3. Add: dask-geopandas, geopandas, pyarrow, dask, distributed, matplotlib, mapclassify, jupyterlab, and requests.

  4. Create the src/, notebooks/, tests/, and results/ structure, add src/__init__.py, and write a README.md with one command reproduction.

  5. Commit the scaffolding.


Part 1: The core, by hand (30 pts)#

Do not use the agent for this part. Writing the core yourself is what lets you judge the agent’s work and the model’s output later.

In src/utils.py, write and document these pure functions, then import and use them in the notebook:

  1. get_country_fields(country_code, country_name, dest_dir): download the FTW GeoParquet for one country from the path above and cache it locally, skipping the download if the file already exists.

  2. load_fields(path): load the country file lazily into a Dask-GeoPandas GeoDataFrame (use a sensible blocksize), so the full file is never read into memory at once. Return the lazy object.

  3. verify_area(gdf, n): for a small sample of n fields, compute the area yourself by reprojecting to an appropriate equal-area or UTM CRS, and compare it against the provided metrics:area. Return both so the notebook can show they agree.

  4. field_area_distribution(ddf): return the data needed to plot the distribution of field sizes.

In the notebook:

  • Load France and Kenya for a single chosen year, and report the number of fields in each.

  • Show the area check: compute area directly from the EPSG:4326 geometry (you will get square degrees, which is meaningless) and then from your reprojected geometry (square meters), and confirm the latter matches metrics:area. Explain in two or three sentences why the direct EPSG:4326 area is wrong.

  • Plot the field-size distribution for both countries. The distribution is heavily skewed, so choose binning (for example log-spaced bins or a log axis) that makes it readable, and explain your choice.

Commit src/utils.py and the notebook as you go.


Part 2: The agentic comparison (40 pts)#

Use GitHub Copilot in Agent mode, guided by an AGENTS.md you write, to build the comparison and the spatial join. The agent adds functions to src/utils.py; your notebook imports them and visualizes the results. Review every change before accepting it, and keep operations lazy until you actually need results.

2.1 Write your AGENTS.md#

Before prompting the agent, write AGENTS.md. At a minimum tell it: this is a pixi-only project; all analysis functions go in src/utils.py and must be pure with no plotting; the notebook is for visualization only; do not create new files or edit reflection.md; the domain rules (FTW GeoParquet in EPSG:4326, areas must be computed in a projected CRS, confidence is 0 to 100, filter to one year); use Dask-GeoPandas and keep work lazy, calling compute only when needed; run tests with pixi run test; and a definition of done. Commit it.

2.2 Functions for the agent to add#

  • confidence_summary(ddf): return the distribution and summary statistics of confidence for a country, including the fraction of fields above a chosen threshold.

  • join_to_admin(ddf, admin_gdf): a scalable spatial join assigning each field to a GADM level-2 administrative unit.

  • admin_stats(joined): per administrative unit, the field count, mean field size, mean confidence, and the fraction of the unit area covered by fields.

For the administrative boundaries, use GADM level-2 for France (FRA) and Kenya (KEN), loaded as GeoDataFrames in EPSG:4326.

2.3 In the notebook, compare and visualize#

  • Compare the confidence of France against Kenya: plot both distributions, report the statistics, and interpret the difference in light of mechanized versus smallholder agriculture and the model’s own caveat about smallholder systems. Be explicit about which part of the difference is the landscape and which part is model certainty.

  • For each country, map two choropleths over the level-2 units: field density (or field count) and mean confidence.

  • Write a short comparison of the two countries, three to five sentences, drawing on both the field-size distributions and the confidence patterns.

Own the analysis

The quiet failures here are CRS and Dask mistakes: computing area in degrees, forgetting to reproject before measuring, calling compute too early and exhausting memory, the wrong sjoin predicate, or mixing the two years. Document at least one place where the agent’s analysis was wrong and how you caught it.


Part 3: Tests and quality assurance (10 pts)#

  1. Add pytest and a test task so the suite runs with pixi run test.

  2. Write tests on small inputs where you know the answer, including: your reprojected area for a sample agrees with metrics:area within a tolerance; the fraction of fields above a confidence threshold decreases as the threshold rises; the per-unit field counts from the spatial join sum to the number of joined fields.

  3. The agent may help, but verify each test is meaningful. Iterate until pixi run test passes.


Part 4: Reflection (10 pts)#

Write reflection.md yourself, without AI assistance (300 to 500 words):

  • The bug you caught: the CRS or Dask mistake the agent made, how you noticed it, and how writing Part 1 by hand helped.

  • Reading the model: what the France versus Kenya confidence comparison told you, and what it means to trust an ML derived dataset differently in different regimes. Remember that a field here is a remote-sensing unit, not a parcel.

  • Context engineering: how your AGENTS.md shaped the agent, and what you would tighten next time.


Deliverables#

Committed to your private geog313-assignments repository under assignment-7/:

  • pixi.toml, pixi.lock, .gitignore.

  • src/utils.py with all analysis functions, and src/__init__.py.

  • notebooks/analysis.ipynb, executed, with all figures rendered on GitHub, importing from src.utils and containing visualization and comparison only.

  • tests/test_utils.py, with pixi run test passing.

  • results/ holding your exported tables, the confidence figures, and the choropleth maps.

  • AGENTS.md, README.md, and reflection.md (written without AI).

  • A commit history showing incremental, reviewed development.