# Tangram Mapping Validation Validate and troubleshoot Tangram spatial mappings using cross-validation, diagnostic plots, and quality metrics. ## Why Validate? - Ensure mapping is biologically meaningful - Identify problematic genes or cell types - Compare different mapping strategies - Build confidence before downstream analysis --- ## Validation Approaches 1. **Training score analysis**: How well do training genes match? 2. **Test gene prediction**: Can we predict held-out genes? 3. **Cross-validation**: Systematic holdout testing 4. **Biological validation**: Do patterns match known biology? --- ## Step 1: Basic Quality Check After running `tangram_map_cells`, check training scores: ```python import scanpy as sc import tangram as tg ad_map = sc.read_h5ad("experiments/tangram_mapping.h5ad") # Training gene scores train_df = ad_map.uns["train_genes_df"] print(f"Average training score: {train_df['train_score'].mean():.3f}") print(f"Median training score: {train_df['train_score'].median():.3f}") # Score distribution print("\nScore distribution:") print(train_df['train_score'].describe()) # Top and bottom genes print("\nTop 10 genes:") print(train_df.head(10)) print("\nBottom 10 genes:") print(train_df.tail(10)) ``` **Interpretation**: - Average > 0.7: Good mapping - Average 0.5-0.7: Moderate, may need tuning - Average < 0.5: Poor, check data compatibility --- ## Step 2: Training Diagnostic Plots **Tool**: `tangram_evaluate` Or manually generate plots: ```python import tangram as tg import matplotlib.pyplot as plt # 4-panel diagnostic plot tg.plot_training_scores(ad_map, bins=20, alpha=0.5) plt.savefig("training_diagnostics.png", dpi=150, bbox_inches="tight") ``` **Panels explained**: 1. **Score histogram**: Distribution of gene scores 2. **Score vs SC sparsity**: Low sparsity genes should score high 3. **Score vs SP sparsity**: Sparse spatial genes often score low 4. **Score vs sparsity diff**: Large diff = dropout mismatch --- ## Step 3: Test Gene Evaluation Project genes and evaluate predictions: ```python # Project genes ad_ge = tg.project_genes(ad_map, adata_sc) ad_ge.write_h5ad("experiments/tangram_projected.h5ad") # Compare with spatial data df_genes = tg.compare_spatial_geneexp(ad_ge, adata_sp, adata_sc) # Separate training and test train_genes = df_genes[df_genes["is_training"] == True] test_genes = df_genes[df_genes["is_training"] == False] print(f"Training genes: {len(train_genes)}, avg score: {train_genes['score'].mean():.3f}") print(f"Test genes: {len(test_genes)}, avg score: {test_genes['score'].mean():.3f}") ``` --- ## Step 4: AUC Evaluation The AUC metric captures overall prediction quality: ```python # Compute AUC metrics, coords = tg.eval_metric(df_genes) print(f"AUC score: {metrics['auc_score']:.3f}") # Plot AUC curve tg.plot_auc(df_genes) plt.savefig("auc_curve.png", dpi=150) ``` **Interpretation**: - AUC > 0.6: Good prediction on test genes - AUC 0.4-0.6: Moderate - AUC < 0.4: Poor, mapping may not generalize --- ## Step 5: Cross-Validation (Optional) For rigorous validation, use leave-one-out or k-fold CV: ```python # Leave-one-out CV (slow but thorough) cv_dict = tg.cross_val( adata_sc, adata_sp, cluster_label="cell_type", mode="clusters", cv_mode="loo", # or "10fold" num_epochs=500, device="cuda:0", verbose=True, ) print(f"CV avg train score: {cv_dict['avg_train_score']:.3f}") print(f"CV avg test score: {cv_dict['avg_test_score']:.3f}") ``` --- ## Step 6: Biological Validation Check that mapped cell types match expected patterns: ```python import scanpy as sc import matplotlib.pyplot as plt adata_sp = sc.read_h5ad("experiments/tangram_annotated.h5ad") # Known spatial patterns to verify # Example: T cells should be in lymphoid regions fig, axes = plt.subplots(2, 3, figsize=(15, 10)) cell_types = ['T cells', 'B cells', 'Macrophages', 'Epithelial', 'Fibroblasts', 'Endothelial'] for ax, ct in zip(axes.flat, cell_types): if ct in adata_sp.obsm["tangram_ct_pred"].columns: adata_sp.obs["_tmp"] = adata_sp.obsm["tangram_ct_pred"][ct] sc.pl.spatial(adata_sp, color="_tmp", ax=ax, show=False, title=ct) del adata_sp.obs["_tmp"] plt.tight_layout() plt.savefig("celltype_validation.png", dpi=150) ``` --- ## Troubleshooting Low Scores ### Problem: Low training scores **Causes**: - Few shared genes between datasets - Different normalization - Batch effects **Solutions**: ```python # Check gene overlap sc_genes = set(adata_sc.var_names.str.lower()) sp_genes = set(adata_sp.var_names.str.lower()) overlap = sc_genes & sp_genes print(f"Shared genes: {len(overlap)}") # Ensure same normalization print(f"SC max: {adata_sc.X.max():.2f}") print(f"SP max: {adata_sp.X.max():.2f}") ``` ### Problem: Good training but poor test scores **Causes**: - Overfitting to training genes - Training genes not representative **Solutions**: - Use more diverse training genes - Increase regularization (lambda_r) - Try `mode="clusters"` for smoother mapping ### Problem: Some cell types poorly mapped **Causes**: - Cell type not in spatial region - Few marker genes for that type **Solutions**: - Check cell type abundance in spatial data - Add more markers for underperforming types --- ## Comparing Mapping Strategies Test different approaches: ```python results = [] # Strategy 1: Cells mode ad_map1 = tg.map_cells_to_space(adata_sc, adata_sp, mode="cells", num_epochs=500) score1 = ad_map1.uns["train_genes_df"]["train_score"].mean() results.append(("cells", score1)) # Strategy 2: Clusters mode ad_map2 = tg.map_cells_to_space(adata_sc, adata_sp, mode="clusters", cluster_label="cell_type", num_epochs=500) score2 = ad_map2.uns["train_genes_df"]["train_score"].mean() results.append(("clusters", score2)) # Compare for mode, score in results: print(f"{mode}: {score:.3f}") ``` --- ## Quality Checklist Before using mapping results: - [ ] Average training score > 0.7 - [ ] AUC score > 0.5 - [ ] Top genes include expected markers - [ ] Cell type patterns match known biology - [ ] No obvious artifacts in spatial plots - [ ] Sparsity analysis shows expected dropout pattern