TimeSeriesList Operations

A TimeSeriesList is not just a data container — it supports arithmetic, comparisons, and transformations. This notebook demonstrates how to manipulate time series data without dropping down to raw arrays.

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

import timedatamodel as tdm

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

generation = tdm.TimeSeriesList(
    tdm.Frequency.PT1H,
    timestamps=timestamps,
    values=[
        120.0, 115.0, 108.0, 105.0, 102.0, 100.0,
        110.0, 135.0, 160.0, 175.0, 180.0, 178.0,
        172.0, 170.0, 168.0, 165.0, 175.0, 190.0,
        200.0, 195.0, 180.0, 165.0, 145.0, 130.0,
    ],
    name="generation",
    unit="MW",
    data_type=tdm.DataType.OBSERVATION,
)

consumption = tdm.TimeSeriesList(
    tdm.Frequency.PT1H,
    timestamps=timestamps,
    values=[
        90.0, 85.0, 80.0, 78.0, 77.0, 80.0,
        95.0, 120.0, 145.0, 155.0, 160.0, 158.0,
        155.0, 150.0, 148.0, 150.0, 160.0, 170.0,
        165.0, 155.0, 140.0, 120.0, 105.0, 95.0,
    ],
    name="consumption",
    unit="MW",
    data_type=tdm.DataType.OBSERVATION,
)

Scalar arithmetic

Scale, offset, negate, or round values with natural Python operators. The result is a new TimeSeriesList with metadata preserved.

[2]:
generation_kw = generation * 1000
generation_kw
[2]:
TimeSeriesList
Namegeneration
Length24
FrequencyPT1H
TimezoneUTC (+00:00)
UnitMW
Data typeOBSERVATION
timestampgeneration
2024-01-15 00:00120000.0
2024-01-15 01:00115000.0
2024-01-15 02:00108000.0
2024-01-15 21:00165000.0
2024-01-15 22:00145000.0
2024-01-15 23:00130000.0
[3]:
offset = generation + 10.0
negated = -generation
rounded = round(generation / 3, 1)

print(f"Original first value:  {generation[0].value}")
print(f"+ 10:                  {offset[0].value}")
print(f"Negated:               {negated[0].value}")
print(f"Divided by 3, rounded: {rounded[0].value}")
Original first value:  120.0
+ 10:                  130.0
Negated:               -120.0
Divided by 3, rounded: 40.0

Element-wise arithmetic between two TimeSeriesList

Subtract consumption from generation to get net surplus. Both series must share the same timestamps.

[4]:
surplus = generation - consumption
surplus
[4]:
TimeSeriesList
Nameunnamed
Length24
FrequencyPT1H
TimezoneUTC (+00:00)
UnitMW
Data typeOBSERVATION
timestampvalue
2024-01-15 00:0030.0
2024-01-15 01:0030.0
2024-01-15 02:0028.0
2024-01-15 21:0045.0
2024-01-15 22:0040.0
2024-01-15 23:0035.0

Comparison operators

Comparisons return a TimeSeriesList of 1.0 / 0.0 (or NaN for missing). Useful for flagging thresholds.

[5]:
high_gen = generation > 170.0
high_gen
[5]:
TimeSeriesList
Nameunnamed
Length24
FrequencyPT1H
TimezoneUTC (+00:00)
timestampvalue
2024-01-15 00:000.0
2024-01-15 01:000.0
2024-01-15 02:000.0
2024-01-15 21:000.0
2024-01-15 22:000.0
2024-01-15 23:000.0
[6]:
gen_exceeds_cons = generation > consumption
gen_exceeds_cons
[6]:
TimeSeriesList
Nameunnamed
Length24
FrequencyPT1H
TimezoneUTC (+00:00)
timestampvalue
2024-01-15 00:001.0
2024-01-15 01:001.0
2024-01-15 02:001.0
2024-01-15 21:001.0
2024-01-15 22:001.0
2024-01-15 23:001.0

abs() — absolute values

Handy when you have signed deviations or residuals.

[7]:
deviation = generation - consumption
abs_deviation = abs(deviation)
abs_deviation.head(6)
[7]:
TimeSeriesList
Nameunnamed
Length6
FrequencyPT1H
TimezoneUTC (+00:00)
UnitMW
Data typeOBSERVATION
timestampvalue
2024-01-15 00:0030.0
2024-01-15 01:0030.0
2024-01-15 02:0028.0

head(), tail(), and copy()

Quickly preview or duplicate a series.

[8]:
print("First 3:")
for dp in generation.head(3):
    print(f"  {dp.timestamp:%H:%M}  {dp.value:.1f} MW")

print("\nLast 3:")
for dp in generation.tail(3):
    print(f"  {dp.timestamp:%H:%M}  {dp.value:.1f} MW")
First 3:
  00:00  120.0 MW
  01:00  115.0 MW
  02:00  108.0 MW

Last 3:
  21:00  165.0 MW
  22:00  145.0 MW
  23:00  130.0 MW
[ ]:

[ ]:

Handling missing values in arithmetic

Missing values (None) propagate through operations as NaN, just like numpy.

[9]:
ts_with_gaps = tdm.TimeSeriesList(
    tdm.Frequency.PT1H,
    timestamps=timestamps[:6],
    values=[100.0, None, 110.0, None, 105.0, 108.0],
    name="sensor",
    unit="MW",
)

doubled = ts_with_gaps * 2
for dp in doubled:
    print(f"{dp.timestamp:%H:%M}  {dp.value}")
00:00  200.0
01:00  None
02:00  220.0
03:00  None
04:00  210.0
05:00  216.0

Summary

You can treat TimeSeriesList like a numeric object:

  • Scalar ops: +, -, *, /, -ts, abs(), round()

  • Element-wise ops between aligned series

  • Comparisons: >, >=, <, <=, ==, !=

  • Missing values propagate cleanly

Next up: nb_05 introduces multivariate time series with TimeSeriesTable.