Calculate cell-level (observation) quality control metrics. Computes comprehensive QC statistics for each cell including expression totals, gene detection rates, and specialized metrics like mitochondrial gene percentages. ## Parameters `adata` - AnnData object containing the dataset `x` - Expression matrix to analyze `expr_type` - Label for expression type (e.g., "counts", "UMI") `var_type` - L
(
adata: &IMAnnData,
x: &IMArrayElement,
expr_type: &str,
var_type: &str,
qc_vars: &[&str],
percent_top: &[usize],
log1p: bool,
)
| 115 | /// - Expression concentration in top N genes |
| 116 | /// - Optional log1p transformations for all count metrics |
| 117 | fn describe_obs( |
| 118 | adata: &IMAnnData, |
| 119 | x: &IMArrayElement, |
| 120 | expr_type: &str, |
| 121 | var_type: &str, |
| 122 | qc_vars: &[&str], |
| 123 | percent_top: &[usize], |
| 124 | log1p: bool, |
| 125 | ) -> anyhow::Result<DataFrame> { |
| 126 | let n_obs = adata.n_obs(); |
| 127 | let n_vars = adata.n_vars(); |
| 128 | |
| 129 | let n_genes_by_counts: Vec<u32> = x.nonzero_whole(&Direction::ROW)?; |
| 130 | let total_counts: Vec<f64> = x.sum_whole(&Direction::ROW)?; |
| 131 | |
| 132 | let mut columns = vec![]; |
| 133 | |
| 134 | let col_name = format!("n_{}_by_{}", var_type, expr_type); |
| 135 | columns.push(Column::new(col_name.into(), n_genes_by_counts.clone())); |
| 136 | |
| 137 | if log1p { |
| 138 | let log_values: Vec<f64> = n_genes_by_counts |
| 139 | .iter() |
| 140 | .map(|&x| (x as f64 + 1.0).ln()) |
| 141 | .collect(); |
| 142 | let col_name = format!("log1p_n_{}_by_{}", var_type, expr_type); |
| 143 | columns.push(Column::new(col_name.into(), log_values)); |
| 144 | } |
| 145 | |
| 146 | let col_name = format!("total_{}", expr_type); |
| 147 | columns.push(Column::new(col_name.into(), total_counts.clone())); |
| 148 | |
| 149 | if log1p { |
| 150 | let log_values: Vec<f64> = total_counts.iter().map(|&x| (x + 1.0).ln()).collect(); |
| 151 | let col_name = format!("log1p_total_{}", expr_type); |
| 152 | columns.push(Column::new(col_name.into(), log_values)); |
| 153 | } |
| 154 | |
| 155 | if !percent_top.is_empty() { |
| 156 | use crate::shared::statistics::ComputeTopSegmentProportions; |
| 157 | let proportions = x.top_segment_proportions(&Direction::ROW, percent_top)?; |
| 158 | for (i, &n) in percent_top.iter().enumerate() { |
| 159 | let values: Vec<f64> = proportions.column(i).iter().map(|&x| x * 100.0).collect(); |
| 160 | let col_name = format!("pct_{}_in_top_{}_{}", expr_type, n, var_type); |
| 161 | columns.push(Column::new(col_name.into(), values)); |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | for qc_var in qc_vars { |
| 166 | let var_mask = adata |
| 167 | .var() |
| 168 | .get_column_from_df(qc_var)? |
| 169 | .bool()? |
| 170 | .into_iter() |
| 171 | .map(|x| x.unwrap_or(false)) |
| 172 | .collect::<Vec<bool>>(); |
| 173 | |
| 174 | let qc_var_indices: Vec<usize> = var_mask |
no test coverage detected