CO₂ concentration at Mauna Loa (the Keeling Curve)

15
Updated:
Files:5
Size:40.9 kB
Formats:csv
License:PDDL-1.0

Atmospheric carbon dioxide concentration from NOAA GML — the Mauna Loa record (the longest continuous direct measurement of atmospheric CO₂, begun by C. David Keeling in March 1958 and maintained by NOAA and Scripps): monthly means from 1958 and annual means from 1959, in parts per million (ppm) of dry air. Also the NOAA global marine-boundary-layer annual mean (from 1979), and NOAA's published annual growth rates (ppm/yr, Jan 1 to Dec 31) for both Mauna Loa and the global network, with a derived decadal-mean-growth table.

API Access

Access dataset files directly from scripts, code, or AI agents.

Browse dataset files
Dataset Files

Each file has a stable URL (r-link) that you can use directly in scripts, apps, or AI agents. These URLs are permanent and safe to hardcode.

/datapressr/co2-ppm/
https://datahub.io/datapressr/co2-ppm/_r/-/README.md
https://datahub.io/datapressr/co2-ppm/_r/-/data/co2-annual-global.csv
https://datahub.io/datapressr/co2-ppm/_r/-/data/co2-annual-mlo.csv
https://datahub.io/datapressr/co2-ppm/_r/-/data/co2-growth-annual.csv
https://datahub.io/datapressr/co2-ppm/_r/-/data/co2-growth-decadal.csv
https://datahub.io/datapressr/co2-ppm/_r/-/data/co2-monthly-mlo.csv
https://datahub.io/datapressr/co2-ppm/_r/-/datapackage.json
Key Files

Start with these files — they give you everything you need to understand and access the dataset.

datapackage.json— metadata & schema
https://datahub.io/datapressr/co2-ppm/_r/-/datapackage.json
README.md— documentation
https://datahub.io/datapressr/co2-ppm/_r/-/README.md
Typical Usage
  1. 1. Fetch datapackage.json to inspect schema and resources
  2. 2. Download data resources listed in datapackage.json
  3. 3. Read README.md for full context

Data Views

The Keeling Curve — annual mean CO₂ at Mauna Loa

Monthly CO₂ and its deseasonalized trend

CO₂ is rising faster each decade (mean growth rate, ppm/yr)

Mean of NOAA annual growth rates per decade, Mauna Loa and global. The 1950s is a single year (1959) and the 2020s is partial; n_years in the resource gives the count.

Data Files

Explore with AI

Monthly mean CO₂ at Mauna Loa

Annual mean CO₂ at Mauna Loa

Global annual mean CO₂

Annual CO₂ growth rate (Mauna Loa and global)

Mean annual CO₂ growth rate by decade (derived)


About this dataset

The longest continuous record of atmospheric carbon dioxide — the Mauna Loa series begun by C. David Keeling in March 1958 — plus the NOAA global mean and NOAA's published growth rates, in parts per million (ppm) of dry air.

  • data/co2-monthly-mlo.csv — Mauna Loa monthly mean (821 rows, 1958-03 → 2026-07)
  • data/co2-annual-mlo.csv — Mauna Loa annual mean (67 rows, 1959 → 2025)
  • data/co2-annual-global.csv — global marine-boundary-layer annual mean (47 rows, 1979 → 2025)
  • data/co2-growth-annual.csv — annual growth rate (ppm/yr, 1 Jan → 31 Dec), Mauna Loa and global, as published by NOAA (67 rows, 1959 → 2025)
  • data/co2-growth-decadal.csv — mean annual growth per decade, derived from the annual growth series (8 rows)
  • build.ts — reproduces all five from the archived NOAA source. Run: node build.ts. The build script and raw snapshot live in the DataPressr repository, not on DataHub.
  • SUMMARY.md — descriptive statistics per resource + what stands out. Stats block regenerated by node enrich.ts.

Stories using this data

  • The Keeling Curve: carbon dioxide at Mauna Loa went from 316 ppm in 1959 to 427 ppm in 2025, the longest continuous direct record of atmospheric CO₂ there is.

Source & licence

