Histograms
A histogram series stores a distribution per step, not a number. There are two ways to read one, and the right choice depends on whether you want the shape or a trend.
Whole histograms
Section titled “Whole histograms”frame = client.fetch_histogram_series("run-001", "weights/l0")A histogram point is two-dimensional, so it cannot fold into a wide value matrix the way float points do. The
frame is long — index (run, series, step), one row per point:
histogram count min max mean stdrun series steprun-001 weights/l0 0 <metrana.Histogram> 4096.0 -0.412 0.398 -0.021 0.147 100 <metrana.Histogram> 4096.0 -0.207 0.211 -0.008 0.088The histogram column holds metrana.Histogram objects — the same type the logger takes, so what you
read back is what you logged. Counts and densities are indistinguishable as bare arrays, and the type settles
which it is structurally:
h = frame.loc[("run-001", "weights/l0", 100), "histogram"]h.bin_edges, h.counts, h.statsThe stat columns beside it are per-point summaries kept vectorised — frame["std"].plot() works without
unpacking a single object. The set depends on the kind: counting histograms carry
count/min/max/mean/std, density histograms min/max/mean/std (a density divides the sample
count out, so there is none to report). They are exact when the logger passed stats=, and the ingestion
service’s bin-approximation otherwise; the raw sum / sum-of-squares primitives remain available on
Histogram.stats if you need to merge points yourself.
Density histograms live under their own value kind — pass kind= to fetch those instead:
from metrana.query import SeriesValueKind
client.fetch_histogram_series("run-001", "acts", kind=SeriesValueKind.DENSITY_HISTOGRAM)Step extents
Section titled “Step extents”include_extents=True attaches each series’ step extents as frame.attrs["extents"], keyed by
(run, series). Each value is a SeriesExtent:
| Field | Meaning |
|---|---|
first_step |
Lowest step the series has, ignoring the steps selection. |
last_step |
Highest step the series has, ignoring the steps selection. |
points_count |
Upper bound on the number of points — counted before deduplication, so re-logged steps inflate it. |
The extent is independent of the steps you asked for, which is what makes it useful: fetch just the last point
and you still learn the series’ full range, enough to render a step slider without a second scan.
from metrana.query import LastSteps
frame = client.fetch_histogram_series("run-001", "acts", steps=LastSteps(1), include_extents=True)extent = frame.attrs["extents"][("run-001", "acts")]slider_range = (extent.first_step, extent.last_step)Every series the fetch matched is reported — including one whose selected step range holds no points at all, which is precisely the case where the extent tells you where the points actually are.
Reduced to a float series
Section titled “Reduced to a float series”To chart a histogram metric you want a line, not 512 bins per step. reduce_histogram turns each point
into one float at query time, so the series flows through the ordinary float endpoints — including the
bucketed one:
from metrana.query import SeriesQuery, HistogramReducer
client.fetch_float_series("run-001", SeriesQuery("grads").reduce_histogram(HistogramReducer.avg()))client.fetch_float_series_buckets( runs, SeriesQuery("grads").reduce_histogram(HistogramReducer.quantile(0.95)), buckets_count=500,)| Reducer | Computed from |
|---|---|
HistogramReducer.min() / .max() / .avg() / .std() |
the point’s stored moments — exact if you logged stats= |
HistogramReducer.quantile(p) |
the bins — carries the binning’s resolution |
HistogramReducer.fraction_above(t) |
the bins — same caveat |
That split matters: moment-based reducers are exact when the logger passed the raw values’ moments, whereas a quantile is only ever as precise as the bin width. It is the same trade the ingestion side describes from the writing end.
Reduction is a point-fetch feature. The summary listings reduce with their own summary function instead,
so reduce_histogram has no place there.