Getting Started¶
Input data¶
rucola expects two DataFrames (or CSV files / DuckDB tables):
| Table | Required columns |
|---|---|
stations |
station_id, latitude, longitude |
values |
station_id, date, value, parameter |
Values must be pre-aggregated to annual resolution and pre-filtered to a single parameter before passing to Rucola.
Loading data¶
From Polars DataFrames¶
import polars as pl
import rucola
values = pl.read_parquet("values.parquet").filter(
pl.col("parameter") == "precipitation_height"
)
stations = pl.read_parquet("stations.parquet")
r = rucola.Rucola.from_polars(values, stations)
From CSV files¶
From a DuckDB file¶
Requires pip install rucola[duckdb].
Running the procedure¶
from rucola import RunConfig
# Precipitation — multiplicative ratio correction
detection = r.run(RunConfig(mode="ratio"))
# Temperature — additive difference correction
detection = r.run(RunConfig(mode="difference"))
run() returns a DetectionResult containing raw breakpoint data for every station across all six steps.
Applying corrections¶
result = detection.normalize()
# Summary table: one row per station
print(result.summary)
# All applied corrections
print(result.corrections)
# Corrected annual series for one station
sid = next(iter(result.station_results))
print(result.station_results[sid].annual_corrected)
Customising tests¶
By default only the SNHT is used. Pass multiple tests for consensus detection:
detection = r.run(
RunConfig(
tests=[
rucola.SNHTTest(),
rucola.BuishandTest(),
rucola.PettittTest(),
],
mode="ratio",
)
)
Normalisation 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,
)
)
Break predicates¶
Predicates let you filter which detected breaks are actually applied during normalization. Combine them with & (AND), | (OR), and ~ (NOT):
from rucola import (
NormalizationConfig,
YearBetween, StationIn, StepIn,
MagnitudeAbove, SignalAbove,
NSignificantAbove, NeighborCountAbove,
)
# Only apply corrections within a historical window
result = detection.normalize(
NormalizationConfig(predicate=YearBetween(min=1960, max=2010))
)
# Require strong statistical evidence and a minimum correction size
result = detection.normalize(
NormalizationConfig(
predicate=SignalAbove(1.5) & MagnitudeAbove(threshold=0.05)
)
)
# At least 3 tests agree and the reference pool had enough stations
result = detection.normalize(
NormalizationConfig(
predicate=NSignificantAbove(3) & NeighborCountAbove(4)
)
)
# Restrict to a known-problematic subset of stations
result = detection.normalize(
NormalizationConfig(predicate=StationIn({"S1", "S3"}))
)
See the normalization guide for a full explanation of all predicates and how they interact with consensus, tiebreak, and overrides.
Saving and loading results¶
# Save
detection.to_json("detection.json")
result.to_json("result.json")
# Load
detection = rucola.DetectionResult.from_json("detection.json")
result = rucola.HomogenizationResult.from_json("result.json")