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]:
| timestamp | load |
|---|---|
| 2024-06-01 00:00 | 156.094 |
| 2024-06-01 01:00 | 129.2 |
| 2024-06-01 02:00 | 165.009 |
| … | … |
| 2024-06-02 21:00 | 154.374 |
| 2024-06-02 22:00 | 167.429 |
| 2024-06-02 23:00 | 154.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]:
| timestamp | power |
|---|---|
| 2024-01-15 00:00 | 100.0 |
| 2024-01-15 01:00 | 112.941 |
| 2024-01-15 02:00 | 125.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 |
|
— |
Values only |
pandas |
|
|
Name, freq, tz |
polars |
|
|
Name |
JSON |
|
|
Full |
CSV |
|
|
Timestamps + values |
Next up: nb_09 covers geographical support — GeoLocation, GeoArea, and spatial queries.