| """Align raw study cohorts to shared gene identifiers before fitting PCA.""" |
| import argparse |
| from pathlib import Path |
| import anndata as ad |
|
|
| p = argparse.ArgumentParser() |
| p.add_argument('--inputs', nargs='+', required=True) |
| p.add_argument('--output', required=True) |
| p.add_argument('--gene-columns', nargs='+', help='One var column per input. Use index for var_names.') |
| p.add_argument('--drop-ambiguous', action='store_true', help='Drop all duplicated gene identifiers') |
| a = p.parse_args() |
| cohorts = [ad.read_h5ad(f) for f in a.inputs] |
| if a.gene_columns and len(a.gene_columns) != len(cohorts): raise ValueError('Supply one gene-column choice per input') |
| for i, cohort in enumerate(cohorts): |
| field = a.gene_columns[i] if a.gene_columns else 'index' |
| if field != 'index': |
| if cohort.var[field].isna().any(): |
| cohort = cohort[:, cohort.var[field].notna()].copy() |
| cohort.var_names = cohort.var[field].astype(str) |
| if not cohort.var_names.is_unique: |
| if not a.drop_ambiguous: raise ValueError('Duplicate gene identifiers. Use --drop-ambiguous to discard these genes.') |
| cohort = cohort[:, ~cohort.var_names.duplicated(keep=False)].copy() |
| cohorts[i] = cohort |
| shared = sorted(set.intersection(*(set(c.var_names) for c in cohorts))) |
| if len(shared) < 100: |
| raise ValueError('Fewer than 100 shared genes. Check identifier conventions.') |
| out = Path(a.output); out.mkdir(parents=True, exist_ok=True) |
| for i, (path, cohort) in enumerate(zip(a.inputs, cohorts)): |
| cohort[:, shared].copy().write_h5ad(out / f'{i}_{Path(path).stem}.h5ad', compression='gzip') |
| (out / 'genes.txt').write_text('\n'.join(shared)+'\n') |
| print(f'Aligned {len(cohorts)} cohorts on {len(shared)} genes') |
|
|