Decimate data#

Decimation reads a fixed number of time buckets, each with the minimum, maximum, mean, count and variance of its points. It charts long, high-rate recordings quickly while keeping spikes visible.

The chart needs pip install plotly, and zooming needs ipywidgets in Jupyter.

Connect the client#

This guide assumes you have installed the client and setup your API keys.

from nominal.core import NominalClient

client = NominalClient.from_profile("default")

Upload sample data#

One hour of a 200 Hz signal is 720,000 points. This example generates one, with a few spikes, and uploads it with upload_dataframe():

from datetime import datetime, timedelta, timezone

import numpy as np
import pandas as pd

from nominal.thirdparty.pandas import upload_dataframe

start = datetime(2026, 9, 10, 14, 0, tzinfo=timezone.utc)
end = start + timedelta(hours=1)
n = 720_000

value = np.random.normal(0, 0.3, n).cumsum() + 50
value[np.random.choice(n, 10)] *= 2  # spikes that decimation keeps
df = pd.DataFrame({"time": start.timestamp() + np.arange(n) / 200, "value": value})

dataset = upload_dataframe(client, df, "Decimation sample", "time", timestamp_type="epoch_seconds")
channel = dataset.get_channel("value")

Read decimated data#

channel_to_dataframe_decimated() returns one row per bucket:

from nominal.thirdparty.pandas import channel_to_dataframe_decimated

df = channel_to_dataframe_decimated(channel, start, end, buckets=2000)

The columns are min, max, mean, count and variance. When the range has fewer than 1,000 points, it returns them unchanged, in one value column. It works for DOUBLE and INT channels.

Chart it and zoom in#

Plotting the bucket minimum and maximum as a band shows the signal’s full range:

import plotly.graph_objects as go


def band(df: pd.DataFrame) -> list[go.Scatter]:
    if "value" in df.columns:  # raw points
        return [go.Scatter(x=df.index, y=df["value"], mode="lines")]
    return [
        go.Scatter(x=df.index, y=df["max"], mode="lines", line_width=0, line_shape="vh"),
        go.Scatter(x=df.index, y=df["min"], mode="lines", line_width=0, line_shape="vh", fill="tonexty"),
    ]


fig = go.FigureWidget(band(df), layout={"showlegend": False})


def on_zoom(layout, x_range):  # fetch 2,000 buckets for the visible range
    zoomed = channel_to_dataframe_decimated(channel, f"{x_range[0]}Z", f"{x_range[1]}Z", buckets=2000)
    with fig.batch_update():
        fig.data = []
        fig.add_traces(band(zoomed))


fig.layout.on_change(on_zoom, "xaxis.range")
fig

Zooming in requests new buckets for the visible range, so detail increases until the chart shows raw points.

Decimated chart