Timeline#

Visualize a Sequence or SequencePool as a timeline using SequenceVisualizer.

Each row spans [start, end] for interval and state pools (horizontal bars); event pools render as scatter points. Two row-organisation modes are available:

  • group_by="id": one row per sequence (default, up to 30 sequences)

  • group_by="category": one row per unique label value

Imports#

import polars as pl

from tanat import build_events, build_intervals
from tanat.dataset import simulate_events, simulate_intervals, simulate_static
from tanat.visualization import SequenceVisualizer

Simulate data#

simulate_intervals() produces one row per interval. The second feature (status) is categorical; it becomes the label rendered on the timeline.

temporal = simulate_intervals(
    n_ids=20,
    seq_length_range=(4, 10),
    features=["value", "status"],
    seed=42,
)
print(temporal.shape, temporal.columns.tolist())
(146, 5) ['id', 'start', 'end', 'value', 'status']
temporal.head()
id start end value status
0 1 2007-01-12 05:26:45.371140 2007-01-16 08:26:01.085327 51 B
1 1 2016-06-26 20:20:24.000557 2016-07-24 09:53:03.279734 38 A
2 1 2018-03-05 21:57:20.246499 2018-03-13 14:11:04.422085 19 B
3 1 2019-03-21 06:56:00.630992 2019-03-23 08:58:21.531747 93 A
4 2 2006-05-26 11:21:25.090273 2006-06-23 14:50:35.859388 79 A


Build the pool#

pool = build_intervals(
    temporal_data=temporal,
    id_column="id",
    start_column="start",
    end_column="end",
)
┌─ Interval SequenceStore
│
│ Step 1/4: Sorting & preparing data
│
│ Step 2/4: Building sequence index
│
│ Step 3/4: Writing entity & time index features
│
│ Step 4/4: Computing & writing metadata
│
└─ Done (20 sequences · 146 entities · 0.00s)
pool.cast_features({"status": pl.Categorical}, is_static=False)
print(pool)
┌────────────────────────────────────────────────┐
│          IntervalSequencePool Summary          │
└────────────────────────────────────────────────┘

Overview
─────────────────────────
  Sequences          20
  Store              /home/runner/.tanat/_quick_interval_4056bf57
  id_column          id

Time Index
─────────────────────────
  Type               Datetime(time_unit='us', time_zone=None) [2000-02-19 14:02:45.190748 → 2024-12-31 19:24:06.829108]
  Columns            ['start', 'end']
  t0                 position=0, anchor=start

Entity Features (2)
─────────────────────────
  • status              Categorical (5 categories)
  • value               Numerical [1 → 100]

Flat timeline: one row per sequence#

group_by="id" (default) assigns one horizontal band per sequence. Each bar spans the [start, end] of that interval.

# fmt: off
SequenceVisualizer.timeline() \
    .title("Interval timeline (flat stacking)") \
    .colors("Set2") \
    .draw(pool, entity_feature="status") \
    .show()
# fmt: on
Interval timeline (flat stacking)

Category stacking: one row per label#

group_by="category" collapses all sequences onto one row per unique label value. Useful to compare when each label is active across the time axis.

# fmt: off
SequenceVisualizer.timeline(group_by="category") \
    .title("Interval timeline (category stacking)") \
    .colors("Set2") \
    .draw(pool, entity_feature="status") \
    .show()
# fmt: on
Interval timeline (category stacking)

Single sequence#

Pass a Sequence directly for a per-individual view.

seq = pool[pool.unique_ids[0]]
print(f"ID {seq.id_value}: {len(seq)} intervals")
ID 1: 4 intervals
# fmt: off
SequenceVisualizer.timeline() \
    .title(f"Single sequence (ID {seq.id_value})") \
    .colors("tab10") \
    .draw(seq, entity_feature="status") \
    .show()
# fmt: on
Single sequence (ID 1)

Event pool: scatter points#

For event pools (point observations) the timeline renders scatter marks instead of horizontal bars.

event_temporal = simulate_events(
    n_ids=15,
    seq_length_range=(5, 15),
    features=["score", "action"],
    seed=0,
)
event_pool = build_events(
    temporal_data=event_temporal,
    id_column="id",
    time_column="time",
)
┌─ Event SequenceStore
│
│ Step 1/4: Sorting & preparing data
│
│ Step 2/4: Building sequence index
│
│ Step 3/4: Writing entity & time index features
│
│ Step 4/4: Computing & writing metadata
│
└─ Done (15 sequences · 148 entities · 0.00s)
event_pool.cast_features({"action": pl.Categorical}, is_static=False)
print(event_pool)
┌────────────────────────────────────────────────┐
│           EventSequencePool Summary            │
└────────────────────────────────────────────────┘

Overview
─────────────────────────
  Sequences          15
  Store              /home/runner/.tanat/_quick_event_c5e6b087
  id_column          id

