Back to Blog
ScanpySingle-Cell

Install Scanpy for Single-Cell Analysis with Conda (Apple Silicon)

Install Scanpy 1.12.2 in an isolated conda environment on an Apple Silicon Mac, with native arm64 packages, Leiden clustering support, and a real smoke test.

SSSudipta SardarJuly 20, 20268 min read
Install Scanpy for Single-Cell Analysis with Conda (Apple Silicon)

Scanpy is the workhorse of single-cell analysis in Python. If you work with scRNA-seq data, it covers the whole pipeline: quality control, normalization, PCA, neighbor graphs, UMAP, Leiden clustering, and differential expression, all built on top of the AnnData data structure. It pulls in a big scientific stack (NumPy, SciPy, pandas, scikit-learn, numba, matplotlib), which is exactly why you want it in its own conda environment rather than smeared across your base install.

The idea behind this whole series is simple: the exact build a tool needs lives inside its environment, independent of your system Python. Break something, delete the env, recreate it in under a minute. Nothing else on your machine notices. This guide installs Scanpy that way on an Apple Silicon Mac, with native arm64 binaries.

Prerequisites

You need a working conda. If you have not set that up yet, start with our Miniconda on Apple Silicon guide and come back here. This walkthrough was run on an Apple Silicon Mac (arm64), macOS 26.5.2, with conda 25.5.1 using the fast libmamba solver.

TL;DR: copy-paste install

bash
conda create -n bu-scanpy --override-channels -c conda-forge -c bioconda scanpy leidenalg python-igraph -y
conda activate bu-scanpy
python -c "import scanpy as sc; print(sc.__version__)"

That is it. The three important choices baked into that one line: an isolated env named bu-scanpy, --override-channels so conda never touches the defaults channel (which trips a Terms-of-Service gate on conda 25.x), and leidenalg + python-igraph added explicitly so clustering works out of the box.

Step-by-step

1. Create an isolated environment

bash
conda create -n bu-scanpy --override-channels -c conda-forge -c bioconda scanpy leidenalg python-igraph -y

A few things worth understanding here:

  • --override-channels -c conda-forge -c bioconda tells conda to use only these two channels and skip defaults. On conda 25.x, touching defaults throws a CondaToSNonInteractiveError about Terms of Service; sidestepping it entirely is cleaner than accepting the ToS.
  • conda-forge is listed first, on purpose. Scanpy ships as a fresh noarch package (1.12.2) on conda-forge. The bioconda copy is frozen at an ancient 1.7.2, so channel order matters: conda-forge wins and you get the modern release.
  • leidenalg and python-igraph are separate packages. Leiden clustering is optional and is not pulled in automatically. Under conda you install these two individually (the pip-style scanpy[leiden] extra does not apply here).

Here is the real solved plan, trimmed to the key packages:

Channels:
 - conda-forge
 - bioconda
Platform: osx-arm64
## Package Plan ##
  environment location: /opt/homebrew/Caskroom/miniconda/base/envs/bu-scanpy
  added / updated specs:
    - leidenalg
    - python-igraph
    - scanpy
The following packages will be downloaded:
    package                    |            build
    ---------------------------|-----------------
    anndata-0.13.2             |     pyhd8ed1ab_0         142 KB  conda-forge
    igraph-1.0.1               |       h1ee73af_0         1.5 MB  conda-forge
    leidenalg-0.12.0           |  py314h4ed92d5_0          86 KB  conda-forge
    numpy-2.4.6                |  py314hb79c6fa_0         6.7 MB  conda-forge
    python-igraph-1.0.0        |  py314h93ecee7_0         633 KB  conda-forge
    scanpy-1.12.2              |     pyhd8ed1ab_0         1.9 MB  conda-forge
    scipy-1.18.0               |  py314h18e1515_0        13.5 MB  conda-forge
    ------------------------------------------------------------
                                           Total:       100.7 MB

And the tail of the run:

#
# To activate this environment, use
#
#     $ conda activate bu-scanpy
#
# To deactivate an active environment, use
#
#     $ conda deactivate
real 44.57
# ...

The whole solve-and-install took about 45 seconds (real 44.57 seconds) and downloaded 100.7 MB. Unpacked, the environment measures about 766 MB on disk (measured with du -sh; because conda hardlinks shared packages, the incremental cost of an extra env is usually less), most of it the compiled scientific stack (SciPy, scikit-learn, numba/llvmlite behind UMAP). That is normal for a full single-cell setup.

