Skip to content

Smoothing in Neptune charts#

Neptune applies two smoothing functions to data displayed in charts:

  • Neptune servers: Downsampling algorithm when fetching data
  • Neptune app (UI): Smoothing function controlled by the user

Neptune servers downsampling#

If there are more than 1024 data points in the fetched data, the Neptune servers automatically downsample the values by taking the average of each consecutive value pair and forming a new value series out of the averages. The length of the new series is thus half of the original series.

This downsampling continues as long as the resulting series contains more than 1024 values.

The function also returns a margin of error or uncertainty, which is displayed as a shaded area in the chart:

Visualized error margin in downsampled values

If you zoom in on a specific region with less than 1024 data points, the chart updates to show the actual logged values.

Neptune UI smoothing#

This function is manually controlled with the smoothing slider on the right of a chart. The values range from a minimum of 1 to a maximum of 100 and increase in increments of 9.

Manual smoothing in Neptune

When you move the smoothing slider, Neptune applies a low-pass filter to the \(y\) values in the chart. Additionally, the function takes as input the margin of error (the minimum and maximum \(y\) values) of the values returned by the downsampling algorithm.

The following example shows how the smoothing function works underneath the hood.

import math


def smooth(last, value):
    # Weighted average if last value is known, else just return the value
    return (
        weight * last + (1 - weight) * value if math.isfinite(last) else value
    )


def lowPassFilter(val):
    if val == 0 or val[1] == 0:
        return 0

    low = smooth(0, val[0])
    mid = smooth(0, val[1])
    high = smooth(0, val[2])

    return [low, mid, high]


def smoothData(data, smoothingValue):

    if len(data) == 0:
        return data

    global weight
    weight = 1 if smoothingValue > 100 else (smoothingValue - 1) / 100

    colCount = len(data[0])

    newData = dict()
    for i in range(len(data)):
        for k in range(1, colCount):
            newData[data[i][0]] = lowPassFilter(data[i][k])

    return newData

The input shape is [x, [minY, Y, maxY]], where

  • x is the \(x\) value
  • Y is the \(y\) value
  • minY and maxY represent the margin of error after downsampling.
data = smoothData(
    data=[
        [0, [1, 1, 1]],
        [1, [2, 2, 2]],
        [2, [3, 3, 3]],
        [3, [4, 4, 4]],
        [4, [5, 5, 5]],
        [5, [6, 6, 6]],
        [6, [7, 7, 7]],
        [7, [8, 8, 8]],
        [8, [9, 9, 9]],
        [9, [10, 10, 10]],
        [10, [11, 11, 11]],
    ],
    smoothingValue=100,
)

print(data)