I/O and Interoperability

TimeDataModel is designed to sit between your domain logic and the broader Python data ecosystem. This notebook shows how to move data seamlessly between TimeSeriesList / TimeSeriesTable and pandas, numpy, polars, JSON, and CSV.

[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)]
values = [100.0 + 50 * np.sin(2 * np.pi * i / 24) for i in range(24)]

ts = tdm.TimeSeriesList(
    tdm.Frequency.PT1H,
    timezone="UTC",
    timestamps=timestamps,
    values=values,
    name="power",
    unit="MW",
    description="Synthetic daily power curve",
    data_type=tdm.DataType.SIMULATION,
)

NumPy

to_numpy() returns a float64 array (None becomes NaN). Use it when you need fast vectorized computation.

[2]:
arr = ts.to_numpy()
print(f"Type:  {type(arr)}")
print(f"Shape: {arr.shape}")
print(f"Mean:  {arr.mean():.2f} MW")
print(f"Max:   {arr.max():.2f} MW")
Type:  <class 'numpy.ndarray'>
Shape: (24,)
Mean:  100.00 MW
Max:   150.00 MW

Pandas — export

to_pandas_dataframe() produces a DataFrame with a DatetimeIndex.

[3]:
df = ts.to_pandas_dataframe()
df.head()
[3]:
power
timestamp
2024-01-15 00:00:00+00:00 100.000000
2024-01-15 01:00:00+00:00 112.940952
2024-01-15 02:00:00+00:00 125.000000
2024-01-15 03:00:00+00:00 135.355339
2024-01-15 04:00:00+00:00 143.301270

Pandas — import with from_pandas()

Create a TimeSeriesList from any pandas DataFrame that has a DatetimeIndex. Frequency and timezone are auto-inferred when possible.

[4]:
import pandas as pd

idx = pd.date_range("2024-06-01", periods=48, freq="h", tz="UTC")
df_external = pd.DataFrame({"load": np.random.default_rng(42).normal(150, 20, 48)}, index=idx)

ts_from_pd = tdm.TimeSeriesList.from_pandas(df_external, unit="MW", data_type=tdm.DataType.OBSERVATION)
ts_from_pd
[4]:
TimeSeriesList
Nameload
Length48
FrequencyPT1H
TimezoneUTC (+00:00)
UnitMW
Data typeOBSERVATION
timestampload
2024-06-01 00:00156.094
2024-06-01 01:00129.2
2024-06-01 02:00165.009
2024-06-02 21:00154.374
2024-06-02 22:00167.429
2024-06-02 23:00154.472
[ ]:

Pandas — TimeSeriesTable round-trip

[5]:
rng = np.random.default_rng(42)
table = tdm.TimeSeriesTable(
    tdm.Frequency.PT1H,
    timestamps=timestamps,
    values=np.column_stack([
        100 + rng.normal(0, 10, 24),
        50 + rng.normal(0, 5, 24),
    ]),
    names=["wind", "solar"],
    units=["MW", "MW"],
)

df_table = table.to_pandas_dataframe()
print(f"Columns: {list(df_table.columns)}")

table_back = tdm.TimeSeriesTable.from_pandas(df_table, units=["MW", "MW"])
print(f"Round-trip equals: {table.equals(table_back)}")
Columns: ['wind', 'solar']
Round-trip equals: True

Polars (optional)

If polars is installed, you get to_polars_dataframe() and from_polars().

[6]:
try:
    df_pl = ts.to_polars_dataframe()
    print(df_pl.head())

    ts_from_pl = tdm.TimeSeriesList.from_polars(df_pl, tdm.Frequency.PT1H, unit="MW")
    print(f"\nRound-trip length: {len(ts_from_pl)}")
except ImportError:
    print("polars not installed — skip this cell or: pip install timedatamodel[polars]")
shape: (5, 2)
┌─────────────────────────┬────────────┐
│ timestamp               ┆ power      │
│ ---                     ┆ ---        │
│ datetime[μs, UTC]       ┆ f64        │
╞═════════════════════════╪════════════╡
│ 2024-01-15 00:00:00 UTC ┆ 100.0      │
│ 2024-01-15 01:00:00 UTC ┆ 112.940952 │
│ 2024-01-15 02:00:00 UTC ┆ 125.0      │
│ 2024-01-15 03:00:00 UTC ┆ 135.355339 │
│ 2024-01-15 04:00:00 UTC ┆ 143.30127  │
└─────────────────────────┴────────────┘

Round-trip length: 24

JSON serialization

to_json() produces an ISO-8601 JSON string. from_json() reconstructs the series with full metadata.

