Files
drug-discovery-prompts/upstream/mims-harvard-ToolUniverse/skills/tooluniverse-rnaseq-deseq2/references/troubleshooting.md

411 lines
9.6 KiB
Markdown

---
title: "Troubleshooting Guide"
task: ""
lineage_type: import
upstream_source: https://github.com/mims-harvard/ToolUniverse/blob/e2520a96/skills/tooluniverse-rnaseq-deseq2/references/troubleshooting.md
upstream_sha: e2520a96
imported_at: 2026-06-26
prompt_class: prompt
upstream_changes: accepted
author: upstream
validated: false
---
# Troubleshooting Guide
Common issues and solutions for RNA-seq analysis with PyDESeq2.
## Data Loading Issues
### "No matching samples between counts and metadata"
**Cause**: Sample names don't match between count matrix and metadata.
**Solutions**:
```python
# Check sample names
print("Count samples:", list(counts.index)[:5])
print("Metadata samples:", list(metadata.index)[:5])
# Try case-insensitive matching
counts.index = counts.index.str.lower()
metadata.index = metadata.index.str.lower()
# Remove whitespace
counts.index = counts.index.str.strip()
metadata.index = metadata.index.str.strip()
# Try transpose
if set(counts.columns) & set(metadata.index):
counts = counts.T
```
### "Non-integer counts detected"
**Cause**: Data is normalized (FPKM, TPM) or has floating point values.
**Solutions**:
```python
# Option 1: Round to integers (acceptable if close to integers)
counts = counts.round().astype(int)
# Option 2: Use t-test instead of DESeq2 for normalized data
from scipy import stats
stat, pval = stats.ttest_ind(group1, group2)
# Check if data is truly raw counts
print(counts.head())
print("Min value:", counts.min().min())
print("Has decimals:", (counts % 1 != 0).any().any())
```
### Matrix orientation wrong
**Cause**: Genes are rows instead of columns.
**Solution**:
```python
# Check shape
print(f"Counts shape: {counts.shape}") # Should be (n_samples, n_genes)
print(f"Samples >> genes? {counts.shape[0] > counts.shape[1] * 5}")
# Transpose if needed
if counts.shape[0] > counts.shape[1] * 5:
counts = counts.T
```
## DESeq2 Execution Issues
### "Dispersion trend did not converge"
**Cause**: Small sample size or low variation.
**Solution**:
```python
# Use mean fit instead of parametric
dds = DeseqDataSet(
counts=counts,
metadata=metadata,
design="~condition",
fit_type='mean', # Instead of 'parametric'
quiet=True
)
dds.deseq2()
```
### "Contrast not found"
**Cause**: Wrong factor/level names in contrast.
**Solution**:
```python
# Check available levels
print("Available factors:", metadata.columns)
print("Condition levels:", metadata['condition'].unique())
# Verify exact names (case-sensitive)
contrast = ['condition', 'treatment', 'control'] # Must match exactly
```
### "All genes filtered out"
**Cause**: Too strict pre-filtering or all counts are zero.
**Solution**:
```python
# Check data quality
print("Zero genes:", (counts.sum(axis=0) == 0).sum())
print("Low count genes:", (counts.sum(axis=0) < 10).sum())
# Remove only zero genes
nonzero = counts.sum(axis=0) > 0
counts = counts.loc[:, nonzero]
# Don't pre-filter too aggressively
# DESeq2 handles low counts internally
```
### "Single replicate per condition"
**Cause**: Only 1 sample per condition - cannot estimate dispersion.
**Solution**:
```python
# Cannot run DESeq2 with single replicates
# Use fold-change only
mean_A = counts[samples_A].mean(axis=1)
mean_B = counts[samples_B].mean(axis=1)
log2fc = np.log2((mean_B + 1) / (mean_A + 1))
# Or pool replicates from similar conditions
```
## Reference Level Issues
### Wrong reference level
**Cause**: Reference level not set correctly.
**Solution**:
```python
# In PyDESeq2, FIRST category is reference
metadata['condition'] = pd.Categorical(
metadata['condition'],
categories=['control', 'treatment'] # Control first = reference
)
# Verify
print("Categories:", metadata['condition'].cat.categories)
print("First category (reference):", metadata['condition'].cat.categories[0])
```
## LFC Shrinkage Issues
### "Coefficient not found for shrinkage"
**Cause**: Wrong coefficient name format.
**Solution**:
```python
# Check available coefficients
print("Available coefficients:", list(dds.varm['LFC'].columns))
# Standard format: factor[T.level]
coeff = 'condition[T.treatment]'
# Verify before shrinking
if coeff in dds.varm['LFC'].columns:
stat_res.lfc_shrink(coeff=coeff)
else:
print(f"ERROR: '{coeff}' not found")
print("Skipping shrinkage")
```
## Result Extraction Issues
### "NaN in padj column"
**Cause**: Independent filtering removed genes with insufficient evidence.
**Solution**:
```python
# This is EXPECTED behavior
# Remove NaN before counting DEGs
sig_genes = results.dropna(subset=['padj'])
sig_genes = sig_genes[sig_genes['padj'] < 0.05]
# Don't include NaN genes in counts
answer = len(sig_genes) # Correct
# NOT: len(results[results['padj'] < 0.05]) # Wrong, includes NaN
```
### "Gene not found in results"
**Cause**: Gene name case mismatch or gene filtered out.
**Solution**:
```python
# Case-insensitive search
def find_gene(results_df, gene_name):
# Exact match
if gene_name in results_df.index:
return gene_name
# Case-insensitive
idx_lower = {g.lower(): g for g in results_df.index}
if gene_name.lower() in idx_lower:
return idx_lower[gene_name.lower()]
# Partial match
matches = [g for g in results_df.index if gene_name.lower() in g.lower()]
if len(matches) == 1:
return matches[0]
elif len(matches) > 1:
print(f"Multiple matches: {matches}")
return None
gene = find_gene(results, "TP53")
if gene:
lfc = results.loc[gene, 'log2FoldChange']
```
## Enrichment Analysis Issues
### "No enrichment results"
**Cause**: Gene list too small or wrong organism/library.
**Solution**:
```python
# Check gene list
print(f"Gene list size: {len(gene_list)}")
print(f"Sample genes: {gene_list[:5]}")
# Try different libraries
libraries = [
'GO_Biological_Process_2023',
'GO_Biological_Process_2021',
'KEGG_2021_Human',
'KEGG_2019_Mouse'
]
for lib in libraries:
try:
enr = gp.enrich(gene_list=gene_list, gene_sets=lib, outdir=None, no_plot=True)
if len(enr.results) > 0:
print(f"✓ {lib}: {len(enr.results)} results")
break
except Exception as e:
print(f"✗ {lib}: {e}")
```
### "gseapy organism parameter deprecated"
**Cause**: Using old gseapy syntax with `organism` parameter.
**Solution**:
```python
# OLD (doesn't work in gseapy >= 1.1)
# enr = gp.enrich(gene_list=gene_list, organism='human', gene_sets='GO_Biological_Process')
# NEW (correct)
enr = gp.enrich(
gene_list=gene_list,
gene_sets='GO_Biological_Process_2023', # Organism in library name
outdir=None
)
```
## Memory Issues
### "Memory error with large gene set"
**Cause**: Too many genes (60K+ genes with many samples).
**Solution**:
```python
# Pre-filter low-count genes
min_count = 10
keep = counts.sum(axis=0) >= min_count
counts_filtered = counts.loc[:, keep]
print(f"Kept {keep.sum()} / {len(keep)} genes")
# Use sparse matrices (if applicable)
from scipy.sparse import csr_matrix
counts_sparse = csr_matrix(counts.values)
```
## Multi-Factor Design Issues
### "Singular design matrix"
**Cause**: Confounded factors (e.g., batch perfectly correlated with condition).
**Solution**:
```python
# Check confounding
print(pd.crosstab(metadata['batch'], metadata['condition']))
# If confounded, cannot separate effects
# Remove confounded factor from design
design = "~condition" # Remove batch if confounded
```
## Performance Issues
### "DESeq2 takes too long"
**Cause**: Large dataset or many factors.
**Solutions**:
```python
# Use parallel processing (if available)
import multiprocessing
n_cpus = multiprocessing.cpu_count()
# Note: PyDESeq2 doesn't directly support n_cpus, but numpy may use multiple cores
# Pre-filter more aggressively
min_count = 10
min_samples = 3
keep = (counts >= min_count).sum(axis=0) >= min_samples
counts_filtered = counts.loc[:, keep]
# Use mean fit instead of parametric (faster)
dds = DeseqDataSet(counts=counts, metadata=metadata, design="~condition",
fit_type='mean', quiet=True)
```
## Validation Issues
### Results don't match expected answer
**Checks**:
```python
# 1. Check reference level
print("Reference:", metadata['condition'].cat.categories[0])
# 2. Check contrast direction
print("Contrast:", contrast) # [factor, numerator, denominator]
# 3. Check filtering thresholds
print(f"padj < {padj_threshold}")
print(f"|log2FC| > {lfc_threshold}")
# 4. Check if shrinkage was applied
print("Shrinkage applied:", 'lfcSE' in results.columns)
# 5. Check for NaN handling
print("Genes with NaN padj:", results['padj'].isna().sum())
```
## Debugging Checklist
Before reporting issues, verify:
- [ ] Count matrix has samples as rows, genes as columns
- [ ] Counts are non-negative integers
- [ ] Metadata index matches count matrix index exactly
- [ ] Design formula references valid column names in metadata
- [ ] Reference level is set correctly (first category in Categorical)
- [ ] Contrast factor and levels exist in metadata
- [ ] LFC shrinkage coefficient name matches pydeseq2 format
- [ ] Filtering thresholds match question exactly
- [ ] NaN values in padj are excluded from DEG counts
## Getting Help
```python
# Print diagnostic information
print("=" * 50)
print("DIAGNOSTIC INFORMATION")
print("=" * 50)
print(f"Counts shape: {counts.shape}")
print(f"Metadata shape: {metadata.shape}")
print(f"Design: {design}")
print(f"Reference level: {metadata['condition'].cat.categories[0]}")
print(f"Contrast: {contrast}")
print(f"PyDESeq2 version: {pydeseq2.__version__}")
print(f"\nSample counts:\n{counts.iloc[:3, :3]}")
print(f"\nMetadata:\n{metadata.head(3)}")
print(f"\nResults:\n{results.head(3)}")
print("=" * 50)
```