A Python implementation of SoupX for removing ambient RNA contamination from droplet-based single-cell RNA sequencing data.
Droplet-based single-cell RNA sequencing (scRNA-seq) experiments contain ambient RNA contamination from cell-free mRNAs present in the input solution. This "soup" of background contamination can significantly confound biological interpretation, particularly in complex tissues where contamination rates can exceed 20%.
SoupX addresses this by:
- Estimating the ambient RNA expression profile from empty droplets
- Quantifying contamination fraction in each cell using marker genes
- Correcting cell expression profiles by removing estimated background
This Python implementation maintains full compatibility with the original R package interface while integrating seamlessly with the Python scRNA-seq ecosystem (scanpy, anndata).
This implementation is based on the method described in:
Young, M.D., Behjati, S. SoupX removes ambient RNA contamination from droplet-based single-cell RNA sequencing data. GigaScience 9, giaa151 (2020). https://doi.org/10.1093/gigascience/giaa151
Please cite the original paper if you use this implementation in your research.
pip install soupx-pythongit clone https://github.com/NiRuff/soupx-python.git
cd soupx-python
pip install -e .- Python ≥3.8
- numpy ≥1.19.0
- pandas ≥1.2.0
- scipy ≥1.6.0
- statsmodels ≥0.12.0
- scanpy ≥1.7.0 (optional, for integration examples)
import soupx
# Load 10X data (cellranger output directory)
sc = soupx.load10X("path/to/cellranger/outs/")
# Automatically estimate contamination
sc = soupx.autoEstCont(sc)
# Generate corrected count matrix
corrected_counts = soupx.adjustCounts(sc)import scanpy as sc
import soupx
import pandas as pd
# Load raw 10X data with both filtered and raw counts
adata_raw = sc.read_10x_mtx("path/to/raw_feature_bc_matrix/", cache=True)
adata_filtered = sc.read_10x_mtx("path/to/filtered_feature_bc_matrix/", cache=True)
# Create SoupChannel
soup_channel = soupx.SoupChannel(
tod=adata_raw.X.T.tocsr(), # raw counts (genes × droplets)
toc=adata_filtered.X.T.tocsr(), # filtered counts (genes × cells)
metaData=pd.DataFrame(index=adata_filtered.obs_names)
)
# Add clustering information (essential for good results)
sc.tl.leiden(adata_filtered, resolution=0.5)
soup_channel.setClusters(adata_filtered.obs['leiden'].values)
# Estimate and remove contamination
soup_channel = soupx.autoEstCont(soup_channel, verbose=True)
corrected_matrix = soupx.adjustCounts(soup_channel)
# Replace counts in AnnData object
adata_corrected = adata_filtered.copy()
adata_corrected.X = corrected_matrix.T # Convert back to cells × genes
# Continue with standard scanpy workflow
sc.pp.highly_variable_genes(adata_corrected)
sc.tl.pca(adata_corrected)
# ... further analysisFor experiments where automatic estimation fails or when you have prior biological knowledge:
# Manually specify contamination fraction
soup_channel.set_contamination_fraction(0.10) # 10% contamination
# Or use specific marker genes (e.g., hemoglobin genes for tissue samples)
hemoglobin_genes = ['HBA1', 'HBA2', 'HBB', 'HBD', 'HBG1', 'HBG2']
non_expressing = soupx.estimateNonExpressingCells(
soup_channel,
hemoglobin_genes,
clusters=soup_channel.metaData['clusters'].values
)
# Calculate contamination using marker genes
soup_channel = soupx.calculateContaminationFraction(
soup_channel,
{'HB': hemoglobin_genes},
non_expressing
)# Different correction methods available:
# 1. Subtraction (default, fastest)
corrected = soupx.adjustCounts(soup_channel, method="subtraction")
# 2. Multinomial (most accurate, slower)
corrected = soupx.adjustCounts(soup_channel, method="multinomial")
# 3. SoupOnly (removes only confidently contaminated genes)
corrected = soupx.adjustCounts(soup_channel, method="soupOnly")
# Round to integers (some downstream tools require this)
corrected = soupx.adjustCounts(soup_channel, roundToInt=True)Main container for scRNA-seq data and contamination analysis.
Parameters:
tod: Raw count matrix (genes × droplets, sparse)toc: Filtered count matrix (genes × cells, sparse)metaData: Cell metadata DataFramecalcSoupProfile: Whether to estimate soup profile automatically (default: True)
Automatically estimate contamination fraction using marker genes.
Parameters:
tfidfMin: Minimum tf-idf for marker genes (default: 1.0)soupQuantile: Quantile threshold for soup genes (default: 0.9)verbose: Print progress information (default: True)
Remove contamination and return corrected count matrix.
Parameters:
method: Correction method ("subtraction", "multinomial", "soupOnly")roundToInt: Round results to integers (default: False)clusters: Cluster assignments (improves accuracy)
Load 10X CellRanger output directory.
Identify cluster marker genes using tf-idf.
This implementation has been validated against the original R version using:
- Species-mixing experiments: Cross-species contamination quantification
- PBMC datasets: Standard benchmark with known marker genes
- Complex tissue samples: Kidney tumor and fetal liver data
Key validation results:
- Contamination estimates: R² > 0.95 correlation with R implementation
- Correction accuracy: >90% reduction in cross-species contamination
- Marker gene specificity: Consistent improvement in fold-change ratios
- Memory usage: Sparse matrices used throughout to minimize memory footprint
- Clustering improves results: Always provide cluster information when possible
- Method selection: Use "subtraction" for speed, "multinomial" for accuracy
- Large datasets: Consider using
method="soupOnly"for >100k cells
Low marker gene detection:
# Reduce stringency for marker detection
sc = soupx.autoEstCont(sc, tfidfMin=0.5, soupQuantile=0.8)High contamination estimates (>50%):
# Force acceptance of high contamination or manually set
sc.set_contamination_fraction(0.20, forceAccept=True)No clustering information:
# SoupX works without clustering but results are less accurate
corrected = soupx.adjustCounts(sc, clusters=False)| Method | Speed | Accuracy | Requires Empty Droplets | Requires Clustering |
|---|---|---|---|---|
| SoupX | Fast | High | Yes | Recommended |
| CellBender | Slow | High | No | No |
| DecontX | Medium | Medium | No | Yes |
We welcome contributions! Please see our Contributing Guidelines for details.
git clone https://github.com/NiRuff/soupx-python.git
cd soupx-python
pip install -e ".[dev]"
pytest tests/This project is licensed under the GNU General Public License v2.0 - see the LICENSE file for details.
- Full R compatibility
- Automated contamination estimation
- Integration with scanpy ecosystem
- Comprehensive validation suite
- Core correction algorithms
- Manual contamination setting
- Basic 10X data loading
- Initial implementation
- Basic SoupChannel functionality
- Issues: GitHub Issues
- Questions: GitHub Discussions
- Citation: Please cite the original SoupX paper (Young & Behjati, 2020)
- Original SoupX developers: Matthew D. Young and Sam Behjati
- R package maintainers and contributors
- Python single-cell community (scanpy, anndata developers)