Skip to content

Use tsdive from Python and pandas

Every command is a function that returns an object: its render() is the text the command prints, its to_dict() the JSON --json prints, and its .frame a pandas DataFrame. This guide builds an archive from a DataFrame, runs the analyses, and handles refusals. Every snippet runs as a test against the demo data that tsdive demo data writes.

Get the demo data

>>> import tsdive
>>> paths = tsdive.write_demo_data("data-copy")
>>> [p.name for p in paths[:2]]
['fic101_demo.parquet', 'tic101_demo.parquet']

An archive from a DataFrame

write_tag takes a DataFrame with timestamp (UTC-aware), value and quality, and the tag's metadata. Declare sample_rate_s: compare and mspc align tags on it, and a hole of up to 3 times it counts as normal spacing instead of data loss.

>>> import numpy as np
>>> import pandas as pd
>>> stamps = pd.date_range("2024-03-30 20:00", periods=120, freq="min", tz="UTC")
>>> frame = pd.DataFrame({
...     "timestamp": stamps,
...     "value": 41.0 + np.sin(np.arange(120) / 10),
...     "quality": "GOOD",
... })
>>> meta = tsdive.TagMeta(
...     identity=tsdive.TagIdentity("plant1", "FI2201.PV"),
...     name="FI-2201 cooling water flow",
...     unit_raw="m3/h",
...     eng_range=tsdive.EngRange(zero=0.0, span=80.0),
...     sample_rate_s=60.0,
... )
>>> path = tsdive.write_tag("FI2201.parquet", frame, meta)
>>> p = tsdive.profile(path)
>>> round(p.physics.coverage.coverage, 3), p.physics.clipping.censored_verdict
(1.0, False)

An existing file raises FileExistsError: an archive is written once. Pass overwrite=True to replace the whole file.

Results into pandas

Each result has a .frame whose rows depend on the analysis:

result .frame holds
Profile every sample of the window: timestamp, value, quality, severity, valid
SegmentAnalysis one row per segment: index, start, end, n, median, mad
ScreenAnalysis the flagged samples only
SpcAnalysis one row per rule hit: timestamp, rule, detail
MspcAnalysis one row per aligned timestamp: timestamp, t2, spe, t2_breach, spe_breach
CompareAnalysis one row per tag, the tag table
>>> m = tsdive.mspc(["data/demo/fic101_demo.parquet", "data/demo/tic101_demo.parquet"],
...                 "2024-03-30T20:00:00Z/2024-03-30T23:00:00Z",
...                 "2024-03-31T04:00:00Z/2024-03-31T06:00:00Z")
>>> m.frame.columns.tolist()
['timestamp', 't2', 'spe', 't2_breach', 'spe_breach']
>>> int(m.frame["spe_breach"].sum()), len(m.frame)
(108, 121)
>>> hourly = m.frame.set_index("timestamp")["spe_breach"].resample("h").sum()
>>> hourly.astype(int).tolist()
[53, 54, 1]

to_dict() returns the document the command prints under --json, ready for json.dumps. It holds everything the text report shows. The command adds result_kind and tsdive_version in front:

>>> s = tsdive.screen("data/demo/fic101_demo.parquet",
...                   "2024-03-30T20:00:00Z/2024-03-31T01:00:00Z",
...                   "2024-03-31T01:00:00Z/2024-03-31T06:00:00Z")
>>> doc = s.to_dict()
>>> doc["n_flagged"], doc["n_screened"], doc["method"]
(29, 300, 'MAD')
>>> report = tsdive.profile("data/demo/fic101_demo.parquet").to_dict()
>>> sorted(report)
['contract', 'coverage', 'flatline', 'name', 'quality', 'range', 'tag', 'timestamps',
 'units', 'values', 'window']
>>> report["range"]["n_clipped"], report["coverage"]["n_data_loss_gaps"]
(29, 1)

Refusals and errors

A refusal raises a subclass of tsdive.TSDiveError. Any other exception means the call was wrong. Catch the first and let the second through:

>>> for baseline in ["2024-03-30T20:00:00Z/2024-03-31T01:00:00Z",
...                  "2024-03-31T00:00:00Z/2024-03-31T03:00:00Z",
...                  "2024-03-31T01:00:00Z/2024-03-31T02:00:00Z"]:
...     try:
...         _ = tsdive.spc("data/demo/fic101_demo.parquet", baseline,
...                        "2024-03-31T03:00:00Z/2024-03-31T06:00:00Z")
...         print("ok")
...     except tsdive.TSDiveError as e:
...         print(type(e).__name__)
ok
InsufficientQuality
ok

The messages name Python keywords, such as rate_s=, where the command line names flags. Errors and refusals lists every class.

Plotting

tsdive draws nothing on screen. .frame feeds any plotting library:

ax = p.frame.plot(x="timestamp", y="value")

The API reference lists every function and class with its parameters and an example.