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]:
| timestamp | wind | solar | hydro |
|---|---|---|---|
| 2024-01-15 00:00 | 81.5236 | 0.0 | 52.0367 |
| 2024-01-15 01:00 | 85.592 | 0.0 | 50.2027 |
| 2024-01-15 02:00 | 104.536 | 1.06462 | 50.8674 |
| … | … | … | … |
| 2024-01-15 21:00 | 55.812 | 0.437377 | 49.4261 |
| 2024-01-15 22:00 | 75.3208 | 1.74286 | 46.1729 |
| 2024-01-15 23:00 | 79.2274 | 0.447191 | 46.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]:
| timestamp | wind |
|---|---|
| 2024-01-15 00:00 | 81.5236 |
| 2024-01-15 01:00 | 85.592 |
| 2024-01-15 02:00 | 104.536 |
| … | … |
| 2024-01-15 21:00 | 55.812 |
| 2024-01-15 22:00 | 75.3208 |
| 2024-01-15 23:00 | 79.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]:
| timestamp | wind | solar | hydro |
|---|---|---|---|
| 2024-01-15 00:00 | 81.5236 | 0.0 | 52.0367 |
| 2024-01-15 01:00 | 85.592 | 0.0 | 50.2027 |
| 2024-01-15 02:00 | 104.536 | 1.06462 | 50.8674 |
| … | … | … | … |
| 2024-01-15 21:00 | 55.812 | 0.437377 | 49.4261 |
| 2024-01-15 22:00 | 75.3208 | 1.74286 | 46.1729 |
| 2024-01-15 23:00 | 79.2274 | 0.447191 | 46.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]:
| timestamp | wind | solar | hydro |
|---|---|---|---|
| 2024-01-15 00:00 | 0.0815236 | 0.0 | 0.0520367 |
| 2024-01-15 01:00 | 0.085592 | 0.0 | 0.0502027 |
| 2024-01-15 02:00 | 0.104536 | 0.00106462 | 0.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
TimeSeriesTablestores multiple aligned columns with per-column metadataselect_column()extracts a single column as aTimeSeriesListTimeSeriesList.merge()combines univariate series into a tableto_univariate_list()decomposes a table back to individual seriesScalar arithmetic applies across all columns
Next up: nb_06 covers TimeSeriesArray and TimeSeriesCollection for higher-dimensional data.