Multivariate Time Series

When you have multiple signals that share the same timestamps — like wind, solar, and hydro power — use TimeSeriesTable (also aliased as MultivariateTimeSeries). It stores per-column metadata and a 2D numpy array of values.

[1]:
from datetime import datetime, timedelta, timezone

import numpy as np

import timedatamodel as tdm

base = datetime(2024, 1, 15, tzinfo=timezone.utc)
timestamps = [base + timedelta(hours=i) for i in range(24)]

Creating a TimeSeriesTable

Pass a 2D array (rows = timestamps, columns = variables) along with per-column metadata.

[2]:
rng = np.random.default_rng(42)

wind = 80 + 40 * np.sin(np.linspace(0, 2 * np.pi, 24)) + rng.normal(0, 5, 24)
solar = np.clip(60 * np.sin(np.linspace(-0.5, np.pi + 0.5, 24)), 0, None) + rng.normal(0, 2, 24)
solar = np.clip(solar, 0, None)
hydro = 50 + rng.normal(0, 3, 24)

values = np.column_stack([wind, solar, hydro])

table = tdm.TimeSeriesTable(
    tdm.Frequency.PT1H,
    timezone="UTC",
    timestamps=timestamps,
    values=values,
    names=["wind", "solar", "hydro"],
    units=["MW", "MW", "MW"],
    data_types=[tdm.DataType.OBSERVATION, tdm.DataType.OBSERVATION, tdm.DataType.OBSERVATION],
)
table
[2]:
TimeSeriesTable
Nameunnamed
Columnswind, solar, hydro
Length24 × 3
FrequencyPT1H
TimezoneUTC (+00:00)
UnitMW, MW, MW
Data typeOBSERVATION, OBSERVATION, OBSERVATION
timestampwindsolarhydro
2024-01-15 00:0081.52360.052.0367
2024-01-15 01:0085.5920.050.2027
2024-01-15 02:00104.5361.0646250.8674
2024-01-15 21:0055.8120.43737749.4261
2024-01-15 22:0075.32081.7428646.1729
2024-01-15 23:0079.22740.44719146.6001

Inspecting table properties

[3]:
print(f"Columns:     {table.column_names}")
print(f"Shape:       ({len(table)}, {table.n_columns})")
print(f"Begin:       {table.begin}")
print(f"End:         {table.end}")
print(f"Has missing: {table.has_missing}")
Columns:     ('wind', 'solar', 'hydro')
Shape:       (24, 3)
Begin:       2024-01-15 00:00:00+00:00
End:         2024-01-15 23:00:00+00:00
Has missing: False

Selecting a single column

select_column() extracts one column as a univariate TimeSeriesList, carrying over its metadata.

[4]:
ts_wind = table.select_column("wind")
ts_wind
[4]:
TimeSeriesList
Namewind
Length24
FrequencyPT1H
TimezoneUTC (+00:00)
UnitMW
Data typeOBSERVATION
timestampwind
2024-01-15 00:0081.5236
2024-01-15 01:0085.592
2024-01-15 02:00104.536
2024-01-15 21:0055.812
2024-01-15 22:0075.3208
2024-01-15 23:0079.2274
[5]:
ts_solar = table.select_column("solar")
print(f"Name: {ts_solar.name}, Unit: {ts_solar.unit}, Data type: {ts_solar.data_type}")
Name: solar, Unit: MW, Data type: OBSERVATION

Merging univariate series into a table

TimeSeriesList.merge() is the reverse operation — combine several univariate series that share the same timestamps.

[6]:
ts_a = tdm.TimeSeriesList(
    tdm.Frequency.PT1H,
    timestamps=timestamps,
    values=wind.tolist(),
    name="wind",
    unit="MW",
)
ts_b = tdm.TimeSeriesList(
    tdm.Frequency.PT1H,
    timestamps=timestamps,
    values=solar.tolist(),
    name="solar",
    unit="MW",
)
ts_c = tdm.TimeSeriesList(
    tdm.Frequency.PT1H,
    timestamps=timestamps,
    values=hydro.tolist(),
    name="hydro",
    unit="MW",
)

merged = tdm.TimeSeriesList.merge([ts_a, ts_b, ts_c])
merged
[6]:
TimeSeriesTable
Nameunnamed
Columnswind, solar, hydro
Length24 × 3
FrequencyPT1H
TimezoneUTC (+00:00)
UnitMW, MW, MW
timestampwindsolarhydro
2024-01-15 00:0081.52360.052.0367
2024-01-15 01:0085.5920.050.2027
2024-01-15 02:00104.5361.0646250.8674
2024-01-15 21:0055.8120.43737749.4261
2024-01-15 22:0075.32081.7428646.1729
2024-01-15 23:0079.22740.44719146.6001

Decomposing a table back to univariate series

[7]:
univariate_list = merged.to_univariate_list()
for ts in univariate_list:
    print(f"{ts.name}: {len(ts)} points, unit={ts.unit}")
wind: 24 points, unit=MW
solar: 24 points, unit=MW
hydro: 24 points, unit=MW

Table arithmetic

Scalar arithmetic works on all columns simultaneously.

[8]:
table_gw = table * 0.001
table_gw.head(5)
[8]:
TimeSeriesTable
Nameunnamed
Columnswind, solar, hydro
Length5 × 3
FrequencyPT1H
TimezoneUTC (+00:00)
UnitMW, MW, MW
Data typeOBSERVATION, OBSERVATION, OBSERVATION
timestampwindsolarhydro
2024-01-15 00:000.08152360.00.0520367
2024-01-15 01:000.0855920.00.0502027
2024-01-15 02:000.1045360.001064620.0508674

Iteration and indexing

Each row is a (timestamp, [values...]) tuple.

[9]:
ts_val, row_vals = table[0]
print(f"Timestamp: {ts_val}")
print(f"Values:    {row_vals}")
Timestamp: 2024-01-15 00:00:00+00:00
Values:    [81.52358539877216, 0.0, 52.036740689215684]
[10]:
for ts_val, row_vals in table.head(4):
    wind_mw, solar_mw, hydro_mw = row_vals
    print(f"{ts_val:%Y-%m-%d %H:%M}  wind={wind_mw:6.1f}  solar={solar_mw:5.1f}  hydro={hydro_mw:5.1f}")
2024-01-15 00:00  wind=  81.5  solar=  0.0  hydro= 52.0
2024-01-15 01:00  wind=  85.6  solar=  0.0  hydro= 50.2
2024-01-15 02:00  wind= 104.5  solar=  1.1  hydro= 50.9
2024-01-15 03:00  wind= 113.9  solar=  3.1  hydro= 51.9

Conversion to pandas DataFrame

[11]:
df = table.to_pandas_dataframe()
df.head()
[11]:
wind solar hydro
timestamp
2024-01-15 00:00:00+00:00 81.523585 0.000000 52.036741
2024-01-15 01:00:00+00:00 85.591950 0.000000 50.202737
2024-01-15 02:00:00+00:00 104.535614 1.064618 50.867358
2024-01-15 03:00:00+00:00 113.936262 3.142702 51.893865
2024-01-15 04:00:00+00:00 105.760233 13.935461 45.628533

Summary

  • TimeSeriesTable stores multiple aligned columns with per-column metadata

  • select_column() extracts a single column as a TimeSeriesList

  • TimeSeriesList.merge() combines univariate series into a table

  • to_univariate_list() decomposes a table back to individual series

  • Scalar arithmetic applies across all columns

Next up: nb_06 covers TimeSeriesArray and TimeSeriesCollection for higher-dimensional data.