Time Index
─────────────────────────
  Type               Datetime(time_unit='us', time_zone=None) [2000-01-03 17:54:05.937674 → 2024-11-15 14:02:43.302235]
  Columns            ['time']
  t0                 position=0, anchor=None

Entity Features (2)
─────────────────────────
  • action              Categorical (5 categories)
  • score               Numerical [1 → 100]
# fmt: off
SequenceVisualizer.timeline() \
    .title("Event timeline (scatter points)") \
    .colors("tab10") \
    .draw(event_pool, entity_feature="action") \
    .show()
# fmt: on
Event timeline (scatter points)

Layout and style#

# Wide figure with a grid, timestamps easier to read
# fmt: off
SequenceVisualizer.timeline() \
    .figsize(12, 5) \
    .grid() \
    .colors("Set2") \
    .x_axis(label="Time", autofmt_xdate=True) \
    .y_axis(show=False) \
    .title("Wide timeline (grid, hidden y-axis)") \
    .draw(pool, entity_feature="status") \
    .show()
# fmt: on
Wide timeline (grid, hidden y-axis)
# Thin semi-transparent bars with a black edge
# fmt: off
SequenceVisualizer.timeline() \
    .colors("Set2") \
    .marker(bar_height=0.3, alpha=0.5, edge_color="black") \
    .title("Thin transparent bars") \
    .draw(pool, entity_feature="status") \
    .show()
# fmt: on
Thin transparent bars

Relative time (aligned to T0)#

time_mode="relative" aligns every sequence to its own T0 reference point so that the x-axis shows an offset in days from that anchor instead of absolute timestamps. Call set_t0() first to define the reference date; without it the lazy default (position=0, i.e. the first row) is used.

Anchor T0 to the first interval of every sequence.

pool.set_t0(position=0, anchor="start")
IntervalSequencePool(n=20, entity_features=2, static_features=0, store='/home/runner/.tanat/_quick_interval_4056bf57')
# Pool: all sequences aligned to their own t0
# fmt: off
SequenceVisualizer.timeline(time_mode="relative") \
    .title("Timeline aligned to T0 (pool)") \
    .x_axis(label="Days from T0") \
    .colors("Set2") \
    .draw(pool, entity_feature="status") \
    .show()
# fmt: on
Timeline aligned to T0 (pool)
# Single sequence: the same shift applied to one individual
# fmt: off
SequenceVisualizer.timeline(time_mode="relative") \
    .title(f"Timeline aligned to T0 (ID {seq.id_value})") \
    .x_axis(label="Days from T0") \
    .colors("Set2") \
    .draw(seq, entity_feature="status") \
    .show()
# fmt: on
Timeline aligned to T0 (ID 1)

Faceting#

.facet() splits the chart into a grid of panels, one per unique value of a chosen feature. Here we attach per-sequence static data and facet on group.

static_df = simulate_static(n_ids=20, features=["age", "group"], seed=0)
pool.add_static_features(static_df)
pool.cast_features({"group": pl.Categorical}, is_static=True)
# fmt: off
SequenceVisualizer.timeline() \
    .facet(by="group", is_static=True, cols=3) \
    .title("Interval timeline faceted by group") \
    .colors("Set2") \
    .draw(pool, entity_feature="status") \
    .show()
# fmt: on
Interval timeline faceted by group, group = A, group = B, group = C, group = D, group = E

Inspect prepare_data()#

prepare_data() exposes the intermediate Polars DataFrame before rendering. Columns: __ID__, __Y_POSITION__, __TIME__, __END__, __LABEL__, and optionally __COLOR__.

builder = SequenceVisualizer.timeline(group_by="category").colors("Set2")
df = builder.prepare_data(pool, entity_feature="status")
df.head(10)
shape: (10, 6)
__ID____TIME____END____LABEL____Y_POSITION____COLOR__
i64datetime[μs]datetime[μs]stri32str
12007-01-12 05:26:45.3711402007-01-16 08:26:01.085327"B"1"#fc8d62"
12016-06-26 20:20:24.0005572016-07-24 09:53:03.279734"A"0"#66c2a5"
12018-03-05 21:57:20.2464992018-03-13 14:11:04.422085"B"1"#fc8d62"
12019-03-21 06:56:00.6309922019-03-23 08:58:21.531747"A"0"#66c2a5"
22006-05-26 11:21:25.0902732006-06-23 14:50:35.859388"A"0"#66c2a5"
22007-04-10 15:52:50.6981762007-04-16 10:26:52.045909"A"0"#66c2a5"
22007-12-06 02:41:48.7038082007-12-08 09:57:16.751757"D"3"#e78ac3"
22009-04-10 06:17:42.3944262009-04-23 21:07:21.587970"D"3"#e78ac3"
22012-11-16 12:02:27.2380432012-12-16 06:44:03.451342"D"3"#e78ac3"
22013-11-14 21:54:21.6417332013-12-11 18:30:48.200062"C"2"#8da0cb"


Total running time of the script: (0 minutes 2.428 seconds)

Gallery generated by Sphinx-Gallery