TimeSeries Operations
A TimeSeries 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.TimeSeries(
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.MEASUREMENT,
)
consumption = tdm.TimeSeries(
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.MEASUREMENT,
)
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[1], line 3
1 from datetime import datetime, timedelta, timezone
----> 3 import timedatamodel as tdm
5 base = datetime(2024, 1, 15, tzinfo=timezone.utc)
6 timestamps = [base + timedelta(hours=i) for i in range(24)]
ModuleNotFoundError: No module named 'timedatamodel'
Scalar arithmetic
Scale, offset, negate, or round values with natural Python operators. The result is a new TimeSeries with metadata preserved.
[2]:
generation_kw = generation * 1000
generation_kw
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[2], line 1
----> 1 generation_kw = generation * 1000
2 generation_kw
NameError: name 'generation' is not defined
[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}")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[3], line 1
----> 1 offset = generation + 10.0
2 negated = -generation
3 rounded = round(generation / 3, 1)
NameError: name 'generation' is not defined
Element-wise arithmetic between two TimeSeries
Subtract consumption from generation to get net surplus. Both series must share the same timestamps.
[4]:
surplus = generation - consumption
surplus
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[4], line 1
----> 1 surplus = generation - consumption
2 surplus
NameError: name 'generation' is not defined
Comparison operators
Comparisons return a TimeSeries of 1.0 / 0.0 (or NaN for missing). Useful for flagging thresholds.
[5]:
high_gen = generation > 170.0
high_gen
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[5], line 1
----> 1 high_gen = generation > 170.0
2 high_gen
NameError: name 'generation' is not defined
[6]:
gen_exceeds_cons = generation > consumption
gen_exceeds_cons
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[6], line 1
----> 1 gen_exceeds_cons = generation > consumption
2 gen_exceeds_cons
NameError: name 'generation' is not defined
abs() — absolute values
Handy when you have signed deviations or residuals.
[7]:
deviation = generation - consumption
abs_deviation = abs(deviation)
abs_deviation.head(6)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[7], line 1
----> 1 deviation = generation - consumption
2 abs_deviation = abs(deviation)
3 abs_deviation.head(6)
NameError: name 'generation' is not defined
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:
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[8], line 2
1 print("First 3:")
----> 2 for dp in generation.head(3):
3 print(f" {dp.timestamp:%H:%M} {dp.value:.1f} MW")
5 print("\nLast 3:")
NameError: name 'generation' is not defined
[ ]:
[ ]:
Handling missing values in arithmetic
Missing values (None) propagate through operations as NaN, just like numpy.
[9]:
ts_with_gaps = tdm.TimeSeries(
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}")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[9], line 1
----> 1 ts_with_gaps = tdm.TimeSeries(
2 tdm.Frequency.PT1H,
3 timestamps=timestamps[:6],
4 values=[100.0, None, 110.0, None, 105.0, 108.0],
5 name="sensor",
6 unit="MW",
7 )
9 doubled = ts_with_gaps * 2
10 for dp in doubled:
NameError: name 'tdm' is not defined
Summary
You can treat TimeSeries 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.