Chatomics Field GuideWhat They Don't Teach You

Comparison · spatial

Squidpy vs Seurat: Which One Should You Use?

Both cluster spatial spots, but only one of them can tell you whether two cell types actually sit next to each other more than chance.

By Ming "Tommy" Tang, Director of Bioinformatics in Big Pharma · Reviewed September 2026 · 5 min read

The verdict

For the typical reader here, coming from a scRNA-seq background and working with 10x Visium spot data, default to Seurat. Load10X_Spatial() gets you from Space Ranger output to a working object in one call, SCTransform and the standard clustering workflow are the same functions you already trust from scRNA-seq, and FindTransferAnchors()/TransferData() let you map labels from an existing scRNA-seq reference onto spatial spots without leaving the object. If your whole pipeline, references, and collaborators are already in R, staying in Seurat avoids a conversion step that is fragile in practice.

Switch to Squidpy, or add it alongside Seurat, the moment your question is actually spatial rather than just visualized spatially. If you need a permutation-tested answer to "do these two cell types co-localize more than chance," that's sq.gr.nhood_enrichment(), and Seurat has no native equivalent. If you're on an imaging-based platform like Xenium or MERFISH where segmentation is part of the job, Squidpy's built-in watershed and Cellpose/StarDist integration handle that in the same object that computes your spatial graph. And if deconvolution accuracy actually matters for the conclusion, skip both tools' native options and go straight to cell2location or RCTD, since the benchmark data doesn't favor either Seurat or Squidpy there.

Seurat and Squidpy start from different assumptions about what your spatial data already looks like. Seurat is the R toolkit that already runs your scRNA-seq QC, normalization and clustering, and it extends the same S4 object to hold spatial coordinates and tissue images, so Load10X_Spatial() drops Visium output straight into the object type you already know. Squidpy assumes you're already on the Python side with an AnnData object from Scanpy, and it doesn't try to own QC or clustering at all; it adds spatial-specific graph statistics and image analysis on top of a pipeline you've already built.

That difference shows up hardest in what each tool treats as the core spatial operation. Seurat mostly runs the standard scRNA-seq workflow, PCA, UMAP, FindNeighbors/FindClusters, on the spatial expression matrix, then layers spatial visualization on top with SpatialDimPlot() and SpatialFeaturePlot(). Its dedicated spatial statistic, FindSpatiallyVariableFeatures(), asks which genes have a spatial pattern using Moran's I or a mark variogram. Squidpy instead builds an explicit spatial neighbor graph with sq.gr.spatial_neighbors() and runs a permutation test on top of it, sq.gr.nhood_enrichment(), that asks a different question: do these two cell types sit next to each other in tissue more often than random chance would predict.

The gap widens further on imaging-based platforms. Squidpy ships built-in segmentation, Otsu-thresholded watershed or hooks into pre-trained Cellpose and StarDist models, tied to the same object that computes your spatial statistics. Seurat has no equivalent; it assumes cells arrive already segmented. For Visium spot data that distinction barely matters. For Xenium or MERFISH, it's the difference between one pipeline and three.

Head to head