Scanpy 1.12 requires Python 3.12 or newer. Because we did not pin a Python version, conda pulled python-3.14.6 with numpy-2.4.6. If a downstream dependency of yours needs a specific interpreter, add python=3.12 (or your target) to the create command.

2. Activate and confirm

bash
conda activate bu-scanpy

Your prompt should now show (bu-scanpy). Everything below runs inside that env.

Apple Silicon / architecture note

You do not need Rosetta for Scanpy. The scanpy package itself is pure-Python noarch, and every compiled dependency it needs has a native osx-arm64 build on conda-forge. You can see that in the install list: NumPy, SciPy, leidenalg and Python all resolved to osx-arm64, while the pure-Python pieces resolved to noarch:

  leidenalg          conda-forge/osx-arm64::leidenalg-0.12.0-py314h4ed92d5_0
  numpy              conda-forge/osx-arm64::numpy-2.4.6-py314hb79c6fa_0
  python             conda-forge/osx-arm64::python-3.14.6-h156bc91_100_cp314
  scanpy             conda-forge/noarch::scanpy-1.12.2-pyhd8ed1ab_0
  scipy              conda-forge/osx-arm64::scipy-1.18.0-py314h18e1515_0

So a plain conda create ... -c conda-forge -c bioconda on an M-series Mac gives you native arm64 binaries. Do not force CONDA_SUBDIR=osx-64 for a normal Scanpy setup, that would run everything under Rosetta and only slow you down. Reserve that fallback for the rare case where some unrelated package you add later has no arm64 build.

Verify the install

Scanpy is a Python-first tool, so we verify by importing it and running a tiny real analysis. Build a small AnnData matrix, normalize it, and log-transform it:

python
import scanpy as sc
import numpy as np
from anndata import AnnData

adata = AnnData(np.random.poisson(1.0, size=(50, 20)).astype("float32"))
sc.pp.normalize_total(adata)
sc.pp.log1p(adata)
print(f"scanpy {sc.__version__} AnnData {adata.shape} normalized+logged OK")

Real output on the test machine:

scanpy 1.12.2 AnnData (50, 20) normalized+logged OK

That single line proves a lot: Scanpy 1.12.2 imported cleanly, AnnData built a 50-cell by 20-gene matrix, and the two most common preprocessing steps (normalize_total then log1p) both ran. If you get that, your environment is healthy.

To confirm the clustering libraries loaded too:

bash
python -c "import leidenalg, igraph; print('leiden ok')"
The AnnData smoke test above was executed on the test machine and its output is copied verbatim. The leiden ok check is a doc-grounded sanity command; run it yourself to confirm your own env, as its output was not captured in this transcript.

Common errors and fixes

ErrorFix
CondaToSNonInteractiveError / "Terms of Service have not been accepted" for defaultsDo not use defaults. Create the env with --override-channels -c conda-forge -c bioconda so conda never touches it.
PackagesNotFoundError: scanpyScanpy lives on conda-forge, not defaults. Add -c conda-forge with --override-channels.
You got scanpy 1.7.2 (very old)That is the stale bioconda build. List conda-forge first so it wins, and you get 1.12.2.
ModuleNotFoundError when calling sc.tl.leidenLeiden is optional. Install leidenalg and python-igraph into the same env (both are in the command above).
"scanpy requires a different Python" when pinning python=3.10 or 3.11Scanpy 1.12.x needs Python 3.12+. Use python=3.12, or pin an older scanpy=1.9 for older Python.
Import-time NumPy ABI errors after pip install scanpyLet conda own the whole scientific stack. Do not pip-install numpy/scipy/scanpy on top of conda-provided ones.

Managing the environment

Update everything to the latest compatible builds:

bash
conda update -n bu-scanpy --override-channels -c conda-forge -c bioconda --all

Snapshot the env so a collaborator can reproduce it exactly:

bash
conda env export -n bu-scanpy --no-builds > environment.yml

For a truly reproducible setup, pin the versions you care about, for example scanpy=1.12.2 anndata=0.13.* python=3.12. That stops a future install from silently pulling a newer NumPy and drifting your results.

Remove the env entirely when you are done, this is the payoff of isolation:

bash
conda remove -n bu-scanpy --all

Nothing else on your system is affected, and you can rebuild in about 45 seconds.

Next steps