Skip to main content

Climate station data homogenization implementing the González-Rouco et al. (2001) six-step procedure with six pluggable breakpoint tests

Project description

rucola

rucola

Climate station data homogenization implementing the six-step procedure from González-Rouco et al. (2001), with six pluggable breakpoint tests.

CI status Docs status Code coverage PyPI version PyPI status PyPI downloads/month Python versions License

Documentation

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:

  1. Build a normalized Q-series for each candidate station relative to its neighbors
  2. Apply one or more statistical breakpoint tests to the Q-series
  3. 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

Authors

rucola was created by Benjamin Gutzmann, with the majority of the implementation written by Claude (Anthropic).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rucola-0.1.0.tar.gz (34.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rucola-0.1.0-py3-none-any.whl (36.8 kB view details)

Uploaded Python 3

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

Hashes for rucola-0.1.0.tar.gz
Algorithm Hash digest
SHA256 493ff84a7969c8020340980475daec687741db27d8044aaa9e111098e1fe8756
MD5 5125b3568151932403eb4b84884a2b24
BLAKE2b-256 337149dc8d7686d5e884de920e702cce5b3c4c6f5abf091f82e63260e09aa7ad

See more details on using hashes here.

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

Hashes for rucola-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bff04bcb967ef148693beb1cee786f3d3de69d3933662571f774b25796656587
MD5 8b03265b7f1e83ede0af8f7a29983573
BLAKE2b-256 3b05f83875f0ea445c82288d18f95920f6c893500c26594b2200a3b573ec680b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page