CriterionSquidpySeuratEdge
Object model and home ecosystemAnnData object in Python, built on the scverse stack (Scanpy, scvi-tools); spatial coords live in obsm['spatial'] by convention.Seurat S4 object in R, the same object type used for scRNA-seq QC, normalization and clustering, extended with spatial-specific slots and images.Tie
Neither is wrong; the choice mostly follows whatever language the rest of your pipeline already uses.
Loading 10x Visium dataNo dedicated Visium loader; you read Space Ranger output through Scanpy's spatial reader and get an AnnData object with coordinates in obsm['spatial'].Load10X_Spatial() reads Space Ranger output directly into a Seurat object in one call, images and coordinates included.Seurat
Spatial statistics (neighborhood enrichment, co-occurrence)sq.gr.spatial_neighbors() builds a connectivity graph, then sq.gr.nhood_enrichment() runs a permutation test (1000 permutations by default) producing z-scores for whether cluster pairs co-localize more than chance.No native permutation-based neighborhood enrichment; spatial pattern detection is done gene-by-gene via FindSpatiallyVariableFeatures() using Moran's I or mark variogram, a different statistical question.Squidpy
Image segmentation for imaging-based platformsBuilt-in SegmentationWatershed (Otsu thresholding) plus SegmentationCustom hooks for pre-trained Cellpose, StarDist and Keras/TensorFlow models, tied to the same object.No native segmentation; you bring in already-segmented cells or coordinates from an external pipeline.Squidpy
Cell-type deconvolutionNot a built-in feature; you export the AnnData object and run a dedicated tool (cell2location, RCTD) separately.Ships deconvolution support, but a 2024 benchmark found it underperforms cell2location, RCTD and spatialDWLS, the top three methods across gold-standard tests.Tie
Neither tool's own deconvolution is the state of the art; both send you outward to cell2location or RCTD for that step.
Label transfer from an scRNA-seq referenceRelies on the broader scverse ecosystem (e.g. scvi-tools, Scanpy's ingest) rather than a Squidpy-native function.FindTransferAnchors() and TransferData() map cell-type labels from an existing scRNA-seq Seurat object onto spatial spots in a couple of lines.Seurat
Normalization defaultsInherits Scanpy conventions, typically total-count normalization and log1p, before any spatial step runs.SCTransform, which models sequencing depth variance across spots directly and produces Pearson residuals, not log-normalized counts.Tie
Different statistical objects; downstream thresholds are not interchangeable between the two.
Visium HD / very large spot countsScales through the same AnnData sparse-matrix backing Scanpy uses, but no HD-specific workflow is documented here.Seurat v5's sketch clustering workflow is built specifically for Visium HD and improves recovery of rare, spatially restricted clusters over standard clustering.Seurat
Cross-tool interoperabilityReading an AnnData object converted from Seurat requires checking that spatial coordinates landed in obsm['spatial'] and that assay/obsm names survived the conversion.Packages like SeuratIntegrate do runtime in-RAM conversion to AnnData to borrow Python methods, but naming conventions are not guaranteed to match.Tie
Conversion is possible both directions but fragile enough that most people avoid ping-ponging mid-analysis.
Documentation and worked examplesAPI-reference-heavy docs with focused tutorial notebooks per analysis type (neighborhood enrichment, segmentation, image features).Long-form vignettes (spatial_vignette, pbmc3k_tutorial) that walk a full workflow end to end, backed by a large scRNA-seq user base and years of forum answers.Seurat

Use Squidpy when

  • You're already working in Scanpy/AnnData for the rest of the project and don't want a second object format to maintain.
  • You need an actual statistical test for cell-type co-localization (neighborhood enrichment z-scores), not just a visual impression from a spatial plot.
  • You're on an imaging-based platform (Xenium, MERFISH, CosMx) where you need segmentation built into the same pipeline that computes spatial statistics.
  • You want to layer in other scverse tools (scvi-tools, image feature extraction) around the same graph object without exporting to R.
  • You need co-occurrence or ripley statistics on cell-type spatial distributions, which fall inside Squidpy's graph-based toolkit.

Use Seurat when

  • Your lab's scRNA-seq work already lives in Seurat objects and you want the spatial step to reuse the same normalization, clustering and plotting functions.
  • You're working with 10x Visium data and want Load10X_Spatial() plus SpatialDimPlot()/SpatialFeaturePlot() without writing a custom reader.
  • You need to transfer cell-type labels from an existing scRNA-seq reference onto spatial spots via FindTransferAnchors()/TransferData().
  • You're analyzing Visium HD data and want the v5 sketch clustering workflow built for that resolution.
  • You need to merge multiple tissue sections or biological replicates into one object for joint clustering and comparison.
  • Your collaborators or reviewers expect R-based, vignette-style reproducible code rather than a Python notebook.

Switching between them

Moving a project from Seurat to Squidpy (or back) is not a drop-in swap. Object format changes completely: Seurat's S4 object with named assays and images becomes an AnnData object where spatial coordinates must sit in obsm['spatial'] for Squidpy and Scanpy to recognize them automatically; conversion tools exist (SeuratIntegrate's in-RAM conversion, sceasy-style bridges) but are sensitive to assay and obsm naming, so verify the coordinates actually landed where they should rather than trusting a successful conversion. Normalization is not comparable across tools: Seurat's SCTransform produces Pearson residuals designed to stabilize variance across spot depth, while a typical Squidpy/Scanpy pipeline runs total-count normalization plus log1p; don't carry SCTransform-scaled values into a Squidpy workflow expecting the same downstream thresholds. Spatial statistics answer different questions and their outputs are not interchangeable: Seurat's FindSpatiallyVariableFeatures() (Moran's I or mark variogram) flags genes with spatial expression patterns, while Squidpy's nhood_enrichment() permutation test flags which cluster labels sit next to each other more than chance; migrating a "significant spatially variable gene" claim into a "neighborhood enrichment" claim, or vice versa, is a category error. Finally, if deconvolution is part of the pipeline, moving between Seurat and Squidpy doesn't solve the accuracy problem either way, since both send you to a dedicated tool like cell2location or RCTD for that step.

