.. _theory_vectorization: ====================== Vectorization Methods ====================== .. contents:: Contents :local: :depth: 2 Introduction ============ **Why Vectorization?** Persistence barcodes are variable-length representations - different images produce different numbers of topological features. However, most machine learning algorithms require **fixed-length feature vectors**. **Vectorization** converts variable-length barcodes into fixed-length vectors suitable for: * Classification * Regression * Clustering * Statistical analysis .. note:: The mathematical definitions and theoretical foundations presented in this section are based on the comprehensive survey by Ali et al. [Ali2023]_, which provides detailed comparisons and benchmarks of vectorization methods for persistent homology barcodes. The Challenge ------------- Given a barcode with n features: :: barcode = [[b₁, d₁], [b₂, d₂], ..., [bₙ, dₙ]] We need a function that produces a fixed-length vector regardless of n: :: vectorize: ℝⁿˣ² → ℝᵐ where m is fixed. Vectorization Strategies ======================== MedTDA provides 8 different vectorization methods, each with different properties and use cases. 1. Persistence Statistics -------------------------- **Idea:** Compute statistical summaries of persistence values. **Features Extracted (38 total):** * **Birth times** * **Death times** * **Midpoints** * **Lifespans** * **Count** * **Entropy** **Output Size:** 38 features **Mathematical Definition:** For a barcode B with intervals [p, q] and multiplicity function :math:`\mu:B \to \mathbb{Z}_+`, the persistence statistics vector consists of: **Statistical measures of:** 1. **Birth times (p):** mean, std, median, IQR, range, percentiles (10th, 25th, 75th, 90th) 2. **Death times (q):** mean, std, median, IQR, range, percentiles (10th, 25th, 75th, 90th) 3. **Midpoints ((p+q)/2):** mean, std, median, IQR, range, percentiles (10th, 25th, 75th, 90th) 4. **Lifespans (q-p):** mean, std, median, IQR, range, percentiles (10th, 25th, 75th, 90th) 5. **Count:** Total number of features 6. **Entropy:** Information-theoretic measure defined as: .. math:: E_\mu = -\sum_{[p,q] \in B} \mu_{p,q} \cdot \left(\frac{q-p}{L_\mu}\right) \cdot \log\left(\frac{q-p}{L_\mu}\right) where :math:`L_\mu` is the weighted sum of all persistence values: .. math:: L_\mu = \sum_{[p,q] \in B} \mu_{p,q} \cdot (q-p) This vectorization was independently introduced and used in multiple works, providing a simple yet effective baseline for classification tasks. **Example:** :: from medtda.vectorizers import persistence_stats stats = persistence_stats(barcode) # Returns array of length ~13 with interpretable values 2. Betti Curves --------------- **Idea:** Count features alive at each filtration value. **Process:** 1. Sample filtration values uniformly 2. At each value, count features alive (birth ≤ value < death) 3. Result is a curve: filtration value → count **Output Size:** Configurable (default: 100) **Mathematical Definition:** The Betti curve :math:`\beta_\mu : \mathbb{R} \to \mathbb{R}` for a barcode with multiplicity function :math:`\mu:B \to \mathbb{Z}_+` is defined as: .. math:: \beta_\mu(t) = \sum_{[p,q] \in B} \mathbb{1}_{p \leq t < q} \cdot \mu_{p,q} where :math:`\mathbb{1}` is the indicator function that equals 1 when the condition is true and 0 otherwise. **Interpretation:** At each filtration value t, the Betti curve counts how many features are "alive" - i.e., features that have already been born (p ≤ t) but not yet died (t < q). **Vectorization:** The curve is sampled at regular intervals :math:`t_1, t_2, \ldots, t_m` to produce a fixed-length feature vector of dimension m. **Example:** :: from medtda.vectorizers import betti_curve curve = betti_curve(barcode, resolution=100) # Returns array of length 100 3. Persistence Images ---------------------- **Idea:** Create a 2D histogram of (birth, persistence) weighted by persistence. **Process:** 1. Transform barcode to (birth, persistence) coordinates 2. Create 2D grid 3. Place Gaussian kernel at each point 4. Weight by persistence 5. Flatten to 1D vector **Output Size:** resolution² (default: 20² = 400) **Mathematical Definition:** Persistence images transform each barcode interval [p,q] into the birth-persistence coordinate (p, q-p), then create a smoothed 2D representation. **Persistence Surface:** Given a weighting function :math:`f:\mathbb{R}^2 \to \mathbb{R}_{\geq 0}` and probability distributions :math:`\psi_{p,q}` (typically Gaussian) centered at each (p, q-p), the persistence surface is: .. math:: \rho^\mu_{f,\Psi}(x,y) = \sum_{[p,q] \in B} \mu_{p,q} \cdot f(p,q-p) \cdot \psi_{p,q}(x,y) **Typical choices:** * Weighting: :math:`f(x,y) = y/\lambda_{\text{max}}` for :math:`0 < y < \lambda_{\text{max}}` (longer persistence → higher weight) * Gaussian kernel: :math:`\psi_{p,q}(x,y) = (1/2\pi\sigma^2) \exp(-(x-p)^2 + (y-(q-p))^2)/(2\sigma^2))` where :math:`\lambda_{\text{max}}` is the maximum persistence in the barcode. **Vectorization:** The surface is discretized on a regular grid and flattened into a 1D vector. For a resolution of :math:`m \times m`, this produces a feature vector of dimension :math:`m^2`. **Example:** :: from medtda.vectorizers import persistence_image pi = persistence_image(barcode, bandwidth=0.2, resolution=20) # Returns 20×20 array, flatten for ML: pi.flatten() 4. Persistence Landscapes -------------------------- **Idea:** Functional representation using piecewise linear functions. **Process:** 1. Create landscape functions from barcode 2. Sample multiple landscape levels 3. Sample each function at regular intervals 4. Concatenate all samples **Output Size:** num_landscapes × resolution (default: 5 × 100 = 500) **Mathematical Definition:** Persistence landscapes define a sequence of continuous functions. For each interval [p,q], define a "tent" function: .. math:: \Delta([p,q], t) = \max(\min(t-p, q-t), 0) This creates a triangular function that: * Equals 0 for t < p or t > q * Increases linearly from p to (p+q)/2 * Decreases linearly from (p+q)/2 to q * Has maximum value (q-p)/2 at the midpoint The **k-th landscape function** :math:`\Lambda^k_\mu : \mathbb{R} \to \mathbb{R}` is defined by taking, at each point t, the k-th largest value among all tent functions: .. math:: \Lambda^k_\mu(t) = k\text{-th largest value in } \{\mu_{p,q} \cdot \Delta([p,q],t) : [p,q] \in B\} **Alternative definition:** .. math:: \Lambda^k_\mu(t) = \sup\left\{s \geq 0 : \left(\sum_{[p,q] \in B} \mathbb{1}_{[t-s,t+s] \subset [p,q]} \cdot \mu_{p,q}\right) \geq k\right\} **Stability:** Persistence landscapes are stable with respect to the bottleneck distance, making them theoretically well-founded for statistical analysis. **Example:** :: from medtda.vectorizers import persistence_landscape landscape = persistence_landscape(barcode, resolution=100, num_landscapes=5) # Returns 5×100 array, flatten for ML: landscape.flatten() 5. Persistence Silhouettes --------------------------- **Idea:** Average persistence landscape weighted by persistence. **Process:** 1. Compute landscape functions 2. Weight by persistence 3. Average across all features 4. Sample at regular intervals **Output Size:** Configurable (default: 100) **Mathematical Definition:** The persistence silhouette is a weighted average of landscape-like functions. Given a weight function :math:`w:B \to \mathbb{R}_+` (typically :math:`w_{p,q} = (q-p)^\alpha` for some :math:`\alpha \geq 0`), the silhouette is: .. math:: \phi^w_\mu(t) = \frac{\sum_{[p,q] \in B} w_{p,q} \cdot \mu_{p,q} \cdot \Delta([p,q],t)}{\sum_{[p,q] \in B} w_{p,q} \cdot \mu_{p,q}} where :math:`\Delta([p,q],t) = \max(\min(t-p, q-t), 0)` is the tent function. **Weighting choices:** * :math:`\alpha = 0`: All features weighted equally * :math:`\alpha = 1`: Features weighted by persistence (longer bars → more influence) * :math:`\alpha = 2`: Quadratic weighting emphasizes long-lived features **Example:** :: from medtda.vectorizers import persistence_silhouette silhouette = persistence_silhouette(barcode, resolution=100) # Returns array of length 100 6. Entropy Summary ------------------- **Idea:** Information-theoretic features from persistence distribution. **Process:** 1. Normalize persistence values 2. Treat as probability distribution 3. Compute entropy and related measures 4. Sample distribution at intervals **Output Size:** Configurable (default: 100) **Mathematical Definition:** The entropy summary function represents the contribution to total entropy at each filtration value. Let :math:`L_\mu` be the total weighted persistence: .. math:: L_\mu = \sum_{[p,q] \in B} \mu_{p,q} \cdot (q-p) The entropy summary function :math:`S_\mu : \mathbb{R} \to \mathbb{R}` is: .. math:: S_\mu(t) = -\sum_{[p,q] \in B} \mathbb{1}_{p \leq t < q} \cdot \mu_{p,q} \cdot \left(\frac{q-p}{L_\mu}\right) \cdot \log\left(\frac{q-p}{L_\mu}\right) where :math:`\mathbb{1}_{p \leq t < q}` is the indicator function (equals 1 if feature is alive at time t). **Interpretation:** * Piecewise constant function * At each t, sums entropy contributions from all alive features * Normalized by total weighted persistence :math:`L_\mu` * Also called the "life entropy curve" in literature **Example:** :: from medtda.vectorizers import entropy_summary entropy = entropy_summary(barcode, resolution=100) # Returns array of length 100 7. Persistence Lifespan ------------------------ **Idea:** Histogram of persistence values. **Process:** 1. Extract all persistence values 2. Create histogram with fixed bins 3. Normalize to probability distribution **Output Size:** Configurable (default: 100 bins) **Example:** :: from medtda.vectorizers import persistence_lifespan lifespan = persistence_lifespan(barcode, resolution=100) # Returns array of length 100 8. Tropical Coordinates ------------------------ **Idea:** Fixed-size encoding using symmetric tropical polynomial invariants of the barcode. **Process:** 1. Convert barcode intervals to (birth, lifespan) representation 2. Sort intervals by lifespan in descending order 3. Evaluate 7 fixed symmetric tropical polynomial functions **Output Size:** 7 features (fixed, independent of barcode size) **Mathematical Definition:** Tropical coordinates use **tropical algebra**, where standard operations (+, ×) are replaced by (max, +). Given a barcode B with intervals sorted by lifespan :math:`\lambda_i = q_i - p_i` in descending order (so :math:`\lambda_1 \geq \lambda_2 \geq \cdots \geq \lambda_n`): **The 7 tropical coordinate features** are: 1. Largest lifespan: :math:`F_1 = \lambda_1` 2. Sum of two largest: :math:`F_2 = \lambda_1 + \lambda_2` 3. Sum of three largest: :math:`F_3 = \lambda_1 + \lambda_2 + \lambda_3` 4. Sum of four largest: :math:`F_4 = \lambda_1 + \lambda_2 + \lambda_3 + \lambda_4` 5. Total lifespan sum: :math:`F_5 = \sum_i \lambda_i` 6. Birth-weighted sum: :math:`F_6 = \sum_i \min(r\lambda_i, p_i)` 7. Complementary spread: :math:`F_7 = \sum_i \left(M - \min(r\lambda_i, p_i) - \lambda_i\right)` where :math:`r` is a scalar parameter (default 28) and :math:`M = \max_i \left(\min(r\lambda_i, p_i) + \lambda_i\right)`. Features F1–F4 are set to 0 when fewer bars are available than required (e.g. F4 = 0 if fewer than 4 bars exist). **Stability:** Tropical coordinates are stable with respect to the bottleneck distance — small changes in the barcode produce bounded changes in coordinate values. **Example:** :: from medtda.vectorizers import persistence_tropical_coordinates coords = persistence_tropical_coordinates(barcode, r=28) # Returns array of length 7 Combining Methods ================= Use multiple vectorization methods together: :: from medtda.vectorizers import ( persistence_stats, betti_curve, persistence_image ) import numpy as np # Extract using multiple methods stats = persistence_stats(barcode) curve = betti_curve(barcode, resolution=100) image = persistence_image(barcode, resolution=20).flatten() # Concatenate all features combined = np.concatenate([stats, curve, image]) print(f"Combined feature vector size: {len(combined)}") # Size: 13 + 100 + 400 = 513 Multi-Dimensional Barcodes =========================== Vectorize each homology dimension separately: :: from medtda import BarcodeExtractor from medtda.vectorizers import persistence_image import numpy as np # Extract barcodes extractor = BarcodeExtractor(max_dimension=2) barcodes = extractor.execute(image_3d) # Vectorize each dimension features = {} for dim in [0, 1, 2]: if len(barcodes[f'H{dim}']) > 0: pi = persistence_image(barcodes[f'H{dim}'], resolution=20) features[f'H{dim}'] = pi.flatten() else: # Handle empty barcodes features[f'H{dim}'] = np.zeros(400) # Concatenate all dimensions all_features = np.concatenate([ features['H0'], features['H1'], features['H2'] ]) print(f"Total feature vector: {len(all_features)}") # 1200 See Also ======== * :doc:`persistent_homology` - Computing barcodes * :doc:`barcodes_diagrams` - Understanding barcodes * :doc:`../user_guide/vectorization` - Vectorization user guide * :doc:`../api/vectorizers` - Vectorization API reference