Skip to main content

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).

Metadata

Release files for rucola 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rucola 0.1.0
File Size Uploaded
rucola-0.1.0.tar.gz 34.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for rucola 0.1.0
File Interpreter ABI Platform
rucola-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 71.3 kB

Release files / rucola-0.1.0.tar.gz

Download URL rucola-0.1.0.tar.gz
Size 34.4 kB
Tags Source
SHA-256 checksum
How to use checksums
493ff84a7969c8020340980475daec687741db27d8044aaa9e111098e1fe8756
BLAKE2b-256 checksum
How to use checksums
337149dc8d7686d5e884de920e702cce5b3c4c6f5abf091f82e63260e09aa7ad
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / rucola-0.1.0-py3-none-any.whl

Download URL rucola-0.1.0-py3-none-any.whl
Size 36.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bff04bcb967ef148693beb1cee786f3d3de69d3933662571f774b25796656587
BLAKE2b-256 checksum
How to use checksums
3b05f83875f0ea445c82288d18f95920f6c893500c26594b2200a3b573ec680b
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page