1. 课程导入与问题界定

作物产量预测是农业数字化中最具经济价值、也最容易被低估难度的一类问题。它要回答的并不是"今年收成好不好"这种模糊判断,而是在收获前若干周甚至若干月,给出一个可量化、可解释、可复盘的产量估计值,例如"某县冬小麦平均单产 6350 kg/hm²,误差不超过 ±5%"。这个数字向下游传导,会影响收购计划、仓储调度、期货套保、保险理赔与政策补贴测算。因此,本课程从一开始就把问题定位为带时间约束的回归预测任务,而不是单纯的数据分析练习。

一、业务场景决定了建模的边界

不同使用方对"预测"的要求差异很大,这直接决定了特征选择与评估口径。下面这张图把四类典型场景与它们对时间提前量、空间粒度、精度要求的差异并列呈现,便于在动手写代码前先锁定目标。

作物产量预测的四类业务场景与约束差异 同一套数据,因使用方不同,预测时点、空间粒度与精度要求必须重新界定 收购与仓储调度 提前量:收获前 4~8 周 粒度:县域 / 片区 精度:MAPE ≤ 8% 关注:总量与入库节奏 特征偏重面积、长势、气象 农业保险定损 提前量:收获前 2~4 周 粒度:地块 / 保单单元 精度:单地块误差可控 关注:减产幅度与赔付线 特征偏重遥感指数、灾情 期货与贸易决策 提前量:播种后即滚动更新 粒度:主产省 / 全国 精度:方向判断优先 关注:同比增减与概率区间 特征偏重气象长序列、政策 农技指导服务 提前量:生长季内多次 粒度:地块 / 农户 精度:相对高低排序 关注:限制因子定位 特征偏重管理措施、土壤 由此推导出的建模约束(示例设定) 目标变量:单产 kg/hm²(连续值回归) | 预测时点:抽穗期后固定截止,禁止使用截止日之后的气象与遥感数据 样本单元:县-年 组合 | 评估口径:按年份滚动回测,报告 MAPE 与 RMSE,并保留残差的空间分布检查 最容易踩的坑:数据泄漏 若把收获期实测气象、后期遥感影像或统计年鉴的最终单产作为特征,模型在训练集上会异常优秀,上线后迅速失效。 判断方法:逐特征核对"该字段在预测截止日当天是否已可获得",不可获得的必须剔除或做滞后处理。

二、核心挑战:不是算法难,而是数据与时间对齐难

作物产量受品种、土壤、管理、气象、病虫害共同影响,其中气象又表现为"累积效应"与"关键期效应"两种形态。真正让初学者受挫的通常不是模型选择,而是以下四类问题:

  • 样本量小且维度高。一个地级市往往只有十余年、几十个县的记录,样本量在数百量级,而气象可展开为逐旬、逐月的温度、降水、日照等上百个变量,极易过拟合。
  • 时空错配。气象站点数据、遥感栅格、统计年鉴三者的空间单元与时间粒度都不一致,需要先统一到"县-年"或"县-旬"的公共面板上。
  • 趋势与波动混杂。单产长期随技术进步上升,年际间又因天气大幅波动。若不分离趋势项,模型会把"技术进步"误学成"某类天气更好"。
  • 评估方式失真。随机划分训练集与测试集会因相邻年份高度相关而高估精度,必须采用按时间顺序的滚动回测。

三、Python 技术栈选型

本课程的技术栈围绕"能读数据、能对齐时空、能建模、能解释、能交付"五件事组织,全部为成熟开源库,避免引入过重框架。

环节 主要库 在本课程中的具体用途
数据处理 pandas、numpy 构建县-年面板,做滞后、滑动累积、同比等特征衍生
时空数据 geopandas、rasterio、xarray 按县界裁剪遥感栅格,提取 NDVI 等指数的区域均值
建模 scikit-learn、xgboost / lightgbm 基线回归、树模型、交叉验证与超参数搜索
解释与检验 shap、statsmodels、matplotlib 特征贡献分析、趋势项分解、残差与误差可视化
交付 joblib、fastapi(可选) 模型持久化,封装为按县查询的预测接口

四、整体开发流程与一个可运行的开端

流程上建议按"问题界定 → 数据面板构建 → 特征工程 → 基线模型 → 树模型与调参 → 滚动回测 → 解释与交付"推进,每一步都以可验证的中间产物收尾。下面给出第一步的实际操作:把原始表整理成建模用的面板,并明确目标列与时间列。

import pandas as pd

# 示例数据:县-年 面板,字段为合理假设,非实测
raw = pd.DataFrame({
    "county": ["A县", "A县", "A县", "B县", "B县", "B县"],
    "year":   [2019, 2020, 2021, 2019, 2020, 2021],
    "yield_kg_ha": [6120, 5980, 6410, 5730, 5560, 5890],
    "sown_area_ha": [32000, 31500, 32800, 21000, 20500, 21400],
    "ndvi_jun": [0.62, 0.55, 0.68, 0.58, 0.51, 0.60],
    "precip_mm_jun": [88, 42, 105, 76, 39, 92],
})

# 1) 明确目标与时间键,按县、年排序,保证滞后特征正确
panel = raw.sort_values(["county", "year"]).reset_index(drop=True)

# 2) 构造滞后特征:上一年的单产,用于分离趋势与波动
panel["yield_lag1"] = panel.groupby("county")["yield_kg_ha"].shift(1)

# 3) 构造同比特征:当年 6 月 NDVI 相对上年的变化
panel["ndvi_jun_diff"] = panel.groupby("county")["ndvi_jun"].diff()

print(panel[["county", "year", "yield_kg_ha", "yield_lag1", "ndvi_jun_diff"]])

运行后应看到每个县的首年 yield_lag1 为 NaN,这是正常现象,说明滞后构造生效;建模时需删除这些行或单独处理。这一步看似简单,却决定了后续所有特征是否"在预测时点可得"——例如 yield_lag1 在预测当年产量时是已知的,而当年的最终统计单产则不可用。

建议的验收标准:在进入第二章之前,你应当能用自己的话说明三件事——预测目标的时间截止点在哪一天;哪些字段在该日之后才产生因而必须排除;用什么指标和什么划分方式来判断模型是否真的可用。这三条界定清楚,后面的特征工程与调参才有意义。

最后需要强调,作物产量预测的成败往往在建模之前就已决定。把业务场景翻译成明确的时间约束与空间粒度,把多源数据对齐成干净的面板,再用滚动回测诚实地评估,这三件事做扎实,即便使用线性回归也能得到可用的结果;反之,再复杂的模型也只会给出漂亮却无法落地的数字。