[7]:
json_str = ts.to_json()
print(json_str[:200], "...")
{"timestamps": ["2024-01-15T00:00:00+00:00", "2024-01-15T01:00:00+00:00", "2024-01-15T02:00:00+00:00", "2024-01-15T03:00:00+00:00", "2024-01-15T04:00:00+00:00", "2024-01-15T05:00:00+00:00", "2024-01-1 ...
[8]:
ts_restored = tdm.TimeSeriesList.from_json(json_str)
print(f"Name:      {ts_restored.name}")
print(f"Unit:      {ts_restored.unit}")
print(f"Frequency: {ts_restored.frequency}")
print(f"Length:    {len(ts_restored)}")
print(f"Equals original: {ts.equals(ts_restored)}")
Name:      power
Unit:      MW
Frequency: PT1H
Length:    24
Equals original: True

JSON for TimeSeriesTable

[9]:
json_table = table.to_json()
table_restored = tdm.TimeSeriesTable.from_json(json_table)
print(f"Columns: {table_restored.column_names}")
print(f"Equals original: {table.equals(table_restored)}")
Columns: ('wind', 'solar')
Equals original: True

CSV serialization

to_csv() and from_csv() write and read simple CSV files.

[10]:
import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as tmp:
    csv_path = Path(tmp) / "power.csv"
    ts.to_csv(csv_path)

    with open(csv_path) as f:
        for i, line in enumerate(f):
            print(line.rstrip())
            if i >= 4:
                print("...")
                break
timestamp,power
2024-01-15T00:00:00+00:00,100.0
2024-01-15T01:00:00+00:00,112.94095225512604
2024-01-15T02:00:00+00:00,125.0
2024-01-15T03:00:00+00:00,135.35533905932738
...
[11]:
with tempfile.TemporaryDirectory() as tmp:
    csv_path = Path(tmp) / "power.csv"
    ts.to_csv(csv_path)
    ts_from_csv = tdm.TimeSeriesList.from_csv(csv_path, tdm.Frequency.PT1H, unit="MW")

print(f"Length: {len(ts_from_csv)}")
print(f"Name:   {ts_from_csv.name}")
ts_from_csv.head()
Length: 24
Name:   power
[11]:
TimeSeriesList
Namepower
Length5
FrequencyPT1H
TimezoneUTC (+00:00)
UnitMW
timestamppower
2024-01-15 00:00100.0
2024-01-15 01:00112.941
2024-01-15 02:00125.0

Multi-index round-trip

TimeSeriesList supports tuple-based timestamps for hierarchical indexing. This is preserved through pandas and JSON round-trips.

[12]:
issue_times = [
    datetime(2024, 1, 15, 0, tzinfo=timezone.utc),
    datetime(2024, 1, 15, 0, tzinfo=timezone.utc),
    datetime(2024, 1, 15, 0, tzinfo=timezone.utc),
    datetime(2024, 1, 15, 12, tzinfo=timezone.utc),
    datetime(2024, 1, 15, 12, tzinfo=timezone.utc),
    datetime(2024, 1, 15, 12, tzinfo=timezone.utc),
]
valid_times = [
    datetime(2024, 1, 15, 0, tzinfo=timezone.utc),
    datetime(2024, 1, 15, 1, tzinfo=timezone.utc),
    datetime(2024, 1, 15, 2, tzinfo=timezone.utc),
    datetime(2024, 1, 15, 12, tzinfo=timezone.utc),
    datetime(2024, 1, 15, 13, tzinfo=timezone.utc),
    datetime(2024, 1, 15, 14, tzinfo=timezone.utc),
]

ts_multi = tdm.TimeSeriesList(
    tdm.Frequency.PT1H,
    timestamps=list(zip(issue_times, valid_times)),
    values=[100.0, 105.0, 110.0, 95.0, 100.0, 108.0],
    name="forecast",
    unit="MW",
    index_names=["issue_time", "valid_time"],
)

df_multi = ts_multi.to_pandas_dataframe()
df_multi
[12]:
forecast
issue_time valid_time
2024-01-15 00:00:00+00:00 2024-01-15 00:00:00+00:00 100.0
2024-01-15 01:00:00+00:00 105.0
2024-01-15 02:00:00+00:00 110.0
2024-01-15 12:00:00+00:00 2024-01-15 12:00:00+00:00 95.0
2024-01-15 13:00:00+00:00 100.0
2024-01-15 14:00:00+00:00 108.0
[13]:
ts_multi_back = tdm.TimeSeriesList.from_pandas(df_multi, tdm.Frequency.PT1H, unit="MW")
print(f"Index names: {ts_multi_back.index_names}")
print(f"Multi-index: {ts_multi_back.is_multi_index}")
print(f"Equals original: {ts_multi.equals(ts_multi_back)}")
Index names: ('issue_time', 'valid_time')
Multi-index: True
Equals original: True

Summary

Format

Export

Import

Metadata preserved

NumPy

to_numpy()

Values only

pandas

to_pandas_dataframe()

from_pandas()

Name, freq, tz

polars

to_polars_dataframe()

from_polars()

Name

JSON

to_json()

from_json()

Full

CSV

to_csv()

from_csv()

Timestamps + values

Next up: nb_09 covers geographical support — GeoLocation, GeoArea, and spatial queries.