Climate station data homogenization implementing the González-Rouco et al. (2001) six-step procedure with six pluggable breakpoint tests
Project description
rucola
Climate station data homogenization implementing the six-step procedure from González-Rouco et al. (2001), with six pluggable breakpoint tests.
Beta: rucola is under active development. The API may change between minor versions before a stable 1.0 release.
Overview
Long climate records from ground stations are frequently affected by non-climatic discontinuities — station relocations, instrument replacements, changes in observation practice. rucola detects and corrects these breakpoints using an iterative reference-station approach:
- Build a normalized Q-series for each candidate station relative to its neighbors
- Apply one or more statistical breakpoint tests to the Q-series
- Correct detected breaks and refine the reference pool across six steps
Six tests are available: SNHT (Alexandersson 1986), Buishand range (Buishand 1982), Pettitt (Pettitt 1979), Worsley likelihood ratio (Worsley 1979), Easterling–Peterson two-phase regression (Easterling & Peterson 1995), and STARS sequential regime-shift test (Rodionov 2004). Tests can be run individually or in consensus combinations.
Both ratio (multiplicative, for precipitation) and difference (additive, for temperature) correction modes are supported.
Installation
pip install rucola # core (Polars + CSV)
pip install rucola[duckdb] # with DuckDB support
Quick start
import rucola
r = rucola.Rucola.from_csv("values.csv", "stations.csv")
# Run the six-step procedure
detection = r.run(rucola.RunConfig(mode="ratio"))
# Apply corrections
result = detection.normalize()
print(result.summary)
print(result.corrections)
Input format
| Table | Required columns |
|---|---|
stations |
station_id, latitude, longitude |
values |
station_id, date, value, parameter |
Values must be at annual resolution and pre-filtered to a single parameter.
Loaders
# from Polars DataFrames
rucola.Rucola.from_polars(values_df, stations_df)
# from CSV files
rucola.Rucola.from_csv("values.csv", "stations.csv")
# from a DuckDB file (requires: pip install rucola[duckdb])
rucola.Rucola.from_duckdb("climate.duckdb")
Multiple tests with consensus detection
detection = r.run(
rucola.RunConfig(
tests=[
rucola.SNHTTest(),
rucola.BuishandTest(),
rucola.PettittTest(),
rucola.StarsTest(l=10), # sequential regime-shift test
],
mode="ratio",
)
)
Normalization options
from rucola import NormalizationConfig
result = detection.normalize(
NormalizationConfig(
consensus="majority", # require >50 % of tests to agree
tiebreak="strongest_signal",
break_window_years=3,
min_correction_magnitude=0.02,
min_relative_signal=1.2, # require signal 1.2× the critical value
min_years_from_end=5, # reject edge-effect artefacts (Hawkins 1977)
)
)
Break predicates
Use composable predicates to filter which detected breaks are applied. Combine them with &, |, and ~:
from rucola import (
NormalizationConfig,
YearBetween, StationIn, StepIn,
MagnitudeAbove, SignalAbove,
NSignificantAbove, NeighborCountAbove,
)
# Trusted year window + minimum correction size
result = detection.normalize(
NormalizationConfig(
predicate=YearBetween(min=1960, max=2010) & MagnitudeAbove(threshold=0.05)
)
)
# Only correct a specific set of stations
result = detection.normalize(
NormalizationConfig(predicate=StationIn({"S1", "S3", "S7"}))
)
# Require strong evidence: signal 1.5× critical value, at least 3 tests agree,
# detected from a solid reference pool, skip early unreliable steps
result = detection.normalize(
NormalizationConfig(
predicate=SignalAbove(1.5) & NSignificantAbove(3)
& NeighborCountAbove(4) & ~StepIn({1, 2})
)
)
Predicates are fully serializable via to_dict() / BreakPredicate.from_dict().
Saving and loading results
detection.to_json("detection.json")
result.to_json("result.json")
detection = rucola.DetectionResult.from_json("detection.json")
result = rucola.HomogenizationResult.from_json("result.json")
Development
git clone https://github.com/earthobservations/rucola
cd rucola
uv sync --all-groups
# code quality
poe format # auto-format with ruff
poe lint # check formatting and linting
poe type # type-check with ty
# testing
poe test # fast unit + constructor tests (default)
poe test-unit # unit tests only, verbose
poe test-slow # full pipeline tests, verbose
poe test-integration # DWD integration tests (requires network)
poe test-all # everything, verbose
poe coverage # run tests and generate coverage.xml for Codecov
# docs
poe docs-serve # live preview at localhost:8000
poe docs-build # build static site
# security / hygiene
poe audit # scan dependencies for vulnerabilities
poe deptry # check for unused/missing dependencies
poe zizmor # audit GitHub Actions workflows
# all-in-one
poe check # lint + type + audit + test
References
- González-Rouco et al. (2001), J. Climate 14(5):964–978. doi:10.1175/1520-0442(2001)014<0964:QCAHOP>2.0.CO;2
- Hawkins (1977), Biometrika 64(2):279–288. doi:10.1093/biomet/64.2.279
- Alexandersson (1986), Int. J. Climatol. 6(6):661–675. doi:10.1002/joc.3370060607
- Alexandersson & Moberg (1997), Int. J. Climatol. 17(1):25–34. doi:10.1002/(SICI)1097-0088(199701)17:1<25::AID-JOC103>3.0.CO;2-J
- Buishand (1982), J. Hydrol. 58(1–2):11–29. doi:10.1016/0022-1694(82)90066-X
- Pettitt (1979), Appl. Stat. 28(2):126–135. doi:10.2307/2346729
- Worsley (1979), J. Amer. Statist. Assoc. 74(366):365–367. doi:10.1080/01621459.1979.10482519
- Easterling & Peterson (1995), Int. J. Climatol. 15(4):369–377. doi:10.1002/joc.3370150403
- Rodionov (2004), Geophys. Res. Lett. 31, L09204. doi:10.1029/2004GL019448
Authors
rucola was created by Benjamin Gutzmann, with the majority of the implementation written by Claude (Anthropic).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rucola-0.1.0.tar.gz.
File metadata
- Download URL: rucola-0.1.0.tar.gz
- Upload date:
- Size: 34.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
493ff84a7969c8020340980475daec687741db27d8044aaa9e111098e1fe8756
|
|
| MD5 |
5125b3568151932403eb4b84884a2b24
|
|
| BLAKE2b-256 |
337149dc8d7686d5e884de920e702cce5b3c4c6f5abf091f82e63260e09aa7ad
|
File details
Details for the file rucola-0.1.0-py3-none-any.whl.
File metadata
- Download URL: rucola-0.1.0-py3-none-any.whl
- Upload date:
- Size: 36.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bff04bcb967ef148693beb1cee786f3d3de69d3933662571f774b25796656587
|
|
| MD5 |
8b03265b7f1e83ede0af8f7a29983573
|
|
| BLAKE2b-256 |
3b05f83875f0ea445c82288d18f95920f6c893500c26594b2200a3b573ec680b
|