Pitfalls with either

  • Treating Seurat's spatially variable gene test (Moran's I / mark variogram) as equivalent to Squidpy's neighborhood enrichment z-score; they test different hypotheses, one about single-gene spatial autocorrelation, the other about cluster co-localization, so pick the function that matches the question you're actually asking.
  • Calling sq.gr.nhood_enrichment() before running sq.gr.spatial_neighbors(); Squidpy's workflow is explicitly two-step and enrichment needs the connectivity graph already computed, so always build the spatial neighbor graph first.
  • Reaching for Seurat's built-in deconvolution and trusting the cell-type proportions at face value; the 2024 benchmark found it trails cell2location, RCTD and spatialDWLS, so run one of those three separately if proportions matter for the conclusion.
  • Converting an AnnData object to Seurat (or back) and assuming dimensions matching means the conversion worked; check that spatial coordinates actually landed in the expected slot (obsm['spatial'] on the Python side) since naming mismatches silently break spatial-aware functions downstream.
  • Feeding SCTransform-normalized values into a Squidpy pipeline that expects Scanpy-style log-normalized counts; renormalize with the target ecosystem's convention instead of assuming the numbers transfer.
  • Picking a tool because it's the one you already know rather than the one that matches your platform; Seurat has no native segmentation for imaging-based data (Xenium, MERFISH), and Squidpy has no Visium-native loader or label-transfer function, so platform and downstream question should decide, not habit.

Questions people ask

Can I use Squidpy and Seurat together on the same project?

Yes, but expect friction. Converting between AnnData and Seurat objects is possible through tools like SeuratIntegrate's in-RAM conversion, but it's sensitive to naming conventions, and you should verify spatial coordinates landed in the expected slot (obsm['spatial'] on the Python side) rather than trusting a successful conversion at face value.

Does Squidpy replace Seurat for Visium data?

No. Squidpy doesn't do QC, normalization or clustering; it assumes those already happened in Scanpy and adds spatial statistics and image analysis on top of an existing AnnData object. If you need to go from raw Space Ranger output to clusters, Seurat's Load10X_Spatial() plus its standard workflow is the more direct path.

Which tool is better for cell-type deconvolution?

Neither, by the numbers. A 2024 benchmark found Seurat's built-in deconvolution underperforms cell2location, RCTD and spatialDWLS. Squidpy doesn't ship deconvolution at all. For that step, plan to export your data and run RCTD or cell2location as a dedicated tool regardless of which one you use for the rest of the analysis.

Do Seurat and Squidpy give the same spatial statistics?

No, they test different things. Seurat's FindSpatiallyVariableFeatures() flags individual genes with spatially patterned expression using Moran's I or a mark variogram. Squidpy's nhood_enrichment() is a permutation test on cluster labels over a spatial connectivity graph, asking whether cell types co-localize more than chance. Don't treat a result from one as equivalent to the other.

Which tool should a beginner learn first for spatial transcriptomics?

If you're coming from scRNA-seq and already know Seurat, start there for Visium data since the object model and functions carry over directly. Add Squidpy once you need permutation-based spatial statistics or you're working with imaging-based platforms that require built-in segmentation.

Related pages

Related reading on the blog

Sources

  1. Analysis, visualization, and integration of spatial datasets with Seurat — Seurat spatial workflow: Load10X_Spatial, SCTransform, FindSpatiallyVariableFeatures, label transfer, merge
  2. Neighbors enrichment analysis - Squidpy — How Squidpy's neighborhood enrichment permutation test works and what the z-scores mean
  3. squidpy.gr.nhood_enrichment API reference — Parameters and output structure for sq.gr.spatial_neighbors and sq.gr.nhood_enrichment
  4. Systematic evaluation of robustness of deconvolution methods in spatial transcriptomics — Benchmark showing Seurat deconvolution underperforms cell2location, RCTD, spatialDWLS
  5. Squidpy: a scalable framework for spatial omics analysis — Original Squidpy publication describing its modular, graph-based design
  6. Cell-segmentation for fluorescence images - Squidpy documentation — Squidpy's built-in watershed segmentation and Cellpose/StarDist integration
  7. Single-cell best practices: Neighborhood analysis — General framing of clustering-based vs graph-based neighborhood analysis approaches