Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Build spatial neighbor graphs for spatial transcriptomics data using Squidpy. Compute k-nearest neighbors, Delaunay triangulation, and radius-based connectivity for downstream spatial analyses. Use when building spatial neighborhood graphs.
.claude/skills/bio-spatial-transcriptomics-spatial-neighbors/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-15 | ✗→✓ | ▲ Improved | — | — |
| case-08 | ✗→✓ | ▲ Improved | — | — |
| case-16 | ✗→✓ | ▲ Improved | — | — |
| case-14 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
Reference examples tested with: matplotlib 3.8+, numpy 1.26+, scanpy 1.10+, scikit-learn 1.4+, scipy 1.12+, squidpy 1.3+
Before using code patterns, verify installed versions match. If versions differ:
pip show <package> then help(module.function) to check signaturesIf code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.
"Build a spatial neighborhood graph" → Construct spatial connectivity graphs using k-nearest neighbors, Delaunay triangulation, or radius-based methods for downstream spatial statistics.
squidpy.gr.spatial_neighbors(adata, coord_type='generic', n_neighs=6)Build spatial neighbor graphs for connectivity-based analyses.
pythonimport squidpy as sq import scanpy as sc import numpy as np
Goal: Construct a spatial KNN graph connecting each spot to its nearest spatial neighbors.
Approach: Use Squidpy's spatial_neighbors with k-nearest neighbors on coordinate distances.
python# Build spatial KNN graph sq.gr.spatial_neighbors(adata, n_neighs=6, coord_type='generic') # Check the graph print(f"Connectivities shape: {adata.obsp['spatial_connectivities'].shape}") print(f"Distances shape: {adata.obsp['spatial_distances'].shape}")
python# Delaunay triangulation (natural neighbors) sq.gr.spatial_neighbors(adata, delaunay=True, coord_type='generic')
python# Connect all spots within a radius sq.gr.spatial_neighbors(adata, radius=100, coord_type='generic')
python# For Visium hexagonal grid, use n_rings sq.gr.spatial_neighbors(adata, n_rings=1, coord_type='grid') # 6 immediate neighbors sq.gr.spatial_neighbors(adata, n_rings=2, coord_type='grid') # Extended neighborhood
python# Get connectivities as sparse matrix conn = adata.obsp['spatial_connectivities'] print(f'Edges in graph: {conn.nnz}') print(f'Mean neighbors per spot: {conn.nnz / adata.n_obs:.1f}') # Get distances dist = adata.obsp['spatial_distances'] nonzero_dist = dist.data[dist.data > 0] print(f'Mean neighbor distance: {nonzero_dist.mean():.1f}')
pythonfrom scipy.sparse import csr_matrix spot_idx = 0 conn = adata.obsp['spatial_connectivities'] # Get neighbor indices neighbor_indices = conn[spot_idx].nonzero()[1] print(f'Spot {spot_idx} has {len(neighbor_indices)} neighbors: {neighbor_indices}') # Get distances to neighbors dist = adata.obsp['spatial_distances'] neighbor_distances = dist[spot_idx, neighbor_indices].toarray().flatten() print(f'Distances: {neighbor_distances}')
python# Standard expression-based neighbors (for comparison) sc.pp.neighbors(adata, n_neighbors=15, n_pcs=30) # Now adata has both: # - adata.obsp['spatial_connectivities'] (spatial) # - adata.obsp['connectivities'] (expression)
Goal: Create a unified neighbor graph that balances spatial proximity with expression similarity.
Approach: Build separate spatial and expression neighbor graphs, normalize each, then combine with a tunable weight parameter.
python# Build both graphs sq.gr.spatial_neighbors(adata, n_neighs=6, coord_type='generic') sc.pp.neighbors(adata, n_neighbors=15, n_pcs=30) # Weighted combination (manual) alpha = 0.5 # Weight for spatial vs expression spatial_conn = adata.obsp['spatial_connectivities'] expr_conn = adata.obsp['connectivities'] # Normalize and combine from sklearn.preprocessing import normalize spatial_norm = normalize(spatial_conn, norm='l1', axis=1) expr_norm = normalize(expr_conn, norm='l1', axis=1) combined = alpha * spatial_norm + (1 - alpha) * expr_norm adata.obsp['combined_connectivities'] = combined
Goal: Display the spatial neighbor graph overlaid on tissue coordinates for visual inspection.
Approach: Draw edges between connected spots and scatter plot the spot positions.
pythonimport matplotlib.pyplot as plt # Get coordinates coords = adata.obsm['spatial'] conn = adata.obsp['spatial_connectivities'] fig, ax = plt.subplots(figsize=(10, 10)) # Draw edges rows, cols = conn.nonzero() for i, j in zip(rows, cols): if i < j: # Avoid drawing twice ax.plot([coords[i, 0], coords[j, 0]], [coords[i, 1], coords[j, 1]], 'k-', alpha=0.1, linewidth=0.5) # Draw nodes ax.scatter(coords[:, 0], coords[:, 1], s=10, c='blue', alpha=0.5) ax.set_aspect('equal') plt.title('Spatial neighbor graph')
Goal: Calculate summary statistics of the spatial neighbor graph (nodes, edges, connectivity).
Approach: Convert the sparse connectivity matrix to a NetworkX graph and compute standard graph metrics.
pythonimport networkx as nx from scipy.sparse import csr_matrix conn = adata.obsp['spatial_connectivities'] G = nx.from_scipy_sparse_array(conn) print(f'Nodes: {G.number_of_nodes()}') print(f'Edges: {G.number_of_edges()}') print(f'Average degree: {2 * G.number_of_edges() / G.number_of_nodes():.2f}') print(f'Connected components: {nx.number_connected_components(G)}')
python# Store different neighborhood sizes for n_neighs in [4, 6, 10]: sq.gr.spatial_neighbors(adata, n_neighs=n_neighs, coord_type='generic') adata.obsp[f'spatial_conn_{n_neighs}'] = adata.obsp['spatial_connectivities'].copy() adata.obsp[f'spatial_dist_{n_neighs}'] = adata.obsp['spatial_distances'].copy()
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.