QC Metrics Visualization
```python
# Assuming adata.obs has QC columns: n_genes, n_counts, percent_mito
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# Plot 1: Histogram of genes per cell
axes[0].hist(adata.obs['n_genes'], bins=50, color='steelblue', edgecolor='black', alpha=0.7)
axes[0].axvline(adata.obs['n_genes'].median(), color='red', linestyle='--', label='Median')
axes[0].set_xlabel('Genes per Cell', fontsize=11)
axes[0].set_ylabel('Frequency', fontsize=11)
axes[0].set_title('Genes per Cell Distribution', fontsize=12, fontweight='bold')
axes[0].legend()
# Plot 2: Scatter UMI vs Genes
axes[1].scatter(adata.obs['n_counts'], adata.obs['n_genes'],
s=5, alpha=0.5, c='coral')
axes[1].set_xlabel('UMI Counts', fontsize=11)
axes[1].set_ylabel('Genes Detected', fontsize=11)
axes[1].set_title('UMIs vs Genes', fontsize=12, fontweight='bold')
# Plot 3: Violin plot of mitochondrial percentage
sns.violinplot(y=adata.obs['percent_mito'], ax=axes[2], color='lightgreen')
axes[2].axhline(y=20, color='red', linestyle='--', label='20% threshold')
axes[2].set_ylabel('Mitochondrial %', fontsize=11)
axes[2].set_title('Mitochondrial Content', fontsize=12, fontweight='bold')
axes[2].legend()
plt.tight_layout()
plt.savefig('qc_metrics.png', dpi=300, bbox_inches='tight')
plt.show()
```
UMAP/tSNE Visualization
```python
# Assuming adata.obsm['X_umap'] exists and adata.obs['clusters'] exists
fig, ax = plt.subplots(figsize=(8, 7))
# Get unique clusters
clusters = adata.obs['clusters'].unique()
n_clusters = len(clusters)
# Generate colors
colors = plt.cm.tab20(np.linspace(0, 1, n_clusters))
# Plot each cluster
for i, cluster in enumerate(clusters):
mask = adata.obs['clusters'] == cluster
ax.scatter(
adata.obsm['X_umap'][mask, 0],
adata.obsm['X_umap'][mask, 1],
c=[colors[i]],
label=f'Cluster {cluster}',
s=10,
alpha=0.7,
edgecolors='none'
)
ax.set_xlabel('UMAP1', fontsize=12)
ax.set_ylabel('UMAP2', fontsize=12)
ax.set_title('UMAP Projection by Cluster', fontsize=14, fontweight='bold')
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', frameon=True, fontsize=9)
plt.tight_layout()
plt.savefig('umap_clusters.png', dpi=300, bbox_inches='tight')
plt.show()
```
Gene Expression Dot Plot
```python
# genes: list of gene names
# clusters: list of cluster IDs
# Create matrix: rows=genes, columns=clusters with mean expression and % expressing
fig, ax = plt.subplots(figsize=(10, 6))
# Prepare data
from matplotlib.colors import Normalize
# dot_size_matrix: % cells expressing (0-100)
# color_matrix: mean expression level
for i, gene in enumerate(genes):
for j, cluster in enumerate(clusters):
# Size proportional to % expressing
size = dot_size_matrix[i, j] * 5 # Scale factor
# Color by expression level
color_val = color_matrix[i, j]
ax.scatter(j, i, s=size, c=[color_val], cmap='Reds',
vmin=0, vmax=color_matrix.max(),
edgecolors='black', linewidths=0.5)
# Labels
ax.set_xticks(range(len(clusters)))
ax.set_xticklabels(clusters, rotation=45, ha='right')
ax.set_yticks(range(len(genes)))
ax.set_yticklabels(genes)
ax.set_xlabel('Cluster', fontsize=12)
ax.set_ylabel('Gene', fontsize=12)
ax.set_title('Marker Gene Expression', fontsize=14, fontweight='bold')
# Colorbar
norm = Normalize(vmin=0, vmax=color_matrix.max())
sm = plt.cm.ScalarMappable(cmap='Reds', norm=norm)
sm.set_array([])
cbar = plt.colorbar(sm, ax=ax, pad=0.02)
cbar.set_label('Mean Expression', rotation=270, labelpad=15)
plt.tight_layout()
plt.savefig('gene_dotplot.png', dpi=300, bbox_inches='tight')
plt.show()
```
Volcano Plot (DEG Analysis)
```python
# Assuming deg_df has columns: gene, log2FC, pvalue
fig, ax = plt.subplots(figsize=(8, 7))
# Calculate -log10(pvalue)
deg_df['-log10_pvalue'] = -np.log10(deg_df['pvalue'])
# Classify genes
deg_df['significant'] = 'Not Significant'
deg_df.loc[(deg_df['log2FC'] > 1) & (deg_df['pvalue'] < 0.05), 'significant'] = 'Up-regulated'
deg_df.loc[(deg_df['log2FC'] < -1) & (deg_df['pvalue'] < 0.05), 'significant'] = 'Down-regulated'
# Plot
for category, color in zip(['Not Significant', 'Up-regulated', 'Down-regulated'],
['gray', 'red', 'blue']):
mask = deg_df['significant'] == category
ax.scatter(deg_df.loc[mask, 'log2FC'],
deg_df.loc[mask, '-log10_pvalue'],
c=color, label=category, s=20, alpha=0.6, edgecolors='none')
# Threshold lines
ax.axvline(x=1, color='black', linestyle='--', linewidth=1, alpha=0.5)
ax.axvline(x=-1, color='black', linestyle='--', linewidth=1, alpha=0.5)
ax.axhline(y=-np.log10(0.05), color='black', linestyle='--', linewidth=1, alpha=0.5)
# Labels
ax.set_xlabel('log2 Fold Change', fontsize=12)
ax.set_ylabel('-log10(p-value)', fontsize=12)
ax.set_title('Volcano Plot: Differential Expression', fontsize=14, fontweight='bold')
ax.legend(frameon=True, loc='upper right')
plt.tight_layout()
plt.savefig('volcano_plot.png', dpi=300, bbox_inches='tight')
plt.show()
```