geocif
MIKES EDITS
Models to visualize and forecast crop conditions and yields
Generate Climatic Impact-Drivers (CIDs) from Earth Observation (EO) data, build ML yield forecasting models, and produce agmet condition monitoring plots.
Climatic Impact-Drivers for Crop Yield Assessment at NASA Harvest
- Free software: MIT license
- Documentation: https://ritviksahajpal.github.io/yield_forecasting/
Setup
Requirements
Pixi provisions Python 3.11 and the compiled geospatial stack (GDAL, rasterio, fiona, shapely, pyproj, rtree, cartopy) from conda-forge, so no system GDAL and no pre-built Windows wheels are needed.
Install
cd geocif # project root (where pyproject.toml lives)
pixi install # resolves pixi.lock into .pixi/
Verify:
pixi run python -c "from osgeo import gdal; import geocif; print(geocif.__version__)"
Run commands inside the environment with pixi run <cmd>, or open a shell in it
with pixi shell.
Extra environments
pixi run -e dev pytest # dev tooling
pixi install -e full # all pixi features
Fresh reinstall
rm -rf .pixi && pixi install
Installing as a library
pip install geocif still works and resolves from [project.dependencies];
this is how the cluster env pulls the package. Note that four optional extras
(shap_fast, powershap, geospann, desreg) pin dependencies that conflict
with geocif's own core pins and cannot currently be installed alongside it — see
the comments in pyproject.toml.
Config files
| File | Purpose | Used by |
|---|---|---|
geobase.txt |
Paths, shapefile column mappings | both |
countries.txt |
Per-country config (boundary files, admin levels, seasons, crops) | both |
crops.txt |
Crop masks, calendar categories (EWCM, AMIS) | both |
geoextract.txt |
Extraction-only settings (method, threshold, parallelism) | geoprepare |
geocif.txt |
Indices/ML/agmet settings, country overrides, runtime selections | geocif |
Usage
Order matters: Config files are loaded left-to-right. When the same key appears in multiple files, the last file wins. The tool-specific file (geoextract.txt or geocif.txt) must be last so its [DEFAULT] values (countries, method, etc.) override the shared defaults in countries.txt.
config_dir = "/path/to/config" # full path to your config directory
cfg_geoprepare = [f"{config_dir}/geobase.txt", f"{config_dir}/countries.txt", f"{config_dir}/crops.txt", f"{config_dir}/geoextract.txt"]
cfg_geocif = [f"{config_dir}/geobase.txt", f"{config_dir}/countries.txt", f"{config_dir}/crops.txt", f"{config_dir}/geocif.txt"]
geoprepare (download, extract, merge)
from geoprepare import geodownload
geodownload.run([f"{config_dir}/geobase.txt"])
from geoprepare import geoextract
geoextract.run(cfg_geoprepare)
from geoprepare import geomerge
geomerge.run(cfg_geoprepare)
geocif (indices, ML, agmet, analysis, experiments)
from geocif import indices_runner
indices_runner.run(cfg_geocif)
from geocif import geocif_runner
geocif_runner.run(cfg_geocif)
from geocif.agmet import geoagmet
geoagmet.run(cfg_geocif)
from geocif import analysis
analysis.run(cfg_geocif)
from geocif import experiments
experiments.run(cfg_geocif, n_trials=30)
from geocif import yield_outlook
yield_outlook.run(cfg_geocif) # uses config defaults (10 years, mean)
# yield_outlook.run(cfg_geocif, current_year=2026, n_years=10, aggregation="median")
# Set [ML] report_lite = True (with make_maps = True) to also emit one
# lightweight per-country PDF per run: cover + clickable TOC + "About GEOCIF" +
# per-crop accuracy (rRMSEp scorecard, scatter), predicted-yield table, and
# predicted/outlook maps for the best model by rRMSEp, plus references.
Cone-of-uncertainty figures
From an outlook DB produced with [ML] run_time_steps = all and
estimate_ci_for_all = True (tabpfn native quantiles at every forecast issue
month for every hindcast year), geocif.viz.cone renders per crop: a per-year
hindcast validation grid of forecast cones vs observed, a full-size live-year
cone with an empirical hindcast-error band, per-stage coverage vs the nominal
level, and per-state mini-cones. Outputs land under
{dir_output}/{project}/ml/analysis/{Month_DD_YYYY_HHhmm}/cone/, split into
plots/ (PNG) and csvs/, with a lookup_plots_csvs.csv manifest in both --
the same shape the outlook products use.
Configure it with a [cone] section and the whole run is one call — which is
how it runs on the cluster:
[cone]
start_year = 2005 ; first harvest year drawn in the grid
crops = ['maize', 'soybean']
; explicit per-crop DBs: an outlook DB written for one crop can also hold
; another crop's table built with the wrong detrend method
dbs = {'maize': 'outlook_09_06_2026_11h31.db', 'soybean': 'outlook_09_06_2026_11h21.db'}
model = tabpfn
units = bu/ac ; or tn/ha
ncols = 5 ; columns in the per-year grid
states_years = [2012, 2025, 2026] ; years to draw per-state cone grids for
min_region_frac = 1.0 ; a stage must cover the FULL region pool to be drawn
use_nass_reference = True ; USDA finals + monthly in-season track
nass_offline = False ; True = cached NASS extract only, never call the API
Set [ML] make_cone_plots = True to render the cones automatically at the end
of a yield_outlook run, into <analysis>/cone beside <analysis>/outlook,
using the DB that run just wrote. It is off by default: the cones are a
publication product rather than part of forecasting, and the NASS reference
calls an external API a compute node may not reach. A failure there is logged
and never discards a completed run. Otherwise render them standalone:
from geocif.viz import cone
cone.run([geobase, countries, crops, geocif]) # everything from [cone]
# CLI equivalent:
# python -m geocif.viz.cone --config geobase.txt --config ... [--start-year 2005]
# or fully explicit, without a config:
# python -m geocif.viz.cone --db X.db --table T --crop maize --out DIR [--no-nass]
USDA NASS reference. With use_nass_reference = True (key from [NASS]
api_key), geocif.viz.nass pulls QuickStats state yields: the final
estimates fill missing observed values, and the monthly in-season forecasts
are drawn as USDA's own track next to the cone, so each forecast issue date is
compared against the USDA number published that month. Two API facts the module
encodes: freq_desc = MONTHLY is rejected outright — the monthly forecasts are
reference_period_desc = "YEAR - AUG FORECAST" (…SEP/OCT/NOV) — and a "YEAR"
row is the final estimate only once it was loaded after its crop year, because
mid-season NASS mirrors the latest forecast into that row. The response is
cached beside the figures so a rerun works without network access.
August is the earliest USDA yield forecast for corn and soybeans — there is
no July or earlier estimate in the archive — so cone points issued in June and
July have no USDA counterpart by construction, and the live season carries at
most one square until the September report (~Sep 11). Use states_years to draw
per-state grids for finished seasons as well, where the full Aug/Sep/Oct track
is available.
No prose on the canvas. Figures carry a title, axis labels and a legend — nothing else. Interval level, coverage, sample sizes and units provenance live in the companion CSVs (and the run log), so they can be quoted in the surrounding text rather than shrinking the plotting area.
Cropmask optimizers
Two consumers of geoprepare extraction outputs that tune the cropland mask used downstream. Run after the corresponding geoprepare extractor has written its outputs.
# Uniform threshold T over the region (single absolute or rank-based knob).
# Reads geoprepare.extract_sweep output:
# ${PATHS:dir_output}/threshold_sweep/{country}/{crop}/{country}_{crop}_s{season}_sweep.csv
from geocif import threshold_optimizer
threshold_optimizer.run(cfg_geocif)
# Per-cell binary mask — independent in/out decision per cropland cell.
# Reads geoprepare.extract_cells output:
# ${PATHS:dir_output}/cell_optimizer/{country}/{crop}/{country}_{crop}_s{season}_cells.parquet
# Writes a production-mask parquet at the same location that geoextract picks up.
from geocif import cell_optimizer
cell_optimizer.run(cfg_geocif)
Configure under [THRESHOLD_OPTIMIZER] and [CELL_OPTIMIZER] in geocif.txt. Outputs land under ${PATHS:dir_output}/ml/analysis/{date}/{threshold_sweep_summary|cell_optimizer}/.
Using the optimized cell mask in production extraction
geoprepare 0.6.273+ can apply the per-cell mask produced by cell_optimizer during EO extraction. Opt in per country (or in [DEFAULT]) in geoextract.txt:
[DEFAULT]
use_optimized_mask = True
When the flag is on, geoprepare.extract_EO reads
${PATHS:dir_output}/cell_optimizer/{country}/{crop}/{country}_{crop}_s{season}_optimized_mask.parquet
for every configured (country, crop, season) and AND-s it with the existing floor/ceiling AFI mask. Cells the optimizer marked included=False are dropped from the per-region aggregate even if they pass the floor/ceiling rule. Multi-season countries get the union across seasons — a cell is kept if any season's optimizer selected it.
Pipeline order with the optimized mask:
geoprepare.extract_cells.run(cfg_geoprepare) # writes per-cell parquets
geocif.cell_optimizer.run(cfg_geocif) # writes optimized_mask.parquet
geoprepare.geoextract.run(cfg_geoprepare) # reads optimized_mask.parquet
extract_EO aborts at startup with a missing-parquet list if use_optimized_mask = True for any country whose mask hasn't been produced yet — silent fallback to the floor/ceiling rule when the operator asked for the overlay would be a confusing footgun, so it doesn't.
Currently wired in process_aef, process_fldas, process_chirps_mfc, process_soilgrids (the static + monthly-forecast EO paths). The daily-EO path through geom_extract (NDVI, daily CHIRPS, ESI, etc.) is not yet wired — track via a future change in geoprepare.
Annual (leave-one-out) masks
Enable annual_mask = True under [CELL_OPTIMIZER] in geocif.txt to produce one mask per historical year instead of a single pooled mask. For each year Y, the GA trains on every OTHER year — year Y's yield never sees the cell selection — and that mask is written to a _y{year}_optimized_mask.parquet file alongside the pooled one. geoprepare.extract_EO prefers the year-specific file when extracting year Y (FLDAS / CHIRPS-MFC, which are per-year datasets) and falls back to the pooled file for forecast / current years. AEF and SoilGrids (static) always use the pooled mask.
This closes the overfitting failure mode where the pooled mask was selected with year Y's yield as part of the training data — visible in pre-0.4.747 runs as regions whose Pearson r between yield and NDVI flipped sign after selection (the GA found anti-correlated cells because R² is sign-blind).
Cost. Roughly (n_years + 1) × the pooled-only default per region. On a country with 25 yield years that's ~26× more GA runs; expect runtime to scale accordingly. Opt in only when the data span justifies it.
Off by default. Existing configs without annual_mask continue to write the single pooled parquet.
ML models
geocif supports the following model types (configured via models in [DEFAULT]):
| Model | Key | Type |
|---|---|---|
| CatBoost | catboost |
Gradient boosting |
| XGBoost | xgboost |
Gradient boosting |
| TabPFN | tabpfn |
Prior-fitted network |
| TabICL | tabicl |
In-context learning |
| NGBoost | ngboost |
Natural gradient boosting |
| YDF | ydf |
Yggdrasil decision forests |
| Oblique RF | oblique |
Oblique random forest |
| Cubist | cubist |
Rule-based regression |
| MERF | merf |
Mixed effects random forest |
| Linear | linear |
LassoCV / LogisticRegressionCV |
| GAM | gam |
Generalized additive model |
| GeoSpaNN | geospaNN |
Geospatial neural network |
| PyGRF | pygrf |
Geographical random forest (global + per-sample local RFs, Moran's-I blend) |
| TabPFN-GSA | tabpfn_gsa |
TabPFN with geospatial sparse attention (local in-context sampling; git-only dep) |
| george | george |
Exact GP regression (dfm/george, fitted kernel + noise; geocif[george] extra) |
| BNN | bnn |
Bayesian NN (Ma et al. 2021 RSE): variational two-headed torch net with native heteroscedastic prediction intervals |
| GPR | gpr |
sklearn Gaussian process regression |
| Median | median |
Median baseline |
| Analog | analog |
Analogous year baseline |
The three spatial models (pygrf, tabpfn_gsa, and geospaNN) read region
centroids from the lat/lon feature columns and therefore require
[ML] include_lat_lon_as_feature = True. Each new model needs its own
[<model>] config section with ML_model = True. Hyperparameter overrides
live in [ML]: pygrf_band_width / pygrf_local_weight /
pygrf_n_estimators / pygrf_max_features / pygrf_kernel /
pygrf_resampled (defaults: adaptive 15%-of-n bandwidth, floor 20; local
weight = global Moran's I of y), tabpfn_gsa_K / tabpfn_gsa_s (grid cells —
rounded to a perfect square — and distant-sampling rate), george_kernel
(expsquared | matern32 | matern52) / george_jitter, and bnn_epochs /
bnn_batch_size / bnn_lr / bnn_prior_sigma / bnn_kl_weight /
bnn_warmup_epochs / bnn_n_mc / bnn_calibrate_sigma.
The bnn model reports its own predictive uncertainty: total sigma =
sqrt(aleatoric² + epistemic²) from Monte-Carlo weight sampling, rescaled by a
factor fitted on the held-out most recent training year (two-pass: fit on
earlier years to estimate the scale, then refit on all years). kl_weight
defaults to 0.05 — the paper's full-KL objective collapses the sigma head to a
near-constant. Its intervals bypass the conformal wrapper and land in the same
"lower CI" / "upper CI" columns.
Feature selection methods
Configured via feature_selection in [ML]:
none, SelectKBest, BorutaPy, Leshy, gOMP, RFECV, RFE, lasso, mrmr, SHAP, stabl, PowerShap, BorutaShap, Genetic, feature_engine, multi
The gOMP tiers cap how many features survive: gOMP_high (500), gOMP_medium (50), gOMP_low (15). A county-scale sweep (usa_admin2 maize, cubist, matched county-years) found gOMP_medium best on every metric and 2.2× faster than both gOMP_high and none — none does not save time, it moves the cost into the model. Feature selection is the dominant cost of a fold (~74% at gOMP_high), so this setting matters more than the model choice.
Selection results are cached on disk and shared across the models of a fold (see Performance below), since the selection depends only on the training data and method.
Performance
Two settings govern how a run uses the machine:
[DEFAULT]
do_parallel_ml = True
fraction_cpus = 0.15 ; concurrent fold tasks, as a fraction of cores
[ML]
cache_feature_selection = True ; reuse selections across models/runs (default)
threads_per_worker = auto ; auto | <int> | -1 (unlimited)
- Feature-selection cache — selection is model-independent, so the first model of a fold computes it and the rest reuse it, on disk (workers are separate processes). Measured at county scale: catboost 32 min/fold computing it, cubist 21 min/fold reusing it. Keys are content-addressed, so any change to the data, method, or selector code is a miss rather than a stale hit.
- Thread budget — each worker gets
cores / workersthreads instead of every worker's model grabbing every core (which measured a load average of 940 on a 128-core node). Notefraction_cpussets concurrency, not total footprint: the budget deliberately fills the machine without oversubscribing it. Setthreads_per_workerexplicitly to leave headroom for other users.
Cluster analysis
Optional analysis that clusters regions by their CID profiles and identifies which CIDs discriminate each cluster. Works with or without yield data — falls back to a proxy CID (e.g., AUC_NDVI) when yield is unavailable. Enabled via [ML]:
run_cluster_analysis = True
cluster_analysis_proxy = AUC_NDVI ; proxy CID when yield is unavailable
cluster_analysis_max_k = 8 ; maximum clusters for silhouette selection
cluster_analysis_top_n = 20 ; top N CIDs in discrimination heatmap
cluster_analysis_variance = 0.85 ; cumulative PCA variance to retain
Pipeline: PCA dimensionality reduction → Ward's hierarchical clustering (silhouette-selected k) → Kruskal-Wallis + Cohen's d for CID discrimination → mutual information for CID-target association. Outputs: cluster map (choropleth), dendrogram, PCA biplot, discrimination heatmap with significance stars, target boxplot, and per-CID maps for top discriminating indices.
Spatial neighbor features
Optional GraphSAGE-style preprocessing that computes yield-correlation-weighted averages of neighboring regions' features. Enabled via [ML]:
use_spatial_neighbors = True
spatial_neighbor_method = knn ; knn or full
spatial_neighbor_k = 5 ; number of nearest neighbors
For each admin region, the neighbor graph is built from training data using haversine distances and Pearson yield correlations as edge weights. Neighbor-aggregated features are added as nbr_* columns and flow through standard feature selection.
Experiments
The experiments runner (geocif.experiments) provides 6 experiments for model selection, feature importance, and hyperparameter tuning:
| # | Config name | Internal name | What it does |
|---|---|---|---|
| 0 | model_comparison |
models |
Runs each model in comparison_models head-to-head. Produces Bradley-Terry ranking, scatter plots, MAPE bars. Identifies best model per country (required by experiments 1 & 2). |
| 1 | cid_ablation |
cids |
Runs the best model once per CID Type in isolation (Cold alone, FLDAS alone, etc.). Shows which climate driver category contributes most. Produces MAPE-by-CID bar chart, region×CID heatmap, year×CID chart, CID rank over time. |
| 2 | region_filter |
region_filter |
Drops low-production regions and re-runs the best model to test if excluding noisy regions improves national accuracy. |
| 3 | optuna |
optuna |
Bayesian (TPE) search over ML hyperparameters (learning rate, depth, regularization, etc.). Produces convergence, parameter importance, and parallel coordinate plots. |
| 4 | optuna_cid_types |
optuna_cid_types |
Bayesian search for the best combination of CID Type categories (e.g. Rain+VI+ESI may beat using all 8 types). |
| 5 | optuna_cid_indices |
optuna_cid_indices |
Bayesian search for the best subset of individual CID indices (e.g. PRCPTOT + AUC_NDVI + TG90p). Capped at max_cid_indices per trial. |
Dependencies: Experiments 1 and 2 require experiment 0 first. Experiments 3–5 are independent.
Configure in geocif.txt:
[experiments]
run_experiments = ["model_comparison", "cid_ablation"]
comparison_models = ["catboost", "tabpfn", "tabicl"]
n_trials = 30
n_trials_cid_types = 30
n_trials_cid_indices = 60
max_cid_indices = 25
Run:
from geocif import experiments
experiments.run(cfg_geocif)
Standalone diagnostics (geocif.experiments package)
Separate from the sweep runner above, geocif/experiments/ holds read-only
diagnostics that answer one question against data already on disk — no model
training, no writes to the outlook DB. Each writes a date-stamped
ml/analysis/<stamp>/explore/<name>/ tree of plots/, tables/ and a
README.md carrying the verdict and the knobs that change it.
| Module | Question it answers |
|---|---|
residuals_vs_cid |
Which CID anomalies does the yield model fail to absorb? |
drought_audit |
Do drought years actually look anomalous in the inputs, or did the pipeline flatten them? |
irrigation_kansas |
Does irrigated share interact with in-season stress to move county yield anomalies — and which stress CID and month carry it? |
from geocif.experiments import irrigation_kansas
irrigation_kansas.run(cfg) # kansas maize, ESI + heat + rain candidates
irrigation_kansas.run(cfg, state="nebraska")
irrigation_kansas.run(cfg, stress_cids=["TX90p", "KDD"])
irrigation_kansas standardises yields within county (Theil-Sen detrended
residual over that county's own residual sd) before correlating with
irr_share. Raw yield levels would only re-measure the irrigated/dryland level
gap (171.3 vs 69.8 bu/ac in Kansas maize); the open question is whether
irrigation buffers year-to-year variability, which is what the anomaly isolates.
It ranks candidate stress CIDs by the R² their irr_share x stress interaction
adds over a no-interaction model, so [ML] irrigation_stress_cid can be chosen
from evidence rather than assumed.
Experiments output
The experiments runner writes to a dedicated DB and analysis folder under dir_output:
{dir_output}/
└── ml/
├── db/
│ └── experiments_{MMMM_DD_YYYY_HH}H.db
│
└── analysis/
└── {MMMM_DD_YYYY}/
├── experiments/ # Experiment 0 (model comparison)
│ ├── experiment_metrics.csv
│ ├── heatmap_models.png
│ ├── boxplot_models.png
│ ├── regional_mape_models_{country}.png
│ ├── error_distribution_models.png
│ └── metric_comparison.png
│
└── optimization/ # Optuna hyperparameter search
├── optuna_trials.csv
├── best_params.csv
├── convergence.png
├── optimization_history.png
├── param_importances.png
└── parallel_coordinate.png
Outlook output
The yield outlook runner produces a diverging choropleth map showing current forecast yield as a percentage of the historical mean/median prediction per region, plus a combined CSV. Per (country, crop, model, stage) it also renders skill choropleths — mape_map, rmse_map, and r2_map (per-region MAPE, RMSE, and R²) — and metric-progression plots across forecast stages, each in both a full per-region version and a decluttered *_national version (single national line with a gray ±1 std-across-regions band).
In addition, model performance is aggregated to every admin level above the run level ([ML] plot_parent_aggregations, default True): an admin_2 (county) run also gets admin_1 (state) and national outputs; an admin_1 run gets national outputs. Observed/predicted yields are area-weighted means per (parent, year) — groups with missing/zero Area (ha) fall back to unweighted means — with the county→state mapping reused from the production-statistics file (ml.stats.admin1_lookup). Per level this writes a pooled obs-vs-pred scatter (+ hexbin), a per-parent metrics CSV (r², rRMSE%, MAPE% + pooled row), a national obs-vs-pred time series, per-parent rRMSE%/r² choropleths (admin_1 only; boundary from [ML] parent_boundary_admin_1, falling back to Level_1.shp — skipped with a warning when missing or when <50% of parent names match), and a lookup_plots_csvs.csv mapping each plot to its companion CSV.
{dir_output}/
└── ml/
└── analysis/
└── {MMMM_DD_YYYY}/
└── outlook/
├── yield_outlook_{country}_{crop}_{model}_{stage}_{year}.png
├── yield_outlook_{year}.csv
├── plots/{model}/{country}/{crop}/{admin_1|national}/
│ ├── scatter_{level}_{country}_{crop}_{model}.png (+ _hexbin)
│ ├── timeseries_national_{country}_{crop}_{model}.png (national only)
│ ├── rrmse_map_admin_1_...png / r2_map_admin_1_...png (admin_1 only)
│ └── lookup_plots_csvs.csv
└── csvs/{model}/{country}/{crop}/{admin_1|national}/
├── aggregated_predictions_{level}_{country}_{crop}_{model}.csv
├── metrics_{level}_{country}_{crop}_{model}.csv
├── scatter/timeseries companion CSVs
└── lookup_plots_csvs.csv
Config file documentation
geobase.txt
Shared paths and dataset settings. All directory paths are derived from dir_base.
[PATHS]
dir_base = /gpfs/data1/cmongp1/GEO
dir_inputs = ${dir_base}/inputs
dir_logs = ${dir_base}/logs
dir_download = ${dir_inputs}/download
dir_intermed = ${dir_inputs}/intermed
dir_metadata = ${dir_inputs}/metadata
dir_condition = ${dir_inputs}/crop_condition
dir_crop_inputs = ${dir_condition}/crop_t20
dir_boundary_files = ${dir_metadata}/boundary_files
dir_crop_calendars = ${dir_metadata}/crop_calendars
dir_crop_masks = ${dir_metadata}/crop_masks
dir_images = ${dir_metadata}/images
dir_production_statistics = ${dir_metadata}/production_statistics
dir_output = ${dir_base}/outputs
[DATASETS]
datasets = ['CHIRPS', 'CPC', 'NDVI', 'ESI', 'NSIDC', 'AEF']
countries.txt
Single source of truth for per-country config. Shared by both geoprepare and geocif.
[DEFAULT]
boundary_file = gaul1_asap_v04.shp
admin_level = admin_1
seasons = [1]
crops = ['maize']
category = AMIS
use_cropland_mask = False
calendar_file = crop_calendar.csv
; AMIS countries (inherit from DEFAULT, override crops if needed)
[argentina]
crops = ['soybean', 'winter_wheat', 'maize']
; EWCM countries (full per-country config)
[kenya]
category = EWCM
admin_level = admin_1
seasons = [1, 2]
use_cropland_mask = True
boundary_file = adm_shapefile.gpkg
calendar_file = EWCM_2025-04-21.xlsx
crops = ['maize']
[malawi]
category = EWCM
admin_level = admin_2
use_cropland_mask = True
boundary_file = adm_shapefile.gpkg
calendar_file = EWCM_2025-04-21.xlsx
crops = ['maize']
Restricting a run to specific regions (run_regions)
Optional per-country knob (falls back to [DEFAULT]). Unset = run every
region, which is the default behaviour.
[usa]
admin_level = admin_2
; names below are admin_1 (state) even though the run is admin_2 (county)
run_regions_level = admin_1
; flat list -> applies to every crop
run_regions = ["illinois", "iowa"]
; or per crop -> a crop missing from the dict runs all its regions
run_regions = {"maize": ["illinois", "iowa"], "soybean": ["arkansas"]}
run_regions_leveldeclares the admin level the names are given at; it defaults to the run's ownadmin_level.- An admin_1 selection on an admin_2 run keeps every county inside the selected states (parent lookup via the production-statistics file, same normalization as the yield join). An admin_2 selection on an admin_1 run maps the county names up to their parent states.
- Names are matched case-insensitively with
_treated as a space, soSouth Dakota,south_dakotaandSOUTH DAKOTAare the same region. - Selected names that match nothing are listed in a warning; a selection that
matches no region raises a
ValueErrorinstead of running empty. - The filter is applied in
_prepare_ml_dataframe, so training, LOOCV, the SQLite output, plots and parent aggregations all see only the selected regions.
crops.txt
Crop mask filenames and calendar category definitions.
; Crop masks
[maize]
mask = Percent_Maize.tif
[winter_wheat]
mask = Percent_Winter_Wheat.tif
[sorghum]
mask = cropland_v9.tif
; Calendar categories
[EWCM]
use_cropland_mask = True
calendar_file = EWCM_2026-01-05.xlsx
crops = ['maize', 'sorghum', 'millet', 'rice', 'winter_wheat', 'teff']
eo_model = ['aef', 'nsidc_surface', 'nsidc_rootzone', 'ndvi', 'cpc_tmax', 'cpc_tmin', 'chirps', 'chirps_gefs', 'esi_4wk']
[AMIS]
calendar_file = AMISCM_2026-01-05.xlsx
geoextract.txt
Extraction-only settings for geoprepare. Loaded last so its [DEFAULT] overrides shared defaults.
[DEFAULT]
method = JRC
redo = False
threshold = True
floor = 20
ceil = 90
countries = ["malawi"]
forecast_seasons = [2022]
[PROJECT]
parallel_extract = True
parallel_merge = False
geocif.txt
Indices, ML, and agmet settings for geocif. Country overrides go here when geocif needs different values than countries.txt (e.g., a subset of crops).
[AGMET]
eo_plot = ['ndvi', 'chirts_era5_tmax', 'chirts_era5_tmin', 'chirps', 'esi_4wk', 'nsidc_surface', 'nsidc_rootzone']
logo_harvest = harvest.png
logo_geoglam = geoglam.png
; Country overrides (only where geocif differs from countries.txt)
[ethiopia]
crops = ['winter_wheat']
[bangladesh]
crops = ['rice']
admin_level = admin_2
boundary_file = bangladesh.shp
; ML model definitions
[catboost]
ML_model = True
[analog]
ML_model = False
[ML]
model_type = REGRESSION
target = Yield (tn per ha)
feature_selection = gOMP
cluster_strategy = single
check_yield_trend = False
use_spatial_neighbors = True
spatial_neighbor_method = knn
spatial_neighbor_k = 5
lag_yield_as_feature = True
lag_years = 3
median_yield_as_feature = False
median_years = 5
include_lat_lon_as_feature = False
panel_model = True
cat_features = ["Harvest Year", "Region_ID", "Region"]
outlook_n_years = 10 ; Number of historical years for yield outlook comparison
outlook_aggregation = mean ; mean or median
run_time_steps = latest ; latest, current, all, N, pre_season, or auto
; pre_season: forecast BEFORE the season starts from
; S2S/FLDAS leads only — forecast_seasons may then
; include a season that has not started yet
; (e.g. south_africa maize 2027 launched Sep 2026)
run_cluster_analysis = False
cluster_analysis_proxy = AUC_NDVI
cluster_analysis_max_k = 8
cluster_analysis_top_n = 20
cluster_analysis_variance = 0.85
[LOGGING]
log_level = INFO
[DEFAULT]
data_source = harvest
method = monthly_r
project_name = geocif
countries = ["kenya"]
crops = ['maize']
admin_level = admin_1
models = ['catboost']
seasons = [1]
threshold = True
floor = 20
FLDAS forecast overlay
When FLDAS columns are present in the merged data (e.g. fldas_tair_tavg_lead0 through _lead5), agmet plots automatically overlay forecast dots on matching panels:
| FLDAS variable | Target panel |
|---|---|
fldas_tair_tavg |
Temperature |
fldas_totalprecip_tavg |
Daily precipitation |
fldas_soilmoist_tavg |
Soil moisture (surface) |
Each lead time (0–5) appears as a diamond marker with decreasing opacity (lead 0 = most opaque). Dots beyond the harvest date are suppressed. No config changes are needed — detection is automatic.
Release
To publish a new version to PyPI:
- Bump
__version__ingeocif/__init__.pyandversioninpyproject.toml - Build and upload:
uv build uvx twine upload dist/geocif-<version>*
- Commit:
git add geocif/__init__.py pyproject.toml git commit -m "Bump to <version>"
Credits
This project was supported by NASA Applied Sciences Grant No. 80NSSC17K0625 through the NASA Harvest Consortium, and the NASA Acres Consortium under NASA Grant #80NSSC23M0034.
Release files for geocif 0.4.1023
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| geocif-0.4.1023.tar.gz | 1.1 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| geocif-0.4.1023-py2.py3-none-any.whl | Python 2, Python 3 | none | any | Details |
Total release size: 2.1 MB
Release files / geocif-0.4.1023.tar.gz
| Download URL | geocif-0.4.1023.tar.gz |
|---|---|
| Size | 1.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fce5f8fe09910518cb2512b21f1a58a175bc02413ab67e0f80b8b5a7c8ea1100
|
|
BLAKE2b-256 checksum How to use checksums |
08350ad98d9e590f7d6130b9c8eacffd9737c82c73a02623f257a2413ccc68d5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.13
|
Release files / geocif-0.4.1023-py2.py3-none-any.whl
| Download URL | geocif-0.4.1023-py2.py3-none-any.whl |
|---|---|
| Size | 934.7 kB |
| Tags | Python 2 Python 3 |
|
SHA-256 checksum How to use checksums |
5185074156658e68c3de46911a4392469bb5767ad15b1c8c1d815f504bbb5380
|
|
BLAKE2b-256 checksum How to use checksums |
ac92e1f5420c1d49b7ad773724e6d6e2ee121dae54a6f896d56c6c538b96c973
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.13
|