Compute highly variable genes using the Seurat method. The Seurat method models the mean-variance relationship by: 1. Calculating dispersion (variance/mean) for each gene 2. Binning genes by expression level 3. Computing expected dispersion within each bin 4. Normalizing dispersions to z-scores 5. Selecting genes with high normalized dispersion ## Method Details - Uses log1p transformation of me
(
adata: &IMAnnData,
x: &IMArrayElement,
params: HVGParams,
)
| 491 | /// - `dispersions_norm`: Z-score normalized dispersions |
| 492 | /// - `highly_variable`: Boolean selection mask |
| 493 | fn compute_seurat_hvg( |
| 494 | adata: &IMAnnData, |
| 495 | x: &IMArrayElement, |
| 496 | params: HVGParams, |
| 497 | ) -> anyhow::Result<()> { |
| 498 | let n_obs = adata.n_obs(); |
| 499 | |
| 500 | // Calculate means from raw counts |
| 501 | let raw_means: Vec<f64> = x |
| 502 | .sum_whole(&Direction::COLUMN)? |
| 503 | .iter() |
| 504 | .map(|sum: &f64| sum / n_obs as f64) |
| 505 | .collect(); |
| 506 | |
| 507 | let variances: Vec<f64> = x.variance_whole::<u32, f64>(&Direction::COLUMN)?; |
| 508 | |
| 509 | // Calculate dispersions with proper handling of zero means |
| 510 | let dispersions: Vec<f64> = raw_means |
| 511 | .iter() |
| 512 | .zip(variances.iter()) |
| 513 | .map(|(&mean, &var)| { |
| 514 | let safe_mean = if mean > 1e-12 { mean } else { 1e-12 }; |
| 515 | var / safe_mean |
| 516 | }) |
| 517 | .collect(); |
| 518 | |
| 519 | // For Seurat flavor, use log1p of means for binning and storage |
| 520 | // This matches what Python does AFTER reverting log normalization |
| 521 | let log1p_means: Vec<f64> = raw_means.iter().map(|&x| (x + 1.0).ln()).collect(); |
| 522 | |
| 523 | // Log dispersions with NaN for zero dispersions (matching Python) |
| 524 | let log_dispersions: Vec<f64> = dispersions |
| 525 | .iter() |
| 526 | .map(|&x| { |
| 527 | if x > 0.0 { |
| 528 | x.ln() |
| 529 | } else { |
| 530 | f64::NAN // Python sets dispersion[dispersion == 0] = np.nan |
| 531 | } |
| 532 | }) |
| 533 | .collect(); |
| 534 | |
| 535 | let n_bins = params.n_bins; |
| 536 | |
| 537 | // Use equal-width binning on log1p_means (like Python's pd.cut) |
| 538 | let (bin_indices, _) = equal_width_binning(&log1p_means, n_bins)?; |
| 539 | |
| 540 | // Calculate mean and std for each bin |
| 541 | let (mut bin_means, mut bin_stds) = |
| 542 | calculate_bin_stats(&log_dispersions, &bin_indices, n_bins)?; |
| 543 | |
| 544 | // Handle single-gene bins (like Python's _postprocess_dispersions_seurat) |
| 545 | postprocess_seurat_dispersions(&mut bin_means, &mut bin_stds)?; |
| 546 | |
| 547 | // Normalize dispersions |
| 548 | let normalized_dispersions = |
| 549 | normalize_dispersions(&log_dispersions, &bin_indices, &bin_means, &bin_stds)?; |
| 550 |
no test coverage detected