Series and Frames

Learn how to use series and frames in the Python client.

Series and frames are the two fundamental structures used for working with channel data in Synnax. This guide will walk you through how to use the Series and Frame classes exposed by the Python client.

Series

A series is a strongly typed array of data samples. It can contain any data you want it to, but in the context of Synnax it almost always represents a set of contiguous samples from a single channel.

Constructing a Series

There are many ways to construct a series:

import synnax as sy
import numpy as np

# Construct a series from a list of samples. In this case, the series 
# will automatically be assumed to be of type int64.
series = sy.Series([1, 2, 3, 4, 5])

# We can also manually specify the type of the series
series = sy.Series([1, 2, 3, 4, 5], data_type=sy.DataType.INT32)

# Construct a series from a numpy array. The type of the series 
# will be inferred from the numpy array.
series = sy.Series(np.array([1, 2, 3, 4, 5], dtype=np.int64))

# Construct a series from a list of strings. In this case, 
# the series will automatically be assumed to be of type string.
series = sy.Series(["apple", "banana", "cherry"])

# Construct a series from dictionaries. In this case, the series 
# will automatically be assumed to be of type json.
series = sy.Series([{"a": 1}, {"b": 2}, {"c": 3}])

Interop with Numpy

The Series class is designed to be directly compatible with numpy, and supports all of the operations possible on a numpy array without needing to convert back and forth. Here’s a simple example:

import synnax as sy
import numpy as np

series = sy.Series([1, 2, 3, 4, 5])

# You can use numpy functions directly on a series
print(np.mean(series))

This means that it’s also possible to pass a series directly into libraries like matplotlib:

import synnax as sy
import matplotlib.pyplot as plt

x_data = sy.Series([1, 2, 3, 4, 5])
y_data = sy.Series([1, 4, 9, 16, 25])

plt.plot(x_data, y_data)
plt.show()

If you’d like to convert a series to a numpy array, you can do so with the to_numpy function or by simply passing the series to np.array:

import synnax as sy

series = sy.Series([1, 2, 3, 4, 5])

# Convert the series to a numpy array
numpy_array = series.to_numpy()

# Convert the series to a numpy array using np.array
numpy_array = np.array(series)

The Time Range Property

Whenever you read a series from Synnax, it will have a time_range property that represents the time range occupied by the samples in the Series. This method can be useful for getting a high-level understanding of when the samples were recorded without needing to query an index channel.

The start field represents the timestamp for the first sample, and the end field represents a timestamp just after the last sample (start-inclusive, end-exclusive).

It’s also easy to define a time range when constructing your own series:

import synnax as sy

start = sy.TimeStamp.now()

series = sy.Series(
    [1, 2, 3, 4, 5], 
    time_range=sy.TimeRange(
        start=start,
        end=start + sy.TimeSpan.SECOND * 6,
    )
)

Frames

A frame is a collection of series from multiple channels. Frames are returned by methods like:

  • The read method on the client (client.read)
  • The read method of a Streamer instance (client.open_streamer)
  • The value property of an Iterator instance (client.open_iterator)

Constructing a Frame

A frame maps the key or name of a channel to one or more series. Here are a few examples of how to construct a frame:

import synnax as sy

# Construct a frame for the given channel names.
frame = sy.Frame({
    "channel1": sy.Series([1, 2, 3, 4, 5]),
    "channel2": sy.Series([5, 4, 3, 2, 1]),
    "channel3": sy.Series([1, 1, 1, 1, 1]),
})

# Construct a frame from individual samples.
frame = sy.Frame({
    "channel1": 1,
    "channel2": 2,
    "channel3": 3,
})

Accessing Frame Data

Using the dictionary interface

Frames can be accessed like dictionaries:

import synnax as sy

data = frame["channel1"]

The data variable will be an instance of a MultiSeries object, which is a special type of series that wraps multiple Series instances, but behaves pretty much exactly like a single series:

import synnax as sy

frame = sy.Frame({
    "channel1": [sy.Series([1, 2, 3, 4, 5]), sy.Series([6, 7, 8, 9, 10])]
    "channel2": sy.Series([1, 2, 3]),
})

# Access the first series in the "channel1" key
data: sy.MultiSeries = frame["channel1"]
print(data[0]) # 1
print(data[-1]) # 10