NOAA Global Monitoring Laboratory, Trends in Atmospheric Carbon Dioxide (gml.noaa.gov/ccgg/trends/data.html). Files co2_mm_mlo.csv and co2_annmean_mlo.csv retrieved 2026-08-30; co2_gr_mlo.csv, co2_annmean_gl.csv and co2_gr_gl.csv retrieved 2026-09-05 (snapshots in archive/).

NOAA GML data are a US Government work, made freely available to the public. The Mauna Loa record is a joint NOAA / Scripps effort begun by C. David Keeling. Released here as PDDL-1.0 with citation requested:

Xin Lan, Pieter Tans and Kirk W. Thoning, Trends in globally-averaged CO₂,
NOAA GML. And: C. D. Keeling et al., Scripps Institution of Oceanography.

Missing values

NOAA uses negative sentinels for "no information": -1 (num_days), -9.99 (std_dev), -0.99 (monthly uncertainty). These are all normalised to empty cells. They occur for every month before May 1974 (that stretch comes from Scripps, which didn't record daily-count statistics) and for interpolated missing months. The co2_ppm value itself is never missing — NOAA interpolates gaps.

Relation to core/co2-ppm

DataHub also has core/co2-ppm, git-synced from datasets/co2-ppm and auto-updated from the same NOAA files. In short, what differs:

  • Resource layout and names. Core has co2-mm-mlo, co2-annmean-mlo, co2-gr-mlo, co2-mm-gl, co2-annmean-gl and co2-gr-gl with Title Case column names (Decimal Date, Average); this dataset has co2-monthly-mlo, co2-annual-mlo, co2-annual-global, co2-growth-annual (Mauna Loa and global side by side) and a derived co2-growth-decadal, with snake_case columns named for NOAA's current meanings. It does not carry the global monthly series.
  • Core's monthly columns are mislabelled. As of 2026-10-09 core's co2-mm-mlo.csv still has a 6-name header over 7-value rows, so its Trend column holds the day count and Number of Days holds the standard deviation (details below). This dataset's build asserts the NOAA header and fails on a change instead.
  • Missing values. NOAA's negative sentinels (-1, -9.99, -0.99) are empty cells here; core keeps them.
  • Snapshot vs living. This is a dated snapshot (NOAA files retrieved 2026-08-30 and 2026-09-05); core updates automatically.

Both use LF line endings.

Comparison detail

Issue #8 called for diffing this against the existing human-made version. What that turned up:

  • The community dataset's monthly file is currently mis-labelled. It is kept up to date by scripts/process.sh + a GitHub Action, but NOAA restructured co2_mm_mlo.csv — it added sdev and unc columns and renamed interpolated to deseasonalized. The shell script wasn't updated, so the published header still reads Date,Decimal Date,Average,Interpolated,Trend,Number of Days (6 names) while every data row now has 7 values. The upshot: their Trend column actually contains ndays, and their Number of Days column actually contains sdev. The declared schema no longer matches the data, and nothing flagged it because there's no check of the data against the schema.
  • This build guards against exactly that. build.ts asserts the NOAA header it expects and throws a clear error if the shape changes, rather than silently emitting shifted columns. The column names here (co2_ppm, co2_ppm_deseasonalized, num_days, std_dev, uncertainty) track NOAA's current meanings, not a 2015-era layout.
  • Scope now overlaps closely. As of 2026-09-05 this dataset also carries the global annual mean and NOAA's annual growth rates (Mauna Loa and global), plus a derived decadal-mean-growth table. It still omits the global monthly series (co2_mm_gl.csv), which is an easy further follow-up. Where the community dataset differences annual means to get a growth rate, this one carries NOAA's own published growth figures (computed from monthly data, Jan 1 → Dec 31).
  • Where they're better: it's genuinely monitored (auto-updating) and has polished views. This one is a point-in-time snapshot with a reproducible build; wiring it to a schedule is the monitor skill's job (#6), not done here.

What the structure skill made easy vs awkward

  • Easy: the missing-value normalisation idiom (one num(raw, sentinels) helper applied per column), the deterministic re-run check, the typed-schema discipline that made the upstream drift obvious the moment the columns were named.
  • Awkward: nothing in the skill covers comment/preamble lines (40 of them here, #-prefixed) — obvious to handle, but it's a near-universal shape for government text data and deserves a line in the playbook. Same for "source ships negative sentinels rather than blanks", which is common enough to name explicitly.