Series summaries
A summary reduces a whole series to one number: its last value, its minimum, its mean. Summaries are pre-computed server-side, so asking for “the final loss of 500 runs” costs one cheap query rather than 500 series fetches.
Two places they appear
Section titled “Two places they appear”As columns of the runs table, when you want them alongside attributes:
client.fetch_runs(series_summaries=[SeriesSummary("loss", "last")])Or on their own, in bulk, when the runs table is not what you’re after:
frame = client.fetch_series_summaries(run_names, ["loss", "accuracy"], functions=["last", "min", "max"])The bulk form returns index (run, series), one float64 column per function requested anywhere in the
call (NaN for a series whose selector did not ask for it), plus last_step (a nullable UInt64, so steps
stay exact) and last_timestamp of that series’ most recent point:
last min max last_step last_timestamprun seriesrun-001 accuracy 0.9412 0.1030 0.9412 4999 2026-07-01 09:14:22.114 loss 0.0921 0.0921 2.4012 4999 2026-07-01 09:14:22.114frame["last"] # every run × series, one functionframe.xs("loss", level="series")The functions
Section titled “The functions”| Whole-series | FIRST, LAST, MIN, MAX, SUM, AVG, COUNT, STD |
| Of the first point’s group | FIRST_COUNT, FIRST_MIN, FIRST_MAX, FIRST_AVG, FIRST_STD |
| Of the last point’s group | LAST_COUNT, LAST_SUM, LAST_MIN, LAST_MAX, LAST_AVG, LAST_STD |
Plain strings work wherever a SummaryFunction does — "last" and SummaryFunction.LAST are the same thing.
All of them fold finite values only: a series that logged NaN/±inf keeps meaningful statistics
(COUNT counts its finite points), and the specials stay visible in the raw points and in
bucketed reads.
Different functions per series
Section titled “Different functions per series”functions= applies the same list to every series, which is the common case and the compact one. When
different series want different functions, that full mesh is wasteful — so pass a SeriesSummariesQuery,
which carries its own:
from metrana.query import SeriesSummariesQuery
client.fetch_series_summaries( run_names, [ SeriesSummariesQuery("loss", "last", "min"), SeriesSummariesQuery("throughput", "avg"), ],)Mixed lists are fine: bare names and SeriesQuery objects take the functions= default, while a
SeriesSummariesQuery overrides it for itself.
Filtering and ordering runs by a summary
Section titled “Filtering and ordering runs by a summary”The same summaries drive SeriesFilter and SeriesOrderBy, so “runs whose final loss beat 0.1, best first”
is one query:
from metrana.query import SeriesFilter, SeriesOrderBy
client.fetch_runs( filter=SeriesFilter("loss").last() < 0.1, order_by=SeriesOrderBy("accuracy", "max").desc(),)Across-label aggregation
Section titled “Across-label aggregation”A summary column, filter or ordering always produces one value per run. Which series that value comes from is up to the labels you pin:
SeriesSummary("loss", "avg").with_label("worker", "w0") # that one seriesSeriesSummary("loss", "avg").with_label_matches("worker", "^gpu.*") # across the matching onesSeriesSummary("loss", "avg") # across every series of the metricLeave a label key out, or use any operator other than equality, and the function is computed across every
matching series of the run — the same operator surface as a
point fetch. The combination is exact, not an average of averages:
MIN/MAX/SUM/COUNT combine the obvious way, and AVG/STD are recomputed from the summed moments of
each series.
A run whose series match nothing contributes nothing: it is filtered out, sorts in natural order, and its summary column is empty.
This is not the same as fetch_series_summaries, which takes the same partial
labels but keeps the series apart — one row per (run, series) instead of one aggregated value per run.