Entity Metric: Combined distance#

This example demonstrates how a distance involving several features can be defined. For that, a combined entity metric proposes to combine several entity metrics. The proposed implementation makes a linear combinaison of several metric values computed between two entity features.

Note

It is also possible to implement your custom metric which can involve several features. This is also to be considered to improve the computational efficiency of your workflow.

Setup#

import polars as pl
from tanat import build_states
from tanat.dataset import simulate_states
from tanat.metric.entity import (
    HammingEntityMetric,
    L2EntityMetric,
    CombinedEntityMetric,
)

Generate synthetic data#

We generate a sequence with entities described by two features: score is a numerical attribute, while status is a categorical attribute.

N_IDS = 10
SEED = 42

raw_df = simulate_states(
    n_ids=N_IDS,
    seq_length_range=(3, 8),
    features=["score", "status"],
    seed=SEED,
)

pool = build_states(
    temporal_data=raw_df,
    id_column="id",
    start_column="start",
    end_column="end",
)

# HammingEntityMetric requires Categorical features
pool.cast_features({"status": pl.Categorical})

print(pool)
┌─ State 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 (10 sequences · 51 entities · 0.00s)
┌────────────────────────────────────────────────┐
│           StateSequencePool Summary            │
└────────────────────────────────────────────────┘

Overview
─────────────────────────
  Sequences          10
  Store              /home/runner/.tanat/_quick_state_f0901351
  id_column          id

Time Index
─────────────────────────
  Type               Datetime(time_unit='us', time_zone=None) [2002-11-11 21:19:37.764236 → 2025-01-01 00:00:00]
  Columns            ['start', 'end']
  t0                 position=0, anchor=start

Entity Features (2)
─────────────────────────
  • score               Numerical [5 → 98]
  • status              Categorical (5 categories)

Create the entity metrics#

In our scenario, we want to compare entities using both the score and the status. Then, we will first define metrics between entities for each feature, and then combine them.

metric_score = HammingEntityMetric(entity_feature="status")
metric_status = L2EntityMetric(entity_feature="score")

metric = CombinedEntityMetric(
    metrics_config=[
        metric_score.to_config(),
        metric_status.to_config(),
    ],
    agg="mean",
)

# .. warning::
#
#       Note that the CombinedEntityMetric waits for configurations
#       in the metrics configs, but not the metric itself.

Compute distance between individual entities#

ids = pool.unique_ids
seq_a = pool[ids[0]]
seq_b = pool[ids[1]]

# Extract first entity from each sequence
ent_a, ent_b = seq_a[0], seq_b[0]
# Entity A
print(ent_a)
┌────────────────────────────────────────────────┐
│              StateEntity Summary               │
└────────────────────────────────────────────────┘

Overview
─────────────────────────
  Sequence ID        1
  Rank               0

Entity Features
─────────────────────────
  score              53
  status             D
# Entity B
print(ent_b)
┌────────────────────────────────────────────────┐
│              StateEntity Summary               │
└────────────────────────────────────────────────┘

Overview
─────────────────────────
  Sequence ID        2
  Rank               0

Entity Features
─────────────────────────
  score              77
  status             C
# Compute Hamming distance
dist = metric(ent_a, ent_b)
print(f"\nCombined distance: {dist}")
Combined distance: 0.5329594872968643

Try multiple pairs#

print("\nDistances between random entity pairs:")
print("-" * 40)

for i in range(5):
    seq_1 = pool[ids[i]]
    seq_2 = pool[ids[i + 1]]

    # Compare first entities from each sequence
    e1, e2 = seq_1[0], seq_2[0]
    d = metric(e1, e2)

    print(
        f"Pair {i+1}: {e1['score']!r:5}/{e1['status']!r:5} vs {e2['score']!r:5}/{e2['status']!r:5}{d:.1f}"
    )
Distances between random entity pairs:
----------------------------------------
Pair 1: 53   /'D'   vs 77   /'C'   → 0.5
Pair 2: 77   /'C'   vs 51   /'A'   → 0.5
Pair 3: 51   /'A'   vs 41   /'B'   → 0.5
Pair 4: 41   /'B'   vs 23   /'A'   → 0.6
Pair 5: 23   /'A'   vs 86   /'B'   → 0.8

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

Gallery generated by Sphinx-Gallery