Isan rice yield benchmark: forecasting models

Trained on synthetic data. These models were fitted to a simulated Northeast Thailand whose data-generating process is documented and whose true yield components are known. They show how the paper's pipeline behaves; they are not forecasts of Thai rice yields and must not be used as such. To forecast real yields, rebuild the features from real inputs and retrain with the code (make all).

Main-season rice yield (kg/rai) of a district, forecast at the end of the season (November information set) from 81 leakage-safe features in eight groups: previous yields, humidity and soil moisture, water (rain, reservoirs, irrigation), climate, satellite indices (NDVI, EVI, LSWI, LST), prices of rice and competing crops, geography, and map layers with spatial lags. Companion models to the preprint Leakage-safe, explainable multi-source machine learning for district-level rice yield forecasting: a ground-truth simulation benchmark for Northeast Thailand by Thanatchon Chongmankhong (read online · code · data · DOI 10.5281/zenodo.24036674).

Independent personal research. Does not represent the views of any employer or institution.

The model

The paper's main model, selected on the validation seasons 2015-2017: the equal-weight average of

  • LightGBM (lightgbm.txt, native format), with the hyperparameters tuned on 2015-2017: num_leaves=63, learning_rate=0.05, n_estimators=400, min_child_samples=80, subsample=0.8, colsample_bytree=0.5, reg_lambda=1.0, max_depth=-1;
  • Ridge regression (ridge.json): median imputation, standardisation and a ridge fit whose penalty RidgeCV chose (alpha = 0.53).

Both are refitted here on all 3,000 district-seasons with features in the main world (200 districts, seasons 2011-2025; 2008-2010 serve only as yield history), the information a forecaster would have before the 2026 season. The level of yield is the target.

Prediction intervals. conformal.json holds split-conformal offsets: empirical quantiles of the ensemble's out-of-sample errors on every season it could forecast with at least three seasons of training data (2014-2025, 2,400 forecasts). Added to a point forecast they give 80% and 90% intervals. In the paper's rolling evaluation the 80% intervals covered 78.3% of outcomes; the worst season (2018) covered 40%.

Accuracy (from the paper, synthetic benchmark)

Rolling origin: each test season 2018-2025 is forecast by models trained on earlier seasons only (1,600 forecasts). RMSE and MAE in kg/rai (multiply by 0.00625 for t/ha); brackets: 95% season-block bootstrap interval; Skill = 1 - MSE / MSE of climatology.

Model RMSE [95% CI] MAE MAPE (%) R2 Skill
LightGBM + Ridge ensemble (this model) 29.9 [24.7, 37.4] 23.4 7.0 0.87 +0.60
LightGBM alone (lightgbm.txt) 27.4 [26.1, 29.0] 21.7 6.4 0.89 +0.66
Ridge alone (ridge.json) 37.9 [25.4, 54.3] 27.9 8.3 0.78 +0.35
Climatology (previous 3-season mean) 47.1 [40.6, 53.2] 37.8 11.4 0.67 +0.00

LightGBM alone had the lowest test error, but the ensemble was the model chosen on the validation seasons, before the test seasons were seen; the paper reports both. Holm-adjusted Diebold-Mariano tests over eight test seasons were not significant. Ridge failed badly in some seasons, which a post-hoc analysis traced to its row-wise leave-one-out penalty choice (Section 5.8 of the paper); predict.py returns each component so you can use LightGBM alone.

Files

file content
lightgbm.txt LightGBM booster (native text format)
ridge.json Ridge pipeline as numbers: imputation medians, scaler means and scales, penalty, coefficients, intercept
features.json the 81 input columns in order, their groups, target and units
conformal.json 80% and 90% split-conformal offsets (kg/rai) and the calibration seasons
metrics.json the rolling-origin test metrics above, for every model in the paper
predict.py loader: load(folder).predict(X) -> LightGBM, Ridge, ensemble and interval columns
sample_input.csv, sample_output.csv the 200 districts of season 2025 and their predictions (a check of the export, not a forecast)

Usage

from huggingface_hub import snapshot_download
import sys, pandas as pd

folder = snapshot_download("thanatchon36/isan-rice-yield-benchmark")
sys.path.insert(0, folder)
from predict import load

model = load(folder)
X = pd.read_csv(f"{folder}/sample_input.csv")
print(model.predict(X)[["ensemble", "lo80", "hi80"]].head())

To build the features for new seasons, use the code repository: put the raw tables in data/raw/ (schema in docs/DATA_SCHEMA.md), then

from isanrice.features import build_features, load_raw
df, groups = build_features(load_raw())          # one row per district-season
X = df[df.year == 2026]                           # columns as in features.json

Features are built so that a season's row uses only information available by the end of that season; the repository's tests check this.

Limitations

  • Synthetic training data: the simulator decides which mechanisms exist and how strong they are. The model has learned that world, not Thailand.
  • One region, one crop (main-season rice), district level, end-of-season information set. Earlier cut-offs (July-October) need models trained on the matching features (E5 in the paper).
  • The explanations of these models are only partly faithful: grouped SHAP recovered drought (r = 0.84) but misattributed heat stress to moisture and absorbed the technology trend into prices.

Reproduce

make setup && make all in the code repository runs the experiments; make model retrains these files (isanrice 0.1.0, LightGBM 4.7.0, scikit-learn 1.9.1).

Citation

@misc{chongmankhong2026isanrice,
  author = {Chongmankhong, Thanatchon},
  title  = {Leakage-safe, explainable multi-source machine learning for district-level rice yield forecasting:
            a ground-truth simulation benchmark for Northeast Thailand},
  year   = {2026},
  doi    = {10.5281/zenodo.24036674},
  url    = {https://thanatchon36.github.io/isan-rice-yield-benchmark/paper/},
  note   = {Preprint; all results are from a synthetic benchmark}
}
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Dataset used to train thanatchon36/isan-rice-yield-benchmark

Evaluation results

  • RMSE (kg/rai) on Isan rice yield benchmark (synthetic)
    self-reported
    29.900
  • MAE (kg/rai) on Isan rice yield benchmark (synthetic)
    self-reported
    23.400
  • R2 on Isan rice yield benchmark (synthetic)
    self-reported
    0.870