Skip to content

Time-series analysis

egms_client.analysis

What the time series say beyond their mean velocity. Needs the tables extra.

Every EGMS CSV comes with one model fitted to each series over the whole release: mean_velocity, acceleration, seasonality (the annual amplitude) and rmse_ts. That model is::

displacement(t) = a + v t + acceleration / 2 * t**2 + A cos(2 pi t) + B sin(2 pi t)

with t in years from the middle of the window. :func:fit is that same model, and over the full window it gives back the archive's columns (the tests check it on real CSVs). What it adds:

  • any window: fit(frame, start=date(2024, 11, 1)) is the velocity of the last year, where the archive only gives five years at once;
  • the seasonal phase: the day of the year the seasonal cycle peaks.

Two things the archive's columns cannot show:

  • :func:trend_change: a velocity that changes at one date, as on a slope that starts to creep. One acceleration over five years averages it away.
  • :func:unwrapping_jumps: steps of half the radar wavelength (about 27.7 mm along the line of sight) between two acquisitions. They come from the processing (phase unwrapping), not from the ground. Only meaningful on L2A / L2B, whose series are along the line of sight.

Every function takes a table of :func:egms_client.tables.read_csv (one row per point, one column per date) and returns one row per point, in the same order, with its pid. Points with a missing value in the window get NaN. Points are processed in blocks, so a whole L3 tile (~170,000 cells) fits in memory.

Least squares treats the residuals as independent, which InSAR residuals are not: read the statistics as rankings, not as exact probabilities.

fit

fit(frame: Any, *, start: date | None = None, end: date | None = None) -> Any

The archive's model fitted to each series, over start to end (inclusive).

Columns: pid, velocity (mm/year), acceleration (mm/year²), seasonal_amplitude (mm), seasonal_peak_doy (the day of the year the seasonal cycle peaks, 1-366), rmse (mm), dates (how many were used). Over the full window, velocity, acceleration, seasonal_amplitude and rmse are the archive's mean_velocity, acceleration, seasonality and rmse_ts.

The acceleration needs two years or more to mean something, and the season needs one full year: shorter windows raise ValueError.

trend_change

trend_change(frame: Any, *, min_segment_days: int = 365, step_days: int = 30, start: date | None = None, end: date | None = None) -> Any

The date where each series changes velocity, if it does.

A continuous two-slope line (with the seasonal cycle) is fitted for every candidate date, one every step_days, at least min_segment_days from either end. The best one is kept.

Columns: pid, breakpoint (the date), velocity_before and velocity_after (mm/year), change (after minus before), delta_bic (how much better two slopes fit than one; above 10 is strong evidence, and below 0 one slope is enough), rmse (mm).

unwrapping_jumps

unwrapping_jumps(frame: Any, *, tolerance_mm: float = 5.0, window: int = 5, start: date | None = None, end: date | None = None) -> Any

Steps of a multiple of half a wavelength between two acquisitions.

A step counts when the difference between two consecutive dates is within tolerance_mm of k times 27.7 mm (k ≥ 1), and the series stays there: the median of the window dates after it, minus the median of the window dates before, is the same step. A single outlier, which goes up and comes back, is not a jump. Meant for L2A / L2B.

Columns: pid, jumps (how many), first_jump (its date, or missing), total_step_mm (their sum, signed).