Distance measures for time series with Dynamic Time Warping as the primary focus
—
Comprehensive visualization capabilities for DTW analysis, including warping path plots, distance matrix heatmaps, and time series alignment visualizations. These tools provide essential insights into DTW behavior, sequence relationships, and clustering results for both 1D and multi-dimensional data.
Visualize the optimal warping path between two time series, showing how elements from one sequence align with elements from another.
def plot_warping(s1, s2, path, filename=None):
"""
Plot optimal warping between two sequences.
Creates a visualization showing both time series with connecting lines
that illustrate the optimal warping path alignment between sequences.
Parameters:
- s1, s2: array-like, input time series sequences
- path: list, warping path as sequence of (i, j) coordinate pairs
- filename: str, optional file path to save the plot
Returns:
tuple: (figure, axes) matplotlib objects
"""
def plot_warpingpaths(s1, s2, paths, path=None, filename=None,
shownumbers=False):
"""
Plot warping paths matrix with sequences.
Displays the full accumulated cost matrix (warping paths) as a heatmap
alongside the input sequences, with optional overlay of the optimal path.
Parameters:
- s1, s2: array-like, input sequences
- paths: 2D array, warping paths matrix from dtw.warping_paths()
- path: list, optional optimal path to overlay
- filename: str, optional file path to save plot
- shownumbers: bool, display numerical values in matrix cells
Returns:
tuple: (figure, axes) matplotlib objects
"""Visualize the transformation of one sequence to match another through DTW warping.
def plot_warp(from_s, to_s, new_s, path, filename=None):
"""
Plot warped sequence relationships.
Shows the original sequence, target sequence, and the warped result
to illustrate how DTW transforms one sequence to better match another.
Parameters:
- from_s: array-like, original source sequence
- to_s: array-like, target sequence to warp towards
- new_s: array-like, warped version of source sequence
- path: list, warping path used for transformation
- filename: str, optional file path to save plot
Returns:
tuple: (figure, axes) matplotlib objects
"""Create heatmap visualizations of distance matrices for analyzing relationships between multiple time series.
def plot_matrix(distances, filename=None, ax=None, shownumbers=False):
"""
Plot distance matrix as heatmap.
Creates a color-coded heatmap representation of the distance matrix
showing pairwise similarities between all sequences in a collection.
Parameters:
- distances: 2D array, symmetric distance matrix
- filename: str, optional file path to save plot
- ax: matplotlib axis, optional axis for plotting
- shownumbers: bool, display distance values in matrix cells
Returns:
tuple: (figure, axes) matplotlib objects
"""from dtaidistance import dtw
from dtaidistance.dtw_visualisation import plot_warping, plot_warpingpaths
import matplotlib.pyplot as plt
# Create two sequences with different temporal patterns
s1 = [0, 1, 2, 3, 2, 1, 0, 0]
s2 = [0, 0, 1, 2, 2, 3, 2, 1, 0]
# Compute optimal warping path
path = dtw.warping_path(s1, s2)
print("Warping path:", path)
# Visualize the warping between sequences
fig, ax = plot_warping(s1, s2, path)
plt.title('DTW Warping Path Between Sequences')
plt.show()
# Also compute and visualize the full warping paths matrix
distance, paths_matrix = dtw.warping_paths(s1, s2)
fig, ax = plot_warpingpaths(s1, s2, paths_matrix, path)
plt.title('DTW Warping Paths Matrix')
plt.show()from dtaidistance import dtw
from dtaidistance.dtw_visualisation import plot_warping, plot_warpingpaths
import numpy as np
import matplotlib.pyplot as plt
# Create sequences with clear temporal distortions
t1 = np.linspace(0, 4*np.pi, 50)
t2 = np.linspace(0, 4*np.pi, 40)
s1 = np.sin(t1) + 0.1 * np.random.randn(len(t1))
s2 = np.sin(t2 * 1.2) + 0.1 * np.random.randn(len(t2)) # Slightly faster
# Compute warping with constraints
distance, paths = dtw.warping_paths(s1, s2, window=10)
path = dtw.best_path(paths)
# Create comprehensive visualization
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(12, 10))
# Plot original sequences
ax1.plot(s1, 'b-', label='Sequence 1', linewidth=2)
ax1.plot(s2, 'r-', label='Sequence 2', linewidth=2)
ax1.set_title('Original Sequences')
ax1.legend()
ax1.grid(True)
# Plot warping paths matrix
im = ax2.imshow(paths, cmap='viridis', origin='lower')
ax2.set_title('Warping Paths Matrix')
ax2.set_xlabel('Sequence 2 Index')
ax2.set_ylabel('Sequence 1 Index')
plt.colorbar(im, ax=ax2)
# Plot optimal path on matrix
if path:
path_i, path_j = zip(*path)
ax2.plot(path_j, path_i, 'white', linewidth=2, alpha=0.8)
# Plot warping connections
ax3.plot(s1, 'b-o', label='Sequence 1', markersize=4)
ax3.plot(s2, 'r-o', label='Sequence 2', markersize=4)
# Draw warping connections
for i, j in path[::5]: # Show every 5th connection for clarity
if i < len(s1) and j < len(s2):
ax3.plot([i, j], [s1[i], s2[j]], 'gray', alpha=0.3, linewidth=0.5)
ax3.set_title('Warping Connections (every 5th)')
ax3.legend()
ax3.grid(True)
# Show warping path coordinates
ax4.plot([p[0] for p in path], label='Sequence 1 indices', marker='o', markersize=3)
ax4.plot([p[1] for p in path], label='Sequence 2 indices', marker='s', markersize=3)
ax4.set_title('Warping Path Coordinates')
ax4.set_xlabel('Path Step')
ax4.set_ylabel('Sequence Index')
ax4.legend()
ax4.grid(True)
plt.tight_layout()
plt.show()
print(f"DTW distance: {distance:.3f}")
print(f"Path length: {len(path)}")from dtaidistance import dtw
from dtaidistance.dtw_visualisation import plot_warp
import numpy as np
import matplotlib.pyplot as plt
# Create source and target sequences
source = np.array([1, 2, 4, 6, 4, 2, 1, 1, 1])
target = np.array([1, 3, 5, 3, 1])
# Perform sequence warping
warped_source, path = dtw.warp(source, target)
# Visualize the warping transformation
fig, ax = plot_warp(source, target, warped_source, path)
plt.title('Sequence Warping Transformation')
plt.show()
# Additional analysis plot
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(10, 8))
# Original sequences
ax1.plot(source, 'b-o', label='Original Source', linewidth=2, markersize=6)
ax1.plot(target, 'r-o', label='Target', linewidth=2, markersize=6)
ax1.set_title('Original Sequences')
ax1.legend()
ax1.grid(True)
# Warped source vs target
ax2.plot(warped_source, 'g-o', label='Warped Source', linewidth=2, markersize=6)
ax2.plot(target, 'r--', label='Target (reference)', linewidth=2, alpha=0.7)
ax2.set_title('Warped Source vs Target')
ax2.legend()
ax2.grid(True)
# Difference after warping
difference = np.array(warped_source) - np.array(target)
ax3.bar(range(len(difference)), difference, color='purple', alpha=0.7)
ax3.set_title('Difference After Warping')
ax3.set_xlabel('Time Point')
ax3.set_ylabel('Difference')
ax3.grid(True)
plt.tight_layout()
plt.show()
print(f"Original DTW distance: {dtw.distance(source, target):.3f}")
print(f"Warped sequence distance: {dtw.distance(warped_source, target):.3f}")from dtaidistance import dtw
from dtaidistance.dtw_visualisation import plot_matrix
import numpy as np
import matplotlib.pyplot as plt
# Create diverse time series collection
np.random.seed(42)
series = []
# Group 1: Sine waves with different frequencies
for freq in [1, 1.5, 2]:
t = np.linspace(0, 4*np.pi, 50)
s = np.sin(freq * t) + 0.1 * np.random.randn(50)
series.append(s)
# Group 2: Cosine waves
for freq in [1, 1.5]:
t = np.linspace(0, 4*np.pi, 50)
s = np.cos(freq * t) + 0.1 * np.random.randn(50)
series.append(s)
# Group 3: Linear trends
for slope in [0.5, 1.0, 1.5]:
t = np.linspace(0, 1, 50)
s = slope * t + 0.1 * np.random.randn(50)
series.append(s)
# Compute distance matrix
distances = dtw.distance_matrix(series, parallel=True)
# Visualize distance matrix
fig, ax = plot_matrix(distances, shownumbers=False)
plt.title('DTW Distance Matrix Heatmap')
# Add series type labels
group_labels = ['Sin 1Hz', 'Sin 1.5Hz', 'Sin 2Hz', 'Cos 1Hz', 'Cos 1.5Hz',
'Linear 0.5', 'Linear 1.0', 'Linear 1.5']
ax.set_xticks(range(len(group_labels)))
ax.set_yticks(range(len(group_labels)))
ax.set_xticklabels(group_labels, rotation=45, ha='right')
ax.set_yticklabels(group_labels)
plt.tight_layout()
plt.show()
# Analyze distance patterns
print("Distance matrix analysis:")
print(f"Matrix shape: {distances.shape}")
print(f"Mean distance: {np.mean(distances[np.triu_indices_from(distances, k=1)]):.3f}")
print(f"Min distance: {np.min(distances[np.triu_indices_from(distances, k=1)]):.3f}")
print(f"Max distance: {np.max(distances):.3f}")from dtaidistance import dtw
from dtaidistance.dtw_visualisation import plot_warping, plot_warpingpaths, plot_matrix
import numpy as np
import matplotlib.pyplot as plt
# Generate test data
np.random.seed(42)
s1 = np.cumsum(np.random.randn(30)) + np.sin(np.linspace(0, 4*np.pi, 30))
s2 = np.cumsum(np.random.randn(25)) + np.cos(np.linspace(0, 3*np.pi, 25))
s3 = np.linspace(0, 5, 35) + 0.5 * np.random.randn(35)
series_collection = [s1, s2, s3]
# Compute various DTW analyses
distance_12, paths_12 = dtw.warping_paths(s1, s2, window=5)
path_12 = dtw.best_path(paths_12)
distances_matrix = dtw.distance_matrix(series_collection)
# Create comprehensive visualization
fig = plt.figure(figsize=(15, 10))
# Layout: 2 rows, 3 columns
ax1 = plt.subplot(2, 3, 1)
ax2 = plt.subplot(2, 3, 2)
ax3 = plt.subplot(2, 3, 3)
ax4 = plt.subplot(2, 3, 4)
ax5 = plt.subplot(2, 3, 5)
ax6 = plt.subplot(2, 3, 6)
# Plot 1: Original time series
for i, s in enumerate(series_collection):
ax1.plot(s, f'C{i}-', label=f'Series {i+1}', linewidth=2)
ax1.set_title('Original Time Series')
ax1.legend()
ax1.grid(True)
# Plot 2: Warping paths matrix
im2 = ax2.imshow(paths_12, cmap='viridis', origin='lower')
ax2.set_title('Warping Paths Matrix (S1 vs S2)')
ax2.set_xlabel('Series 2 Index')
ax2.set_ylabel('Series 1 Index')
if path_12:
path_i, path_j = zip(*path_12)
ax2.plot(path_j, path_i, 'white', linewidth=2)
# Plot 3: Distance matrix heatmap
im3 = ax3.imshow(distances_matrix, cmap='hot', origin='lower')
ax3.set_title('Distance Matrix')
ax3.set_xlabel('Series Index')
ax3.set_ylabel('Series Index')
plt.colorbar(im3, ax=ax3)
# Plot 4: Warping between S1 and S2
ax4.plot(s1, 'b-o', label='Series 1', markersize=4)
ax4.plot(s2, 'r-o', label='Series 2', markersize=4)
# Draw some warping connections
for i, (idx1, idx2) in enumerate(path_12[::3]): # Every 3rd connection
if idx1 < len(s1) and idx2 < len(s2):
ax4.plot([idx1, idx2], [s1[idx1], s2[idx2]], 'gray', alpha=0.4, linewidth=0.5)
ax4.set_title('Warping Connections (S1 vs S2)')
ax4.legend()
ax4.grid(True)
# Plot 5: Path analysis
if path_12:
path_i, path_j = zip(*path_12)
ax5.plot(path_i, 'b-', label='Series 1 indices', linewidth=2)
ax5.plot(path_j, 'r-', label='Series 2 indices', linewidth=2)
ax5.set_title('Optimal Path Indices')
ax5.set_xlabel('Path Step')
ax5.set_ylabel('Sequence Index')
ax5.legend()
ax5.grid(True)
# Plot 6: Distance comparison
distances_pairwise = [
dtw.distance(series_collection[0], series_collection[1]),
dtw.distance(series_collection[0], series_collection[2]),
dtw.distance(series_collection[1], series_collection[2])
]
pair_labels = ['S1-S2', 'S1-S3', 'S2-S3']
bars = ax6.bar(pair_labels, distances_pairwise, color=['blue', 'green', 'red'], alpha=0.7)
ax6.set_title('Pairwise DTW Distances')
ax6.set_ylabel('DTW Distance')
# Add value labels on bars
for bar, distance in zip(bars, distances_pairwise):
height = bar.get_height()
ax6.text(bar.get_x() + bar.get_width()/2., height + 0.1,
f'{distance:.2f}', ha='center', va='bottom')
plt.tight_layout()
plt.show()
print(f"DTW distance S1-S2: {distance_12:.3f}")
print(f"Path length: {len(path_12)}")
print(f"Series lengths: S1={len(s1)}, S2={len(s2)}, S3={len(s3)}")from dtaidistance import dtw
from dtaidistance.dtw_visualisation import plot_warpingpaths
import numpy as np
import matplotlib.pyplot as plt
def compare_constraints(s1, s2, constraints_list):
"""Compare DTW results with different constraint settings."""
fig, axes = plt.subplots(2, len(constraints_list), figsize=(15, 8))
for i, constraints in enumerate(constraints_list):
# Compute DTW with constraints
distance, paths = dtw.warping_paths(s1, s2, **constraints)
path = dtw.best_path(paths)
# Plot warping paths matrix
im = axes[0, i].imshow(paths, cmap='viridis', origin='lower')
axes[0, i].set_title(f'Window={constraints.get("window", "None")}\\n'
f'Distance={distance:.2f}')
if path:
path_i, path_j = zip(*path)
axes[0, i].plot(path_j, path_i, 'white', linewidth=2)
# Plot sequences with warping connections
axes[1, i].plot(s1, 'b-o', label='S1', markersize=3)
axes[1, i].plot(s2, 'r-o', label='S2', markersize=3)
# Draw warping connections (sample every 5th)
for j, (idx1, idx2) in enumerate(path[::5]):
if idx1 < len(s1) and idx2 < len(s2):
axes[1, i].plot([idx1, idx2], [s1[idx1], s2[idx2]],
'gray', alpha=0.3, linewidth=0.5)
axes[1, i].set_title(f'Warping Connections')
axes[1, i].grid(True)
if i == 0:
axes[1, i].legend()
plt.tight_layout()
plt.show()
# Test different constraint settings
s1 = np.sin(np.linspace(0, 4*np.pi, 40)) + 0.1*np.random.randn(40)
s2 = np.sin(np.linspace(0, 3*np.pi, 30)) + 0.1*np.random.randn(30)
constraint_sets = [
{}, # No constraints
{'window': 5}, # Narrow window
{'window': 15}, # Wide window
{'window': 10, 'max_dist': 20.0} # Window + early stopping
]
compare_constraints(s1, s2, constraint_sets)This comprehensive visualization module provides essential tools for understanding DTW behavior, analyzing sequence relationships, and validating clustering results through intuitive graphical representations.
Install with Tessl CLI
npx tessl i tessl/pypi-dtaidistance