信息速览
OISST 是海洋机器学习里那块最不起眼的底板:想要某片海某一天有多热,几乎都会先打开它。它的分量不在分辨率,而在于把 1981 年 9 月以来的每一天,压成了同一张 720×1440 的网格、同一套处理链、同一个文件格式。
它同时是「输入的原料」和「答案的标尺」。预测海温的模型拿它当训练目标,做海洋热浪、珊瑚白化、渔业资源评估的项目拿它当判定依据。这两件事共用同一份数据,也正是大量结论无法外推的根源。
这份条目只讲 v2.1 的 AVHRR-only 逐日 0.25° 产品——不是 1° 周平均的老 OI.v2,也不是 2002 至 2011 年那段加微波的组合产品。一屏速览见下表。
| 项目 | 数值 | 来源 |
|---|---|---|
| 官方全名 | NOAA 0.25-degree Daily OISST, Version 2.1 | DOI 元数据页标题 |
| 制作方 | NOAA/NCEI,CDR 计划,首席研究员 Boyin Huang | CDR 产品页 |
| 时间覆盖 | 1981-09-01 至 2026-10-27,逐日 | 目录索引逐月清点 |
| 空间网格 | 0.25°×0.25°,720 纬 × 1440 经 | 文件坐标变量 |
| 每文件变量 | sst、anom、err、ice 四个 | 文件结构 |
| 单文件体积 | 中位 1.68 MB(1.49–1.76 MB) | 目录索引 Size 列 |
| 全量 | 542 个月 / 16,493 个文件 / 27.43 GB | 目录索引逐月求和 |
| 容器 | NetCDF-4(HDF5),int16 打包 | _NCProperties 属性 |
| 更新 | 逐日;未定版 1 天,终版约 15 天 | 文件 summary 属性 |
| 许可 | 公有领域,无 SPDX 标识 | CDR 使用协议 PDF |
| DOI | 10.25921/RE9P-PT57 | DOI 解析至 C01606 |
| 访问门槛 | 无注册,HTTP 与 S3 均可匿名访问 | 目录可直接列出 |
§1 速览:它是什么,也不是什么
§1.1 一句话定义
OISST v2.1 是一张每天一张、全球海洋、0.25 度一格的海温图,由 NOAA 把卫星、船舶、浮标、Argo 四种来源的观测按最优插值的方法融合而成,从 1981 年 9 月 1 日起一天一个文件,至今没有断过。
它不是卫星原始影像,不是浮标的定点读数,也不是一份「观测记录」。每一格的值都是解出来的:观测落进网格,缺的地方由插值补上,补的方式受误差协方差约束。所以它处处有值——包括那天没有任何仪器经过的海域。
§1.2 为什么它不是「又一份海温产品」
第一层原因是混合的比例是可变的。1981 年靠的是几条 NOAA 极轨卫星上的 AVHRR 加商船报;2020 年代靠的是 MetOp 系列加更密的浮标网与 Argo。同一张网格背后,观测平台换过好几代,而产品名一直叫 OISST v2.1。这意味着「1982 年的格点」和「2024 年的格点」不是同一种东西,尽管它们在文件里长得一模一样。
第二层原因是它自己就是一条链。翻开源码文档会看到一句很容易被忽略的话:这份分析的初猜(first guess)是前一天的分析结果。于是第 N 天不是从零算起的,它继承了第 N−1 天的场。这个设计让逐日的场在时间上平滑,也让「把日文件当成独立样本」这件事从根上不成立。
第三层原因是它的元数据比数据更容易骗人。文件自带的 title 属性会被截到 110 个字符,未定版那一份的结尾停在「- Inter」;全局属性写纬度范围 -90 到 90,坐标变量却是 -89.875 起;有一个叫 zlev 的维度,属性写着「海面高度、单位米」,实际恒为 0。这些都无害,但每一条都能让写脚本的人多花半天。
§1.3 三十秒决策卡:你的处境,它能帮你什么
| 你的处境 | 它能帮你什么 |
|---|---|
| 要训练一个逐日海温预测或补缺模型 | 四十五年逐日、全球同网格、口径一致的场,训练与验证可取自同一套,缺日为零 |
| 要判定某年某海域是否发生海洋热浪 | 距平场已按 1971–2000 气候态算好,阈值法可以直接用,不用自己建气候态 |
| 要给渔业、养殖、珊瑚监测配一份环境背景 | 逐日 0.25°,覆盖全部海域,匿名可下,成本近乎为零 |
| 要做海气耦合或卫星产品的交叉验证 | 与微波、红外观测的独立性足够,常被当作参照场 |
| 要研究近岸、海湾、养殖区尺度的水温 | 给不了,见下一节 |
| 要做逐小时或公里级的海洋预报 | 给不了,时间步长是一天,格距是几十公里 |
§1.4 不适合谁
如果需求是近岸与半封闭水域,这份数据会误导。0.25° 一格在赤道约 27.8 公里,在 40°N 约 21 公里;一个海湾、一片养殖区往往不足一格。近岸格点里同时含有陆地与浅海,值是整格的平均,而陆地部分是被掩膜掉后用邻域插出来的。用它判断「我这一片筏架今天水温多少」,误差可以大到改变结论。
如果需求是逐小时变化,它也给不了。一天只有一个时刻(UTC 正午),日内潮汐、日较差、夜间降温全部被平均掉。要小时级,得去看现场浮标、高频雷达或区域模式。
如果需求是未经平滑的观测,同样不合适。这是分析场,插值的目的就是填洞,填出来的值与「那里真实的水温」不是一回事。误差场能告诉你哪里的填补更不可信,但它不改变「处处有值」这一事实。
如果需求是跨 2016 年的严格同质序列,需要额外处理。2016 年 1 月 1 日起 v2.1 换了现场观测的来源、加了 Argo、换了卫星输入、改了冰转换方法,四件事同时发生。前后两段并非同一套处理规则,做长期趋势时必须交代这一点。
§2 数据构成:一天一个文件,四个变量,两维半
§2.1 全量规模:哪些是官方口径,哪些是数出来的
官方页只给了一句话:1981 年 9 月 1 日至今、逐日、0.25°、每日更新。目录里的实际体量没有任何页面写,只能自己数。
| 维度 | 数值 | 怎么得到的 | 性质 |
|---|---|---|---|
| 月目录数 | 542 | 顶层索引计数,198109 至 202610 | 目录 |
| 日文件数 | 16,493 | 逐月索引求和 | 目录 |
| 其中未定版 | 14 | 文件名含 _preliminary |
目录 |
| 覆盖天数 | 与文件数相同 | 1981-09-01 至 2026-10-27 的天数 | 推算 |
| 总体积 | 27.43 GB | 逐月 Size 列求和,27,433,714,184 字节 | 目录 |
| 单文件中位 | 1.68 MB | 同上,中位数 1,676,973 字节 | 目录 |
| 单文件范围 | 1.49–1.76 MB | 同上,最小 1,489,788、最大 1,761,991 | 目录 |
| 每变量格点数 | 1,036,800 | 720×1440 | 文件形状 |
| 每日数值个数 | 4,147,200 | 四变量 × 格点数 | 推算 |
| 全量数值个数 | 约 684 亿 | 上项 × 天数 | 推算 |
两个数字值得单独说。
一个是覆盖天数与文件数完全相等。1981-09-01 到 2026-10-27 是一万六千四百九十三天,目录里也是一万六千四百九十三个文件,未定版的十四个各自占着一个还没出终版的日期。也就是说这份数据四十五年没有缺过一天。这一点不靠信任:把顶层索引的 542 个目录名念一遍,把每月文件数加一遍,两个数自然对上。
顺带说清「不缺一天」这句话的边界。它说的是日期完整,不是每天都有效。二十世纪八十年代的南大洋、北极边缘,很多格点在那一天没有任何观测进入分析,值是插出来的。文件不缺、格点不缺,缺的是「这一天这一格的结论有多可靠」——那件事由 err 场承担,不是由文件名承担。
另一个是体积不能用单一样本外推。最早那一批 1981 年的文件是 1.71 MB 左右,2026 年的文件是 1.51 MB 左右,极差接近 18%。按 1.71 MB 乘 16,500 天会得到 28 GB 出头,比真实的 27.43 GB 高估约 3%。做容量规划时按中位数 1.68 MB 算,误差在一两个百分点以内。
§2.2 一个文件里到底有什么
文件是 NetCDF-4 容器(HDF5 底层,_NCProperties 写作 version=2,netcdf=4.8.1,hdf5=1.12.1),sst、anom、err、ice 四个数据变量都做了 gzip 压缩加 shuffle 预调。
| 变量 | 形状 | 存储类型 | 物理量 | 属性域 | 填充值 |
|---|---|---|---|---|---|
| sst | (1, 1, 720, 1440) | int16 | 日海表温度 | -3.00 至 45.00 °C | -999 |
| anom | (1, 1, 720, 1440) | int16 | 距平 | -12.00 至 12.00 °C | -999 |
| err | (1, 1, 720, 1440) | int16 | 误差标准差 | 0 至 10.00 °C | -999 |
| ice | (1, 1, 720, 1440) | int16 | 海冰密集度 | 0 至 100 % | -999 |
| time | (1,) | float32 | 当日中心时刻 | 标量 | 无 |
| zlev | (1,) | float32 | 恒为 0 的伪维度 | 0 | 无 |
| lat | (720,) | float32 | 纬度格心 | -89.875–89.875 | 无 |
| lon | (1440,) | float32 | 经度格心 | 0.125–359.875 | 无 |
四个数据变量共用 scale_factor = 0.01、add_offset = 0。也就是说存进去的整数 1832 表示 18.32 °C,整数 -999 表示没有值。忘记乘这个系数,会读出一片几千度的海。
属性域那两栏是从 valid_min 与 valid_max 换算来的,不是猜的:sst 写作 -300 与 4500,anom 写作 ±1200,err 写作 0 与 1000。它们同时说明了这份数据的硬边界——它不接受低于 -3 °C 或高于 45 °C 的海温,超出就钳在这里。
§2.3 四个变量不是四份观测
初学者常把四个变量当成四种测量,其实它们是一份分析的四张面孔。
sst 是主角,最优插值的输出。anom 是同一天的 sst 减去气候态,气候态取自 1971–2000 年的 OI.v2,由 1° 月产品插值到这张 0.25° 的日网格上。err 是这次分析自己估的误差标准差:一部分来自 OI 的随机与采样误差,一部分来自偏差订正后的残余误差。ice 是海冰密集度,取的是 7 天中值而不是当日值;它在冰缘带被换算成代理海温,用来把开阔海域的温度插值一直推到季节性冰缘。
四者的关系可以用一句话概括:sst 是结果,anom 是结果减背景,err 是结果的自我评价,ice 是结果在冰区的替代品。读懂这一句,就不会问「为什么 anom 和 sst 高度相关」这种问题——它们当然相关,差的只是一个随经纬度和日期变化的气候态。
顺带说一个口径问题。官方 CDR 页的「CDR Variables」只列了三项:海表温度、海面高度、海冰密集度。距平与误差被归到「Additional Data」里,是配套地图而非 CDR 变量。引用时把 anom 说成「CDR 定义的变量」在措辞上不严谨,尽管它在文件里与 sst 平级。那个「海面高度」正是 zlev 这个空维度的来源。
§2.4 一条格点记录的逐项解剖
取 2026-10-13 那份终版文件,看北太平洋副热带上某个格点:
| 层面 | 内容 | 要注意什么 |
|---|---|---|
| 索引 | sst[0, 0, 450, 630] |
四维下标,前两维恒为 0 |
| 纬度 | 450 × 0.25 − 89.875 = 22.625°N | 升序,北在数组末尾 |
| 经度 | 630 × 0.25 + 0.125 = 157.625°E | 0 度在列首,无负经度 |
| 时间 | 当日 UTC 正午 | units 的 1978 纪元与数据无关 |
| 存储值 | 2913 | int16 原始值 |
| 物理值 | 29.13 °C | 乘 0.01 |
| 填充 | -999 | 陆地、无分析结果都长这样 |
| 单位 | Celsius | 属性里写的是全称,不是 °C |
这张表里最容易被忽略的是第三行和最后两行。时间轴的单位写着「1978 年 1 月 1 日 12 时起的天数」,1978 只是 NCEP 网格模板的纪元常数,数据并不从 1978 年开始——把纪元当起点,会把整条时间轴往前挪三年多。单位那一行看似琐碎,实际会影响自动化管线:按 units == "degC" 做字符串匹配的代码,在这里会匹配失败。
§2.5 空间与时间的边界
格心与格边差半格。 坐标变量给的是格心,-89.875 到 89.875、0.125 到 359.875;全局属性给的是包络,-90 到 90、0 到 360。前者是点,后者是边。用后者当栅格范围去配准,整幅图会平移半格——在 0.25° 的尺度上就是十几公里。做 GIS 叠置之前必须确认用的是哪一套。
经度没有负值。 数组从 0.125°E 排到 359.875°E,跨日界线的区域(比如从 170°E 到 170°W)会被切成数组的两端。任何按经度切片再求平均的脚本,如果不做滚列,得到的是两段相隔半个地球的海。
纬度越高,格子越窄。 0.25° 在赤道约 27.8 公里,40°N 约 19.7 公里,60°N 约 13.9 公里。做全球平均、算热含量、统计某一纬度带的面积占比,都必须乘纬度余弦,否则高纬会被系统性高估——这不是精度问题,是数量级问题。
zlev 是一个幽灵维度。 它的 long_name 写着「Sea surface height」,units 是米,positive 是 down,而 actual_range 是 0 到 0。它存在的唯一理由是这套网格模板天生四维。用 xarray 打开会看到 (time: 1, zlev: 1, lat: 720, lon: 1440),用裸数组读会拿到四维 ndarray。写 sst[0] 得到的是三维,写 sst[0, 0] 才是二维——两者都对,但混用会在拼接时炸出形状错误。
时间不是轴,是链。 这是全篇最重要的一条边界,值得单独一节展开,见 §4.1。
§2.6 与同类资源的定位差异
| 资源 | 核心内容 | 与 OISST v2.1 的差别 |
|---|---|---|
| 1° 周平均 OI.v2 | 1970 年代起的周平均,1° 网格 | 时间与空间都粗一档;不是把日产品做七天平均得来的,插值前就已聚到 1° |
| AMSR+AVHRR 组合 | 微波加红外,2002–2011 | 微波不受云影响、开阔洋面更准,但只覆盖九年,且已于 v2 时代结束 |
| ERA5 的海表温度 | 再分析场里的海温 | 来自海洋模式的同化,时间平滑、物理自洽,但把它当观测用会低估极值 |
| 现场浮标 | 单点逐小时 | 真值性质最好,但只有点,无空间覆盖,且分布极不均匀 |
| 卫星 L3 级海温 | 单星反演,逐日 | 有云就有洞,未做插值填补;OISST 正是拿这类产品当输入 |
判断规则可以压成一句:要全球、逐日、无洞、长序列 → OISST;要单点真值 → 浮标;要物理自洽的多要素场 → 再分析;要看单星原始反演 → L3。
§3 定位层:先分清「哪一个 OISST」
§3.1 一个名字,四条产品线
「OISST」在文献里至少指四样不同的东西,混用会直接导致数字对不上。
| 名称 | 分辨率 | 时间 | 状态 |
|---|---|---|---|
| 1° 周平均 OI.v2 | 1° | 1981 年起,周 | 2022-12-05 起 NCEP 侧停止 |
| 0.25° 逐日 v2 | 0.25° | 1981-09 起 | 2020-04-26 停产,被 v2.1 取代 |
| 0.25° 逐日 v2.1 | 0.25° | 1981-09 起 | 现役,逐日更新 |
| AMSR+AVHRR 组合 | 0.25° | 2002–2011 | 随 v2 结束,现由 THREDDS 侧保留 |
四条线里被引用最多的是第三条,也就是本篇的对象。第一条容易被误当成「日产品的周平均」,官方 FAQ 专门澄清过:周产品是把一周的卫星观测先在 1° 网格上平均、再做插值,不是对 0.25° 日产品做时间平均。两者不能互相推导。
§3.2 v2.1 与 v2 的真实差别
2020 年 4 月 1 日 v2.1 顶替 v2,v2 在 4 月 26 日停止生产。但「换版」这件事并不像版本号暗示的那样整齐。
官方页写得很清楚:2016 年 1 月 1 日之后的数据有实质质量改进,此前的数据与 v2 相同——只是从 NetCDF-3 换成了 NetCDF-4,并更新了元数据。文件里的 comment 属性补了时间点:这次容器转换发生在 2017 年 11 月。
于是同一份 v2.1 里其实躺着两段历史:2016 年前是「换了包装的 v2」,2016 年后才是真正的新算法。做跨 2015/2016 的分析时,先确认自己比较的是数据还是包装。
§3.3 三个版本号写在同一句话里
产品名是 v2.1,文件名里是 v02r01,页面正文里还会出现 v2.1a 与 v2.1b。这三个词指的不是同一层东西。
| 标记 | 含义 | 时间 | 影响范围 |
|---|---|---|---|
| v2.1 / v02r01 | 产品版本与文件命名 | 2020-04 起 | 全序列 |
| v2.1a | 卫星海温输入由 Navy 换成 NOAA ACSPO | 2021-10-01 起 | 仅前向生产 |
| v2.1b | 船舶偏差订正由常数改为逐月时变 | 2023-04-01 起 | 仅前向生产 |
关键在于「仅前向生产」这四个字。v2.1a 是因为 MetOp-A 退役,ACSPO 能同时吃 AVHRR 与 VIIRS;v2.1b 是因为船测偏差随时间漂移。两次改动都只对改动之后的日子生效,此前的历史没有重算。所以序列里存在两处台阶:2021 年 10 月前后,卫星源换了;2023 年 4 月前后,船测订正方法换了。
这两处台阶不像 2016 年那次有官方大张旗鼓的说明,它们藏在「Product Update History」的小节里。做 2021 或 2023 年前后的趋势比较,如果不查这一节,会把处理规则的切换读成海洋的变化。
§3.4 名字、文件名与唯一指认
唯一可靠的键是文件名:oisst-avhrr-v02r01.YYYYMMDD.nc,未定版加 _preliminary。日期是 UTC 日,与文件内 time 一致。
文件内的 title 属性不能当键用。终版写的是「… Version 2.1 - Final」,未定版写的是「… Version 2.1 - Inter」——Interim 被硬截断在第 110 个字符上,五个字母只剩三个半。用 title.endswith("Final") 判断尚可,用完整字符串做精确匹配必然失败。
第二条陷阱是排序。文件名里的日期是定长的八位数字,字典序即时间序,这一点比很多数据集友好。但目录按月分,202610 与 20261 不会同时出现,跨月拼接时按「目录名 + 文件名」二级排序才安全。
§3.5 final 与 preliminary 不是两条流
很多二手介绍会写「这个产品有 final 和 preliminary 两条数据流」。这个说法会把人带偏。
v2.1 的 access 下只有一个 avhrr 目录,没有 final/、没有 preliminary/。两者同处一地,靠文件名后缀区分。一天的数据先生成未定版(延迟 1 天),约 15 天后被同名去掉后缀的终版覆盖。覆盖之后,未定版在原路径上就不存在了。
覆盖发生的那一刻,内容也变了。文件里的 history 属性写明:终版以未定版为初猜,再补三天的 AVHRR 数据重算;未定版只用一天的数据。所以这不是「同一个东西换个名字」,而是两次不同的计算。
真正需要当心的是云端。AWS 的镜像里,data/v2.1/avhrr/202610/ 这个前缀下同时躺着 20261001.nc 与 20261001_preliminary.nc 两份;历史月份则只有一份、零个未定版。也就是说,未定版在云上没有立刻被删,而是在几天后被清掉。任何按 *.nc 通配去扫前缀的脚本,在最近这半个月里都会把同一天数两遍。这一点在 §5.6 单独给检查办法。
§4 质量层:它最容易被误用的四个地方
§4.1 逐日递归:第 N 天不是独立样本
这是全篇最该记住的一句:每一天的分析,以上一天的分析作为初猜。
算法文档在定义符号时写得很直白:增量是相对于「第一猜参考系」的差,而这份产品的一猜就是前一天的 analysis。产品逻辑上这很合理——海温在一天之内不会跳变,用昨天当起点能压掉噪声。
后果有三个层面。
物理层面,误差在时间上相关。今天的插值误差会带进明天的初猜,于是连续几天的异常可能是一段被平滑过的同一个信号,而不是几次独立的事件。
统计层面,样本不独立。把四十五年的日文件打乱做训练与测试划分,等于让模型在同一条马尔可夫链上前后偷看;随机切分得到的精度会高于按年、按块切分,而后者才反映真实外推能力。
工程层面,重算很贵。想改某一天的算法,不能只重跑那一天——后面所有日子都以它为起点。这也解释了为什么官方对 v2.1a、v2.1b 这类改动一律「只前向、不回溯」:回溯等于重跑整条链。
§4.2 贯穿案例:某海水养殖区的热浪预警(本案例)
后面几节会反复回到这个场景,先把来龙去脉交代清楚。
一支给海水养殖做风险服务的团队,要给北纬 38° 附近一片约 30 公里见方的筏式养殖区做夏季高温预警。他们手上只有两样东西:2018 年才布设的一台浮标,测表层水温,逐小时;以及养殖企业 2019 年以来的损失记录——哪几天出现了大面积死亡。
预警要覆盖 2019 年以前的年份,而浮标那时还不存在。于是他们打算用 OISST 补:先用 2018 年至今的浮标与同格点的 OISST 做一次配对,看偏差有多大;再用 OISST 的距平场定一个阈值(比如超过 90 分位连续五天),回溯到 1982 年,看看历史上这类事件多久发生一次。
方案听起来顺理成章,实际会依次撞上四堵墙。第一堵是格点与养殖区不是一回事(§2.5 与 §4.3);第二堵是距平的基准期不是他们以为的那个(§4.4);第三堵是 2016 年与 2021 年的两处台阶会污染「历史上多久一次」的统计(§4.5);第四堵是误差场不能当置信区间用(§4.6)。四堵墙分别落在下面四小节,每一节末尾都会回到这个团队。
先说第一堵的轮廓:养殖区 30 公里见方,在 0.25° 网格上只落到一到两个格点,而这两个格点里都有陆地。格点值是整格的平均,还掺了掩膜后的插值。团队第一次把浮标与格点画在同一张图上,发现夏季偏差能到 1.5 °C,冬季反而接近零。这不是产品错了,是空间代表性差异在暖季被放大。
§4.3 -3 到 45 的硬截断与 -999 的三种含义
sst 的 valid_min 与 valid_max 是 -300 和 4500,换算过来就是 -3 °C 与 45 °C。这是打包格式与质量控制的联合产物:低于海冰形成温度的开放海水在物理上罕见,高于 45 °C 的表层水温也不存在。落在域外的值会被钳到边界上。
于是有一个反直觉的推论:你读到的 -3.00 与 45.00 可能是钳位,不是测量。极地边缘容易贴上 -3 的下界,封闭浅海在盛夏容易贴上 45 的上界。做极值统计时,把这两端当成真实观测会低估尾部。
填充值 -999 承担了三种完全不同的含义:陆地、无有效分析结果的格点、以及被质量控制剔除的格点。文件里没有字段区分它们。想区分只能自己造掩膜——比如用 ice 是否大于零判断冰区,用地表掩膜判断陆地。这正是 §5.5 里那几行校验代码存在的理由。
回到 §4.2 的团队:他们最初的筛选逻辑是「去掉 -999」,结果把冬季结冰的养殖区格点也一起删了。后来改成「保留 ice > 0 的格点,只用 sst」,才把冬季样本救回来。这个改动让他们多出三个月的可用序列,也让阈值法在冬季有了对照组。
§4.4 距平的基准期是 1971–2000
这是跨产品比较时最常见的错位来源。
OISST 的 anom 用的气候态是 1971–2000 年,来自 OI.v2 的月产品,插值到 0.25° 日网格。文件 references 属性里明写「Climatology is based on 1971-2000 OI.v2 SST」。
而 2020 年代之后新出的很多海温产品、很多海洋热浪研究,习惯用 1991–2020 作基准。两个基准之间,全球平均海温差了约零点几度。于是同一片海、同一天,「距平」在两个体系里能差出半个标准差——足以让「是否超过 90 分位」的判断翻转。
处理办法只有两个:要么统一到 1971–2000,要么放弃 anom 字段,自己用 sst 重建气候态。后者更干净,代价是得把四十五年重算一遍。
§4.2 的案例在这里又撞了一次:他们一开始直接拿 anom 的 90 分位当阈值,后来发现与合作方(用的是 1991–2020 基准的分析产品)给出的「热浪日」对不上,差了将近三成。改法是把两边的 sst 都取出来,用同一段 1982–2011 自建气候态,重新算距平。工作量不大,但这一步不做,两家的预警会永远互相矛盾。
§4.5 误差场不是置信区间
err 的 long_name 写的是「Estimated error standard deviation of analysed_sst」,即估计的误差标准差。它由两部分合成:OI 的随机与采样误差,以及偏差订正后的残余误差。
这意味着三件事。它是一格一个数的标量场,不是一格里的一条分布;它是模型估的,不是与真值比出来的;它只反映「这次插值有多不确定」,不反映「观测本身偏了多少」。
算法文档给了几个能把这件事说实的参数:偏差订正用 130 个经验正交模态,在 2° 网格上求解后插回 0.25°;求解窗口是当天前后各 7 天共 15 天;模态权重再经 5 天二项滤波平滑;最优插值的搜索半径 400 公里,每格最多取 22 个超级观测。
这几个数的组合说明了一件事:在观测稀疏的海域,误差场会大,但那不是「知道得少」,而是「插出来的东西占了大头」。南大洋与北极周边是典型,官方在算法文档的局限一节里也直说,偏差订正依赖现场观测,没有现场观测的地方(尤其极区)不可靠。
实践里的用法是阈值筛:err 超过某个值(比如 0.5 °C)的格点直接剔除,或者按 1/err² 加权。把它当成 95% 置信区间去画误差棒,是把标准差当成了分位数。
§4.6 传感器同质性:一条被写进假设的妥协
算法文档的「传感器性能」一节有一句很重的话:OI 假设 1981 年至今所有 AVHRR 具有相同的统计特性,例如相同的信噪比。
这句话是整条序列能够拼起来的前提,也是一个已知的妥协。AVHRR 从 /2 升级到 /3,搭载平台从 NOAA 系列换到 MetOp 系列,定标方式、过境时刻、通道响应都在变;2021 年后输入还混进了 VIIRS。产品层面用偏差订正把这些差异压到可接受的范围,但「压到可接受」不等于「没有」。
同一节还写着另一句:代码不是为通用卫星写的,每换一颗星都要改代码。这句话解释了为什么 v2.1a 的切换要等到 MetOp-A 退役才做——不是不想早换,是改一次代码要重新验证整条链。
对使用者而言,这条假设的意义是:跨年代的差异里,有一部分来自传感器,不来自海洋。做长期趋势时,这一部分无法从数据内部估计出来,只能靠外部证据(比如与浮标、与其它产品的交叉比对)来约束。
§4.7 冰缘:代理值不是观测
ice 是 7 天中值的海冰密集度。它在产品里有双重身份:既是独立的一张图,又是冰缘带生成代理海温的原料。
机制在官方页写得很清楚:在观测稀缺的冰缘带,用海冰密集度换算出一个代理海温,让插值能把开阔海域的温度一路推到季节性冰缘。换句话说,冰缘带里那些看起来连续的 sst,是从冰情推出来的,不是测出来的。
这带来一个使用边界:在冰缘附近做「海温趋势」,很可能测的是冰情的趋势。北极边缘与南大洋的冰缘位置年际变化大,同一个格点今年是开阔水、明年是浮冰,sst 序列就会出现跳变——那一段不是海温变了,是格点换了性质。
判别办法是同时把 ice 读出来,看目标格点在时间上是否长期为零。任何 ice 大于零的日期,其 sst 都应被标注为「含代理成分」。
§5 工具层:怎么取、怎么读、怎么验
§5.1 四条获取路径
| 路径 | 形态 | 适合 | 注意 |
|---|---|---|---|
| NCEI 直连目录 | 按年月分目录的 .nc 文件,HTTP 列目录 |
要原始文件、要可引用路径 | 无注册、无令牌;单连接速度一般 |
| THREDDS | 目录服务,可按变量与时间切片 | 要按需取子集 | 官方 CDR 页列出的入口 |
| ERDDAP | 表格式查询与子集 | 要少量点位的时序 | 同样是官方页列出的入口 |
| AWS / GCS 镜像 | 同一批 .nc,另有逐日 Zarr |
云上训练、要随机读 | Zarr 只在 AWS 那份里有 |
四条路给的字节应当一致,但目录结构不一致:直连是 access/avhrr/YYYYMM/,S3 是 data/v2.1/avhrr/YYYYMM/,GCS 与 S3 同构但没有 avhrr-zarr。写死路径的脚本换一条路就要改。
§5.2 先算体积,再决定怎么下
单文件 1.5 MB 看起来很小,乘上四十五年的天数就是 27 GB;再乘四变量解包成 float32,落盘会膨胀到上百 GB。先量一遍再动手。
#-- 只读目录索引,不下载数据;返回该月文件数与字节数
import re
import urllib.request
base = "https://www.ncei.noaa.gov/data/sea-surface-temperature-optimum-interpolation/v2.1/access/avhrr/"
html = urllib.request.urlopen(base + "202610/", timeout=60).read().decode("utf-8", "ignore")
sizes = [int(x) for x in re.findall(r'<td align="right">(\d{7,})</td>', html)]
print(len(sizes), sum(sizes), round(sum(sizes) / 1024 ** 2, 1), "MiB")
#-- 期望:文件数等于当月天数(含未定版),体积在 38–40 MiB 之间
这段脚本的正则只抓七位以上的纯数字单元格,日期列带连字符不会被误抓。把它套在 542 个目录上循环,得到的就是 §2.1 那张表。
§5.3 读一个文件
xarray 会自动应用 scale_factor 与 _FillValue,读出来直接是摄氏度、缺测为 NaN。
#-- 依赖:xarray、netCDF4(或 h5netcdf)
import xarray as xr
ds = xr.open_dataset("oisst-avhrr-v02r01.20261013.nc")
print(dict(ds.sizes))
print(ds.sst.attrs["scale_factor"], ds.sst.attrs["_FillValue"])
sst = ds.sst.isel(time=0, zlev=0)
print(float(sst.sel(lat=22.625, lon=157.625, method="nearest")))
print(float(ds.anom.isel(time=0, zlev=0).sel(lat=22.625, lon=157.625, method="nearest")))
#-- 期望:sizes 为 {'time': 1, 'zlev': 1, 'lat': 720, 'lon': 1440}
#-- 期望:scale_factor 为 0.01,_FillValue 为 -999
#-- 期望:两个数都在 -3 到 45 与 -12 到 12 之间,且量级相近
如果不想让 xarray 代劳,用 h5py 直接读会拿到 int16 原始值,需要自己乘 0.01、自己把 -999 换成 NaN。两种做法都对,混用会出错:xarray 读出来的已经是浮点,再乘一次 0.01 就变成百分之一度。
§5.4 拼成时间立方体
一年的数据是 365 个文件,直接 open_mfdataset 会把 365 个文件的坐标对齐、按 time 拼成一维,得到 (time, lat, lon) 的三维立方。
#-- 按月分块拼接,避免一次性打开上千个文件句柄
import glob
import xarray as xr
files = sorted(glob.glob("oisst-avhrr-v02r01.2025*.nc"))
ds = xr.open_mfdataset(files, combine="by_coords", chunks={"time": 30})
print(ds.sst.shape, ds.time.values[0], ds.time.values[-1])
#-- 期望:形状为 (365 或 366, 720, 1440)
#-- 期望:首尾时间相差一年减一天
按年拼、按需 chunks,是唯一可行的策略。想一次性把整个序列的 sst 全读进内存,需要「天数 × 1,036,800 × 4 字节」,约 68 TB。
§5.5 四项校验
拉回来之后先别急着建模,跑一遍这个。
#-- 形状、维度、值域、填充比例,四项一次性过
import numpy as np
import xarray as xr
ds = xr.open_dataset("oisst-avhrr-v02r01.20261013.nc")
sst = ds.sst.isel(time=0, zlev=0)
assert sst.shape == (720, 1440), sst.shape
assert ds.sizes["time"] == 1 and ds.sizes["zlev"] == 1
assert float(sst.min()) >= -3.0 and float(sst.max()) <= 45.0
assert abs(float(ds.lat[0]) + 89.875) < 1e-6
print("缺测比例", round(float(np.isnan(sst).mean()), 3))
#-- 期望:缺测比例在 0.3 到 0.4 之间,主要来自陆地
四项里最有价值的是第二项和最后一项。维度那一项能立刻抓出「把 zlev 当变量名」的低级错误;缺测比例那一项是健康指标——它常年稳定在三成上下(陆地约占地球表面三成,另有一小部分海洋格点当日无有效分析),某天若明显偏离,多半是掩膜或打包出了问题,而不是海平面上升了。
§5.6 一个不报错的错:S3 上同一天有两份
前面提过,云端镜像里的未定版不会立刻删。检查办法只有一条:按日期去重,而不是按对象数。
#-- 列出某个前缀下的对象,数一数有没有重复日期
curl -s "https://noaa-cdr-sea-surface-temp-optimum-interpolation-pds.s3.amazonaws.com/?list-type=2&prefix=data/v2.1/avhrr/202610/&max-keys=1000" \
| grep -o 'oisst-avhrr-v02r01\.[0-9]\{8\}' \
| sort | uniq -c | sort -rn | head
#-- 期望:最近十几天的计数为 2(终版与未定版并存),更早的为 1
这个错不会抛异常。它只会让「平均海温」被某些日子重复计入,让时间序列的采样间隔变得不均匀。所有按对象前缀枚举的流程,都该在下载前做一次这样的去重。
§5.7 云端 Zarr:为随机读准备的那一份
AWS 镜像里有一棵 data/v2.1/avhrr-zarr/YYYY/MM/oisst-avhrr-v02r01.YYYYMMDD.zarr/ 的树。每个日文件对应一个独立的 Zarr 存储,带 .zmetadata 汇总与按变量的分块目录,单块十几到三十几 KB。
它的价值在于按块读。想取某个小区域、某几天的 sst,走 Zarr 只需要读几个块,不必把整个 1.5 MB 文件拉下来再切片。对训练时的随机采样,这个差别是数量级的。
代价是它多一层工具依赖(zarr / xarray 的 zarr 后端),且只有 AWS 那份有。做可复现实验时,把「用的哪条路径、哪个前缀」写进记录,比写「用的是 OISST」有用得多。
§5.8 §4.2 的案例在这一层的落地
回到那支养殖预警团队。他们最后定下的取数纪律是五条:
- 只取目标格点与周围八邻域,用 ERDDAP 或 Zarr 按块取,不下全量;
- 每次取数先跑 §5.5 的四项校验,缺测比例异常的那天单独标记;
- 时间轴按年拼、按块读,绝不把日文件当独立样本随机打乱;
- 下载前按日期去重,避免未定版与终版重复计入;
- 把
err与ice一起取回来,作为筛选与标注的依据,而不是只用sst。
五条里有三条是为了对付「不报错的错」。这也是这份数据在工程上最需要纪律的地方:它太容易拿到,以至于没人愿意在拿到之后再多做一步检查。
§5.9 批量下载:清单、并发与续传
把四十五年一次拉完是件很平常的事,27 GB 在今天的带宽下也就是几小时。真正会出问题的不是速度,是「拉回来一半、不知道缺了哪一半」。
第一步该做的不是下载,是生成清单。日期不能用「从 1981 数到 2026」手写,闰年、月末天数、以及 1981 年 9 月只有三十天这些事实,交给日历库比自己算可靠。
#-- 按真实日历逐日生成 URL 清单,跨年不跳月
import datetime as dt
d, end, urls = dt.date(1981, 9, 1), dt.date(2026, 10, 27), []
while d <= end:
urls.append(f"{d:%Y%m}/oisst-avhrr-v02r01.{d:%Y%m%d}.nc")
d += dt.timedelta(days=1)
open("urls.txt", "w").write("\n".join(urls))
print(len(urls))
#-- 期望:16493,与目录索引清点的文件数一致;不一致就说明日历或终点写错了
第二步是并发与续传。
#-- 限四路并发、断点续传、单文件失败重试两次
base="https://www.ncei.noaa.gov/data/sea-surface-temperature-optimum-interpolation/v2.1/access/avhrr"
xargs -P 4 -I{} curl -sS --retry 2 -C - --create-dirs -o "data/{}" "$base/{}" < urls.txt
三处细节值得解释。并发开到四而不是十六,是因为这条链路对单连接有节流,并发太高反而整体变慢,且更容易被中途断开;-C - 让中断后重跑只补差额,不必从头再来;重试写在 curl 里而不是套一层循环,避免一次失败导致整批重排。
第三步是回来对数:find data -name "*.nc" | wc -l 的结果必须等于清单行数。少了就是少了,不要用「应该差不多」放过——少的那几天,往往集中在同一段网络抖动的时刻,正好形成一段时间上的窟窿,而窟窿在聚合统计里不会报错,只会让曲线悄悄变平。
§6 层与层之间:它在本库中的位置
§6.1 库内的海洋与气候家底
这套库以医学数据为主,地球观测是近几年才补进来的一块。能与 OISST 形成上下游关系的,目前有三类:再分析场(ERA5 单层)、陆地站点观测(GHCNd)、卫星影像分类基准(EuroSAT)。三者与 OISST 的关系都不是替代,而是「同一套坐标,不同的物理量」。
§6.2 与 ERA5 的分工:同格距,不同出身
两者都是 0.25°,都把全球切成 720×1440,都能给出逐日的海表温度。差别在出身。
ERA5 的海温是海洋模式同化的产物:它继承了模式的时间平滑与物理自洽,也继承了模式对极值的压缩。OISST 的海温是卫星反演加现场订正再插值:它更贴观测,也更容易在观测稀疏处失真。
实践中的分工很清楚:要一个自洽的多要素背景场(风、气压、辐射、海温一起用)→ ERA5;要一个尽量贴观测的海温判定(热浪、白化、阈值)→ OISST。 两者同时用也常见:ERA5 给大气强迫,OISST 给海洋响应。
§6.3 与 GHCNd 的分工:海与陆
GHCNd 是陆地站点的逐日气温与降水,点是点,天是天。OISST 是海洋的格点场。
两者在同一项研究里经常配对出现:陆地上的热浪用 GHCNd 判,海面上的热浪用 OISST 判,再比较两者的同步性。这时要注意两边的「日」定义不同——GHCNd 的日值来自当地观测时段的统计,OISST 的日值是 UTC 正午的一个分析时刻。跨日界线比较时,会差一天。
§6.4 与 EuroSAT 的分工:同一片海,两种看法
EuroSAT 是 Sentinel-2 的 64×64 瓦片,任务是地表覆盖分类;OISST 是海温场。二者在「海岸带」这个场景里会相遇:用 EuroSAT 判海岸带的地物类型(养殖区、滩涂、红树林),用 OISST 给同一片海配温度背景。
分辨率差得很远——10 米对 20 多公里,差了三个数量级。这种配合只能做「区域背景」层面,不能做「逐像素修正」。
§6.5 与 AQS 的分工:一个已在前文用过的类比
AQS 是地面空气质量站点,与 OISST 没有物理关系,但在方法上同构:都是「观测稀疏、需要空间代表性判断、需要区分真值与插值」。
把 AQS 的站点与 OISST 的格点放在一起看,会发现同一类问题在两套数据里长得一样:站点/格点与目标位置的空间错配、时间聚合口径的错配、以及「数据有值不等于数据可信」。这类问题不随领域变化,只随尺度变化。
§6.6 检索路径建议
按需求找:要海温本身 → 本篇;要大气多要素 → ERA5;要陆地气温 → GHCNd;要卫星影像 → EuroSAT;要地面空气 → AQS。要做「海洋热浪 + 沿岸人群健康」的组合研究,从本篇出发到公共卫生类条目,中间那层「格点聚合到行政单元」需要自己写,且聚合权重(面积还是人口)会显著改变结论。
§7 质量评估与局限:它不该按什么标准评价
§7.0 阅读本节前需要知道的一件事
用「观测数据集」的尺子量它,会得出「全是插值、处处有值、不可信」的结论;用「机器学习基准」的尺子量它,会得出「没有标签、没有划分、没有排行榜」的结论。两种结论都对,但都没回答它为什么被引用了四十年。
它被引用的原因在于连续与一致:同一个网格、同一条处理链、同一套变量定义,贯穿四十五年。观测数据做不到这一点——卫星换了六代,浮标网从几十个长到几千个,南大洋几十年没有船去过。对于任何想把「历史海温」当作输入或标签的模型,连续比单点精度重要得多。
所以评价它该用的尺子是:作为一条跨越四十五年的分析链,它的同质性、可追溯性与边界标注做得怎么样。
§7.1 三种常见的评价框架错位
| 常见错位 | 表现 | 应该怎么想 |
|---|---|---|
| 拿浮标的精度标准套它 | 抱怨某格点与浮标差 1.5 °C | 格点与点本来就有代表性差异,该比的是偏差的统计结构与季节依赖 |
| 拿原始观测的标准套它 | 抱怨「到处都有值,肯定是编的」 | 插值填洞是产品定义的一部分,不填洞就不叫 analysis |
| 拿排行榜数据集的标准套它 | 抱怨没有训练测试划分、没有指标 | 它是原料,不是考题;划分与指标由任务定义 |
§7.2 DAIMS 二十四项逐条对照
DAIMS 原本面向社会科学调查数据,这里借用它的检查点,把这份格点产品的成熟度摊开。
| # | 检查项 | 状态 | 说明 |
|---|---|---|---|
| 1 | 宽格式 | ⚠️ | 网格数据不是表格;要宽表化须先按点位或区域抽取 |
| 2 | 唯一标识 | ✅ | 文件名日期即主键,全球唯一且定长 |
| 3 | 特殊字符 | ✅ | 文件名全为 ASCII,无空格 |
| 4 | 重复行 | ✅ | 一天一文件,无重复日期;云端未定版需按日期去重 |
| 5 | 缺失编码 | ✅ | -999 统一填充,_FillValue 显式声明 |
| 6 | 标签标识 | ✅ | 四个变量名固定,CF 与 ACDD 双重约定 |
| 7 | 罕见类分组 | ✅ | 无类别概念;冰区与陆地的区分由 ice 与掩膜承担 |
| 8 | 偏倚评估 | ⚠️ | 观测系统的时变偏倚有文献讨论,产品内无量化字段 |
| 9 | 数据字典 | ✅ | 变量属性齐全,另有 C-算法文档 与 CDR 变量表 |
| 10 | 信息性缺失解释 | ⚠️ | -999 不区分陆地、冰下、质控剔除三种成因 |
| 11 | 设备记录 | ✅ | platform、instrument、source 三属性列明来源 |
| 12 | 共线性 | ⚠️ | sst 与 anom 高度相关,同作特征等于重复计权 |
| 13 | 编码映射 | ✅ | 冰密集度到代理海温的转换在文档中有说明 |
| 14 | 时间戳处理 | ✅ | 单一 UTC 日,time 与文件名一致 |
| 15 | 划分建议 | ❌ | 无任何训练/验证划分建议,逐日样本非独立 |
| 16 | 泄漏讨论 | ❌ | 官方未讨论逐日递归带来的样本相关问题 |
| 17 | 标签分布 | ✅ | 无标签概念;值域由 valid_min/max 显式约束 |
| 18 | 测量偏倚 | ⚠️ | 近岸格点含陆地、冰缘为代理值,两类偏倚无逐格标注 |
| 19 | 外部验证建议 | ⚠️ | 文献中有大量交叉验证,产品页未给统一协议 |
| 20 | 版本记录 | ✅ | DOI、DSI 编号、C01606 与文件内版本属性齐备 |
| 21 | 预处理脚本 | ⚠️ | 源码包公开,但只覆盖前向生产,不含回溯重算 |
| 22 | 合规要求 | ✅ | 使用协议原文明确,ISO 约束字段完整 |
| 23 | 多模态对齐 | ⚠️ | 与微波组合产品的时间窗只覆盖 2002–2011 |
| 24 | 去标识化 | ✅ | 无个人数据;船舶观测在上游已聚合到网格 |
DAIMS 评分:18 / 24。按满项 1 分、半分项 0.5 分、零分 0 分计,十四个满项、八个半分项、两个零分项。
评分解读:分数落在中上段,与「文档扎实、口径清晰,但对现代机器学习的使用习惯毫无预设」的定位吻合。满项集中在标识、编码、版本、合规这些「一旦做好就不用动」的维度;零分集中在划分与泄漏这两项「需要为下游重新设计」的维度——它们不是疏漏,而是这份数据诞生时还没有人这样用它。
对你意味着什么:不要指望官方告诉你该怎么切分训练集。逐日递归这件事,官方在算法文档里说了机制,但没有说它对建模意味着什么。那一层判断只能自己做。
§7.3 逐条局限
| 局限 | 具体表现 | 后果 |
|---|---|---|
| 逐日递归 | 当天分析以上一天为初猜 | 日样本不独立,随机划分高估精度 |
| 硬截断 | sst 被钳在 -3 至 45 °C | 极值统计低估尾部 |
| 近岸混像元 | 0.25° 格内含陆地 | 沿岸与海湾偏差大且季节相关 |
| 冰缘代理 | 冰缘 sst 由冰密集度反推 | 冰缘趋势可能测的是冰情 |
| 断点不回溯 | 2016、2021、2023 三处前向改动 | 跨断点长序列非同质 |
| 传感器假设 | 假设全部 AVHRR 同信噪比 | 跨年代差异含仪器成分 |
| 距平基准旧 | 1971–2000 | 与 1991–2020 体系不可直接比 |
| 云端重复 | 未定版与终版短时并存 | 按对象枚举会重复计日 |
§7.4 该被追问什么、不该被追问什么
用「为什么没有逐格的不确定度分解」去问责它是合理的,因为 err 确实是合成的标量。但用「为什么没有原始卫星像元」去问责它不合理——它的定义就是分析场,原始像元在 L1/L2 产品里。
真正该追问的有三件。
第一,2016 年那次改版的影响能否被分离。官方给了四类改动(现场观测来源、Argo、卫星输入、冰转换),但没有给逐项归因的试验。想把「海温变化」与「算法变化」分开,只能靠外部证据。
第二,近岸的可用性边界在哪。产品没有任何字段标注「这一格离岸多远、含多少陆地」。使用者只能自己用地表掩膜算,而不同的掩膜会给出不同的边界。这是一个真实存在的空白。§4.2 的案例撞上的正是这堵墙:他们要的那片海,在两套掩膜下分别落在「可用」与「不可用」两侧,结论因此翻转。
第三,未定版与终版的差异有多大。官方说终版以未定版为初猜、补三天数据重算,但没有公布两者的统计差异。做近实时应用的人,实际上是在一个未量化的误差上做决策。
三件事共同说明一件事:这份数据的「连续性」是靠一条不断打补丁的链换来的,不是靠一次设计到位的规范。 用对了,它是一把长尺;用错了,它会把三次换零件读成三次气候变化。
§7.5 外部验证:怎么用别的数据检验它
| 参照 | 能验证什么 | 注意 |
|---|---|---|
| 现场浮标 | 单点偏差与季节依赖 | 点位极少且集中在北半球航线附近 |
| 微波海温 | 开阔洋面的独立性检验 | 微波在近岸失效,与 OISST 的强项互补 |
| Argo 剖面 | 次表层到表层的代表性的 | 剖面深度不等,需先插到表层 |
| 其它再分析海温 | 大尺度结构的一致性 | 同化体系不同,极值处差异最大 |
这些组合没有官方协议,因此「提升几个点」「偏差多少」这类说法必须附上时段、区域与匹配方法才成立。
§8 生态与版本:它为什么还能被用下去
§8.1 版本时间线
| 时间 | 事件 | 证据 |
|---|---|---|
| 1981-09-01 | 序列起点 | 目录首个日文件 |
| 2007 | Reynolds 等给出逐日 0.25° 的方法 | J. Climate 20:5473 |
| 2013-09 | CDR 算法文档 Rev.2 定稿 | 算法基线文档 Rev.2 |
| 2016-01-01 | v2.1 的算法改进生效(现场观测、Argo、卫星、冰转换) | 产品页与 DOI 元数据 |
| 2017-11 | 全序列由 NetCDF-3 转 NetCDF-4 | 文件 comment 属性 |
| 2020-03 | v2.1 发布 | DOI 元数据页 |
| 2020-04-01 | v2.1 顶替 v2 | 产品页 |
| 2020-04-26 | v2 停止生产 | 产品页 |
| 2021-10-01 | v2.1a:卫星输入换 ACSPO | 产品页 Update History |
| 2022-12-05 | 1° 周平均 OI.v2 在 NCEP 侧停止 | 产品 FAQ |
| 2023-04-01 | v2.1b:船测偏差订正改为逐月时变 | 产品页 Update History |
时间线里最值得注意的一格是 2017-11。它意味着「v2.1 覆盖 1981 年至今」这句话,在字节层面是 2017 年之后才成立的——在此之前,早年的文件是另一种容器。做字节级校验(比如按 md5 比对历史版本)的人,会在这里撞上整批不一致。
§8.2 它在生态中的位置
OISST 处在一个很特殊的位置:它既是研究的原料,又是业务的基础设施。
在业务侧,海洋热浪监测、珊瑚白化预警、渔业管理、海洋牧场保险的触发条件,很多都以它为判定依据。在科研侧,它是最常被拿来当「参照场」的那一个——新出的海温产品几乎都要与它比一遍。这种双重身份带来一个副作用:当它自己出错时,错误会同时出现在判定和验证两端。
§8.3 谁在依赖它
- 业务层:海洋热浪与白化监测的阈值判定,多数以逐日距平为基础
- 科研层:海温产品互比、模式评估、海气通量估算的标准参照
- 工程层:区域海洋模式的边界条件、卫星产品的融合背景场
- 教学层:海洋数据处理的入门样例——文件小、变量少、格式标准
§8.4 它为什么能持续
一套 1981 年起步、分辨率只有二十几公里、每天只有一个时刻的产品,为什么没被高分辨率产品取代?答案有三层。
第一层是长度不可复制。四十五年、逐日、无缺日的海温序列,全世界只有个位数。任何新产品想积累到同样的长度,只能等。
第二层是口径稳定。变量名、单位、网格、填充值四十年没变过(容器变过一次,语义没变)。这意味着二十年前写的读取脚本今天还能跑。对基础设施而言,这是最高的美德。
第三层是维护是活的。它不是一份冻结的存档:v2.1a、v2.1b 说明这条链还在被修,产品页的更新历史还在增长。冻结的数据会过时,活着的链会持续输出。
三层叠加的结果是:没有人把它当最终答案,但也没有人能绕开它做时间序列。
§8.5 引用与许可
许可这件事,官方给了三份互不一致的材料。
CDR 使用协议 PDF 是最接近许可声明的一份,原文写「the CDR data sets are non-proprietary, publicly available, and no restrictions are placed upon their use」。同一份文件还写明:生产软件可能不属于政府所有,不在开放数据政策范围内。
ISO 19115 元数据里,accessConstraints 与 useConstraints 两条都是 otherRestrictions,otherConstraints 写的是「Cite as: …」。这是机器可读层面唯一能查到的约束,含义是「另有限制,须按指定方式引用」。
数据文件本身没有任何 license 属性。CF-1.6 与 ACDD-1.3 的约定里没有这一项,所以文件层面查不到。
三份材料合起来,能写出的最准确表述是:公有领域性质,无使用限制,但要求按指定格式引用;官方未给 SPDX 标识。 写成 CC0 或 CC BY 都是替官方做主,写成「保留所有权利」则是反向的错。
还有一处细节值得记下:那份使用协议 PDF 至今仍写着 NCDC(2015 年已并入 NCEI)、仍引用 v2 的 DOI 10.7289/V5SQ8XB5、仍把 Reynolds 列为数据作者。它是 v2 时代的文本,没有随 v2.1 更新。真正该引的是 10.25921/RE9P-PT57。
§9 使用指引:三条路径,一份最小实现
§9.1 三种角色,三条路
| 你的角色 | 建议路径 | 关键动作 |
|---|---|---|
| 想快速验证一个时序模型 | 取一个海区、十年的日文件,按年拼 | 先跑通形状与缺测校验,再谈模型 |
| 要做海洋热浪或异常判定 | 用 sst 自建气候态,或用 anom 但统一基准 | 阈值法之前先定基准期,见 §4.4 |
| 要做全球尺度的预训练 | 走 Zarr,按块随机采样 | 按块而非按日切分,避免相邻日泄漏 |
§9.2 在 AI 流水线中的位置
卫星 L2/L3 海温 + 浮标 + 船舶 + Argo
↓ 偏差订正、最优插值、冰缘代理
OISST 日文件(0.25°,一天一张) ← 训练目标、验证参照、业务阈值
↓ 聚合到区域 / 时间窗
热浪指数、白化风险、渔场环境特征、模式边界条件
这条链说明一个常见误用:把 OISST 当「真值」去训练一个超分辨率模型,然后用同一份 OISST 的高分辨率版本验证。两边同源,验证的是插值一致性,不是分辨率提升。
§9.3 一份最小可跑的实现
下面这段不依赖 xarray,只用 h5py 与 numpy,把「读文件—去填充—打包成样本」这条链完全摊开。
#-- 依赖:h5py、numpy、torch;输入为若干日文件路径
import h5py
import numpy as np
import torch
from torch.utils.data import Dataset
class OISSTWindows(Dataset):
def __init__(self, paths, win=7, size=32):
self.paths, self.win, self.size = paths, win, size
def __len__(self):
return len(self.paths) - self.win
def _read(self, path):
with h5py.File(path, "r") as f:
raw = f["sst"][0, 0].astype("float32")
sst = raw * 0.01
sst[raw == -999] = np.nan
return sst
def __getitem__(self, i):
cube = np.stack([self._read(p) for p in self.paths[i:i + self.win]])
y, x = 300, 800
patch = cube[:, y:y + self.size, x:x + self.size]
patch = np.nan_to_num(patch, nan=0.0)
return torch.from_numpy(patch), torch.from_numpy(patch[-1])
ds = OISSTWindows(sorted(__import__("glob").glob("oisst-avhrr-v02r01.2025*.nc")))
x, y = ds[0]
print(x.shape, y.shape, float(y.mean()))
#-- 期望:x 为 (7, 32, 32),y 为 (32, 32)
#-- 期望:均值在 10 到 30 之间,取决于取的是哪片海
注意 paths 必须按时间排序。这个 Dataset 的样本是「连续七天的一小片海」,打乱文件顺序会得到七段互不相邻的日期拼成的立方体,训练仍能收敛,但学到的不是时序。
§9.4 评估时最少要报的三件事
| 要报的 | 为什么 | 怎么报 |
|---|---|---|
| 时间划分方式 | 逐日递归使样本相关,随机划分高估精度 | 写明「按年」「按块」还是「随机」,并给切点 |
| 距平基准期 | 1971–2000 与 1991–2020 不可直接比 | 写清用的是文件自带 anom 还是自建气候态 |
| 是否剔除高误差格 | err 大的格点插值占主导 |
给出阈值与剔除比例 |
§9.5 三种流水线的形态,与它们各自的错法
同一份数据,三种任务,三种切法。切错了,模型照样收敛,指标照样好看。
| 任务 | 数据形态 | 常见错法 | 该怎么做 |
|---|---|---|---|
| 单点海温时序预测 | 取一格、四十五年,成一维序列 | 随机抽点做训练与验证 | 按年切,留出完整年份做测试 |
| 区域场预测(如热浪识别) | 取一片海、若干年,成三维立方 | 逐帧随机切分 | 按时间块切,且预测窗口不得落在训练窗口之后 |
| 全球预训练与补缺 | Zarr 按块采样 | 按日文件顺序顺序读,块内样本高度相似 | 打乱块,但同一块内的相邻日不要分散到训练与测试 |
第二行和第三行的错法有个共同根源:滑动窗口让样本互相重叠。取七天窗口、步长一天,相邻两个样本有六天相同。把它们随机分到两边,等于让模型在测试集里认出了训练集的邻居。这条规律在任何时序数据上都成立,只是 OISST 的逐日递归让它更严重——不仅窗口重叠,连「不重叠的两段」之间也通过初猜相连。
§4.2 的案例在这一层给出的教训是:那支团队最初的验证方式是「随机抽 20% 的日子做测试」,精度高得反常;改成按年切之后,误差翻了一倍多。翻倍之后的数字才是他们能拿去和浮标对的数字。
§10 AI 使用声明
| 栏目 | 内容 |
|---|---|
| 性质 | 卫星与现场观测经最优插值得到的分析场,非纯观测 |
| 用途 | 海温时序建模、热浪与异常判定、产品互比、区域模式边界 |
| 不建议用途 | 近岸与养殖区尺度判定、逐小时应用、把日样本当独立样本、跨断点直接比趋势 |
| 伦理 | 无个人数据;下游若涉及渔业保险或灾害赔付,须披露空间代表性误差 |
| 引用 | DOI 10.25921/RE9P-PT57 + Huang et al. 2020 |
| 许可 | 公有领域性质、无使用限制、须按指定格式引用;无 SPDX |
| 版本 | 产品名 v2.1 与文件名 v02r01 需同时写出,并注明取数日期 |
| 状态 | 在线,逐日更新,终版延迟约 15 天 |
§11 常见误读与避坑清单
| 常见说法 | 实际情况 | 证据 |
|---|---|---|
| 「OISST 只有 v2 一个版本」 | 现役是 v2.1,v2 已于 2020-04-26 停产 | 产品页 Version 2.1 一节 |
| 「final 与 preliminary 是两个目录」 | 同一目录同一路径,靠文件名后缀区分,终版覆盖未定版 | access/ 下只有 avhrr |
| 「未定版会被立刻删除」 | 直连目录里被覆盖,AWS 镜像里两份并存数日 | S3 前缀下同日期两个对象 |
| 「许可是 CC0」 | 使用协议写 no restrictions,但无 SPDX,ISO 标 otherRestrictions | Use Agreement PDF 与 ISO 字段 |
| 「距平是对 1991–2020 算的」 | 基准期是 1971–2000 | 文件 references 属性 |
| 「sst 单位是 degC」 | 属性写作 Celsius,按 degC 匹配会失败 | 变量 units 属性 |
| 「纬度从 -90 开始」 | 格心从 -89.875 开始,-90 是格边 | 坐标变量与全局属性不同源 |
| 「四个变量是四种观测」 | anom 是 sst 减气候态,err 是自评误差,ice 是冰情 | 变量 long_name |
| 「每天的场是独立算的」 | 当天分析以上一天的分析为初猜 | C-算法文档 §1.2 与 §3.4 |
| 「0.25° 在全球一样大」 | 赤道约 27.8 公里,60°N 约 13.9 公里 | 经纬网格几何 |
| 「v2.1 是全序列统一重算的」 | 三处改动只前向生效,历史未重算 | 产品页 Update History |
| 「单文件 1.7 MB,×16,500 就是总量」 | 实际 27.43 GB,线性外推高估约 3% | 逐月索引求和 |
| 「有值就代表测到了」 | 冰缘带 sst 由冰密集度反推 | 产品页与 C-算法文档 §3.3 |
附录 A 速查卡
| 键 | 值 |
|---|---|
| 产品名 | OISST v2.1(AVHRR-only,0.25° 逐日) |
| 文件命名 | oisst-avhrr-v02r01.YYYYMMDD[_preliminary].nc |
| 直连目录 | …/v2.1/access/avhrr/YYYYMM/ |
| S3 前缀 | data/v2.1/avhrr/YYYYMM/、data/v2.1/avhrr-zarr/YYYY/MM/ |
| DOI | 10.25921/RE9P-PT57 |
| 元数据号 | gov.noaa.ncdc:C01606、NCEI DSI 3650_02 |
| 变量 | sst、anom、err、ice |
| 打包 | int16,scale_factor 0.01,填充 -999 |
| 网格 | 720×1440,格心 -89.875–89.875 / 0.125–359.875 |
| 时间 | 逐日,UTC 正午,纪元 1978-01-01 12:00 |
| 延迟 | 未定版 1 天,终版约 15 天 |
| 许可 | 公有领域性质,须引用,无 SPDX |
附录 B 变量与属性表
| 属性 | sst | anom | err | ice |
|---|---|---|---|---|
long_name |
Daily sea surface temperature | Daily sea surface temperature anomalies | Estimated error standard deviation of analysed_sst | Sea ice concentration |
units |
Celsius | Celsius | Celsius | % |
valid_min |
-300 | -1200 | 0 | 0 |
valid_max |
4500 | 1200 | 1000 | 100 |
scale_factor |
0.01 | 0.01 | 0.01 | 0.01 |
_FillValue |
-999 | -999 | -999 | -999 |
附录 C 术语表
| 术语 | 含义 |
|---|---|
| OI | 最优插值,按误差协方差给观测加权并填补空缺的统计方法 |
| AVHRR | 甚高分辨率辐射计,红外通道反演海温,1981 年起的核心输入 |
| DOISST | 逐日最优插值海温的另一种缩写,与 OISST 同指 |
| 超级观测 | 落在同一网格内的多个像元先平均成一个观测,再进 OI |
| EOT | 经验正交函数,用于把卫星与现场观测的偏差分解到空间模态上 |
| 距平 | 当日海温减气候态,本文基准期为 1971–2000 |
| 气候态 | 多年平均的参考场,决定距平的零点 |
| 冰缘带 | 海冰边缘的开阔水域,观测稀缺,用代理海温填充 |
| 代理海温 | 由海冰密集度换算出的海温,不是直接测量 |
| CDR | 气候数据记录,NOAA 对长序列、可持续产品的正式认定 |
| C-算法文档 | 气候算法理论基线文档,描述算法原理与参数 |
| 掩膜 | 标出哪些格点有效、哪些是陆地或无结果的布尔场 |
| 格心 / 格边 | 网格单元的中心点与边界,二者差半格 |
附录 D 常见问题
Q1:这份数据要注册才能下吗?
不用。直连目录、S3 与 GCS 都允许匿名列举与下载,没有令牌、没有点击同意的环节。CDR 页有一个可选的用户登记表,用于接收产品更新通知,不登记不影响取数。
Q2:为什么最早的文件比最新的文件还大?
压缩率不同。早期分析场里有效海域相对少、陆地掩膜占比高,压缩后的体积反而偏大;后期观测更密、场更平滑,压缩率更高。极差约 18%,做容量规划用中位数。
Q3:能不能只用 anom,不用 sst?
可以,但要写清基准期。anom 是相对 1971–2000 的距平,如果你的阈值来自另一套基准的产品,两者不可直接比。跨产品比较时,最稳的做法是各自取 sst、自建同一段气候态。
Q4:为什么有些格点的 sst 是 -3 度?
可能是真的接近冰点,也可能是被 valid_min 钳住了。区分办法是看同一格的 ice:冰密集度大于零时,sst 通常由冰情反推,接近冰点是正常的。
Q5:S3 上的 Zarr 和 .nc 是两份数据吗?
同一份数据的两种组织方式。Zarr 按块切分,适合随机读;.nc 是一天一个完整文件,适合顺序读与归档。两者内容应当一致,但 Zarr 只在 AWS 那份镜像里有。
Q6:为什么我的时间轴从 1978 年开始?
因为 time 的单位是 days since 1978-01-01 12:00:00。1978 是网格模板的纪元常数,不是数据起点。数据起点是 1981-09-01。
Q7:能拿它算全球平均海温吗?
能,但必须乘纬度余弦做面积加权。1,036,800 个格点在高纬代表的面积远小于赤道,直接求平均会系统性高估高纬权重。
Q8:这算不算「AI-ready」?
按本文的口径算:格式标准、变量显式、许可清晰、可匿名获取、有云端 Zarr 版本。它缺的是划分与泄漏提示,那两项要使用者自己补。
编辑说明
核验时间 2026-10-29。方法:逐层列举 NCEI 直连目录(顶层 542 个月目录的索引、以及 542 个月目录内的文件清单),按 Size 列求和得到总体积与单文件中位、极值,并用日期区间天数交叉验证文件数;下载 2026-10-13 终版与 2026-10-27 未定版两个文件,用 h5py 读取全部全局属性、变量属性、形状与压缩设置,取得打包系数、属性域、填充值、坐标步长与 title 截断长度;读取产品页、CDR 页、DOI 解析后的元数据页与 ISO 19115 XML,取版本沿革、子版本时间、气候态基准、约束字段与引用格式;读取 CDR 使用协议 PDF、C-算法文档(Rev.2, 2013)与成熟度矩阵 PDF,取许可原文、OI 参数、传感器假设与文档版本;列举 S3 与 GCS 的公开 bucket 前缀,确认镜像结构、Zarr 的存在范围与未定版并存现象。未做任何分布统计,因此全文没有海温均值、极值频率或区域偏差数字;未下载全量 27 GB,总体积来自目录索引求和而非下载计量;THREDDS 与 ERDDAP 两条通道在本环境未逐一验证内容,正文只按官方 CDR 页的入口标注陈述,未延伸描述其目录结构。文中「赤道 27.8 公里」等尺度数字为按 0.25° 与地球半径换算,非官方表述。成熟度矩阵 PDF 为单页空白模板(页眉标注 2013-07-11),未含本产品的评级,故正文未引用其结论。
相关数据集导航
以下为站内 AI-Ready 数据集百科中与本词条共享多个主题标签的相关数据集,按相关度降序排列:
- era5-single-levels — 共享标签:公共卫生与流行病学 / 时序数据 / 全球健康 / 疾病监测
- ibtracs — 共享标签:公共卫生与流行病学 / 时序数据 / 全球健康 / 疾病监测
- argo-gdac-floats — 共享标签:公共卫生与流行病学 / 时序数据 / 全球健康 / 疾病监测
- our-world-in-data — 共享标签:公共卫生与流行病学 / 时序数据 / 全球健康
- ndbc-buoy — 共享标签:公共卫生与流行病学 / 时序数据 / 疾病监测
- noaa-cors-gnss — 共享标签:公共卫生与流行病学 / 时序数据 / 疾病监测
- amrnet — 共享标签:公共卫生与流行病学 / 全球健康 / 疾病监测
- wpp — 共享标签:公共卫生与流行病学 / 时序数据 / 全球健康
- ghcn-daily — 共享标签:公共卫生与流行病学 / 时序数据 / 全球健康
- usgs-comcat — 共享标签:公共卫生与流行病学 / 时序数据 / 全球健康
导航说明:本章节由全站统一标签体系自动计算生成(标签重合度算法),双向可达;点击链接可跳转至对应数据集词条。

