From 0e7f1df967688289d245b5397ca58d77500136de Mon Sep 17 00:00:00 2001 From: Muhammed Hasan Celik Date: Wed, 10 Dec 2025 19:02:26 +0000 Subject: [PATCH 1/4] vep attributions --- .../6-variant-effect-attribution.ipynb | 270 ++++++++++++++++++ src/decima/cli/__init__.py | 2 + src/decima/cli/vep_attribution.py | 121 ++++++++ src/decima/core/attribution.py | 169 ++++++++++- src/decima/utils/io.py | 97 ++++++- src/decima/vep/__init__.py | 255 +---------------- src/decima/vep/attributions.py | 135 +++++++++ src/decima/vep/vep.py | 264 +++++++++++++++++ tests/test_cli.py | 15 + tests/test_vep_attributions.py | 61 ++++ 10 files changed, 1126 insertions(+), 263 deletions(-) create mode 100644 docs/tutorials/6-variant-effect-attribution.ipynb create mode 100644 src/decima/cli/vep_attribution.py create mode 100644 src/decima/vep/attributions.py create mode 100644 src/decima/vep/vep.py create mode 100644 tests/test_vep_attributions.py diff --git a/docs/tutorials/6-variant-effect-attribution.ipynb b/docs/tutorials/6-variant-effect-attribution.ipynb new file mode 100644 index 0000000..a27df7c --- /dev/null +++ b/docs/tutorials/6-variant-effect-attribution.ipynb @@ -0,0 +1,270 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b0f8e93b", + "metadata": {}, + "source": [ + "# Variant Effect Prediction with Motif Intpretation with Attributions " + ] + }, + { + "cell_type": "markdown", + "id": "91fc519b", + "metadata": {}, + "source": [ + "## CLI API" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7d51a63c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Usage: decima vep-attribution [OPTIONS]\n", + "\n", + " Predict variant effect and save to parquet\n", + "\n", + " Examples:\n", + "\n", + " >>> decima vep-attribution -v \"data/sample.vcf\" -o \"vep_results.h5\"\n", + "\n", + "Options:\n", + " -v, --variants PATH Path to the variant file .vcf file. VCF file\n", + " need to be normalized. Try normalizing th\n", + " vcf file incase of an error. `bcftools norm\n", + " -f ref.fasta input.vcf.gz -o output.vcf.gz`\n", + " -o, --output_h5 PATH Path to the output h5 file.\n", + " --tasks TEXT Tasks to predict. If not provided, all tasks\n", + " will be predicted.\n", + " --off-tasks TEXT Tasks to contrast against. If not provided,\n", + " no contrast will be performed.\n", + " --model INTEGER Model to use for attribution analysis.\n", + " Available options: ['v1_rep0', 'v1_rep1',\n", + " 'v1_rep2', 'v1_rep3', 'ensemble', 'rep0',\n", + " 'rep1', 'rep2', 'rep3', 0, 1, 2, 3, '0',\n", + " '1', '2', '3'].\n", + " --metadata TEXT Path to the metadata anndata file or name of\n", + " the model. If not provided, the compabilite\n", + " metadata for the model will be used.\n", + " Default: ensemble.\n", + " --method TEXT Method to use for attribution analysis.\n", + " Available options: 'inputxgradient',\n", + " 'saliency', 'integratedgradients'.\n", + " --transform [specificity|aggregate]\n", + " Transform to use for attribution analysis.\n", + " Available options: 'specificity',\n", + " 'aggregate'.\n", + " --device TEXT Device to use. Default: None which\n", + " automatically selects the best device.\n", + " --num-workers INTEGER Number of workers for the loader. Default: 4\n", + " --distance-type TEXT Type of distance. Default: tss.\n", + " --min-distance FLOAT Minimum distance from the end of the gene.\n", + " Default: 0.\n", + " --max-distance FLOAT Maximum distance from the TSS. Default:\n", + " 524288.\n", + " --gene-col TEXT Column name for gene names. Default: None.\n", + " --genome TEXT Genome build. Default: hg38.\n", + " --float-precision TEXT Floating-point precision to be used in\n", + " calculations. Avaliable options include:\n", + " '16-true', '16-mixed', 'bf16-true',\n", + " 'bf16-mixed', '32-true', '64-true', '32',\n", + " '16', and 'bf16'.\n", + " --help Show this message and exit.\n", + "\u001b[0m" + ] + } + ], + "source": [ + "! decima vep-attribution --help" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "963b41a8", + "metadata": {}, + "outputs": [], + "source": [ + "! decima vep -v \"data/sample.vcf\" -o \"vep_vcf_attributions.h5\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a477e150", + "metadata": {}, + "outputs": [], + "source": [ + "! ls vep_vcf_attributions.*" + ] + }, + { + "cell_type": "markdown", + "id": "7fc2c6db", + "metadata": {}, + "source": [ + "\n", + "## Python API" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9309f1d5", + "metadata": {}, + "outputs": [], + "source": [ + "import h5py\n", + "import torch\n", + "from decima.core.attribution import VariantAttributionResult\n", + "from decima.vep import variant_effect_attribution\n", + "\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d9ad6a4", + "metadata": {}, + "outputs": [], + "source": [ + "variant_effect_attribution(\n", + " \"tests/data/test.vcf\",\n", + " \"vep_vcf_attributions.h5\",\n", + " model=0,\n", + " method=\"inputxgradient\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4126fb8d", + "metadata": {}, + "outputs": [], + "source": [ + "with h5py.File(\"vep_vcf_attributions.h5\", \"r\") as f:\n", + " print(f.keys())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3022232e", + "metadata": {}, + "outputs": [], + "source": [ + "with VariantAttributionResult(\"vep_vcf_attributions.h5\", tss_distance=10_000, num_workers=1) as ar:\n", + " genes = ar.genes\n", + " variants = ar.variants\n", + " print(genes)\n", + " print(variants)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2cb59750", + "metadata": {}, + "outputs": [], + "source": [ + "with VariantAttributionResult(\"vep_vcf_attributions.h5\", tss_distance=10_000, num_workers=1) as ar:\n", + " seqs_ref, attrs_ref, seqs_alt, attrs_alt = ar.load(variants, genes)\n", + " print(seqs_ref)\n", + " print(attrs_ref)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8b3b4ed", + "metadata": {}, + "outputs": [], + "source": [ + "with VariantAttributionResult(\"vep_vcf_attributions.h5\", tss_distance=10_000, num_workers=1) as ar:\n", + " attribution_ref, attribution_alt = ar.load(variants, genes)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fcc21b89", + "metadata": {}, + "outputs": [], + "source": [ + "with VariantAttributionResult(\"vep_vcf_attributions.h5\", tss_distance=10_000, num_workers=1) as ar:\n", + " df_peaks, df_motifs = ar.recursive_seqlet_calling(variants, genes)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0e65e660", + "metadata": {}, + "outputs": [], + "source": [ + "df_peaks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6eb265ff", + "metadata": {}, + "outputs": [], + "source": [ + "df_motifs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b983e884", + "metadata": {}, + "outputs": [], + "source": [ + "attribution_ref.plot_seqlogo(relative_loc=291)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fae648d8", + "metadata": {}, + "outputs": [], + "source": [ + "attribution_alt.plot_seqlogo(relative_loc=291)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "decima2", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/decima/cli/__init__.py b/src/decima/cli/__init__.py index 9ae6871..6656199 100644 --- a/src/decima/cli/__init__.py +++ b/src/decima/cli/__init__.py @@ -13,6 +13,7 @@ from decima.cli.vep import cli_predict_variant_effect from decima.cli.finetune import cli_finetune from decima.cli.vep import cli_vep_ensemble +from decima.cli.vep_attribution import cli_vep_attribution from decima.cli.modisco import ( cli_modisco_attributions, cli_modisco_patterns, @@ -51,6 +52,7 @@ def main(): main.add_command(cli_attributions_recursive_seqlet_calling, name="attributions-recursive-seqlet-calling") main.add_command(cli_predict_variant_effect, name="vep") main.add_command(cli_vep_ensemble, name="vep-ensemble") +main.add_command(cli_vep_attribution, name="vep-attribution") main.add_command(cli_finetune, name="finetune") main.add_command(cli_modisco, name="modisco") main.add_command(cli_modisco_attributions, name="modisco-attributions") diff --git a/src/decima/cli/vep_attribution.py b/src/decima/cli/vep_attribution.py new file mode 100644 index 0000000..5db09ab --- /dev/null +++ b/src/decima/cli/vep_attribution.py @@ -0,0 +1,121 @@ +""" +Interpretation of Variant Effect Prediction with Attribution Analysis CLI. +""" + +import click +from decima.constants import DECIMA_CONTEXT_SIZE, DEFAULT_ENSEMBLE, MODEL_METADATA +from decima.cli.callback import parse_metadata, parse_model +from decima.vep.attributions import variant_effect_attribution + + +@click.command() +@click.option( + "-v", + "--variants", + type=click.Path(exists=True), + help="Path to the variant file .vcf file. VCF file need to be normalized. Try normalizing th vcf file incase of an error. `bcftools norm -f ref.fasta input.vcf.gz -o output.vcf.gz`", +) +@click.option("-o", "--output_h5", type=click.Path(), help="Path to the output h5 file.") +@click.option("--tasks", type=str, default=None, help="Tasks to predict. If not provided, all tasks will be predicted.") +@click.option( + "--off-tasks", + type=str, + default=None, + help="Tasks to contrast against. If not provided, no contrast will be performed.", +) +@click.option( + "--model", + default=0, + callback=parse_model, + help=f"Model to use for attribution analysis. Available options: {list(MODEL_METADATA.keys())}.", +) +@click.option( + "--metadata", + default=None, + callback=parse_metadata, + help=f"Path to the metadata anndata file or name of the model. If not provided, the compabilite metadata for the model will be used. Default: {DEFAULT_ENSEMBLE}.", +) +@click.option( + "--method", + type=str, + default="inputxgradient", + help="Method to use for attribution analysis. Available options: 'inputxgradient', 'saliency', 'integratedgradients'.", +) +@click.option( + "--transform", + type=click.Choice(["specificity", "aggregate"]), + default="specificity", + help="Transform to use for attribution analysis. Available options: 'specificity', 'aggregate'.", +) +@click.option( + "--device", type=str, default=None, help="Device to use. Default: None which automatically selects the best device." +) +@click.option("--num-workers", type=int, default=4, help="Number of workers for the loader. Default: 4") +@click.option("--distance-type", type=str, default="tss", help="Type of distance. Default: tss.") +@click.option( + "--min-distance", + type=float, + default=0, + help="Minimum distance from the end of the gene. Default: 0.", +) +@click.option( + "--max-distance", + type=float, + default=DECIMA_CONTEXT_SIZE, + help=f"Maximum distance from the TSS. Default: {DECIMA_CONTEXT_SIZE}.", +) +@click.option( + "--gene-col", + type=str, + default=None, + help="Column name for gene names. Default: None.", +) +@click.option("--genome", type=str, default="hg38", help="Genome build. Default: hg38.") +@click.option( + "--float-precision", + type=str, + default="32", + help="Floating-point precision to be used in calculations. Avaliable options include: '16-true', '16-mixed', 'bf16-true', 'bf16-mixed', '32-true', '64-true', '32', '16', and 'bf16'.", +) +def cli_vep_attribution( + variants, + output_h5, + tasks, + off_tasks, + model, + metadata, + method, + transform, + device, + num_workers, + distance_type, + min_distance, + max_distance, + gene_col, + genome, + float_precision, +): + """Predict variant effect and save to parquet + + Examples: + + >>> decima vep-attribution -v "data/sample.vcf" -o "vep_results.h5" + """ + variant_effect_attribution( + variants, + output_h5=output_h5, + tasks=tasks, + off_tasks=off_tasks, + model=model, + metadata_anndata=metadata, + method=method, + transform=transform, + num_workers=num_workers, + device=device, + distance_type=distance_type, + min_distance=min_distance, + max_distance=max_distance, + gene_col=gene_col, + genome=genome, + float_precision=float_precision, + ) diff --git a/src/decima/core/attribution.py b/src/decima/core/attribution.py index 43d7938..348dc1d 100644 --- a/src/decima/core/attribution.py +++ b/src/decima/core/attribution.py @@ -624,7 +624,15 @@ def aggregate(seqs, attrs, agg_func: Optional[str] = None): raise ValueError(f"Invalid aggregation function: {agg_func}") @staticmethod - def _load(attribution_h5, idx: int, tss_distance: int, correct_grad: bool, gene_mask: bool = False): + def _load( + attribution_h5, + idx: int, + tss_distance: int, + correct_grad: bool, + gene_mask: bool = False, + sequence_key: str = "sequence", + attribution_key: str = "attribution", + ): """Load the attribution scores.""" with h5py.File(str(attribution_h5), "r") as f: gene = f["genes"][idx].decode("utf-8") @@ -647,11 +655,11 @@ def _load(attribution_h5, idx: int, tss_distance: int, correct_grad: bool, gene_ seqs = np.zeros((4, DECIMA_CONTEXT_SIZE + padding * 2)) seqs[:4, padding : DECIMA_CONTEXT_SIZE + padding] = convert_input_type( - f["sequence"][idx].astype("int"), "one_hot", input_type="indices" + f[sequence_key][idx].astype("int"), "one_hot", input_type="indices" ) attrs = np.zeros((4, DECIMA_CONTEXT_SIZE + padding * 2)) - attrs[:, padding : DECIMA_CONTEXT_SIZE + padding] = f["attribution"][idx].astype(np.float32) + attrs[:, padding : DECIMA_CONTEXT_SIZE + padding] = f[attribution_key][idx].astype(np.float32) if tss_distance is not None: start = padding + gene_mask_start - tss_distance @@ -677,17 +685,21 @@ def _load_multiple( correct_grad: bool, gene_mask: bool = False, agg_func: Optional[str] = None, + sequence_key: str = "sequence", + attribution_key: str = "attribution", ): """Load the attribution scores from multiple attribution h5 files.""" seqs, attrs = zip( *( - AttributionResult._load(attribution_h5_file, idx, tss_distance, correct_grad, gene_mask) + AttributionResult._load( + attribution_h5_file, idx, tss_distance, correct_grad, gene_mask, sequence_key, attribution_key + ) for idx, attribution_h5_file in zip(indices, attribution_h5_files) ) ) return AttributionResult.aggregate(np.array(seqs), np.array(attrs), agg_func) - def load(self, genes: List[str], gene_mask: bool = False): + def load(self, genes: List[str], gene_mask: bool = False, **kwargs): """Load the attribution scores for a list of genes. Args: @@ -708,6 +720,7 @@ def load(self, genes: List[str], gene_mask: bool = False): "tss_distance": self.tss_distance, "correct_grad": self.correct_grad, "gene_mask": gene_mask, + **kwargs, } if isinstance(self.attribution_h5, list): load_func = self._load_multiple @@ -736,11 +749,15 @@ def _load_attribution( max_seqlet_len: int = 25, additional_flanks: int = 0, pattern_type: str = "both", + sequence_key: str = "sequence", + attribution_key: str = "attribution", ): kwargs = { "tss_distance": tss_distance, "correct_grad": False, "gene_mask": True, + "sequence_key": sequence_key, + "attribution_key": attribution_key, } if isinstance(attribution_h5, list): assert agg_func is not None, "Aggregation function must be set to use recursive seqlet calling." @@ -798,6 +815,7 @@ def load_attribution( max_seqlet_len: int = 25, additional_flanks: int = 0, pattern_type: str = "both", + **kwargs, ): """Load the attribution scores for a gene. @@ -829,6 +847,7 @@ def load_attribution( max_seqlet_len=max_seqlet_len, additional_flanks=additional_flanks, pattern_type=pattern_type, + **kwargs, ) @staticmethod @@ -847,6 +866,7 @@ def _recursive_seqlet_calling( additional_flanks: int = 0, pattern_type: str = "both", meme_motif_db: str = "hocomoco_v13", + **kwargs, ): attribution = AttributionResult._load_attribution( attribution_h5, @@ -862,6 +882,7 @@ def _recursive_seqlet_calling( max_seqlet_len=max_seqlet_len, additional_flanks=additional_flanks, pattern_type=pattern_type, + **kwargs, ) df_peaks = attribution.peaks_to_bed() df_motifs = attribution.scan_motifs(motifs=meme_motif_db) @@ -935,3 +956,141 @@ def __repr__(self): def __str__(self): return f"AttributionResult({self.attribution_h5})" + + +class VariantAttributionResult(AttributionResult): + def __init__( + self, + attribution_h5: Union[str, List[str]], + tss_distance: Optional[int] = None, + correct_grad: bool = True, + num_workers: Optional[int] = -1, + agg_func: Optional[str] = None, + ): + super().__init__(attribution_h5, tss_distance, correct_grad, num_workers, agg_func) + + def open(self): + super().open() + # self.relative_dist = self.h5.attrs["relative_dist"] + if isinstance(self.attribution_h5, list): + for attribution_h5_file in self.attribution_h5: + with h5py.File(str(attribution_h5_file), "r") as f: + for i, (variant, gene) in enumerate(zip(f["variants"][:], f["genes"][:])): + gene = gene.decode("utf-8") + variant = variant.decode("utf-8") + self._idx[(variant, gene)].append(i) + self._idx = dict(self._idx) + else: + self._idx = { + (variant.decode("utf-8"), gene.decode("utf-8")): i + for i, (variant, gene) in enumerate(zip(self.h5["variants"][:], self.h5["genes"][:])) + } + + def load(self, variants: List[str], genes: List[str], gene_mask: bool = False): + """Load the attribution scores for a list of genes and variants.""" + variant_gene = list(zip(variants, genes)) + seqs_ref, attrs_ref = super().load( + variant_gene, gene_mask, sequence_key="sequence", attribution_key="attribution" + ) + seqs_alt, attrs_alt = super().load( + variant_gene, gene_mask, sequence_key="sequence_alt", attribution_key="attribution_alt" + ) + return seqs_ref, attrs_ref, seqs_alt, attrs_alt + + def load_attribution( + self, + variant: str, + gene: str, + metadata_anndata: Optional[str] = None, + custom_genome: bool = False, + threshold: float = 5e-4, + min_seqlet_len: int = 4, + max_seqlet_len: int = 25, + additional_flanks: int = 0, + pattern_type: str = "both", + **kwargs, + ): + chroms, starts, ends = self._get_metadata(gene, metadata_anndata, custom_genome) + attribution_ref = self._load_attribution( + self.attribution_h5, + self._idx[(variant, gene)], + gene, + self.tss_distance, + chroms, + starts, + ends, + self.agg_func, + threshold=threshold, + min_seqlet_len=min_seqlet_len, + max_seqlet_len=max_seqlet_len, + additional_flanks=additional_flanks, + pattern_type=pattern_type, + sequence_key="sequence", + attribution_key="attribution", + ) + attribution_alt = self._load_attribution( + self.attribution_h5, + self._idx[(variant, gene)], + gene, + self.tss_distance, + chroms, + starts, + ends, + self.agg_func, + threshold=threshold, + min_seqlet_len=min_seqlet_len, + max_seqlet_len=max_seqlet_len, + additional_flanks=additional_flanks, + pattern_type=pattern_type, + sequence_key="sequence_alt", + attribution_key="attribution_alt", + ) + return attribution_ref, attribution_alt + + def recursive_seqlet_calling( + self, + variants: List[str], + genes: Optional[List[str]] = None, + metadata_anndata: Optional[str] = None, + custom_genome: bool = False, + threshold: float = 5e-4, + min_seqlet_len: int = 4, + max_seqlet_len: int = 25, + additional_flanks: int = 0, + pattern_type: str = "both", + meme_motif_db: str = "hocomoco_v13", + ): + variant_gene = list(zip(variants, genes)) + df_peaks_ref, df_motifs_ref = super().recursive_seqlet_calling( + variant_gene, + metadata_anndata, + custom_genome, + threshold, + min_seqlet_len, + max_seqlet_len, + additional_flanks, + pattern_type, + meme_motif_db, + sequence_key="sequence", + attribution_key="attribution", + ) + df_peaks_alt, df_motifs_alt = super().recursive_seqlet_calling( + variant_gene, + metadata_anndata, + custom_genome, + threshold, + min_seqlet_len, + max_seqlet_len, + additional_flanks, + pattern_type, + meme_motif_db, + sequence_key="sequence_alt", + attribution_key="attribution_alt", + ) + df_peaks = pd.concat([df_peaks_ref.assign(allele="ref"), df_peaks_alt.assign(allele="alt")]).reset_index( + drop=True + ) + df_motifs = pd.concat([df_motifs_ref.assign(allele="ref"), df_motifs_alt.assign(allele="alt")]).reset_index( + drop=True + ) + return df_peaks, df_motifs diff --git a/src/decima/utils/io.py b/src/decima/utils/io.py index e695cda..d711935 100644 --- a/src/decima/utils/io.py +++ b/src/decima/utils/io.py @@ -316,15 +316,15 @@ def add( raise ValueError( "Either `gene_mask_start` and `gene_mask_end` must be provided together or both must be None." ) - - self.h5_writer["gene_mask_start"][self.idx[gene]] = int(gene_mask_start) - self.h5_writer["gene_mask_end"][self.idx[gene]] = int(gene_mask_end) - self.h5_writer["sequence"][self.idx[gene], :] = convert_input_type( + idx = self.idx[gene] + self.h5_writer["gene_mask_start"][idx] = int(gene_mask_start) + self.h5_writer["gene_mask_end"][idx] = int(gene_mask_end) + self.h5_writer["sequence"][idx, :] = convert_input_type( torch.from_numpy(seqs), # convert_input_type only support Tensor "indices", input_type="one_hot", )[np.newaxis].astype("i1") - self.h5_writer["attribution"][self.idx[gene], :, :] = attrs.astype("float32") + self.h5_writer["attribution"][idx, :, :] = attrs.astype("float32") if self.bigwig: if self.custom_genes: @@ -332,7 +332,7 @@ def add( start = 0 end = DECIMA_CONTEXT_SIZE else: - gene_meta = self.result.get_gene_metadata(gene) + gene_meta = self.result.get_gene_metadata(self.genes[idx]) chrom = gene_meta.chrom start = gene_meta.start end = gene_meta.end @@ -351,3 +351,88 @@ def close(self): def __exit__(self, exc_type, exc_value, traceback): """Context manager exit - closes files.""" self.close() + + +class VariantAttributionWriter(AttributionWriter): + def __init__( + self, + path, + genes, + variants, + model_name, + metadata_anndata=None, + genome: str = "hg38", + ): + super().__init__( + path, + genes, + model_name, + metadata_anndata, + genome, + bigwig=False, + custom_genes=False, + ) + assert len(variants) == len( + genes + ), "Number of variants must be equal to number of genes. AttributionWriter saves variant gene pairs." + self.idx = {(v, g): i for i, (v, g) in enumerate(zip(variants, genes))} + self.variants = variants + + def open(self): + """Open HDF5 file and optional BigWig file for writing.""" + super().open() + self.h5_writer.create_dataset( + "variants", + (len(self.variants),), + dtype="S100", + compression="gzip", + ) + self.h5_writer.create_dataset( + "attribution_alt", + (len(self.genes), 4, DECIMA_CONTEXT_SIZE), + chunks=(1, 4, DECIMA_CONTEXT_SIZE), + dtype="float32", + compression="gzip", + ) + self.h5_writer.create_dataset( + "sequence_alt", + (len(self.genes), DECIMA_CONTEXT_SIZE), + chunks=(1, DECIMA_CONTEXT_SIZE), + dtype="i1", + compression="gzip", + ) + self.h5_writer["variants"][:] = np.array(self.variants, dtype="S100") + + def add( + self, + variant: str, + gene: str, + seqs_ref: np.ndarray, + attrs_ref: np.ndarray, + seqs_alt: np.ndarray, + attrs_alt: np.ndarray, + gene_mask_start: int, + gene_mask_end: int, + ): + """Add attribution data for a variant gene pair. + + Args: + variant: Variant name from the variants list. + gene: Gene name from the genes list. + attrs: Attribution scores, shape (4, DECIMA_CONTEXT_SIZE). + seqs: One-hot DNA sequence, shape (4, DECIMA_CONTEXT_SIZE). + """ + super().add((variant, gene), seqs_ref, attrs_ref, gene_mask_start, gene_mask_end) + idx = self.idx[(variant, gene)] + self.h5_writer["variants"][idx] = np.array(variant, dtype="S100") + self.h5_writer["sequence_alt"][idx, :] = convert_input_type( + torch.from_numpy(seqs_alt), # convert_input_type only support Tensor + "indices", + input_type="one_hot", + )[np.newaxis].astype("i1") + self.h5_writer["attribution_alt"][idx, :, :] = attrs_alt.astype("float32") + + def close(self): + """Close HDF5 file and optional BigWig file.""" + super().close() + self.h5_writer.close() diff --git a/src/decima/vep/__init__.py b/src/decima/vep/__init__.py index 53fb49a..1e2d2d1 100644 --- a/src/decima/vep/__init__.py +++ b/src/decima/vep/__init__.py @@ -1,254 +1,5 @@ -import logging -import warnings -from pathlib import Path -from collections import Counter -from typing import Optional, Union, List +from decima.vep.vep import predict_variant_effect +from decima.vep.attributions import variant_effect_attribution -import pandas as pd -from grelu.transforms.prediction_transforms import Aggregate -from decima.constants import SUPPORTED_GENOMES, DEFAULT_ENSEMBLE -from decima.model.metrics import WarningType -from decima.utils import get_compute_device -from decima.utils.dataframe import chunk_df, ChunkDataFrameWriter -from decima.utils.io import read_vcf_chunks -from decima.data.dataset import VariantDataset -from decima.hub import load_decima_model - - -def _predict_variant_effect( - df_variant: Union[pd.DataFrame, str], - tasks: Optional[Union[str, List[str]]] = None, - model: Union[str, int] = DEFAULT_ENSEMBLE, - metadata_anndata: Optional[str] = None, - batch_size: int = 1, - num_workers: int = 16, - device: Optional[str] = None, - include_cols: Optional[List[str]] = None, - gene_col: Optional[str] = None, - distance_type: Optional[str] = "tss", - min_distance: Optional[float] = 0, - max_distance: Optional[float] = float("inf"), - genome: str = "hg38", - save_replicates: bool = False, - reference_cache: bool = True, - float_precision: str = "32", -) -> pd.DataFrame: - """Predict variant effect on a set of variants - - Args: - df_variant (pd.DataFrame): DataFrame with variant information - tasks (str, optional): Tasks to predict. Defaults to None. - model (int, optional): Model to use. Defaults to 0. - metadata_anndata (str, optional): Path to anndata file. Defaults to None. - batch_size (int, optional): Batch size. Defaults to 1. - num_workers (int, optional): Number of workers. Defaults to 16. - device (str, optional): Device to use. Defaults to None. - include_cols (list, optional): Columns to include in the output. Defaults to None. - gene_col (str, optional): Column name for gene names. Defaults to None. - distance_type (str, optional): Type of distance. Defaults to "tss". - min_distance (float, optional): Minimum distance from the end of the gene. Defaults to 0 (inclusive). - max_distance (float, optional): Maximum distance from the TSS. Defaults to inf (exclusive). - genome (str, optional): Genome name or path to the genome fasta file. Defaults to "hg38". - save_replicates (bool, optional): Save the replicates in the output. Defaults to False. - reference_cache (bool, optional): Whether to use reference cache. Defaults to True. - float_precision (str, optional): Floating-point precision. Defaults to "32". - - Returns: - pd.DataFrame: DataFrame with variant effect predictions - """ - if (genome not in SUPPORTED_GENOMES) and (not Path(genome).exists()): - raise ValueError(f"Genome {genome} not supported. Currently only hg38 is supported.") - include_cols = include_cols or list() - - try: - dataset = VariantDataset( - df_variant, - include_cols=include_cols, - metadata_anndata=metadata_anndata, - gene_col=gene_col, - distance_type=distance_type, - min_distance=min_distance, - max_distance=max_distance, - model_name=model.name, - reference_cache=reference_cache, - genome=genome, - ) - except ValueError as e: - if str(e).startswith("NoOverlapError"): - warnings.warn("No overlapping gene and variant found. Skipping this chunk...") - return pd.DataFrame(columns=[*VariantDataset.DEFAULT_COLUMNS, *include_cols]), [], 0 - else: - raise e - - if tasks is not None: - tasks = dataset.result.query_cells(tasks) - - model.reset_transform() - agg_transform = Aggregate(tasks=tasks, model=model) - model.add_transform(agg_transform) - else: - tasks = dataset.result.cells - - logging.getLogger("decima").info(f"Performing predictions on {dataset}") - results = model.predict_on_dataset( - dataset, device=device, batch_size=batch_size, num_workers=num_workers, float_precision=float_precision - ) - - df = dataset.variants.reset_index(drop=True) - df_pred = pd.DataFrame(results["expression"], columns=tasks) - - if save_replicates: - df_pred = pd.concat( - [ - df_pred, - *[ - pd.DataFrame(pred, columns=tasks).rename(columns=lambda x: f"{x}_{model.name}") - for i, (pred, model) in enumerate(zip(results["ensemble_preds"], model.models)) - ], - ], - axis=1, - ) - return pd.concat([df, df_pred], axis=1), results["warnings"], results["expression"].shape[0] - - -def predict_variant_effect( - df_variant: Union[pd.DataFrame, str], - output_pq: Optional[str] = None, - tasks: Optional[Union[str, List[str]]] = None, - model: Union[int, str, List[str]] = DEFAULT_ENSEMBLE, - metadata_anndata: Optional[str] = None, - chunksize: int = 10_000, - batch_size: int = 1, - num_workers: int = 16, - device: Optional[str] = None, - include_cols: Optional[List[str]] = None, - gene_col: Optional[str] = None, - distance_type: Optional[str] = "tss", - min_distance: Optional[float] = 0, - max_distance: Optional[float] = float("inf"), - genome: str = "hg38", - save_replicates: bool = False, - reference_cache: bool = True, - float_precision: str = "32", -) -> None: - """Predict variant effect and save to parquet - - Args: - df_variant (pd.DataFrame or str): DataFrame with variant information or path to variant file - output_pq (str, optional): Path to save the parquet file. Defaults to None. - tasks (str, optional): Tasks to predict. Defaults to None. - model (int, optional): Model to use. Defaults to DEFAULT_ENSEMBLE. - metadata_anndata (str, optional): Path to anndata file. Defaults to None. - chunksize (int, optional): Number of variants to predict in each chunk. Defaults to 10_000. - batch_size (int, optional): Batch size. Defaults to 1. - num_workers (int, optional): Number of workers. Defaults to 16. - device (str, optional): Device to use. Defaults to None. - include_cols (list, optional): Columns to include in the output. Defaults to None. - gene_col (str, optional): Column name for gene names. Defaults to None. - distance_type (str, optional): Type of distance. Defaults to "tss". - min_distance (float, optional): Minimum distance from the end of the gene. Defaults to 0 (inclusive). - max_distance (float, optional): Maximum distance from the TSS. Defaults to inf (exclusive). - genome (str, optional): Genome name or path to the genome fasta file. Defaults to "hg38". - save_replicates (bool, optional): Save the replicates in the output. Defaults to False. - reference_cache (bool, optional): Whether to use reference cache. Defaults to True. - float_precision (str, optional): Floating-point precision. Defaults to "32". - """ - logger = logging.getLogger("decima") - device = get_compute_device(device) - logger.info(f"Using device: {device} and genome: {genome}") - - if isinstance(df_variant, pd.DataFrame): - chunks = chunk_df(df_variant, chunksize) - elif isinstance(df_variant, str): - if df_variant.endswith(".tsv"): - chunks = pd.read_csv(df_variant, sep="\t", chunksize=chunksize) - elif df_variant.endswith(".csv"): - chunks = pd.read_csv(df_variant, sep=",", chunksize=chunksize) - elif df_variant.endswith(".vcf") or df_variant.endswith(".vcf.gz"): - chunks = read_vcf_chunks(df_variant, chunksize) - else: - raise ValueError(f"Unsupported file extension: {df_variant}. Must be .tsv or .vcf.") - else: - raise ValueError( - f"Unsupported input type: {type(df_variant)}. Must be pd.DataFrame or str (path to .tsv or .vcf)." - ) - - model = load_decima_model(model=model, device=device) - - results = ( - _predict_variant_effect( - df_variant=df_chunk, - tasks=tasks, - model=model, - metadata_anndata=metadata_anndata, - batch_size=batch_size, - num_workers=num_workers, - device=device, - include_cols=include_cols, - gene_col=gene_col, - distance_type=distance_type, - min_distance=min_distance, - max_distance=max_distance, - genome=genome, - save_replicates=save_replicates, - reference_cache=reference_cache, - float_precision=float_precision, - ) - for df_chunk in chunks - ) - - warning_counter = Counter() - num_variants = 0 - - if Path(genome).exists(): - genome_path = genome - else: - import genomepy - - genome_path = genomepy.Genome(genome).filename - - if output_pq is not None: - metadata = { - "genome": genome, - "model": model.name, - "min_distance": int(min_distance) if min_distance is not None else None, - "max_distance": max_distance, - } - with ChunkDataFrameWriter(output_pq, metadata=metadata) as writer: - for df, warnings, _num_variants in results: - if df.shape[0] == 0: - warnings.append("no_overlap_found_for_chunk") - else: - writer.write(df) - num_variants += _num_variants - warning_counter.update(warnings) - if warning_counter.total(): - with open(output_pq + ".warnings.log", "w") as f: - for warning, count in warning_counter.items(): - f.write(f"{warning}: {count} / {num_variants} \n") - else: - _df = list() - for df, warnings, _num_variants in results: - num_variants += _num_variants - warning_counter.update(warnings) - _df.append(df) - - if warning_counter.total(): - logger.warning("Warnings:") - - for warning, count in warning_counter.items(): - if count == 0: - continue - if warning == WarningType.ALLELE_MISMATCH_WITH_REFERENCE_GENOME.value: - logger.warning( - f"{warning}: {count} alleles out of {num_variants} predictions mismatched with the genome file {genome_path}." - "If this is not expected, please check if you are using the correct genome version." - ) - elif warning == "no_overlap_found_for_chunk": - logger.warning(f"{warning}: {count} chunks with no overlap found with genes.") - else: - logger.warning(f"{warning}: {count} out of {num_variants} variants") - - if output_pq is None: - return pd.concat(_df) +__all__ = ["predict_variant_effect", "variant_effect_attribution"] diff --git a/src/decima/vep/attributions.py b/src/decima/vep/attributions.py new file mode 100644 index 0000000..fc54d05 --- /dev/null +++ b/src/decima/vep/attributions.py @@ -0,0 +1,135 @@ +import logging +from typing import Optional, Union, List + +import torch +import pandas as pd +from tqdm import tqdm + +from decima.utils import get_compute_device, _get_on_off_tasks +from decima.utils.io import read_vcf_chunks, VariantAttributionWriter +from decima.core.result import DecimaResult +from decima.data.dataset import VariantDataset +from decima.interpret.attributer import DecimaAttributer +from decima.model.metrics import WarningCounter +from decima.vep.vep import _log_vep_warnings, _write_vep_warnings + + +def variant_effect_attribution( + df_variant: Union[pd.DataFrame, str], + output_h5: Optional[str] = None, + tasks: Optional[Union[str, List[str]]] = None, + off_tasks: Optional[Union[str, List[str]]] = None, + model: int = 0, + metadata_anndata: Optional[str] = None, + method: str = "inputxgradient", + transform: str = "specificity", + num_workers: int = 4, + device: Optional[str] = None, + gene_col: Optional[str] = None, + distance_type: Optional[str] = "tss", + min_distance: Optional[float] = 0, + max_distance: Optional[float] = float("inf"), + genome: str = "hg38", + float_precision: str = "32", +): + """ """ + + logger = logging.getLogger("decima") + device = get_compute_device(device) + logger.info(f"Using device: {device} and genome: {genome}") + + if isinstance(df_variant, pd.DataFrame): + pass + elif isinstance(df_variant, str): + if df_variant.endswith(".tsv"): + df_variant = pd.read_csv(df_variant, sep="\t") + elif df_variant.endswith(".csv"): + df_variant = pd.read_csv(df_variant, sep=",") + elif df_variant.endswith(".vcf") or df_variant.endswith(".vcf.gz"): + df_variant = next(read_vcf_chunks(df_variant, chunksize=float("inf"))) + else: + raise ValueError(f"Unsupported file extension: {df_variant}. Must be .tsv or .vcf.") + else: + raise ValueError( + f"Unsupported input type: {type(df_variant)}. Must be pd.DataFrame or str (path to .tsv or .vcf)." + ) + + result = DecimaResult.load(metadata_anndata) + + tasks, off_tasks = _get_on_off_tasks(result, tasks, off_tasks) + attributer = DecimaAttributer.load_decima_attributer( + model_name=model, + tasks=tasks, + off_tasks=off_tasks, + method=method, + transform=transform, + device=device, + ) + + warning_counter = WarningCounter() + + dataset = VariantDataset( + df_variant, + metadata_anndata=metadata_anndata, + gene_col=gene_col, + distance_type=distance_type, + min_distance=min_distance, + max_distance=max_distance, + reference_cache=False, + genome=genome, + ) + variants = ( + dataset.variants["chrom"] + + "_" + + dataset.variants["pos"].astype(str) + + "_" + + dataset.variants["ref"] + + "_" + + dataset.variants["alt"] + ) + genes = dataset.variants["gene"] + dl = torch.utils.data.DataLoader(dataset, batch_size=1, num_workers=num_workers, collate_fn=dataset.collate_fn) + + seqs = list() + attrs = list() + for batch in tqdm(dl, total=len(dataset), desc="Computing attributions..."): + seqs.append(batch["seq"].cpu().numpy()) + attrs.append(attributer.attribute(batch["seq"].to(device)).detach().cpu().float().numpy()) + warning_counter.update(batch["warning"]) + + with VariantAttributionWriter( + path=output_h5, + genes=genes, + variants=variants, + model_name=attributer.model.name, + metadata_anndata=result, + genome=genome, + ) as writer: + for variant, gene, gene_mask_start, gene_mask_end, seqs_ref, seqs_alt, attrs_ref, attrs_alt in tqdm( + zip( + variants, + genes, + dataset.variants["gene_mask_start"], + dataset.variants["gene_mask_end"], + seqs[::2], + seqs[1::2], + attrs[::2], + attrs[1::2], + ), + total=len(variants), + desc="Writing attributions...", + ): + writer.add( + variant=variant, + gene=gene, + seqs_ref=seqs_ref[0, :4], + seqs_alt=seqs_alt[0, :4], + attrs_ref=attrs_ref[0, :4], + attrs_alt=attrs_alt[0, :4], + gene_mask_start=gene_mask_start, + gene_mask_end=gene_mask_end, + ) + + warning_counter = warning_counter.compute() + _log_vep_warnings(warning_counter, len(variants), genome) + _write_vep_warnings(warning_counter, len(variants), output_h5) diff --git a/src/decima/vep/vep.py b/src/decima/vep/vep.py new file mode 100644 index 0000000..7466b99 --- /dev/null +++ b/src/decima/vep/vep.py @@ -0,0 +1,264 @@ +import logging +import warnings +from pathlib import Path +from collections import Counter +from typing import Optional, Union, List + +import pandas as pd +from grelu.transforms.prediction_transforms import Aggregate + +from decima.constants import SUPPORTED_GENOMES, DEFAULT_ENSEMBLE +from decima.model.metrics import WarningType +from decima.utils import get_compute_device +from decima.utils.dataframe import chunk_df, ChunkDataFrameWriter +from decima.utils.io import read_vcf_chunks +from decima.data.dataset import VariantDataset +from decima.hub import load_decima_model + + +def _predict_variant_effect( + df_variant: Union[pd.DataFrame, str], + tasks: Optional[Union[str, List[str]]] = None, + model: Union[str, int] = DEFAULT_ENSEMBLE, + metadata_anndata: Optional[str] = None, + batch_size: int = 1, + num_workers: int = 16, + device: Optional[str] = None, + include_cols: Optional[List[str]] = None, + gene_col: Optional[str] = None, + distance_type: Optional[str] = "tss", + min_distance: Optional[float] = 0, + max_distance: Optional[float] = float("inf"), + genome: str = "hg38", + save_replicates: bool = False, + reference_cache: bool = True, + float_precision: str = "32", +) -> pd.DataFrame: + """Predict variant effect on a set of variants + + Args: + df_variant (pd.DataFrame): DataFrame with variant information + tasks (str, optional): Tasks to predict. Defaults to None. + model (int, optional): Model to use. Defaults to 0. + metadata_anndata (str, optional): Path to anndata file. Defaults to None. + batch_size (int, optional): Batch size. Defaults to 1. + num_workers (int, optional): Number of workers. Defaults to 16. + device (str, optional): Device to use. Defaults to None. + include_cols (list, optional): Columns to include in the output. Defaults to None. + gene_col (str, optional): Column name for gene names. Defaults to None. + distance_type (str, optional): Type of distance. Defaults to "tss". + min_distance (float, optional): Minimum distance from the end of the gene. Defaults to 0 (inclusive). + max_distance (float, optional): Maximum distance from the TSS. Defaults to inf (exclusive). + genome (str, optional): Genome name or path to the genome fasta file. Defaults to "hg38". + save_replicates (bool, optional): Save the replicates in the output. Defaults to False. + reference_cache (bool, optional): Whether to use reference cache. Defaults to True. + float_precision (str, optional): Floating-point precision. Defaults to "32". + + Returns: + pd.DataFrame: DataFrame with variant effect predictions + """ + if (genome not in SUPPORTED_GENOMES) and (not Path(genome).exists()): + raise ValueError(f"Genome {genome} not supported. Currently only hg38 is supported.") + include_cols = include_cols or list() + + try: + dataset = VariantDataset( + df_variant, + include_cols=include_cols, + metadata_anndata=metadata_anndata, + gene_col=gene_col, + distance_type=distance_type, + min_distance=min_distance, + max_distance=max_distance, + model_name=model.name, + reference_cache=reference_cache, + genome=genome, + ) + except ValueError as e: + if str(e).startswith("NoOverlapError"): + warnings.warn("No overlapping gene and variant found. Skipping this chunk...") + return pd.DataFrame(columns=[*VariantDataset.DEFAULT_COLUMNS, *include_cols]), [], 0 + else: + raise e + + if tasks is not None: + tasks = dataset.result.query_cells(tasks) + + model.reset_transform() + agg_transform = Aggregate(tasks=tasks, model=model) + model.add_transform(agg_transform) + else: + tasks = dataset.result.cells + + logging.getLogger("decima").info(f"Performing predictions on {dataset}") + results = model.predict_on_dataset( + dataset, device=device, batch_size=batch_size, num_workers=num_workers, float_precision=float_precision + ) + + df = dataset.variants.reset_index(drop=True) + df_pred = pd.DataFrame(results["expression"], columns=tasks) + + if save_replicates: + df_pred = pd.concat( + [ + df_pred, + *[ + pd.DataFrame(pred, columns=tasks).rename(columns=lambda x: f"{x}_{model.name}") + for i, (pred, model) in enumerate(zip(results["ensemble_preds"], model.models)) + ], + ], + axis=1, + ) + return pd.concat([df, df_pred], axis=1), results["warnings"], results["expression"].shape[0] + + +def predict_variant_effect( + df_variant: Union[pd.DataFrame, str], + output_pq: Optional[str] = None, + tasks: Optional[Union[str, List[str]]] = None, + model: Union[int, str, List[str]] = DEFAULT_ENSEMBLE, + metadata_anndata: Optional[str] = None, + chunksize: int = 10_000, + batch_size: int = 1, + num_workers: int = 16, + device: Optional[str] = None, + include_cols: Optional[List[str]] = None, + gene_col: Optional[str] = None, + distance_type: Optional[str] = "tss", + min_distance: Optional[float] = 0, + max_distance: Optional[float] = float("inf"), + genome: str = "hg38", + save_replicates: bool = False, + reference_cache: bool = True, + float_precision: str = "32", +) -> None: + """Predict variant effect and save to parquet + + Args: + df_variant (pd.DataFrame or str): DataFrame with variant information or path to variant file + output_pq (str, optional): Path to save the parquet file. Defaults to None. + tasks (str, optional): Tasks to predict. Defaults to None. + model (int, optional): Model to use. Defaults to DEFAULT_ENSEMBLE. + metadata_anndata (str, optional): Path to anndata file. Defaults to None. + chunksize (int, optional): Number of variants to predict in each chunk. Defaults to 10_000. + batch_size (int, optional): Batch size. Defaults to 1. + num_workers (int, optional): Number of workers. Defaults to 16. + device (str, optional): Device to use. Defaults to None. + include_cols (list, optional): Columns to include in the output. Defaults to None. + gene_col (str, optional): Column name for gene names. Defaults to None. + distance_type (str, optional): Type of distance. Defaults to "tss". + min_distance (float, optional): Minimum distance from the end of the gene. Defaults to 0 (inclusive). + max_distance (float, optional): Maximum distance from the TSS. Defaults to inf (exclusive). + genome (str, optional): Genome name or path to the genome fasta file. Defaults to "hg38". + save_replicates (bool, optional): Save the replicates in the output. Defaults to False. + reference_cache (bool, optional): Whether to use reference cache. Defaults to True. + float_precision (str, optional): Floating-point precision. Defaults to "32". + """ + logger = logging.getLogger("decima") + device = get_compute_device(device) + logger.info(f"Using device: {device} and genome: {genome}") + + if isinstance(df_variant, pd.DataFrame): + chunks = chunk_df(df_variant, chunksize) + elif isinstance(df_variant, str): + if df_variant.endswith(".tsv"): + chunks = pd.read_csv(df_variant, sep="\t", chunksize=chunksize) + elif df_variant.endswith(".csv"): + chunks = pd.read_csv(df_variant, sep=",", chunksize=chunksize) + elif df_variant.endswith(".vcf") or df_variant.endswith(".vcf.gz"): + chunks = read_vcf_chunks(df_variant, chunksize) + else: + raise ValueError(f"Unsupported file extension: {df_variant}. Must be .tsv or .vcf.") + else: + raise ValueError( + f"Unsupported input type: {type(df_variant)}. Must be pd.DataFrame or str (path to .tsv or .vcf)." + ) + + model = load_decima_model(model=model, device=device) + + results = ( + _predict_variant_effect( + df_variant=df_chunk, + tasks=tasks, + model=model, + metadata_anndata=metadata_anndata, + batch_size=batch_size, + num_workers=num_workers, + device=device, + include_cols=include_cols, + gene_col=gene_col, + distance_type=distance_type, + min_distance=min_distance, + max_distance=max_distance, + genome=genome, + save_replicates=save_replicates, + reference_cache=reference_cache, + float_precision=float_precision, + ) + for df_chunk in chunks + ) + + warning_counter = Counter() + num_variants = 0 + + if Path(genome).exists(): + genome_path = genome + else: + import genomepy + + genome_path = genomepy.Genome(genome).filename + + if output_pq is not None: + metadata = { + "genome": genome, + "model": model.name, + "min_distance": int(min_distance) if min_distance is not None else None, + "max_distance": max_distance, + } + with ChunkDataFrameWriter(output_pq, metadata=metadata) as writer: + for df, warnings, _num_variants in results: + if df.shape[0] == 0: + warnings.append("no_overlap_found_for_chunk") + else: + writer.write(df) + num_variants += _num_variants + warning_counter.update(warnings) + _write_vep_warnings(warning_counter, num_variants, output_pq) + else: + _df = list() + for df, warnings, _num_variants in results: + num_variants += _num_variants + warning_counter.update(warnings) + _df.append(df) + + _log_vep_warnings(warning_counter, num_variants, genome_path) + + if output_pq is None: + return pd.concat(_df) + + +def _log_vep_warnings(warning_counter: Counter, num_variants: int, genome_path: str): + logger = logging.getLogger("decima") + + if warning_counter.total(): + logger.warning("Warnings:") + + for warning, count in warning_counter.items(): + if count == 0: + continue + if warning == WarningType.ALLELE_MISMATCH_WITH_REFERENCE_GENOME.value: + logger.warning( + f"{warning}: {count} alleles out of {num_variants} predictions mismatched with the genome file {genome_path}." + "If this is not expected, please check if you are using the correct genome version." + ) + elif warning == "no_overlap_found_for_chunk": + logger.warning(f"{warning}: {count} chunks with no overlap found with genes.") + else: + logger.warning(f"{warning}: {count} out of {num_variants} variants") + + +def _write_vep_warnings(warning_counter: Counter, num_variants: int, output_path: str): + if warning_counter.total(): + with open(Path(output_path).with_suffix(".warnings.log"), "w") as f: + for warning, count in warning_counter.items(): + f.write(f"{warning}: {count} / {num_variants} \n") diff --git a/tests/test_cli.py b/tests/test_cli.py index 4439c03..5be3573 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -325,3 +325,18 @@ def test_cli_modisco(tmp_path): assert (output_prefix.with_suffix(".attributions.h5")).exists() assert (output_prefix.with_suffix(".modisco.h5")).exists() assert Path(str(output_prefix) + "_report").exists() + + +@pytest.mark.long_running +def test_cli_vep_attributions(tmp_path): + output_file = tmp_path / "test_vep_attributions.h5" + runner = CliRunner() + result = runner.invoke(main, [ + "vep-attribution", + "-v", "tests/data/variants.tsv", + "-o", str(output_file), + "--model", "0", + "--device", device, + ]) + assert result.exit_code == 0, result.__dict__ + assert (output_file.exists()) diff --git a/tests/test_vep_attributions.py b/tests/test_vep_attributions.py new file mode 100644 index 0000000..d166d37 --- /dev/null +++ b/tests/test_vep_attributions.py @@ -0,0 +1,61 @@ +import pytest +import h5py +from decima.constants import DECIMA_CONTEXT_SIZE +from decima.vep.attributions import variant_effect_attribution +from decima.core.attribution import VariantAttributionResult + + +@pytest.mark.long_running +def test_variant_effect_attribution(): + variant_effect_attribution( + "tests/data/test.vcf", + "tests/data/test.h5", + model=0, + method="inputxgradient", + ) + with h5py.File("tests/data/test.h5", "r") as f: + assert len(f['variants'][:]) == 82 + assert len(f['genes'][:]) == 82 + assert f['attribution'].shape == (82, 4, DECIMA_CONTEXT_SIZE) + assert f['sequence'].shape == (82, DECIMA_CONTEXT_SIZE) + assert f['attribution_alt'].shape == (82, 4, DECIMA_CONTEXT_SIZE) + assert f['sequence_alt'].shape == (82, DECIMA_CONTEXT_SIZE) + + +def test_VariantAttributionResult(tmp_path, attribution_data): + + h5_path = tmp_path / "vep_test_attributions.h5" + + variants = ['chr1_1000018_G_A'] * len(attribution_data['genes']) + + with h5py.File(h5_path, 'w') as f: + f.create_dataset('genes', data=[name.encode('utf-8') for name in attribution_data['genes']]) + f.create_dataset('variants', data=[variant.encode('utf-8') for variant in variants]) + f.create_dataset('sequence', data=attribution_data['sequences']) + f.create_dataset('attribution', data=attribution_data['attributions']) + f.create_dataset('sequence_alt', data=attribution_data['sequences']) + f.create_dataset('attribution_alt', data=attribution_data['attributions']) + f.create_dataset('gene_mask_start', data=attribution_data['gene_mask_start']) + f.create_dataset('gene_mask_end', data=attribution_data['gene_mask_end']) + f.attrs['model_name'] = 'v1_rep0' + f.attrs['genome'] = 'hg38' + + with VariantAttributionResult(str(h5_path), tss_distance=10_000, num_workers=1) as ar: + genes = ar.genes + seqs_ref, attrs_ref, seqs_alt, attrs_alt = ar.load(variants, genes) + + assert seqs_ref.shape == (len(attribution_data['genes']), 4, 20_000) + assert attrs_ref.shape == (len(attribution_data['genes']), 4, 20_000) + assert seqs_alt.shape == (len(attribution_data['genes']), 4, 20_000) + assert attrs_alt.shape == (len(attribution_data['genes']), 4, 20_000) + + attribution_ref, attribution_alt = ar.load_attribution(variants[0], genes[0]) + assert attribution_ref.gene == genes[0] + assert attribution_ref.chrom == 'chr15' + assert attribution_ref.start == 43736410 + assert attribution_ref.end == 43756410 + + assert attribution_alt.gene == genes[0] + assert attribution_alt.chrom == 'chr15' + assert attribution_alt.start == 43736410 + assert attribution_alt.end == 43756410 From 37db685f10a46fd38076668298700914dd3134b8 Mon Sep 17 00:00:00 2001 From: Muhammed Hasan Celik Date: Tue, 16 Dec 2025 22:46:58 +0000 Subject: [PATCH 2/4] tutorial added and more testcases --- .gitignore | 2 + .../6-variant-effect-attribution.ipynb | 6466 ++++++++++++++++- src/decima/cli/vep_attribution.py | 23 +- src/decima/core/attribution.py | 200 +- src/decima/utils/io.py | 8 + src/decima/vep/attributions.py | 149 +- src/decima/vep/vep.py | 13 + tests/test_attribution.py | 5 +- tests/test_cli.py | 6 +- tests/test_interpret_attribution.py | 82 + tests/test_vep.py | 2 +- tests/test_vep_attributions.py | 28 +- 12 files changed, 6736 insertions(+), 248 deletions(-) diff --git a/.gitignore b/.gitignore index c599a11..c72956b 100644 --- a/.gitignore +++ b/.gitignore @@ -187,3 +187,5 @@ docs/api docs/tutorials/example docs/wandb run_tutorial.s* + +logs/ diff --git a/docs/tutorials/6-variant-effect-attribution.ipynb b/docs/tutorials/6-variant-effect-attribution.ipynb index a27df7c..02b361b 100644 --- a/docs/tutorials/6-variant-effect-attribution.ipynb +++ b/docs/tutorials/6-variant-effect-attribution.ipynb @@ -8,6 +8,14 @@ "# Variant Effect Prediction with Motif Intpretation with Attributions " ] }, + { + "cell_type": "markdown", + "id": "a14c1ad1", + "metadata": {}, + "source": [ + "This API provides a methodology for interpreting the effects of genetic variants. Specifically, it enables the detection of regulatory elements within both reference and alternative sequences containing variants. This is achieved by calculating gradients for both sequences with respect to the expression of specific genes within a given cellular context." + ] + }, { "cell_type": "markdown", "id": "91fc519b", @@ -16,233 +24,6397 @@ "## CLI API" ] }, + { + "cell_type": "markdown", + "id": "c9aceeef", + "metadata": {}, + "source": [ + "Command line api to calculate gradients for genetic variatns. " + ] + }, { "cell_type": "code", "execution_count": 1, "id": "7d51a63c", + "metadata": { + "execution": { + "iopub.execute_input": "2025-12-16T22:13:35.186905Z", + "iopub.status.busy": "2025-12-16T22:13:35.186779Z", + "iopub.status.idle": "2025-12-16T22:13:45.592934Z", + "shell.execute_reply": "2025-12-16T22:13:45.592177Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Usage: decima vep-attribution [OPTIONS]\r\n", + "\r\n", + " Predict variant effect and save to parquet\r\n", + "\r\n", + " Examples:\r\n", + "\r\n", + " >>> decima vep-attribution -v \"data/sample.vcf\" -o \"vep_results\"\r\n", + "\r\n", + "Options:\r\n", + " -v, --variants PATH Path to the variant file .vcf file. VCF file\r\n", + " need to be normalized. Try normalizing th\r\n", + " vcf file incase of an error. `bcftools norm\r\n", + " -f ref.fasta input.vcf.gz -o output.vcf.gz`\r\n", + " -o, --output_prefix PATH Path to the output prefix.\r\n", + " --tasks TEXT Tasks to predict. If not provided, all tasks\r\n", + " will be predicted.\r\n", + " --off-tasks TEXT Tasks to contrast against. If not provided,\r\n", + " no contrast will be performed.\r\n", + " --model TEXT Model to use for attribution analysis.\r\n", + " Available options: ['v1_rep0', 'v1_rep1',\r\n", + " 'v1_rep2', 'v1_rep3', 'ensemble', 'rep0',\r\n", + " 'rep1', 'rep2', 'rep3', 0, 1, 2, 3, '0',\r\n", + " '1', '2', '3'].\r\n", + " --metadata TEXT Path to the metadata anndata file or name of\r\n", + " the model. If not provided, the compabilite\r\n", + " metadata for the model will be used.\r\n", + " Default: ensemble.\r\n", + " --method TEXT Method to use for attribution analysis.\r\n", + " Available options: 'inputxgradient',\r\n", + " 'saliency', 'integratedgradients'.\r\n", + " --transform [specificity|aggregate]\r\n", + " Transform to use for attribution analysis.\r\n", + " Available options: 'specificity',\r\n", + " 'aggregate'.\r\n", + " --device TEXT Device to use. Default: None which\r\n", + " automatically selects the best device.\r\n", + " --batch-size INTEGER Batch size for the loader. Default: 1\r\n", + " --num-workers INTEGER Number of workers for the loader. Default: 4\r\n", + " --distance-type TEXT Type of distance. Default: tss.\r\n", + " --min-distance FLOAT Minimum distance from the end of the gene.\r\n", + " Default: 0.\r\n", + " --max-distance FLOAT Maximum distance from the TSS. Default:\r\n", + " 524288.\r\n", + " --gene-col TEXT Column name for gene names. Default: None.\r\n", + " --genome TEXT Genome build. Default: hg38.\r\n", + " --help Show this message and exit.\r\n", + "\u001b[0m" + ] + } + ], + "source": [ + "! decima vep-attribution --help" + ] + }, + { + "cell_type": "markdown", + "id": "3e6aa7ea", "metadata": {}, + "source": [ + "Following command will calculate input x gradients of genetics variatns in the sample.vcf file for aggreation of all tasks avaliable in the decima metadata." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "963b41a8", + "metadata": { + "execution": { + "iopub.execute_input": "2025-12-16T22:13:45.594898Z", + "iopub.status.busy": "2025-12-16T22:13:45.594710Z", + "iopub.status.idle": "2025-12-16T22:19:23.887193Z", + "shell.execute_reply": "2025-12-16T22:19:23.886257Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "decima - INFO - Using device: 0 and genome: hg38\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34m\u001b[1mwandb\u001b[0m: Currently logged in as: \u001b[33mmhcelik\u001b[0m (\u001b[33mmhcw\u001b[0m) to \u001b[32mhttps://api.wandb.ai\u001b[0m. Use \u001b[1m`wandb login --relogin`\u001b[0m to force relogin\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34m\u001b[1mwandb\u001b[0m: Downloading large artifact 'metadata:latest', 3122.32MB. 1 files...\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34m\u001b[1mwandb\u001b[0m: 1 of 1 files downloaded. \r\n", + "Done. 00:00:02.1 (1496.1MB/s)\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34m\u001b[1mwandb\u001b[0m: Downloading large artifact 'rep0:latest', 720.03MB. 1 files...\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34m\u001b[1mwandb\u001b[0m: 1 of 1 files downloaded. \r\n", + "Done. 00:00:00.5 (1396.4MB/s)\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34m\u001b[1mwandb\u001b[0m: Downloading large artifact 'metadata:latest', 3122.32MB. 1 files...\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34m\u001b[1mwandb\u001b[0m: 1 of 1 files downloaded. \r\n", + "Done. 00:00:01.8 (1740.2MB/s)\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\r", + "Computing attributions...: 0%| | 0/96 [00:00\n" + ] + } + ], + "source": [ + "with h5py.File(\"example/vep_vcf_attributions.h5\", \"r\") as f:\n", + " print(f.keys())" + ] + }, + { + "cell_type": "markdown", + "id": "f5e89a07", + "metadata": {}, + "source": [ + "Load the analysis results from an H5 file using the `VariantAttributionResult` class. Once loaded, you can access the variant-gene metadata via the `.df_variant` attribute.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3022232e", + "metadata": { + "execution": { + "iopub.execute_input": "2025-12-16T22:19:46.984364Z", + "iopub.status.busy": "2025-12-16T22:19:46.984223Z", + "iopub.status.idle": "2025-12-16T22:19:46.994711Z", + "shell.execute_reply": "2025-12-16T22:19:46.994165Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Usage: decima vep-attribution [OPTIONS]\n", - "\n", - " Predict variant effect and save to parquet\n", - "\n", - " Examples:\n", - "\n", - " >>> decima vep-attribution -v \"data/sample.vcf\" -o \"vep_results.h5\"\n", - "\n", - "Options:\n", - " -v, --variants PATH Path to the variant file .vcf file. VCF file\n", - " need to be normalized. Try normalizing th\n", - " vcf file incase of an error. `bcftools norm\n", - " -f ref.fasta input.vcf.gz -o output.vcf.gz`\n", - " -o, --output_h5 PATH Path to the output h5 file.\n", - " --tasks TEXT Tasks to predict. If not provided, all tasks\n", - " will be predicted.\n", - " --off-tasks TEXT Tasks to contrast against. If not provided,\n", - " no contrast will be performed.\n", - " --model INTEGER Model to use for attribution analysis.\n", - " Available options: ['v1_rep0', 'v1_rep1',\n", - " 'v1_rep2', 'v1_rep3', 'ensemble', 'rep0',\n", - " 'rep1', 'rep2', 'rep3', 0, 1, 2, 3, '0',\n", - " '1', '2', '3'].\n", - " --metadata TEXT Path to the metadata anndata file or name of\n", - " the model. If not provided, the compabilite\n", - " metadata for the model will be used.\n", - " Default: ensemble.\n", - " --method TEXT Method to use for attribution analysis.\n", - " Available options: 'inputxgradient',\n", - " 'saliency', 'integratedgradients'.\n", - " --transform [specificity|aggregate]\n", - " Transform to use for attribution analysis.\n", - " Available options: 'specificity',\n", - " 'aggregate'.\n", - " --device TEXT Device to use. Default: None which\n", - " automatically selects the best device.\n", - " --num-workers INTEGER Number of workers for the loader. Default: 4\n", - " --distance-type TEXT Type of distance. Default: tss.\n", - " --min-distance FLOAT Minimum distance from the end of the gene.\n", - " Default: 0.\n", - " --max-distance FLOAT Maximum distance from the TSS. Default:\n", - " 524288.\n", - " --gene-col TEXT Column name for gene names. Default: None.\n", - " --genome TEXT Genome build. Default: hg38.\n", - " --float-precision TEXT Floating-point precision to be used in\n", - " calculations. Avaliable options include:\n", - " '16-true', '16-mixed', 'bf16-true',\n", - " 'bf16-mixed', '32-true', '64-true', '32',\n", - " '16', and 'bf16'.\n", - " --help Show this message and exit.\n", - "\u001b[0m" + "['ABO']\n", + "['chr9_133251979_C_T']\n" ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
variantgenerel_postss_dist
0chr9_133251979_C_TABO18788524045
\n", + "
" + ], + "text/plain": [ + " variant gene rel_pos tss_dist\n", + "0 chr9_133251979_C_T ABO 187885 24045" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "! decima vep-attribution --help" + "from decima.core.attribution import VariantAttributionResult\n", + "\n", + "with VariantAttributionResult(\"example/vep_vcf_attributions.h5\", tss_distance=10_000, num_workers=1) as ar:\n", + " genes = ar.genes\n", + " variants = ar.variants\n", + " print(genes)\n", + " print(variants)\n", + " df_variants = ar.df_variants\n", + "\n", + "df_variants" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "963b41a8", + "cell_type": "markdown", + "id": "b91d2969", "metadata": {}, - "outputs": [], "source": [ - "! decima vep -v \"data/sample.vcf\" -o \"vep_vcf_attributions.h5\"" + "The variant is located 24,045 base pairs upstream of the Transcription Start Site (TSS) of the ABO gene. You can load attributions for specific variant-gene pairs by passing a list of variants and their corresponding genes. Note that the lengths of the two lists must be equal `(len(variants) == len(genes))`, and the order of genes must match the order of variants." ] }, { "cell_type": "code", - "execution_count": null, - "id": "a477e150", - "metadata": {}, - "outputs": [], - "source": [ - "! ls vep_vcf_attributions.*" - ] - }, - { - "cell_type": "markdown", - "id": "7fc2c6db", - "metadata": {}, + "execution_count": 9, + "id": "2cb59750", + "metadata": { + "execution": { + "iopub.execute_input": "2025-12-16T22:19:46.996082Z", + "iopub.status.busy": "2025-12-16T22:19:46.995943Z", + "iopub.status.idle": "2025-12-16T22:19:47.217301Z", + "shell.execute_reply": "2025-12-16T22:19:47.216694Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\r", + "Loading attributions and sequences...: 0%| | 0/1 [00:00" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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ggULUFZWhri4OKhUKuh0Orz++uu49957AdA1SXN8fHyQmJiIJUuWoG/fvggJCcGqVauwd+9e9OzZk7abjTpUMERIa5k9ezZSUlKwe/duVxelXejTpw+OHTuG0tJS/PTTT5g1axaSk5NdXaw2LTMzE3PnzsXWrVub1P4Ry4RaZQAYOHAgRowYgejoaKxZswYeHh4uLFnbptfrMWzYMLzxxhsAgMGDByMlJQWffvopZs2a5eLStR9ffvklbrnlFoSHh7u6KG3emjVr8MMPP2DlypXo168fjh07hnnz5iE8PJz2uRZ89913eOihhxAREQGVSoUhQ4bgnnvuweHDh11dtHanQ3WTCwoKgkqlapKhJS8vD6GhoS4qVfsibCfahs2bM2cOfv31V+zYsQORkZHG5aGhoairq0NJSYnJ62nbARqNBj179sTQoUOxdOlSJCQk4P3336dtZsHhw4eRn5+PIUOGwM3NDW5ubkhOTsYHH3wANzc3hISE0Lazkr+/P3r37o3z58/TPmdBWFgY4uPjTZb17dvX2MWQzg8tu3z5MrZt24ZHHnnEuIz2ueY999xzWLBgAe6++24MGDAA999/P5555hksXboUAO1zlvTo0QPJycmoqKhAZmYmDhw4gPr6esTGxtJ2s1GHCoY0Gg2GDh2KpKQk4zK9Xo+kpCQkJia6sGTtR0xMDEJDQ022YVlZGfbv39/ptyFjDHPmzMH69euxfft2xMTEmDw/dOhQqNVqk2139uxZZGRkdPpt15her0dtbS1tMwvGjh2LkydP4tixY8bbsGHDcO+99xof07azTkVFBS5cuICwsDDa5ywYNWpUk+kCzp07h+joaAB0frDG119/ja5du2LSpEnGZbTPNa+qqgpKpemlqEqlgl6vB0D7nDW8vLwQFhaG4uJibNmyBVOnTqXtZitXZ3Bwth9//JFptVr2zTffsNOnT7PHHnuM+fv7s9zcXFcXrc0oLy9nR48eZUePHmUA2H/+8x929OhRdvnyZcYYT8fo7+/PfvnlF3bixAk2depUSsfIGHvyySeZn58f27lzp0kK1aqqKuNrnnjiCdatWze2fft2dujQIZaYmMgSExNdWGrXW7BgAUtOTmYXL15kJ06cYAsWLGAKhYL98ccfjDHaZraQZpNjjLZdc5599lm2c+dOdvHiRfbXX3+xcePGsaCgIJafn88Yo+3WnAMHDjA3Nzf2+uuvs7S0NPbDDz8wT09P9v333xtfQ+eH5ul0OtatWzf2wgsvNHmO9jnzZs2axSIiIoyptX/++WcWFBTEnn/+eeNraJ8zb/PmzWzTpk0sPT2d/fHHHywhIYGNGDGC1dXVMcZou9miwwVDjDH24Ycfsm7dujGNRsOGDx/O9u3b5+oitSk7duxgAJrcZs2axRjjqSxfeeUVFhISwrRaLRs7diw7e/asawvdBpjbZgDY119/bXxNdXU1e+qpp1hAQADz9PRkt99+O8vJyXFdoduAhx56iEVHRzONRsOCg4PZ2LFjjYEQY7TNbNE4GKJtZ96MGTNYWFgY02g0LCIigs2YMcNkrhzabs373//+x/r378+0Wi2Li4tjn332mcnzdH5o3pYtWxgAs9uD9jnzysrK2Ny5c1m3bt2Yu7s7i42NZf/85z9ZbW2t8TW0z5m3evVqFhsbyzQaDQsNDWWzZ89mJSUlxudpu1lPwZhkml9CCCGEEEII6SQ61JghQgghhBBCCLEWBUOEEEIIIYSQTomCIUIIIYQQQkinRMEQIYQQQgghpFOiYIgQQgghhBDSKVEwRAghhBBCCOmUKBgihBBCCCGEdEoUDBFCCCGEEEI6JQqGCCGEEEIIIZ0SBUOEEEIIIYSQTomCIUIIIYQQQkin9P+rLSY8pNZMoAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "with VariantAttributionResult(\"vep_vcf_attributions.h5\", tss_distance=10_000, num_workers=1) as ar:\n", - " seqs_ref, attrs_ref, seqs_alt, attrs_alt = ar.load(variants, genes)\n", - " print(seqs_ref)\n", - " print(attrs_ref)" + "attribution_ref.plot_seqlogo(relative_loc=24045)" ] }, { "cell_type": "code", - "execution_count": null, - "id": "e8b3b4ed", - "metadata": {}, - "outputs": [], + "execution_count": 14, + "id": "1684816e", + "metadata": { + "execution": { + "iopub.execute_input": "2025-12-16T22:19:52.232145Z", + "iopub.status.busy": "2025-12-16T22:19:52.232007Z", + "iopub.status.idle": "2025-12-16T22:19:53.044712Z", + "shell.execute_reply": "2025-12-16T22:19:53.044181Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "with VariantAttributionResult(\"vep_vcf_attributions.h5\", tss_distance=10_000, num_workers=1) as ar:\n", - " attribution_ref, attribution_alt = ar.load(variants, genes)" + "attribution_alt.plot_seqlogo(relative_loc=24045)" ] }, { "cell_type": "code", - "execution_count": null, - "id": "fcc21b89", - "metadata": {}, - "outputs": [], + "execution_count": 15, + "id": "d6695d34", + "metadata": { + "execution": { + "iopub.execute_input": "2025-12-16T22:19:53.046114Z", + "iopub.status.busy": "2025-12-16T22:19:53.045953Z", + "iopub.status.idle": "2025-12-16T22:19:53.875337Z", + "shell.execute_reply": "2025-12-16T22:19:53.874787Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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9R5//HHMmjUL48ePR69evfDnP/8Zf/3rX5GcnAyA77madOzYEevXr0dhYSHOnTuH7du3o6ysDB06dOBxc1OjCoZ0Oh0GDBiAlJQU6zSz2YyUlBQMHjzYh3t25Wjfvj1iY2MdjmF+fj62bdvW5I+hiGDatGn4+uuv8fPPP6N9+/YO8wcMGICAgACHY3f06FGcPXu2yR+7ysxmMwwGA49ZDUaMGIH9+/djz5491tvAgQMxceJE62MeO9cUFhbi5MmTaNWqFd9zNRg6dGiV4QKOHTuGdu3aAeDvgys++ugjtGzZEmPGjLFO43uuesXFxfDzczwV1Wq1MJvNAPiec0VISAhatWqFS5cu4ccff8Stt97K4+YuX1dw8LSlS5eKXq+Xjz/+WA4dOiQPPvigREZGSkZGhq93rcEoKCiQ3bt3y+7duwWAvPnmm7J79245c+aMiKhyjJGRkfLNN9/Ivn375NZbb2U5RhF55JFHJCIiQtatW+dQQrW4uNi6zMMPPyxt27aVn3/+WXbs2CGDBw+WwYMH+3CvfW/WrFmyfv16OX36tOzbt09mzZolGo1GfvrpJxHhMXOHfTU5ER676vztb3+TdevWyenTp2XTpk2SlJQk0dHRkpWVJSI8btXZvn27+Pv7y0svvSTHjx+XTz/9VIKDg+V///ufdRn+PlTPZDJJ27Zt5cknn6wyj+855yZNmiTx8fHW0tpfffWVREdHyxNPPGFdhu8551atWiU//PCDnDp1Sn766Sfp06ePJCYmitFoFBEeN3c0umBIROSf//yntG3bVnQ6nQwaNEi2bt3q611qUNauXSsAqtwmTZokIqqU5bPPPisxMTGi1+tlxIgRcvToUd/udAPg7JgBkI8++si6TElJifzf//2fREVFSXBwsNx+++2Snp7uu51uAKZMmSLt2rUTnU4nLVq0kBEjRlgDIREeM3dUDoZ47JwbN26ctGrVSnQ6ncTHx8u4ceMcxsrhcaved999Jz179hS9Xi9du3aVDz74wGE+fx+q9+OPPwoAp8eD7znn8vPzZfr06dK2bVsJDAyUDh06yDPPPCMGg8G6DN9zzi1btkw6dOggOp1OYmNjZerUqZKbm2udz+PmOo2I3TC/RERERERETUSj6jNERERERETkKgZDRERERETUJDEYIiIiIiKiJonBEBERERERNUkMhoiIiIiIqEliMERERERERE0SgyEiIiIiImqSGAwREREREVGTxGCIiIiIiIiaJAZDRERERETUJDEYIiIiIiKiJun/AbC1rm1aiSNoAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "with VariantAttributionResult(\"vep_vcf_attributions.h5\", tss_distance=10_000, num_workers=1) as ar:\n", - " df_peaks, df_motifs = ar.recursive_seqlet_calling(variants, genes)" + "(attribution_alt - attribution_ref).plot_seqlogo(relative_loc=24045)" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "0e65e660", + "cell_type": "markdown", + "id": "4cb3192b", "metadata": {}, - "outputs": [], "source": [ - "df_peaks" + "If you wish to identify regulatory motifs, you can obtain seqlets using recursive seqlet calling for the variant-gene pairs with the following command:" ] }, { "cell_type": "code", - "execution_count": null, - "id": "6eb265ff", - "metadata": {}, - "outputs": [], + "execution_count": 16, + "id": "fcc21b89", + "metadata": { + "execution": { + "iopub.execute_input": "2025-12-16T22:19:53.876806Z", + "iopub.status.busy": "2025-12-16T22:19:53.876675Z", + "iopub.status.idle": "2025-12-16T22:20:11.703509Z", + "shell.execute_reply": "2025-12-16T22:20:11.702965Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[34m\u001b[1mwandb\u001b[0m: Downloading large artifact 'metadata:latest', 3122.32MB. 1 files...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[34m\u001b[1mwandb\u001b[0m: 1 of 1 files downloaded. \n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Done. 00:00:01.8 (1764.0MB/s)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\r", + "Computing recursive seqlet calling...: 0%| | 0/1 [00:00\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
chromstartendnamescorestrandattributionallele
0chr9133269430133269437neg.chr9_133251979_C_T_ABO@-65943.68934.-0.107681ref
1chr9133271380133271384neg.chr9_133251979_C_T_ABO@-46443.42949.-0.054561ref
2chr9133271429133271433neg.chr9_133251979_C_T_ABO@-45953.35865.-0.051218ref
3chr9133272960133272968neg.chr9_133251979_C_T_ABO@-30643.54188.-0.108948ref
4chr9133272984133272990neg.chr9_133251979_C_T_ABO@-30403.34346.-0.065654ref
...........................
129chr9133300139133300143neg.chr9_133251979_C_T_ABO@241153.30114.-0.057818alt
130chr9133300192133300198neg.chr9_133251979_C_T_ABO@241683.88320.-0.095046alt
131chr9133322208133322221neg.chr9_133251979_C_T_ABO@461843.52179.-0.247486alt
132chr9133322235133322241neg.chr9_133251979_C_T_ABO@462113.36768.-0.077182alt
133chr9133322241133322246neg.chr9_133251979_C_T_ABO@462173.72926.-0.078502alt
\n", + "

134 rows × 8 columns

\n", + "" + ], + "text/plain": [ + " chrom start end name score \\\n", + "0 chr9 133269430 133269437 neg.chr9_133251979_C_T_ABO@-6594 3.68934 \n", + "1 chr9 133271380 133271384 neg.chr9_133251979_C_T_ABO@-4644 3.42949 \n", + "2 chr9 133271429 133271433 neg.chr9_133251979_C_T_ABO@-4595 3.35865 \n", + "3 chr9 133272960 133272968 neg.chr9_133251979_C_T_ABO@-3064 3.54188 \n", + "4 chr9 133272984 133272990 neg.chr9_133251979_C_T_ABO@-3040 3.34346 \n", + ".. ... ... ... ... ... \n", + "129 chr9 133300139 133300143 neg.chr9_133251979_C_T_ABO@24115 3.30114 \n", + "130 chr9 133300192 133300198 neg.chr9_133251979_C_T_ABO@24168 3.88320 \n", + "131 chr9 133322208 133322221 neg.chr9_133251979_C_T_ABO@46184 3.52179 \n", + "132 chr9 133322235 133322241 neg.chr9_133251979_C_T_ABO@46211 3.36768 \n", + "133 chr9 133322241 133322246 neg.chr9_133251979_C_T_ABO@46217 3.72926 \n", + "\n", + " strand attribution allele \n", + "0 . -0.107681 ref \n", + "1 . -0.054561 ref \n", + "2 . -0.051218 ref \n", + "3 . -0.108948 ref \n", + "4 . -0.065654 ref \n", + ".. ... ... ... \n", + "129 . -0.057818 alt \n", + "130 . -0.095046 alt \n", + "131 . -0.247486 alt \n", + "132 . -0.077182 alt \n", + "133 . -0.078502 alt \n", + "\n", + "[134 rows x 8 columns]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "attribution_ref.plot_seqlogo(relative_loc=291)" + "df_peaks" ] }, { "cell_type": "code", - "execution_count": null, - "id": "fae648d8", - "metadata": {}, - "outputs": [], + "execution_count": 18, + "id": "d0060440", + "metadata": { + "execution": { + "iopub.execute_input": "2025-12-16T22:20:11.713216Z", + "iopub.status.busy": "2025-12-16T22:20:11.713088Z", + "iopub.status.idle": "2025-12-16T22:20:11.720849Z", + "shell.execute_reply": "2025-12-16T22:20:11.720413Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
motifpeakstartendstrandscorep-valuematched_seqsite_attr_scoremotif_attr_scorefrom_tssallele
0Z585A.H13CORE.0.P.Cneg.chr9_133251979_C_T_ABO@21535215052173-21.0433452.714465e-08TCTTTGTTTTCCCTTTTTTGTCT-0.003653-0.0087402150ref
1CTCFL.H13CORE.0.P.Bpos.chr9_133251979_C_T_ABO@7605075650772+20.8217673.492460e-08GCCGGGAGGGGGCGCC0.0169340.059062756ref
2CTCFL.H13CORE.0.P.Bpos.chr9_133251979_C_T_ABO@7555075650772+20.8217673.492460e-08GCCGGGAGGGGGCGCC0.0169340.059062756ref
3GRHL1.H13CORE.0.PSM.Aneg.chr9_133251979_C_T_ABO@251227511175130-20.6598633.644891e-08AACCTGAAAAACCGGTTCA-0.001653-0.00610425111ref
4GRHL3.H13CORE.0.SB.Aneg.chr9_133251979_C_T_ABO@251227511275130+20.0238423.720925e-08ACCTGAAAAACCGGTTCA-0.001693-0.00616025112ref
.......................................
9765ZN124.H13CORE.0.P.Cpos.chr9_133251979_C_T_ABO@7995079750809-6.5142144.991293e-04CCGGGCGGAAGG0.0304020.064038797alt
9766ZN320.H13CORE.0.P.Bneg.chr9_133251979_C_T_ABO@241077410274125+4.0262174.993357e-04GGGGGCGGAGTGGGGACCAGACC-0.001394-0.00153624102alt
9767ZN320.H13CORE.0.P.Bneg.chr9_133251979_C_T_ABO@241157410274125+4.0262174.993357e-04GGGGGCGGAGTGGGGACCAGACC-0.001394-0.00153624102alt
9768CGGBP1.H13CORE.0.PSGIB.Aneg.chr9_133251979_C_T_ABO@7365073750748-9.1822524.999638e-04GGGGCGGCGGG-0.001905-0.002937737alt
9769CGGBP1.H13CORE.0.PSGIB.Aneg.chr9_133251979_C_T_ABO@7515073750748-9.1822524.999638e-04GGGGCGGCGGG-0.001905-0.002937737alt
\n", + "

9770 rows × 12 columns

\n", + "
" + ], + "text/plain": [ + " motif peak start \\\n", + "0 Z585A.H13CORE.0.P.C neg.chr9_133251979_C_T_ABO@2153 52150 \n", + "1 CTCFL.H13CORE.0.P.B pos.chr9_133251979_C_T_ABO@760 50756 \n", + "2 CTCFL.H13CORE.0.P.B pos.chr9_133251979_C_T_ABO@755 50756 \n", + "3 GRHL1.H13CORE.0.PSM.A neg.chr9_133251979_C_T_ABO@25122 75111 \n", + "4 GRHL3.H13CORE.0.SB.A neg.chr9_133251979_C_T_ABO@25122 75112 \n", + "... ... ... ... \n", + "9765 ZN124.H13CORE.0.P.C pos.chr9_133251979_C_T_ABO@799 50797 \n", + "9766 ZN320.H13CORE.0.P.B neg.chr9_133251979_C_T_ABO@24107 74102 \n", + "9767 ZN320.H13CORE.0.P.B neg.chr9_133251979_C_T_ABO@24115 74102 \n", + "9768 CGGBP1.H13CORE.0.PSGIB.A neg.chr9_133251979_C_T_ABO@736 50737 \n", + "9769 CGGBP1.H13CORE.0.PSGIB.A neg.chr9_133251979_C_T_ABO@751 50737 \n", + "\n", + " end strand score p-value matched_seq \\\n", + "0 52173 - 21.043345 2.714465e-08 TCTTTGTTTTCCCTTTTTTGTCT \n", + "1 50772 + 20.821767 3.492460e-08 GCCGGGAGGGGGCGCC \n", + "2 50772 + 20.821767 3.492460e-08 GCCGGGAGGGGGCGCC \n", + "3 75130 - 20.659863 3.644891e-08 AACCTGAAAAACCGGTTCA \n", + "4 75130 + 20.023842 3.720925e-08 ACCTGAAAAACCGGTTCA \n", + "... ... ... ... ... ... \n", + "9765 50809 - 6.514214 4.991293e-04 CCGGGCGGAAGG \n", + "9766 74125 + 4.026217 4.993357e-04 GGGGGCGGAGTGGGGACCAGACC \n", + "9767 74125 + 4.026217 4.993357e-04 GGGGGCGGAGTGGGGACCAGACC \n", + "9768 50748 - 9.182252 4.999638e-04 GGGGCGGCGGG \n", + "9769 50748 - 9.182252 4.999638e-04 GGGGCGGCGGG \n", + "\n", + " site_attr_score motif_attr_score from_tss allele \n", + "0 -0.003653 -0.008740 2150 ref \n", + "1 0.016934 0.059062 756 ref \n", + "2 0.016934 0.059062 756 ref \n", + "3 -0.001653 -0.006104 25111 ref \n", + "4 -0.001693 -0.006160 25112 ref \n", + "... ... ... ... ... \n", + "9765 0.030402 0.064038 797 alt \n", + "9766 -0.001394 -0.001536 24102 alt \n", + "9767 -0.001394 -0.001536 24102 alt \n", + "9768 -0.001905 -0.002937 737 alt \n", + "9769 -0.001905 -0.002937 737 alt \n", + "\n", + "[9770 rows x 12 columns]" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "attribution_alt.plot_seqlogo(relative_loc=291)" + "df_motifs" ] } ], diff --git a/src/decima/cli/vep_attribution.py b/src/decima/cli/vep_attribution.py index 5db09ab..0024b64 100644 --- a/src/decima/cli/vep_attribution.py +++ b/src/decima/cli/vep_attribution.py @@ -15,7 +15,7 @@ type=click.Path(exists=True), help="Path to the variant file .vcf file. VCF file need to be normalized. Try normalizing th vcf file incase of an error. `bcftools norm -f ref.fasta input.vcf.gz -o output.vcf.gz`", ) -@click.option("-o", "--output_h5", type=click.Path(), help="Path to the output h5 file.") +@click.option("-o", "--output_prefix", type=click.Path(), help="Path to the output prefix.") @click.option("--tasks", type=str, default=None, help="Tasks to predict. If not provided, all tasks will be predicted.") @click.option( "--off-tasks", @@ -25,7 +25,7 @@ ) @click.option( "--model", - default=0, + default=DEFAULT_ENSEMBLE, callback=parse_model, help=f"Model to use for attribution analysis. Available options: {list(MODEL_METADATA.keys())}.", ) @@ -50,6 +50,7 @@ @click.option( "--device", type=str, default=None, help="Device to use. Default: None which automatically selects the best device." ) +@click.option("--batch-size", type=int, default=1, help="Batch size for the loader. Default: 1") @click.option("--num-workers", type=int, default=4, help="Number of workers for the loader. Default: 4") @click.option("--distance-type", type=str, default="tss", help="Type of distance. Default: tss.") @click.option( @@ -71,15 +72,9 @@ help="Column name for gene names. Default: None.", ) @click.option("--genome", type=str, default="hg38", help="Genome build. Default: hg38.") -@click.option( - "--float-precision", - type=str, - default="32", - help="Floating-point precision to be used in calculations. Avaliable options include: '16-true', '16-mixed', 'bf16-true', 'bf16-mixed', '32-true', '64-true', '32', '16', and 'bf16'.", -) def cli_vep_attribution( variants, - output_h5, + output_prefix, tasks, off_tasks, model, @@ -87,23 +82,23 @@ def cli_vep_attribution( method, transform, device, + batch_size, num_workers, distance_type, min_distance, max_distance, gene_col, genome, - float_precision, ): """Predict variant effect and save to parquet Examples: - >>> decima vep-attribution -v "data/sample.vcf" -o "vep_results.h5" + >>> decima vep-attribution -v "data/sample.vcf" -o "vep_results" """ variant_effect_attribution( - variants, - output_h5=output_h5, + variants=variants, + output_prefix=output_prefix, tasks=tasks, off_tasks=off_tasks, model=model, @@ -111,11 +106,11 @@ def cli_vep_attribution( method=method, transform=transform, num_workers=num_workers, + batch_size=batch_size, device=device, distance_type=distance_type, min_distance=min_distance, max_distance=max_distance, gene_col=gene_col, genome=genome, - float_precision=float_precision, ) diff --git a/src/decima/core/attribution.py b/src/decima/core/attribution.py index 348dc1d..4cfa413 100644 --- a/src/decima/core/attribution.py +++ b/src/decima/core/attribution.py @@ -3,7 +3,6 @@ """ import warnings -from collections import defaultdict from typing import List, Optional, Union import numpy as np @@ -535,6 +534,39 @@ def save_peaks(self, bed_path: str): """ self.peaks_to_bed().to_csv(bed_path, sep="\t", header=False, index=False) + def __sub__(self, other): + assert self.chrom == other.chrom, "Chromosomes must be the same to subtract attributions." + assert self.start == other.start, "Starts must be the same to subtract attributions." + assert self.end == other.end, "Ends must be the same to subtract attributions." + assert self.strand == other.strand, "Strands must be the same to subtract attributions." + + if ( + (self.threshold != other.threshold) + or (self.min_seqlet_len != other.min_seqlet_len) + or (self.max_seqlet_len != other.max_seqlet_len) + or (self.additional_flanks != other.additional_flanks) + or (self.pattern_type != other.pattern_type) + ): + warnings.warn( + "`threshold`, `min_seqlet_len`, `max_seqlet_len`, `additional_flanks`, and `pattern_type` are not same overriding " + "them with the values of the first attribution object." + ) + + return Attribution( + inputs=self.inputs, + attrs=self.attrs - other.attrs, + gene=f"{self.gene} - {other.gene}", + chrom=self.chrom, + start=self.start, + end=self.end, + strand=self.strand, + threshold=self.threshold, + min_seqlet_len=self.min_seqlet_len, + max_seqlet_len=self.max_seqlet_len, + additional_flanks=self.additional_flanks, + pattern_type=self.pattern_type, + ) + class AttributionResult: """ @@ -572,32 +604,31 @@ def open(self): """Open the attribution h5 files.""" if isinstance(self.attribution_h5, list): self.h5 = [h5py.File(str(attribution_h5), "r") for attribution_h5 in self.attribution_h5] - self._idx = defaultdict(list) - for attribution_h5_file in self.attribution_h5: + for i, attribution_h5_file in enumerate(self.attribution_h5): with h5py.File(str(attribution_h5_file), "r") as f: - for i, gene in enumerate(f["genes"][:]): - gene = gene.decode("utf-8") - self._idx[gene].append(i) - self._idx = dict(self._idx) - - assert all( - len(indices) == len(self.attribution_h5) for indices in self._idx.values() - ), "All genes must be present in all attribution files." - self.genome = self.h5[0].attrs["genome"] - assert all( - self.h5[i].attrs["genome"] == self.genome for i in range(len(self.h5)) - ), "All attribution files must have the same genome version." + if i == 0: + self.genes = f["genes"][:].astype("U100") + self.genome = f.attrs["genome"] + else: + assert all(self.genes == f["genes"][:].astype("U100")), ( + "All genes must be the same in all attribution files. " + f"Expected: {self.genes}, Found: {f['genes'][:].astype('U100')}" + ) + assert ( + self.genome == f.attrs["genome"] + ), "All attribution files must have the same genome version." self.model_name = list() for h5 in self.h5: self.model_name.append(h5.attrs["model_name"]) else: self.h5 = h5py.File(str(self.attribution_h5), "r") - self._idx = {gene.decode("utf-8"): i for i, gene in enumerate(self.h5["genes"][:])} + self.genes = self.h5["genes"][:].astype("U100") self.model_name = self.h5.attrs["model_name"] self.genome = self.h5.attrs["genome"] + self.genes = self.h5["genes"][:].astype("U100") - self.genes = list(self._idx.keys()) + self._idx = {gene: i for i, gene in enumerate(self.genes)} def close(self): """Close the attribution h5 files.""" @@ -680,7 +711,7 @@ def _load( @staticmethod def _load_multiple( attribution_h5_files, - indices: List[int], + idx: int, tss_distance: int, correct_grad: bool, gene_mask: bool = False, @@ -694,7 +725,7 @@ def _load_multiple( AttributionResult._load( attribution_h5_file, idx, tss_distance, correct_grad, gene_mask, sequence_key, attribution_key ) - for idx, attribution_h5_file in zip(indices, attribution_h5_files) + for attribution_h5_file in attribution_h5_files ) ) return AttributionResult.aggregate(np.array(seqs), np.array(attrs), agg_func) @@ -751,6 +782,9 @@ def _load_attribution( pattern_type: str = "both", sequence_key: str = "sequence", attribution_key: str = "attribution", + differential: bool = False, + alt_sequence_key: str = "sequence_alt", + alt_attribution_key: str = "attribution_alt", ): kwargs = { "tss_distance": tss_distance, @@ -764,7 +798,8 @@ def _load_attribution( seqs, attrs = AttributionResult._load_multiple(attribution_h5, idx, agg_func=agg_func, **kwargs) else: seqs, attrs = AttributionResult._load(attribution_h5, idx, **kwargs) - return Attribution( + + attribution = Attribution( inputs=seqs, attrs=attrs, gene=gene, @@ -778,7 +813,31 @@ def _load_attribution( pattern_type=pattern_type, ) - def _get_metadata(self, genes: List[str], metadata_anndata: Optional[str] = None, custom_genome: bool = False): + if not differential: + return attribution + else: + attribution_alt = AttributionResult._load_attribution( + attribution_h5, + idx, + gene, + tss_distance, + chrom, + start, + end, + agg_func, + threshold=threshold, + min_seqlet_len=min_seqlet_len, + max_seqlet_len=max_seqlet_len, + additional_flanks=additional_flanks, + pattern_type=pattern_type, + attribution_key=alt_attribution_key, + sequence_key=alt_sequence_key, + differential=False, + ) + return attribution_alt - attribution + + def _get_metadata(self, idx: List[str], metadata_anndata: Optional[str] = None, custom_genome: bool = False): + genes = self.genes[idx] if custom_genome: chroms = genes starts = [0] * len(genes) @@ -832,11 +891,12 @@ def load_attribution( Returns: Attribution object. """ - chroms, starts, ends = self._get_metadata(gene, metadata_anndata, custom_genome) + idx = self._idx[gene] + chroms, starts, ends = self._get_metadata(idx, metadata_anndata, custom_genome) return self._load_attribution( self.attribution_h5, - self._idx[gene], - gene, + idx, + self.genes[idx], self.tss_distance, chroms, starts, @@ -884,9 +944,13 @@ def _recursive_seqlet_calling( pattern_type=pattern_type, **kwargs, ) + # if isinstance(attribution, list) or isinstance(attribution, tuple): + # df_peaks = pd.concat(df_peaks for df_peaks in attribution).reset_index(drop=True) + # df_motifs = pd.concat(df_motifs for df_motifs in attribution).reset_index(drop=True) + # return df_peaks, df_motifs + # else: df_peaks = attribution.peaks_to_bed() df_motifs = attribution.scan_motifs(motifs=meme_motif_db) - return df_peaks, df_motifs def recursive_seqlet_calling( @@ -900,6 +964,7 @@ def recursive_seqlet_calling( additional_flanks: int = 0, pattern_type: str = "both", meme_motif_db: str = "hocomoco_v13", + **kwargs, ): """Perform recursive seqlet calling on the attribution scores. @@ -921,14 +986,14 @@ def recursive_seqlet_calling( if genes is None: genes = self.genes - chroms, starts, ends = self._get_metadata(genes, metadata_anndata, custom_genome) + chroms, starts, ends = self._get_metadata([self._idx[gene] for gene in genes], metadata_anndata, custom_genome) df_peaks, df_motifs = zip( *Parallel(n_jobs=self.num_workers)( delayed(AttributionResult._recursive_seqlet_calling)( self.attribution_h5, self._idx[gene], - gene, + "_".join(gene), self.tss_distance, chrom, start, @@ -940,6 +1005,7 @@ def recursive_seqlet_calling( additional_flanks=additional_flanks, pattern_type=pattern_type, meme_motif_db=meme_motif_db, + **kwargs, ) for gene, chrom, start, end in tqdm( zip(genes, chroms, starts, ends), desc="Computing recursive seqlet calling...", total=len(genes) @@ -971,20 +1037,39 @@ def __init__( def open(self): super().open() - # self.relative_dist = self.h5.attrs["relative_dist"] if isinstance(self.attribution_h5, list): - for attribution_h5_file in self.attribution_h5: + for i, attribution_h5_file in enumerate(self.attribution_h5): with h5py.File(str(attribution_h5_file), "r") as f: - for i, (variant, gene) in enumerate(zip(f["variants"][:], f["genes"][:])): - gene = gene.decode("utf-8") - variant = variant.decode("utf-8") - self._idx[(variant, gene)].append(i) - self._idx = dict(self._idx) + if i == 0: + self.genes = f["genes"][:].astype("U100") + self.variants = f["variants"][:].astype("U100") + self.rel_pos = f["rel_pos"][:].astype(int) + else: + assert all(self.genes == f["genes"][:].astype("U100")), ( + "All genes must be the same in all attribution files. " + f"Expected: {self.genes}, Found: {f['genes'][:].astype('U100')}" + ) + assert all(self.variants == f["variants"][:].astype("U100")), ( + "All variants must be the same in all attribution files. " + f"Expected: {self.variants}, Found: {f['variants'][:].astype('U100')}" + ) + self._idx = {(variant, gene): i for i, (variant, gene) in enumerate(zip(self.variants, self.genes))} + gene_mask_start = self.h5[0]["gene_mask_start"][:].astype(int) else: - self._idx = { - (variant.decode("utf-8"), gene.decode("utf-8")): i - for i, (variant, gene) in enumerate(zip(self.h5["variants"][:], self.h5["genes"][:])) + self.genes = self.h5["genes"][:].astype("U100") + self.variants = self.h5["variants"][:].astype("U100") + self.rel_pos = self.h5["rel_pos"][:].astype(int) + gene_mask_start = self.h5["gene_mask_start"][:].astype(int) + + self.df_variants = pd.DataFrame( + { + "variant": self.variants, + "gene": self.genes, + "rel_pos": self.rel_pos, + "tss_dist": self.rel_pos - gene_mask_start, } + ) + self._idx = {(variant, gene): i for i, (variant, gene) in enumerate(zip(self.variants, self.genes))} def load(self, variants: List[str], genes: List[str], gene_mask: bool = False): """Load the attribution scores for a list of genes and variants.""" @@ -1010,10 +1095,11 @@ def load_attribution( pattern_type: str = "both", **kwargs, ): - chroms, starts, ends = self._get_metadata(gene, metadata_anndata, custom_genome) + idx = self._idx[(variant, gene)] + chroms, starts, ends = self._get_metadata(idx, metadata_anndata, custom_genome) attribution_ref = self._load_attribution( self.attribution_h5, - self._idx[(variant, gene)], + idx, gene, self.tss_distance, chroms, @@ -1030,8 +1116,8 @@ def load_attribution( ) attribution_alt = self._load_attribution( self.attribution_h5, - self._idx[(variant, gene)], - gene, + idx, + f"{variant}_{gene}", self.tss_distance, chroms, starts, @@ -1050,9 +1136,8 @@ def load_attribution( def recursive_seqlet_calling( self, variants: List[str], - genes: Optional[List[str]] = None, + genes: Optional[List[str]], metadata_anndata: Optional[str] = None, - custom_genome: bool = False, threshold: float = 5e-4, min_seqlet_len: int = 4, max_seqlet_len: int = 25, @@ -1061,29 +1146,30 @@ def recursive_seqlet_calling( meme_motif_db: str = "hocomoco_v13", ): variant_gene = list(zip(variants, genes)) + df_peaks_ref, df_motifs_ref = super().recursive_seqlet_calling( variant_gene, metadata_anndata, - custom_genome, - threshold, - min_seqlet_len, - max_seqlet_len, - additional_flanks, - pattern_type, - meme_motif_db, + custom_genome=False, + threshold=threshold, + min_seqlet_len=min_seqlet_len, + max_seqlet_len=max_seqlet_len, + additional_flanks=additional_flanks, + pattern_type=pattern_type, + meme_motif_db=meme_motif_db, sequence_key="sequence", attribution_key="attribution", ) df_peaks_alt, df_motifs_alt = super().recursive_seqlet_calling( variant_gene, metadata_anndata, - custom_genome, - threshold, - min_seqlet_len, - max_seqlet_len, - additional_flanks, - pattern_type, - meme_motif_db, + custom_genome=False, + threshold=threshold, + min_seqlet_len=min_seqlet_len, + max_seqlet_len=max_seqlet_len, + additional_flanks=additional_flanks, + pattern_type=pattern_type, + meme_motif_db=meme_motif_db, sequence_key="sequence_alt", attribution_key="attribution_alt", ) diff --git a/src/decima/utils/io.py b/src/decima/utils/io.py index d711935..72ad854 100644 --- a/src/decima/utils/io.py +++ b/src/decima/utils/io.py @@ -387,6 +387,12 @@ def open(self): dtype="S100", compression="gzip", ) + self.h5_writer.create_dataset( + "rel_pos", + (len(self.variants),), + dtype="i4", + compression="gzip", + ) self.h5_writer.create_dataset( "attribution_alt", (len(self.genes), 4, DECIMA_CONTEXT_SIZE), @@ -407,6 +413,7 @@ def add( self, variant: str, gene: str, + rel_pos: int, seqs_ref: np.ndarray, attrs_ref: np.ndarray, seqs_alt: np.ndarray, @@ -425,6 +432,7 @@ def add( super().add((variant, gene), seqs_ref, attrs_ref, gene_mask_start, gene_mask_end) idx = self.idx[(variant, gene)] self.h5_writer["variants"][idx] = np.array(variant, dtype="S100") + self.h5_writer["rel_pos"][idx] = int(rel_pos) self.h5_writer["sequence_alt"][idx, :] = convert_input_type( torch.from_numpy(seqs_alt), # convert_input_type only support Tensor "indices", diff --git a/src/decima/vep/attributions.py b/src/decima/vep/attributions.py index fc54d05..ebbd531 100644 --- a/src/decima/vep/attributions.py +++ b/src/decima/vep/attributions.py @@ -1,10 +1,32 @@ +""" +Variant Effect Attribution Module. + +This module provides functionality to compute feature attributions for genetic variants. +It calculates the contribution of input sequences to model predictions, allowing for the +interpretation of variant effects in motifs of transcription factors. + +Examples: + >>> variant_effect_attribution( + ... df_variant="variants.vcf", + ... output_h5="attributions.h5", + ... tasks=[ + ... "T_cell", + ... "B_cell", + ... ], + ... model=0, + ... metadata_anndata="results.h5ad", + ... ) +""" + import logging from typing import Optional, Union, List +from pathlib import Path import torch import pandas as pd from tqdm import tqdm +from decima.constants import ENSEMBLE_MODELS, MODEL_METADATA from decima.utils import get_compute_device, _get_on_off_tasks from decima.utils.io import read_vcf_chunks, VariantAttributionWriter from decima.core.result import DecimaResult @@ -15,14 +37,15 @@ def variant_effect_attribution( - df_variant: Union[pd.DataFrame, str], - output_h5: Optional[str] = None, + variants: Union[pd.DataFrame, str], + output_prefix: str, tasks: Optional[Union[str, List[str]]] = None, off_tasks: Optional[Union[str, List[str]]] = None, model: int = 0, metadata_anndata: Optional[str] = None, method: str = "inputxgradient", transform: str = "specificity", + batch_size: int = 1, num_workers: int = 4, device: Optional[str] = None, gene_col: Optional[str] = None, @@ -30,28 +53,109 @@ def variant_effect_attribution( min_distance: Optional[float] = 0, max_distance: Optional[float] = float("inf"), genome: str = "hg38", - float_precision: str = "32", ): - """ """ + """ + Computes variant effect attributions for a set of variants and writes them to an HDF5 file. + + This function calculates the contribution of input features (sequence) to the model's + prediction for specific tasks (cell types), contrasting with off-target tasks if specified. + It supports various attribution methods (e.g., InputXGradient) and transformations + (e.g., Specificity). + + Args: + df_variant (Union[pd.DataFrame, str]): Input variants. Can be a pandas DataFrame or a path + to a file (.tsv, .csv, or .vcf). If a file path is provided, it will be loaded. + Required columns/fields depend on the input format but generally include chromosome, + position, reference allele, alternate allele. + output_prefix (str): Path to the output HDF5 file where attributions will be saved. + If None, results might not be persisted. + tasks (Union[str, List[str]], optional): Specific task(s) or cell type(s) to compute + attributions for. If None, uses all available tasks or a default set. Defaults to None. + off_tasks (Union[str, List[str]], optional): Task(s) to use as a background or negative + set for specificity calculations. Defaults to None. + model (int, optional): Index or identifier of the model to use from the ensemble. + Defaults to 0. + metadata_anndata (str, optional): Path to the AnnData file containing model metadata and + results (DecimaResult). Used to resolve task names and indices. Defaults to None. + method (str, optional): The attribution method to use. Options: "inputxgradient", + "saliency", "integratedgradients". Defaults to "inputxgradient". + transform (str, optional): Transformation to apply to the model output before attribution. + Options: "specificity" (target - off_target) or "aggregate". Defaults to "specificity". + num_workers (int, optional): Number of worker processes for data loading. Defaults to 4. + device (str, optional): Compute device to use (e.g., "cpu", "cuda", "cuda:0"). + If None, automatically detects available device. Defaults to None. + gene_col (str, optional): Name of the column in `df_variant` containing gene identifiers. + If provided, variants are associated with specific genes. Defaults to None. + distance_type (str, optional): Method to calculate distance between variant and gene. + Options: "tss" (Transcription Start Site). Defaults to "tss". + min_distance (float, optional): Minimum distance from the gene feature (e.g., TSS) for a + variant to be included. Defaults to 0. + max_distance (float, optional): Maximum distance from the gene feature (e.g., TSS) for a + variant to be included. Defaults to infinity. + genome (str, optional): Genome assembly version (e.g., "hg38"). Defaults to "hg38". + + Returns: + List[str]: List of paths to the output HDF5 files. + + Examples: + Compute attributions for variants in a VCF file for specific tasks: + + >>> variant_effect_attribution( + ... variants="variants.vcf", + ... output_prefix="attributions", + ... tasks=[ + ... "T_cell", + ... "B_cell", + ... ], + ... model=0, + ... metadata_anndata="results.h5ad", + ... ) + """ + if (model in ENSEMBLE_MODELS) or isinstance(model, (list, tuple)): + if model in ENSEMBLE_MODELS: + models = MODEL_METADATA[model] + else: + models = model + return [ + variant_effect_attribution( + variants=variants, + output_prefix=(str(output_prefix) + "_{model}").format(model=idx), + tasks=tasks, + off_tasks=off_tasks, + model=model, + metadata_anndata=metadata_anndata, + method=method, + transform=transform, + batch_size=batch_size, + num_workers=num_workers, + device=device, + gene_col=gene_col, + distance_type=distance_type, + min_distance=min_distance, + max_distance=max_distance, + genome=genome, + ) + for idx, model in enumerate(models) + ] logger = logging.getLogger("decima") device = get_compute_device(device) logger.info(f"Using device: {device} and genome: {genome}") - if isinstance(df_variant, pd.DataFrame): - pass - elif isinstance(df_variant, str): - if df_variant.endswith(".tsv"): - df_variant = pd.read_csv(df_variant, sep="\t") - elif df_variant.endswith(".csv"): - df_variant = pd.read_csv(df_variant, sep=",") - elif df_variant.endswith(".vcf") or df_variant.endswith(".vcf.gz"): - df_variant = next(read_vcf_chunks(df_variant, chunksize=float("inf"))) + if isinstance(variants, str): + if variants.endswith(".tsv"): + variants = pd.read_csv(variants, sep="\t") + elif variants.endswith(".csv"): + variants = pd.read_csv(variants, sep=",") + elif variants.endswith(".vcf") or variants.endswith(".vcf.gz"): + variants = next(read_vcf_chunks(variants, chunksize=float("inf"))) else: - raise ValueError(f"Unsupported file extension: {df_variant}. Must be .tsv or .vcf.") + raise ValueError(f"Unsupported file extension: {variants}. Must be .tsv or .vcf.") + elif isinstance(variants, pd.DataFrame): + pass else: raise ValueError( - f"Unsupported input type: {type(df_variant)}. Must be pd.DataFrame or str (path to .tsv or .vcf)." + f"Unsupported input type: {type(variants)}. Must be pd.DataFrame or str (path to .tsv or .vcf)." ) result = DecimaResult.load(metadata_anndata) @@ -69,7 +173,7 @@ def variant_effect_attribution( warning_counter = WarningCounter() dataset = VariantDataset( - df_variant, + variants, metadata_anndata=metadata_anndata, gene_col=gene_col, distance_type=distance_type, @@ -88,7 +192,9 @@ def variant_effect_attribution( + dataset.variants["alt"] ) genes = dataset.variants["gene"] - dl = torch.utils.data.DataLoader(dataset, batch_size=1, num_workers=num_workers, collate_fn=dataset.collate_fn) + dl = torch.utils.data.DataLoader( + dataset, batch_size=batch_size, num_workers=num_workers, collate_fn=dataset.collate_fn + ) seqs = list() attrs = list() @@ -97,6 +203,9 @@ def variant_effect_attribution( attrs.append(attributer.attribute(batch["seq"].to(device)).detach().cpu().float().numpy()) warning_counter.update(batch["warning"]) + Path(output_prefix).parent.mkdir(parents=True, exist_ok=True) + + output_h5 = str(output_prefix) + ".h5" with VariantAttributionWriter( path=output_h5, genes=genes, @@ -105,10 +214,11 @@ def variant_effect_attribution( metadata_anndata=result, genome=genome, ) as writer: - for variant, gene, gene_mask_start, gene_mask_end, seqs_ref, seqs_alt, attrs_ref, attrs_alt in tqdm( + for variant, gene, rel_pos, gene_mask_start, gene_mask_end, seqs_ref, seqs_alt, attrs_ref, attrs_alt in tqdm( zip( variants, genes, + dataset.variants["rel_pos"], dataset.variants["gene_mask_start"], dataset.variants["gene_mask_end"], seqs[::2], @@ -122,6 +232,7 @@ def variant_effect_attribution( writer.add( variant=variant, gene=gene, + rel_pos=rel_pos, seqs_ref=seqs_ref[0, :4], seqs_alt=seqs_alt[0, :4], attrs_ref=attrs_ref[0, :4], @@ -133,3 +244,5 @@ def variant_effect_attribution( warning_counter = warning_counter.compute() _log_vep_warnings(warning_counter, len(variants), genome) _write_vep_warnings(warning_counter, len(variants), output_h5) + + return output_h5 diff --git a/src/decima/vep/vep.py b/src/decima/vep/vep.py index 7466b99..91d82ca 100644 --- a/src/decima/vep/vep.py +++ b/src/decima/vep/vep.py @@ -1,3 +1,16 @@ +""" +Variant Effect Prediction Module. + +This module provides functionality to predict the effect of genetic variants on gene expression. + +Examples: + >>> predict_variant_effect( + ... df_variant="variants.vcf", + ... output_pq="predictions.parquet", + ... model=0, + ... ) +""" + import logging import warnings from pathlib import Path diff --git a/tests/test_attribution.py b/tests/test_attribution.py index d0d33f8..6fe4665 100644 --- a/tests/test_attribution.py +++ b/tests/test_attribution.py @@ -13,11 +13,10 @@ def test_AttributionResult(attribution_h5_file, attribution_data): with AttributionResult(str(attribution_h5_file), tss_distance=10_000, num_workers=1) as ar: assert len(ar.genes) == 10 - assert ar.genes == attribution_data['genes'] + assert all(ar.genes == attribution_data['genes']) assert ar.model_name == 'v1_rep0' assert ar.genome == 'hg38' - assert ar.genes == attribution_data['genes'] genes = attribution_data['genes'] seqs, attrs = ar._load(str(attribution_h5_file), 0, 10_000, True) @@ -72,7 +71,7 @@ def test_AttributionResult(attribution_h5_file, attribution_data): with AttributionResult([str(attribution_h5_file), str(attribution_h5_file)], tss_distance=10_000) as ar: assert len(ar.genes) == 10 - assert ar.genes == attribution_data['genes'] + assert all(ar.genes == attribution_data['genes']) assert ar.model_name == ['v1_rep0', 'v1_rep0'] assert ar.genome == 'hg38' diff --git a/tests/test_cli.py b/tests/test_cli.py index 5be3573..77e8924 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -329,14 +329,14 @@ def test_cli_modisco(tmp_path): @pytest.mark.long_running def test_cli_vep_attributions(tmp_path): - output_file = tmp_path / "test_vep_attributions.h5" + output_prefix = tmp_path / "test_vep_attributions" runner = CliRunner() result = runner.invoke(main, [ "vep-attribution", "-v", "tests/data/variants.tsv", - "-o", str(output_file), + "-o", str(output_prefix), "--model", "0", "--device", device, ]) assert result.exit_code == 0, result.__dict__ - assert (output_file.exists()) + assert Path(str(output_prefix) + ".h5").exists() diff --git a/tests/test_interpret_attribution.py b/tests/test_interpret_attribution.py index e2bca28..3d7d84a 100644 --- a/tests/test_interpret_attribution.py +++ b/tests/test_interpret_attribution.py @@ -403,3 +403,85 @@ def test_recursive_seqlet_calling(tmp_path, attribution_h5_file): ) assert (output_prefix.with_suffix(".seqlets.bed")).exists() assert (output_prefix.with_suffix(".motifs.tsv")).exists() + + +def test_Attribution_sub(attributions): + # Test valid subtraction + other_attrs = attributions.attrs * 2 + other = Attribution( + gene="OTHER_GENE", + inputs=attributions.inputs, + attrs=other_attrs, + chrom=attributions.chrom, + start=attributions.start, + end=attributions.end, + strand=attributions.strand, + threshold=attributions.threshold, + ) + + diff = other - attributions + assert (diff.attrs == attributions.attrs).all() + assert diff.gene == "OTHER_GENE - TEST2" + assert diff.chrom == attributions.chrom + assert diff.start == attributions.start + + # Test failure: different inputs + other_diff_inputs = Attribution( + gene="OTHER_GENE", + inputs=torch.zeros_like(attributions.inputs), + attrs=other_attrs, + chrom=attributions.chrom, + start=attributions.start, + end=attributions.end, + strand=attributions.strand, + threshold=attributions.threshold, + ) + with pytest.raises(AssertionError, match="Sequences of attribution objects must be the same"): + _ = other_diff_inputs - attributions + + # Test failure: different metadata + other_diff_chrom = Attribution( + gene="OTHER_GENE", + inputs=attributions.inputs, + attrs=other_attrs, + chrom="chr2", + start=attributions.start, + end=attributions.end, + strand=attributions.strand, + threshold=attributions.threshold, + ) + with pytest.raises(AssertionError, match="Chromosomes must be the same"): + _ = other_diff_chrom - attributions + + # Test warning: different parameters + other_diff_thresh = Attribution( + gene="OTHER_GENE", + inputs=attributions.inputs, + attrs=other_attrs, + chrom=attributions.chrom, + start=attributions.start, + end=attributions.end, + strand=attributions.strand, + threshold=attributions.threshold * 2, + ) + with pytest.warns(UserWarning, match="`threshold`.*are not same"): + res = other_diff_thresh - attributions + # Should use the left operand's threshold + assert res.threshold == other_diff_thresh.threshold + + +def test_Attribution__sub__(attributions): + other = Attribution( + gene="OTHER", + inputs=attributions.inputs, + attrs=attributions.attrs + 1, + chrom=attributions.chrom, + start=attributions.start, + end=attributions.end, + strand=attributions.strand, + threshold=attributions.threshold, + ) + + diff = other - attributions + assert np.allclose(diff.attrs, 1.0) + assert diff.gene == "OTHER - TEST2" diff --git a/tests/test_vep.py b/tests/test_vep.py index 45ad91b..d469637 100644 --- a/tests/test_vep.py +++ b/tests/test_vep.py @@ -10,7 +10,7 @@ from decima.hub import load_decima_model from decima.data.dataset import VariantDataset from decima.model.metrics import WarningType -from decima.vep import _predict_variant_effect, predict_variant_effect +from decima.vep.vep import _predict_variant_effect, predict_variant_effect from decima.utils.io import read_vcf_chunks from conftest import device diff --git a/tests/test_vep_attributions.py b/tests/test_vep_attributions.py index d166d37..6d15a48 100644 --- a/tests/test_vep_attributions.py +++ b/tests/test_vep_attributions.py @@ -6,14 +6,15 @@ @pytest.mark.long_running -def test_variant_effect_attribution(): +def test_variant_effect_attribution(tmp_path): + output_prefix = tmp_path / "test" variant_effect_attribution( "tests/data/test.vcf", - "tests/data/test.h5", + output_prefix, model=0, method="inputxgradient", ) - with h5py.File("tests/data/test.h5", "r") as f: + with h5py.File(str(output_prefix) + ".h5", "r") as f: assert len(f['variants'][:]) == 82 assert len(f['genes'][:]) == 82 assert f['attribution'].shape == (82, 4, DECIMA_CONTEXT_SIZE) @@ -25,7 +26,6 @@ def test_variant_effect_attribution(): def test_VariantAttributionResult(tmp_path, attribution_data): h5_path = tmp_path / "vep_test_attributions.h5" - variants = ['chr1_1000018_G_A'] * len(attribution_data['genes']) with h5py.File(h5_path, 'w') as f: @@ -37,6 +37,7 @@ def test_VariantAttributionResult(tmp_path, attribution_data): f.create_dataset('attribution_alt', data=attribution_data['attributions']) f.create_dataset('gene_mask_start', data=attribution_data['gene_mask_start']) f.create_dataset('gene_mask_end', data=attribution_data['gene_mask_end']) + f.create_dataset('rel_pos', data=list(range(len(variants)))) f.attrs['model_name'] = 'v1_rep0' f.attrs['genome'] = 'hg38' @@ -55,7 +56,24 @@ def test_VariantAttributionResult(tmp_path, attribution_data): assert attribution_ref.start == 43736410 assert attribution_ref.end == 43756410 - assert attribution_alt.gene == genes[0] + assert attribution_alt.gene == f'{variants[0]}_{genes[0]}' assert attribution_alt.chrom == 'chr15' assert attribution_alt.start == 43736410 assert attribution_alt.end == 43756410 + + assert ar.df_variants.shape[0] == 10 + assert ar.df_variants.columns.tolist() == ['variant', 'gene', 'rel_pos', 'tss_dist'] + + with VariantAttributionResult([str(h5_path), str(h5_path)], num_workers=1, agg_func="sum") as ar: + genes = ar.genes + seqs_ref, attrs_ref, seqs_alt, attrs_alt = ar.load(variants, genes) + + assert seqs_ref.shape == (len(attribution_data['genes']), 4, DECIMA_CONTEXT_SIZE) + assert attrs_ref.shape == (len(attribution_data['genes']), 4, DECIMA_CONTEXT_SIZE) + assert seqs_alt.shape == (len(attribution_data['genes']), 4, DECIMA_CONTEXT_SIZE) + assert attrs_alt.shape == (len(attribution_data['genes']), 4, DECIMA_CONTEXT_SIZE) + + with VariantAttributionResult(str(h5_path), tss_distance=10_000, num_workers=1) as ar: + df_peaks, df_motifs = ar.recursive_seqlet_calling(['chr1_1000018_G_A', 'chr1_1000018_G_A'], ['PDIA3', 'EIF2S3']) + assert df_peaks.shape == (70, 8) + assert df_motifs.shape == (2080, 12) From 020e083d334466e4a78aa7e756b5dc829f8a83dc Mon Sep 17 00:00:00 2001 From: Muhammed Hasan Celik Date: Thu, 25 Dec 2025 05:00:49 +0000 Subject: [PATCH 3/4] docs added & test fix --- .../6-variant-effect-attribution.ipynb | 2258 +++++++++++------ src/decima/core/attribution.py | 9 +- tests/test_interpret_attribution.py | 35 +- tests/test_vep.py | 2 +- 4 files changed, 1430 insertions(+), 874 deletions(-) diff --git a/docs/tutorials/6-variant-effect-attribution.ipynb b/docs/tutorials/6-variant-effect-attribution.ipynb index 02b361b..1f0afcf 100644 --- a/docs/tutorials/6-variant-effect-attribution.ipynb +++ b/docs/tutorials/6-variant-effect-attribution.ipynb @@ -38,10 +38,10 @@ "id": "7d51a63c", "metadata": { "execution": { - "iopub.execute_input": "2025-12-16T22:13:35.186905Z", - "iopub.status.busy": "2025-12-16T22:13:35.186779Z", - "iopub.status.idle": "2025-12-16T22:13:45.592934Z", - "shell.execute_reply": "2025-12-16T22:13:45.592177Z" + "iopub.execute_input": "2025-12-20T01:03:56.214581Z", + "iopub.status.busy": "2025-12-20T01:03:56.214457Z", + "iopub.status.idle": "2025-12-20T01:05:48.511166Z", + "shell.execute_reply": "2025-12-20T01:05:48.510364Z" } }, "outputs": [ @@ -117,10 +117,10 @@ "id": "963b41a8", "metadata": { "execution": { - "iopub.execute_input": "2025-12-16T22:13:45.594898Z", - "iopub.status.busy": "2025-12-16T22:13:45.594710Z", - "iopub.status.idle": "2025-12-16T22:19:23.887193Z", - "shell.execute_reply": "2025-12-16T22:19:23.886257Z" + "iopub.execute_input": "2025-12-20T01:05:48.512916Z", + "iopub.status.busy": "2025-12-20T01:05:48.512767Z", + "iopub.status.idle": "2025-12-20T01:13:14.876515Z", + "shell.execute_reply": "2025-12-20T01:13:14.875597Z" } }, "outputs": [ @@ -150,7 +150,7 @@ "output_type": "stream", "text": [ "\u001b[34m\u001b[1mwandb\u001b[0m: 1 of 1 files downloaded. \r\n", - "Done. 00:00:02.1 (1496.1MB/s)\r\n" + "Done. 00:00:07.1 (439.7MB/s)\r\n" ] }, { @@ -165,7 +165,7 @@ "output_type": "stream", "text": [ "\u001b[34m\u001b[1mwandb\u001b[0m: 1 of 1 files downloaded. \r\n", - "Done. 00:00:00.5 (1396.4MB/s)\r\n" + "Done. 00:00:08.4 (86.1MB/s)\r\n" ] }, { @@ -180,7 +180,7 @@ "output_type": "stream", "text": [ "\u001b[34m\u001b[1mwandb\u001b[0m: 1 of 1 files downloaded. \r\n", - "Done. 00:00:01.8 (1740.2MB/s)\r\n" + "Done. 00:00:07.8 (399.0MB/s)\r\n" ] }, { @@ -196,7 +196,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 1%|▏ | 1/96 [00:01<02:24, 1.52s/it]" + "Computing attributions...: 1%|▏ | 1/96 [00:09<14:39, 9.26s/it]" ] }, { @@ -204,7 +204,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 2%|▎ | 2/96 [00:01<01:25, 1.10it/s]" + "Computing attributions...: 2%|▎ | 2/96 [00:09<06:24, 4.09s/it]" ] }, { @@ -212,7 +212,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 3%|▌ | 3/96 [00:02<01:06, 1.40it/s]" + "Computing attributions...: 3%|▌ | 3/96 [00:10<03:47, 2.44s/it]" ] }, { @@ -220,7 +220,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 4%|▋ | 4/96 [00:02<00:57, 1.61it/s]" + "Computing attributions...: 4%|▋ | 4/96 [00:10<02:33, 1.67s/it]" ] }, { @@ -228,7 +228,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 5%|▉ | 5/96 [00:03<00:51, 1.75it/s]" + "Computing attributions...: 5%|▉ | 5/96 [00:11<01:52, 1.24s/it]" ] }, { @@ -236,7 +236,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 6%|█ | 6/96 [00:03<00:48, 1.85it/s]" + "Computing attributions...: 6%|█ | 6/96 [00:11<01:28, 1.02it/s]" ] }, { @@ -244,7 +244,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 7%|█▏ | 7/96 [00:04<00:46, 1.92it/s]" + "Computing attributions...: 7%|█▏ | 7/96 [00:12<01:12, 1.22it/s]" ] }, { @@ -252,7 +252,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 8%|█▍ | 8/96 [00:04<00:44, 1.97it/s]" + "Computing attributions...: 8%|█▍ | 8/96 [00:12<01:02, 1.41it/s]" ] }, { @@ -260,7 +260,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 9%|█▌ | 9/96 [00:05<00:43, 2.00it/s]" + "Computing attributions...: 9%|█▌ | 9/96 [00:13<00:55, 1.57it/s]" ] }, { @@ -268,7 +268,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 10%|█▋ | 10/96 [00:05<00:42, 2.02it/s]" + "Computing attributions...: 10%|█▋ | 10/96 [00:13<00:50, 1.70it/s]" ] }, { @@ -276,7 +276,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 11%|█▊ | 11/96 [00:06<00:41, 2.04it/s]" + "Computing attributions...: 11%|█▊ | 11/96 [00:14<00:47, 1.80it/s]" ] }, { @@ -284,7 +284,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 12%|██ | 12/96 [00:06<00:40, 2.05it/s]" + "Computing attributions...: 12%|██ | 12/96 [00:14<00:44, 1.88it/s]" ] }, { @@ -292,7 +292,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 14%|██▏ | 13/96 [00:07<00:40, 2.06it/s]" + "Computing attributions...: 14%|██▏ | 13/96 [00:15<00:42, 1.94it/s]" ] }, { @@ -300,7 +300,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 15%|██▎ | 14/96 [00:07<00:39, 2.06it/s]" + "Computing attributions...: 15%|██▎ | 14/96 [00:15<00:41, 1.98it/s]" ] }, { @@ -308,7 +308,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 16%|██▌ | 15/96 [00:08<00:39, 2.07it/s]" + "Computing attributions...: 16%|██▌ | 15/96 [00:15<00:40, 2.01it/s]" ] }, { @@ -316,7 +316,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 17%|██▋ | 16/96 [00:08<00:38, 2.07it/s]" + "Computing attributions...: 17%|██▋ | 16/96 [00:16<00:39, 2.03it/s]" ] }, { @@ -324,7 +324,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 18%|██▊ | 17/96 [00:09<00:38, 2.07it/s]" + "Computing attributions...: 18%|██▊ | 17/96 [00:16<00:38, 2.05it/s]" ] }, { @@ -332,7 +332,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 19%|███ | 18/96 [00:09<00:37, 2.07it/s]" + "Computing attributions...: 19%|███ | 18/96 [00:17<00:37, 2.06it/s]" ] }, { @@ -340,7 +340,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 20%|███▏ | 19/96 [00:10<00:37, 2.08it/s]" + "Computing attributions...: 20%|███▏ | 19/96 [00:17<00:37, 2.06it/s]" ] }, { @@ -348,7 +348,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 21%|███▎ | 20/96 [00:10<00:36, 2.08it/s]" + "Computing attributions...: 21%|███▎ | 20/96 [00:18<00:36, 2.07it/s]" ] }, { @@ -356,7 +356,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 22%|███▌ | 21/96 [00:11<00:36, 2.08it/s]" + "Computing attributions...: 22%|███▌ | 21/96 [00:18<00:36, 2.07it/s]" ] }, { @@ -364,7 +364,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 23%|███▋ | 22/96 [00:11<00:35, 2.08it/s]" + "Computing attributions...: 23%|███▋ | 22/96 [00:19<00:35, 2.08it/s]" ] }, { @@ -372,7 +372,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 24%|███▊ | 23/96 [00:12<00:35, 2.08it/s]" + "Computing attributions...: 24%|███▊ | 23/96 [00:19<00:35, 2.08it/s]" ] }, { @@ -380,7 +380,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 25%|████ | 24/96 [00:12<00:34, 2.08it/s]" + "Computing attributions...: 25%|████ | 24/96 [00:20<00:34, 2.08it/s]" ] }, { @@ -388,7 +388,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 26%|████▏ | 25/96 [00:13<00:34, 2.08it/s]" + "Computing attributions...: 26%|████▏ | 25/96 [00:20<00:34, 2.08it/s]" ] }, { @@ -396,7 +396,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 27%|████▎ | 26/96 [00:13<00:33, 2.08it/s]" + "Computing attributions...: 27%|████▎ | 26/96 [00:21<00:33, 2.08it/s]" ] }, { @@ -404,7 +404,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 28%|████▌ | 27/96 [00:14<00:33, 2.08it/s]" + "Computing attributions...: 28%|████▌ | 27/96 [00:21<00:33, 2.08it/s]" ] }, { @@ -412,7 +412,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 29%|████▋ | 28/96 [00:14<00:32, 2.08it/s]" + "Computing attributions...: 29%|████▋ | 28/96 [00:22<00:32, 2.08it/s]" ] }, { @@ -420,7 +420,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 30%|████▊ | 29/96 [00:14<00:32, 2.08it/s]" + "Computing attributions...: 30%|████▊ | 29/96 [00:22<00:32, 2.08it/s]" ] }, { @@ -428,7 +428,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 31%|█████ | 30/96 [00:15<00:31, 2.08it/s]" + "Computing attributions...: 31%|█████ | 30/96 [00:23<00:31, 2.08it/s]" ] }, { @@ -436,7 +436,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 32%|█████▏ | 31/96 [00:15<00:31, 2.08it/s]" + "Computing attributions...: 32%|█████▏ | 31/96 [00:23<00:31, 2.08it/s]" ] }, { @@ -444,7 +444,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 33%|█████▎ | 32/96 [00:16<00:30, 2.08it/s]" + "Computing attributions...: 33%|█████▎ | 32/96 [00:24<00:30, 2.08it/s]" ] }, { @@ -452,7 +452,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 34%|█████▌ | 33/96 [00:16<00:30, 2.08it/s]" + "Computing attributions...: 34%|█████▌ | 33/96 [00:24<00:30, 2.08it/s]" ] }, { @@ -460,7 +460,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 35%|█████▋ | 34/96 [00:17<00:29, 2.08it/s]" + "Computing attributions...: 35%|█████▋ | 34/96 [00:25<00:29, 2.08it/s]" ] }, { @@ -468,7 +468,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 36%|█████▊ | 35/96 [00:17<00:29, 2.08it/s]" + "Computing attributions...: 36%|█████▊ | 35/96 [00:25<00:29, 2.08it/s]" ] }, { @@ -476,7 +476,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 38%|██████ | 36/96 [00:18<00:28, 2.08it/s]" + "Computing attributions...: 38%|██████ | 36/96 [00:26<00:28, 2.08it/s]" ] }, { @@ -484,7 +484,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 39%|██████▏ | 37/96 [00:18<00:28, 2.08it/s]" + "Computing attributions...: 39%|██████▏ | 37/96 [00:26<00:28, 2.08it/s]" ] }, { @@ -492,7 +492,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 40%|██████▎ | 38/96 [00:19<00:27, 2.08it/s]" + "Computing attributions...: 40%|██████▎ | 38/96 [00:27<00:27, 2.08it/s]" ] }, { @@ -500,7 +500,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 41%|██████▌ | 39/96 [00:19<00:27, 2.07it/s]" + "Computing attributions...: 41%|██████▌ | 39/96 [00:27<00:27, 2.08it/s]" ] }, { @@ -508,7 +508,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 42%|██████▋ | 40/96 [00:20<00:26, 2.08it/s]" + "Computing attributions...: 42%|██████▋ | 40/96 [00:27<00:26, 2.08it/s]" ] }, { @@ -516,7 +516,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 43%|██████▊ | 41/96 [00:20<00:26, 2.08it/s]" + "Computing attributions...: 43%|██████▊ | 41/96 [00:28<00:26, 2.08it/s]" ] }, { @@ -524,7 +524,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 44%|███████ | 42/96 [00:21<00:26, 2.08it/s]" + "Computing attributions...: 44%|███████ | 42/96 [00:28<00:25, 2.08it/s]" ] }, { @@ -532,7 +532,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 45%|███████▏ | 43/96 [00:21<00:25, 2.08it/s]" + "Computing attributions...: 45%|███████▏ | 43/96 [00:29<00:25, 2.08it/s]" ] }, { @@ -540,7 +540,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 46%|███████▎ | 44/96 [00:22<00:25, 2.08it/s]" + "Computing attributions...: 46%|███████▎ | 44/96 [00:29<00:24, 2.08it/s]" ] }, { @@ -548,7 +548,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 47%|███████▌ | 45/96 [00:22<00:24, 2.08it/s]" + "Computing attributions...: 47%|███████▌ | 45/96 [00:30<00:24, 2.08it/s]" ] }, { @@ -556,7 +556,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 48%|███████▋ | 46/96 [00:23<00:24, 2.07it/s]" + "Computing attributions...: 48%|███████▋ | 46/96 [00:30<00:23, 2.08it/s]" ] }, { @@ -564,7 +564,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 49%|███████▊ | 47/96 [00:23<00:23, 2.07it/s]" + "Computing attributions...: 49%|███████▊ | 47/96 [00:31<00:23, 2.08it/s]" ] }, { @@ -572,7 +572,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 50%|████████ | 48/96 [00:24<00:23, 2.07it/s]" + "Computing attributions...: 50%|████████ | 48/96 [00:31<00:23, 2.08it/s]" ] }, { @@ -580,7 +580,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 51%|████████▏ | 49/96 [00:24<00:22, 2.07it/s]" + "Computing attributions...: 51%|████████▏ | 49/96 [00:32<00:22, 2.08it/s]" ] }, { @@ -588,7 +588,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 52%|████████▎ | 50/96 [00:25<00:22, 2.07it/s]" + "Computing attributions...: 52%|████████▎ | 50/96 [00:32<00:22, 2.09it/s]" ] }, { @@ -596,7 +596,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 53%|████████▌ | 51/96 [00:25<00:21, 2.07it/s]" + "Computing attributions...: 53%|████████▌ | 51/96 [00:33<00:21, 2.08it/s]" ] }, { @@ -604,7 +604,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 54%|████████▋ | 52/96 [00:26<00:21, 2.07it/s]" + "Computing attributions...: 54%|████████▋ | 52/96 [00:33<00:21, 2.08it/s]" ] }, { @@ -612,7 +612,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 55%|████████▊ | 53/96 [00:26<00:20, 2.07it/s]" + "Computing attributions...: 55%|████████▊ | 53/96 [00:34<00:20, 2.08it/s]" ] }, { @@ -620,7 +620,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 56%|█████████ | 54/96 [00:27<00:20, 2.07it/s]" + "Computing attributions...: 56%|█████████ | 54/96 [00:34<00:20, 2.09it/s]" ] }, { @@ -628,7 +628,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 57%|█████████▏ | 55/96 [00:27<00:19, 2.07it/s]" + "Computing attributions...: 57%|█████████▏ | 55/96 [00:35<00:19, 2.09it/s]" ] }, { @@ -636,7 +636,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 58%|█████████▎ | 56/96 [00:28<00:19, 2.06it/s]" + "Computing attributions...: 58%|█████████▎ | 56/96 [00:35<00:19, 2.06it/s]" ] }, { @@ -644,7 +644,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 59%|█████████▌ | 57/96 [00:28<00:18, 2.06it/s]" + "Computing attributions...: 59%|█████████▌ | 57/96 [00:36<00:18, 2.07it/s]" ] }, { @@ -652,7 +652,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 60%|█████████▋ | 58/96 [00:28<00:18, 2.07it/s]" + "Computing attributions...: 60%|█████████▋ | 58/96 [00:36<00:18, 2.07it/s]" ] }, { @@ -660,7 +660,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 61%|█████████▊ | 59/96 [00:29<00:17, 2.07it/s]" + "Computing attributions...: 61%|█████████▊ | 59/96 [00:37<00:17, 2.08it/s]" ] }, { @@ -668,7 +668,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 62%|██████████ | 60/96 [00:29<00:17, 2.07it/s]" + "Computing attributions...: 62%|██████████ | 60/96 [00:37<00:17, 2.08it/s]" ] }, { @@ -676,7 +676,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 64%|██████████▏ | 61/96 [00:30<00:16, 2.07it/s]" + "Computing attributions...: 64%|██████████▏ | 61/96 [00:38<00:16, 2.08it/s]" ] }, { @@ -684,7 +684,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 65%|██████████▎ | 62/96 [00:30<00:16, 2.07it/s]" + "Computing attributions...: 65%|██████████▎ | 62/96 [00:38<00:16, 2.08it/s]" ] }, { @@ -692,7 +692,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 66%|██████████▌ | 63/96 [00:31<00:15, 2.07it/s]" + "Computing attributions...: 66%|██████████▌ | 63/96 [00:39<00:15, 2.08it/s]" ] }, { @@ -700,7 +700,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 67%|██████████▋ | 64/96 [00:31<00:15, 2.07it/s]" + "Computing attributions...: 67%|██████████▋ | 64/96 [00:39<00:15, 2.09it/s]" ] }, { @@ -708,7 +708,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 68%|██████████▊ | 65/96 [00:32<00:14, 2.07it/s]" + "Computing attributions...: 68%|██████████▊ | 65/96 [00:39<00:14, 2.09it/s]" ] }, { @@ -716,7 +716,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 69%|███████████ | 66/96 [00:32<00:14, 2.07it/s]" + "Computing attributions...: 69%|███████████ | 66/96 [00:40<00:14, 2.09it/s]" ] }, { @@ -724,7 +724,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 70%|███████████▏ | 67/96 [00:33<00:13, 2.07it/s]" + "Computing attributions...: 70%|███████████▏ | 67/96 [00:40<00:13, 2.09it/s]" ] }, { @@ -732,7 +732,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 71%|███████████▎ | 68/96 [00:33<00:13, 2.07it/s]" + "Computing attributions...: 71%|███████████▎ | 68/96 [00:41<00:13, 2.09it/s]" ] }, { @@ -740,7 +740,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 72%|███████████▌ | 69/96 [00:34<00:13, 2.07it/s]" + "Computing attributions...: 72%|███████████▌ | 69/96 [00:41<00:12, 2.09it/s]" ] }, { @@ -748,7 +748,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 73%|███████████▋ | 70/96 [00:34<00:12, 2.07it/s]" + "Computing attributions...: 73%|███████████▋ | 70/96 [00:42<00:12, 2.09it/s]" ] }, { @@ -756,7 +756,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 74%|███████████▊ | 71/96 [00:35<00:12, 2.07it/s]" + "Computing attributions...: 74%|███████████▊ | 71/96 [00:42<00:11, 2.09it/s]" ] }, { @@ -764,7 +764,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 75%|████████████ | 72/96 [00:35<00:11, 2.07it/s]" + "Computing attributions...: 75%|████████████ | 72/96 [00:43<00:11, 2.09it/s]" ] }, { @@ -772,7 +772,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 76%|████████████▏ | 73/96 [00:36<00:11, 2.07it/s]" + "Computing attributions...: 76%|████████████▏ | 73/96 [00:43<00:11, 2.09it/s]" ] }, { @@ -780,7 +780,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 77%|████████████▎ | 74/96 [00:36<00:10, 2.07it/s]" + "Computing attributions...: 77%|████████████▎ | 74/96 [00:44<00:10, 2.09it/s]" ] }, { @@ -788,7 +788,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 78%|████████████▌ | 75/96 [00:37<00:10, 2.07it/s]" + "Computing attributions...: 78%|████████████▌ | 75/96 [00:44<00:10, 2.09it/s]" ] }, { @@ -796,7 +796,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 79%|████████████▋ | 76/96 [00:37<00:09, 2.07it/s]" + "Computing attributions...: 79%|████████████▋ | 76/96 [00:45<00:09, 2.09it/s]" ] }, { @@ -804,7 +804,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 80%|████████████▊ | 77/96 [00:38<00:09, 2.07it/s]" + "Computing attributions...: 80%|████████████▊ | 77/96 [00:45<00:09, 2.09it/s]" ] }, { @@ -812,7 +812,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 81%|█████████████ | 78/96 [00:38<00:08, 2.07it/s]" + "Computing attributions...: 81%|█████████████ | 78/96 [00:46<00:08, 2.09it/s]" ] }, { @@ -820,7 +820,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 82%|█████████████▏ | 79/96 [00:39<00:08, 2.07it/s]" + "Computing attributions...: 82%|█████████████▏ | 79/96 [00:46<00:08, 2.09it/s]" ] }, { @@ -828,7 +828,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 83%|█████████████▎ | 80/96 [00:39<00:07, 2.07it/s]" + "Computing attributions...: 83%|█████████████▎ | 80/96 [00:47<00:07, 2.09it/s]" ] }, { @@ -836,7 +836,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 84%|█████████████▌ | 81/96 [00:40<00:07, 2.07it/s]" + "Computing attributions...: 84%|█████████████▌ | 81/96 [00:47<00:07, 2.09it/s]" ] }, { @@ -844,7 +844,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 85%|█████████████▋ | 82/96 [00:40<00:06, 2.07it/s]" + "Computing attributions...: 85%|█████████████▋ | 82/96 [00:48<00:06, 2.09it/s]" ] }, { @@ -852,7 +852,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 86%|█████████████▊ | 83/96 [00:41<00:06, 2.07it/s]" + "Computing attributions...: 86%|█████████████▊ | 83/96 [00:48<00:06, 2.09it/s]" ] }, { @@ -860,7 +860,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 88%|██████████████ | 84/96 [00:41<00:05, 2.07it/s]" + "Computing attributions...: 88%|██████████████ | 84/96 [00:49<00:05, 2.09it/s]" ] }, { @@ -868,7 +868,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 89%|██████████████▏ | 85/96 [00:42<00:05, 2.07it/s]" + "Computing attributions...: 89%|██████████████▏ | 85/96 [00:49<00:05, 2.08it/s]" ] }, { @@ -876,7 +876,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 90%|██████████████▎ | 86/96 [00:42<00:04, 2.07it/s]" + "Computing attributions...: 90%|██████████████▎ | 86/96 [00:50<00:04, 2.08it/s]" ] }, { @@ -884,7 +884,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 91%|██████████████▌ | 87/96 [00:42<00:04, 2.07it/s]" + "Computing attributions...: 91%|██████████████▌ | 87/96 [00:50<00:04, 2.08it/s]" ] }, { @@ -892,7 +892,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 92%|██████████████▋ | 88/96 [00:43<00:03, 2.07it/s]" + "Computing attributions...: 92%|██████████████▋ | 88/96 [00:51<00:03, 2.08it/s]" ] }, { @@ -900,7 +900,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 93%|██████████████▊ | 89/96 [00:43<00:03, 2.07it/s]" + "Computing attributions...: 93%|██████████████▊ | 89/96 [00:51<00:03, 2.08it/s]" ] }, { @@ -908,7 +908,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 94%|███████████████ | 90/96 [00:44<00:02, 2.07it/s]" + "Computing attributions...: 94%|███████████████ | 90/96 [00:51<00:02, 2.08it/s]" ] }, { @@ -916,7 +916,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 95%|███████████████▏| 91/96 [00:44<00:02, 2.08it/s]" + "Computing attributions...: 95%|███████████████▏| 91/96 [00:52<00:02, 2.09it/s]" ] }, { @@ -924,7 +924,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 96%|███████████████▎| 92/96 [00:45<00:01, 2.08it/s]" + "Computing attributions...: 96%|███████████████▎| 92/96 [00:52<00:01, 2.09it/s]" ] }, { @@ -932,7 +932,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 97%|███████████████▌| 93/96 [00:45<00:01, 2.08it/s]" + "Computing attributions...: 97%|███████████████▌| 93/96 [00:53<00:01, 2.09it/s]" ] }, { @@ -940,7 +940,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 98%|███████████████▋| 94/96 [00:46<00:00, 2.08it/s]" + "Computing attributions...: 98%|███████████████▋| 94/96 [00:53<00:00, 2.09it/s]" ] }, { @@ -948,7 +948,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 99%|███████████████▊| 95/96 [00:46<00:00, 2.08it/s]" + "Computing attributions...: 99%|███████████████▊| 95/96 [00:54<00:00, 2.09it/s]" ] }, { @@ -956,7 +956,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 100%|████████████████| 96/96 [00:47<00:00, 2.08it/s]" + "Computing attributions...: 100%|████████████████| 96/96 [00:54<00:00, 2.09it/s]" ] }, { @@ -964,7 +964,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 100%|████████████████| 96/96 [00:47<00:00, 2.02it/s]\r\n" + "Computing attributions...: 100%|████████████████| 96/96 [00:54<00:00, 1.75it/s]\r\n" ] }, { @@ -980,7 +980,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 2%|▍ | 1/48 [00:00<00:22, 2.06it/s]" + "Writing attributions...: 2%|▍ | 1/48 [00:00<00:24, 1.91it/s]" ] }, { @@ -988,7 +988,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 4%|▊ | 2/48 [00:00<00:21, 2.13it/s]" + "Writing attributions...: 4%|▊ | 2/48 [00:00<00:22, 2.05it/s]" ] }, { @@ -996,7 +996,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 6%|█▏ | 3/48 [00:01<00:20, 2.16it/s]" + "Writing attributions...: 6%|█▏ | 3/48 [00:01<00:21, 2.11it/s]" ] }, { @@ -1004,7 +1004,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 8%|█▌ | 4/48 [00:01<00:20, 2.17it/s]" + "Writing attributions...: 8%|█▌ | 4/48 [00:01<00:20, 2.11it/s]" ] }, { @@ -1012,7 +1012,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 10%|█▉ | 5/48 [00:02<00:19, 2.17it/s]" + "Writing attributions...: 10%|█▉ | 5/48 [00:02<00:20, 2.12it/s]" ] }, { @@ -1020,7 +1020,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 12%|██▍ | 6/48 [00:02<00:19, 2.17it/s]" + "Writing attributions...: 12%|██▍ | 6/48 [00:02<00:19, 2.13it/s]" ] }, { @@ -1028,7 +1028,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 15%|██▊ | 7/48 [00:03<00:18, 2.17it/s]" + "Writing attributions...: 15%|██▊ | 7/48 [00:03<00:19, 2.14it/s]" ] }, { @@ -1036,7 +1036,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 17%|███▏ | 8/48 [00:03<00:18, 2.17it/s]" + "Writing attributions...: 17%|███▏ | 8/48 [00:03<00:18, 2.15it/s]" ] }, { @@ -1044,7 +1044,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 19%|███▌ | 9/48 [00:04<00:17, 2.17it/s]" + "Writing attributions...: 19%|███▌ | 9/48 [00:04<00:18, 2.15it/s]" ] }, { @@ -1052,7 +1052,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 21%|███▊ | 10/48 [00:04<00:17, 2.17it/s]" + "Writing attributions...: 21%|███▊ | 10/48 [00:04<00:17, 2.16it/s]" ] }, { @@ -1060,7 +1060,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 23%|████▏ | 11/48 [00:05<00:17, 2.17it/s]" + "Writing attributions...: 23%|████▏ | 11/48 [00:05<00:17, 2.16it/s]" ] }, { @@ -1068,7 +1068,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 25%|████▌ | 12/48 [00:05<00:16, 2.17it/s]" + "Writing attributions...: 25%|████▌ | 12/48 [00:05<00:16, 2.16it/s]" ] }, { @@ -1076,7 +1076,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 27%|████▉ | 13/48 [00:06<00:16, 2.17it/s]" + "Writing attributions...: 27%|████▉ | 13/48 [00:06<00:16, 2.16it/s]" ] }, { @@ -1092,7 +1092,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 31%|█████▋ | 15/48 [00:06<00:15, 2.16it/s]" + "Writing attributions...: 31%|█████▋ | 15/48 [00:07<00:15, 2.16it/s]" ] }, { @@ -1132,7 +1132,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 42%|███████▌ | 20/48 [00:09<00:12, 2.16it/s]" + "Writing attributions...: 42%|███████▌ | 20/48 [00:09<00:12, 2.15it/s]" ] }, { @@ -1180,7 +1180,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 54%|█████████▊ | 26/48 [00:12<00:10, 2.14it/s]" + "Writing attributions...: 54%|█████████▊ | 26/48 [00:12<00:10, 2.15it/s]" ] }, { @@ -1188,7 +1188,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 56%|██████████▏ | 27/48 [00:12<00:09, 2.15it/s]" + "Writing attributions...: 56%|██████████▏ | 27/48 [00:12<00:09, 2.16it/s]" ] }, { @@ -1196,7 +1196,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 58%|██████████▌ | 28/48 [00:12<00:09, 2.15it/s]" + "Writing attributions...: 58%|██████████▌ | 28/48 [00:13<00:09, 2.16it/s]" ] }, { @@ -1204,7 +1204,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 60%|██████████▉ | 29/48 [00:13<00:08, 2.15it/s]" + "Writing attributions...: 60%|██████████▉ | 29/48 [00:13<00:08, 2.16it/s]" ] }, { @@ -1212,7 +1212,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 62%|███████████▎ | 30/48 [00:13<00:08, 2.15it/s]" + "Writing attributions...: 62%|███████████▎ | 30/48 [00:13<00:08, 2.16it/s]" ] }, { @@ -1220,7 +1220,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 65%|███████████▋ | 31/48 [00:14<00:07, 2.15it/s]" + "Writing attributions...: 65%|███████████▋ | 31/48 [00:14<00:07, 2.16it/s]" ] }, { @@ -1236,7 +1236,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 69%|████████████▍ | 33/48 [00:15<00:06, 2.15it/s]" + "Writing attributions...: 69%|████████████▍ | 33/48 [00:15<00:06, 2.16it/s]" ] }, { @@ -1244,7 +1244,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 71%|████████████▊ | 34/48 [00:15<00:06, 2.15it/s]" + "Writing attributions...: 71%|████████████▊ | 34/48 [00:15<00:06, 2.16it/s]" ] }, { @@ -1252,7 +1252,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 73%|█████████████▏ | 35/48 [00:16<00:06, 2.15it/s]" + "Writing attributions...: 73%|█████████████▏ | 35/48 [00:16<00:06, 2.16it/s]" ] }, { @@ -1260,7 +1260,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 75%|█████████████▌ | 36/48 [00:16<00:05, 2.15it/s]" + "Writing attributions...: 75%|█████████████▌ | 36/48 [00:16<00:05, 2.16it/s]" ] }, { @@ -1268,7 +1268,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 77%|█████████████▉ | 37/48 [00:17<00:05, 2.15it/s]" + "Writing attributions...: 77%|█████████████▉ | 37/48 [00:17<00:05, 2.16it/s]" ] }, { @@ -1276,7 +1276,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 79%|██████████████▎ | 38/48 [00:17<00:04, 2.15it/s]" + "Writing attributions...: 79%|██████████████▎ | 38/48 [00:17<00:04, 2.16it/s]" ] }, { @@ -1284,7 +1284,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 81%|██████████████▋ | 39/48 [00:18<00:04, 2.15it/s]" + "Writing attributions...: 81%|██████████████▋ | 39/48 [00:18<00:04, 2.16it/s]" ] }, { @@ -1292,7 +1292,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 83%|███████████████ | 40/48 [00:18<00:03, 2.15it/s]" + "Writing attributions...: 83%|███████████████ | 40/48 [00:18<00:03, 2.16it/s]" ] }, { @@ -1300,7 +1300,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 85%|███████████████▍ | 41/48 [00:19<00:03, 2.15it/s]" + "Writing attributions...: 85%|███████████████▍ | 41/48 [00:19<00:03, 2.16it/s]" ] }, { @@ -1308,7 +1308,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 88%|███████████████▊ | 42/48 [00:19<00:02, 2.15it/s]" + "Writing attributions...: 88%|███████████████▊ | 42/48 [00:19<00:02, 2.16it/s]" ] }, { @@ -1316,7 +1316,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 90%|████████████████▏ | 43/48 [00:19<00:02, 2.15it/s]" + "Writing attributions...: 90%|████████████████▏ | 43/48 [00:19<00:02, 2.16it/s]" ] }, { @@ -1324,7 +1324,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 92%|████████████████▌ | 44/48 [00:20<00:01, 2.15it/s]" + "Writing attributions...: 92%|████████████████▌ | 44/48 [00:20<00:01, 2.16it/s]" ] }, { @@ -1332,7 +1332,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 94%|████████████████▉ | 45/48 [00:20<00:01, 2.15it/s]" + "Writing attributions...: 94%|████████████████▉ | 45/48 [00:20<00:01, 2.16it/s]" ] }, { @@ -1348,7 +1348,7 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 98%|█████████████████▋| 47/48 [00:21<00:00, 2.14it/s]" + "Writing attributions...: 98%|█████████████████▋| 47/48 [00:21<00:00, 2.15it/s]" ] }, { @@ -1356,8 +1356,8 @@ "output_type": "stream", "text": [ "\r", - "Writing attributions...: 100%|██████████████████| 48/48 [00:22<00:00, 2.14it/s]\r", - "Writing attributions...: 100%|██████████████████| 48/48 [00:22<00:00, 2.16it/s]\r\n", + "Writing attributions...: 100%|██████████████████| 48/48 [00:22<00:00, 2.15it/s]\r", + "Writing attributions...: 100%|██████████████████| 48/48 [00:22<00:00, 2.15it/s]\r\n", "decima - WARNING - Warnings:\r\n", "decima - WARNING - allele_mismatch_with_reference_genome: 10 alleles out of 48 predictions mismatched with the genome file hg38.If this is not expected, please check if you are using the correct genome version.\r\n" ] @@ -1381,7 +1381,7 @@ "output_type": "stream", "text": [ "\u001b[34m\u001b[1mwandb\u001b[0m: 1 of 1 files downloaded. \r\n", - "Done. 00:00:01.8 (1756.6MB/s)\r\n" + "Done. 00:00:01.9 (1603.5MB/s)\r\n" ] }, { @@ -1396,7 +1396,7 @@ "output_type": "stream", "text": [ "\u001b[34m\u001b[1mwandb\u001b[0m: 1 of 1 files downloaded. \r\n", - "Done. 00:00:00.5 (1405.5MB/s)\r\n" + "Done. 00:00:02.0 (356.3MB/s)\r\n" ] }, { @@ -1411,7 +1411,7 @@ "output_type": "stream", "text": [ "\u001b[34m\u001b[1mwandb\u001b[0m: 1 of 1 files downloaded. \r\n", - "Done. 00:00:01.8 (1771.6MB/s)\r\n" + "Done. 00:00:10.5 (297.8MB/s)\r\n" ] }, { @@ -1427,7 +1427,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 1%|▏ | 1/96 [00:01<01:46, 1.12s/it]" + "Computing attributions...: 1%|▏ | 1/96 [00:00<01:15, 1.27it/s]" ] }, { @@ -1435,7 +1435,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 2%|▎ | 2/96 [00:01<01:09, 1.34it/s]" + "Computing attributions...: 2%|▎ | 2/96 [00:01<00:57, 1.64it/s]" ] }, { @@ -1443,7 +1443,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 3%|▌ | 3/96 [00:02<00:58, 1.60it/s]" + "Computing attributions...: 3%|▌ | 3/96 [00:01<00:51, 1.82it/s]" ] }, { @@ -1451,7 +1451,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 4%|▋ | 4/96 [00:02<00:52, 1.76it/s]" + "Computing attributions...: 4%|▋ | 4/96 [00:02<00:48, 1.91it/s]" ] }, { @@ -1459,7 +1459,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 5%|▉ | 5/96 [00:03<00:48, 1.86it/s]" + "Computing attributions...: 5%|▉ | 5/96 [00:02<00:46, 1.97it/s]" ] }, { @@ -1467,7 +1467,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 6%|█ | 6/96 [00:03<00:46, 1.93it/s]" + "Computing attributions...: 6%|█ | 6/96 [00:03<00:44, 2.00it/s]" ] }, { @@ -1475,7 +1475,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 7%|█▏ | 7/96 [00:04<00:45, 1.97it/s]" + "Computing attributions...: 7%|█▏ | 7/96 [00:03<00:43, 2.03it/s]" ] }, { @@ -1483,7 +1483,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 8%|█▍ | 8/96 [00:04<00:43, 2.00it/s]" + "Computing attributions...: 8%|█▍ | 8/96 [00:04<00:43, 2.04it/s]" ] }, { @@ -1491,7 +1491,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 9%|█▌ | 9/96 [00:04<00:42, 2.03it/s]" + "Computing attributions...: 9%|█▌ | 9/96 [00:04<00:42, 2.05it/s]" ] }, { @@ -1499,7 +1499,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 10%|█▋ | 10/96 [00:05<00:42, 2.04it/s]" + "Computing attributions...: 10%|█▋ | 10/96 [00:05<00:41, 2.06it/s]" ] }, { @@ -1507,7 +1507,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 11%|█▊ | 11/96 [00:05<00:41, 2.05it/s]" + "Computing attributions...: 11%|█▊ | 11/96 [00:05<00:41, 2.06it/s]" ] }, { @@ -1515,7 +1515,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 12%|██ | 12/96 [00:06<00:40, 2.06it/s]" + "Computing attributions...: 12%|██ | 12/96 [00:06<00:40, 2.07it/s]" ] }, { @@ -1523,7 +1523,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 14%|██▏ | 13/96 [00:06<00:40, 2.06it/s]" + "Computing attributions...: 14%|██▏ | 13/96 [00:06<00:40, 2.07it/s]" ] }, { @@ -1531,7 +1531,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 15%|██▎ | 14/96 [00:07<00:39, 2.06it/s]" + "Computing attributions...: 15%|██▎ | 14/96 [00:07<00:39, 2.07it/s]" ] }, { @@ -1563,7 +1563,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 19%|███ | 18/96 [00:09<00:37, 2.07it/s]" + "Computing attributions...: 19%|███ | 18/96 [00:08<00:37, 2.07it/s]" ] }, { @@ -1571,7 +1571,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 20%|███▏ | 19/96 [00:09<00:37, 2.07it/s]" + "Computing attributions...: 20%|███▏ | 19/96 [00:09<00:37, 2.08it/s]" ] }, { @@ -1579,7 +1579,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 21%|███▎ | 20/96 [00:10<00:36, 2.08it/s]" + "Computing attributions...: 21%|███▎ | 20/96 [00:09<00:36, 2.08it/s]" ] }, { @@ -1587,7 +1587,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 22%|███▌ | 21/96 [00:10<00:36, 2.08it/s]" + "Computing attributions...: 22%|███▌ | 21/96 [00:10<00:35, 2.08it/s]" ] }, { @@ -1595,7 +1595,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 23%|███▋ | 22/96 [00:11<00:35, 2.08it/s]" + "Computing attributions...: 23%|███▋ | 22/96 [00:10<00:35, 2.08it/s]" ] }, { @@ -1603,7 +1603,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 24%|███▊ | 23/96 [00:11<00:35, 2.08it/s]" + "Computing attributions...: 24%|███▊ | 23/96 [00:11<00:34, 2.09it/s]" ] }, { @@ -1611,7 +1611,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 25%|████ | 24/96 [00:12<00:34, 2.08it/s]" + "Computing attributions...: 25%|████ | 24/96 [00:11<00:34, 2.08it/s]" ] }, { @@ -1627,7 +1627,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 27%|████▎ | 26/96 [00:13<00:33, 2.08it/s]" + "Computing attributions...: 27%|████▎ | 26/96 [00:12<00:33, 2.08it/s]" ] }, { @@ -1643,7 +1643,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 29%|████▋ | 28/96 [00:14<00:32, 2.07it/s]" + "Computing attributions...: 29%|████▋ | 28/96 [00:13<00:32, 2.08it/s]" ] }, { @@ -1651,7 +1651,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 30%|████▊ | 29/96 [00:14<00:32, 2.07it/s]" + "Computing attributions...: 30%|████▊ | 29/96 [00:14<00:32, 2.08it/s]" ] }, { @@ -1659,7 +1659,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 31%|█████ | 30/96 [00:15<00:31, 2.07it/s]" + "Computing attributions...: 31%|█████ | 30/96 [00:14<00:31, 2.08it/s]" ] }, { @@ -1667,7 +1667,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 32%|█████▏ | 31/96 [00:15<00:31, 2.07it/s]" + "Computing attributions...: 32%|█████▏ | 31/96 [00:15<00:31, 2.08it/s]" ] }, { @@ -1675,7 +1675,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 33%|█████▎ | 32/96 [00:16<00:30, 2.07it/s]" + "Computing attributions...: 33%|█████▎ | 32/96 [00:15<00:30, 2.08it/s]" ] }, { @@ -1691,7 +1691,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 35%|█████▋ | 34/96 [00:17<00:29, 2.07it/s]" + "Computing attributions...: 35%|█████▋ | 34/96 [00:16<00:29, 2.07it/s]" ] }, { @@ -1723,7 +1723,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 40%|██████▎ | 38/96 [00:18<00:28, 2.07it/s]" + "Computing attributions...: 40%|██████▎ | 38/96 [00:18<00:27, 2.07it/s]" ] }, { @@ -1779,7 +1779,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 47%|███████▌ | 45/96 [00:22<00:24, 2.07it/s]" + "Computing attributions...: 47%|███████▌ | 45/96 [00:21<00:24, 2.07it/s]" ] }, { @@ -1795,7 +1795,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 49%|███████▊ | 47/96 [00:23<00:23, 2.07it/s]" + "Computing attributions...: 49%|███████▊ | 47/96 [00:22<00:23, 2.07it/s]" ] }, { @@ -1811,7 +1811,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 51%|████████▏ | 49/96 [00:24<00:22, 2.07it/s]" + "Computing attributions...: 51%|████████▏ | 49/96 [00:23<00:22, 2.07it/s]" ] }, { @@ -1827,7 +1827,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 53%|████████▌ | 51/96 [00:25<00:21, 2.07it/s]" + "Computing attributions...: 53%|████████▌ | 51/96 [00:24<00:21, 2.07it/s]" ] }, { @@ -1843,7 +1843,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 55%|████████▊ | 53/96 [00:26<00:20, 2.07it/s]" + "Computing attributions...: 55%|████████▊ | 53/96 [00:25<00:20, 2.07it/s]" ] }, { @@ -1859,7 +1859,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 57%|█████████▏ | 55/96 [00:27<00:19, 2.07it/s]" + "Computing attributions...: 57%|█████████▏ | 55/96 [00:26<00:19, 2.07it/s]" ] }, { @@ -1875,7 +1875,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 59%|█████████▌ | 57/96 [00:28<00:18, 2.07it/s]" + "Computing attributions...: 59%|█████████▌ | 57/96 [00:27<00:18, 2.07it/s]" ] }, { @@ -1883,7 +1883,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 60%|█████████▋ | 58/96 [00:28<00:18, 2.08it/s]" + "Computing attributions...: 60%|█████████▋ | 58/96 [00:28<00:18, 2.07it/s]" ] }, { @@ -1891,7 +1891,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 61%|█████████▊ | 59/96 [00:29<00:17, 2.08it/s]" + "Computing attributions...: 61%|█████████▊ | 59/96 [00:28<00:17, 2.07it/s]" ] }, { @@ -1899,7 +1899,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 62%|██████████ | 60/96 [00:29<00:17, 2.08it/s]" + "Computing attributions...: 62%|██████████ | 60/96 [00:29<00:17, 2.07it/s]" ] }, { @@ -1907,7 +1907,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 64%|██████████▏ | 61/96 [00:30<00:16, 2.08it/s]" + "Computing attributions...: 64%|██████████▏ | 61/96 [00:29<00:16, 2.07it/s]" ] }, { @@ -1915,7 +1915,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 65%|██████████▎ | 62/96 [00:30<00:16, 2.08it/s]" + "Computing attributions...: 65%|██████████▎ | 62/96 [00:30<00:16, 2.07it/s]" ] }, { @@ -1923,7 +1923,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 66%|██████████▌ | 63/96 [00:31<00:15, 2.08it/s]" + "Computing attributions...: 66%|██████████▌ | 63/96 [00:30<00:15, 2.07it/s]" ] }, { @@ -1939,7 +1939,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 68%|██████████▊ | 65/96 [00:31<00:14, 2.08it/s]" + "Computing attributions...: 68%|██████████▊ | 65/96 [00:31<00:14, 2.07it/s]" ] }, { @@ -1971,7 +1971,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 72%|███████████▌ | 69/96 [00:33<00:13, 2.08it/s]" + "Computing attributions...: 72%|███████████▌ | 69/96 [00:33<00:12, 2.08it/s]" ] }, { @@ -1979,7 +1979,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 73%|███████████▋ | 70/96 [00:34<00:12, 2.07it/s]" + "Computing attributions...: 73%|███████████▋ | 70/96 [00:34<00:12, 2.08it/s]" ] }, { @@ -1987,7 +1987,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 74%|███████████▊ | 71/96 [00:34<00:12, 2.07it/s]" + "Computing attributions...: 74%|███████████▊ | 71/96 [00:34<00:12, 2.08it/s]" ] }, { @@ -1995,7 +1995,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 75%|████████████ | 72/96 [00:35<00:11, 2.07it/s]" + "Computing attributions...: 75%|████████████ | 72/96 [00:34<00:11, 2.08it/s]" ] }, { @@ -2003,7 +2003,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 76%|████████████▏ | 73/96 [00:35<00:11, 2.07it/s]" + "Computing attributions...: 76%|████████████▏ | 73/96 [00:35<00:11, 2.08it/s]" ] }, { @@ -2011,7 +2011,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 77%|████████████▎ | 74/96 [00:36<00:10, 2.07it/s]" + "Computing attributions...: 77%|████████████▎ | 74/96 [00:35<00:10, 2.08it/s]" ] }, { @@ -2027,7 +2027,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 79%|████████████▋ | 76/96 [00:37<00:09, 2.07it/s]" + "Computing attributions...: 79%|████████████▋ | 76/96 [00:36<00:09, 2.07it/s]" ] }, { @@ -2035,7 +2035,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 80%|████████████▊ | 77/96 [00:37<00:09, 2.07it/s]" + "Computing attributions...: 80%|████████████▊ | 77/96 [00:37<00:09, 2.08it/s]" ] }, { @@ -2043,7 +2043,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 81%|█████████████ | 78/96 [00:38<00:08, 2.07it/s]" + "Computing attributions...: 81%|█████████████ | 78/96 [00:37<00:08, 2.08it/s]" ] }, { @@ -2051,7 +2051,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 82%|█████████████▏ | 79/96 [00:38<00:08, 2.07it/s]" + "Computing attributions...: 82%|█████████████▏ | 79/96 [00:38<00:08, 2.08it/s]" ] }, { @@ -2059,7 +2059,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 83%|█████████████▎ | 80/96 [00:39<00:07, 2.07it/s]" + "Computing attributions...: 83%|█████████████▎ | 80/96 [00:38<00:07, 2.08it/s]" ] }, { @@ -2067,7 +2067,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 84%|█████████████▌ | 81/96 [00:39<00:07, 2.07it/s]" + "Computing attributions...: 84%|█████████████▌ | 81/96 [00:39<00:07, 2.08it/s]" ] }, { @@ -2075,7 +2075,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 85%|█████████████▋ | 82/96 [00:40<00:06, 2.07it/s]" + "Computing attributions...: 85%|█████████████▋ | 82/96 [00:39<00:06, 2.08it/s]" ] }, { @@ -2083,7 +2083,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 86%|█████████████▊ | 83/96 [00:40<00:06, 2.07it/s]" + "Computing attributions...: 86%|█████████████▊ | 83/96 [00:40<00:06, 2.08it/s]" ] }, { @@ -2091,7 +2091,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 88%|██████████████ | 84/96 [00:41<00:05, 2.07it/s]" + "Computing attributions...: 88%|██████████████ | 84/96 [00:40<00:05, 2.08it/s]" ] }, { @@ -2099,7 +2099,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 89%|██████████████▏ | 85/96 [00:41<00:05, 2.07it/s]" + "Computing attributions...: 89%|██████████████▏ | 85/96 [00:41<00:05, 2.08it/s]" ] }, { @@ -2107,7 +2107,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 90%|██████████████▎ | 86/96 [00:42<00:04, 2.07it/s]" + "Computing attributions...: 90%|██████████████▎ | 86/96 [00:41<00:04, 2.08it/s]" ] }, { @@ -2115,7 +2115,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 91%|██████████████▌ | 87/96 [00:42<00:04, 2.07it/s]" + "Computing attributions...: 91%|██████████████▌ | 87/96 [00:42<00:04, 2.09it/s]" ] }, { @@ -2123,7 +2123,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 92%|██████████████▋ | 88/96 [00:43<00:03, 2.07it/s]" + "Computing attributions...: 92%|██████████████▋ | 88/96 [00:42<00:03, 2.08it/s]" ] }, { @@ -2131,7 +2131,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 93%|██████████████▊ | 89/96 [00:43<00:03, 2.07it/s]" + "Computing attributions...: 93%|██████████████▊ | 89/96 [00:43<00:03, 2.08it/s]" ] }, { @@ -2139,7 +2139,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 94%|███████████████ | 90/96 [00:44<00:02, 2.07it/s]" + "Computing attributions...: 94%|███████████████ | 90/96 [00:43<00:02, 2.08it/s]" ] }, { @@ -2147,7 +2147,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 95%|███████████████▏| 91/96 [00:44<00:02, 2.07it/s]" + "Computing attributions...: 95%|███████████████▏| 91/96 [00:44<00:02, 2.08it/s]" ] }, { @@ -2155,7 +2155,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 96%|███████████████▎| 92/96 [00:45<00:01, 2.07it/s]" + "Computing attributions...: 96%|███████████████▎| 92/96 [00:44<00:01, 2.08it/s]" ] }, { @@ -2163,7 +2163,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 97%|███████████████▌| 93/96 [00:45<00:01, 2.07it/s]" + "Computing attributions...: 97%|███████████████▌| 93/96 [00:45<00:01, 2.09it/s]" ] }, { @@ -2171,7 +2171,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 98%|███████████████▋| 94/96 [00:45<00:00, 2.07it/s]" + "Computing attributions...: 98%|███████████████▋| 94/96 [00:45<00:00, 2.09it/s]" ] }, { @@ -2179,7 +2179,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 99%|███████████████▊| 95/96 [00:46<00:00, 2.08it/s]" + "Computing attributions...: 99%|███████████████▊| 95/96 [00:46<00:00, 2.09it/s]" ] }, { @@ -2187,7 +2187,7 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 100%|████████████████| 96/96 [00:46<00:00, 2.08it/s]" + "Computing attributions...: 100%|████████████████| 96/96 [00:46<00:00, 2.09it/s]" ] }, { @@ -2195,7 +2195,13 @@ "output_type": "stream", "text": [ "\r", - "Computing attributions...: 100%|████████████████| 96/96 [00:47<00:00, 2.03it/s]\r\n", + "Computing attributions...: 100%|████████████████| 96/96 [00:46<00:00, 2.06it/s]\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\r", "Writing attributions...: 0%| | 0/48 [00:00" + "" ] }, "execution_count": 13, @@ -5721,17 +5728,17 @@ "id": "1684816e", "metadata": { "execution": { - "iopub.execute_input": "2025-12-16T22:19:52.232145Z", - "iopub.status.busy": "2025-12-16T22:19:52.232007Z", - "iopub.status.idle": "2025-12-16T22:19:53.044712Z", - "shell.execute_reply": "2025-12-16T22:19:53.044181Z" + "iopub.execute_input": "2025-12-20T01:14:22.662542Z", + "iopub.status.busy": "2025-12-20T01:14:22.662415Z", + "iopub.status.idle": "2025-12-20T01:14:23.471789Z", + "shell.execute_reply": "2025-12-20T01:14:23.471367Z" } }, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 14, @@ -5759,17 +5766,17 @@ "id": "d6695d34", "metadata": { "execution": { - "iopub.execute_input": "2025-12-16T22:19:53.046114Z", - "iopub.status.busy": "2025-12-16T22:19:53.045953Z", - "iopub.status.idle": "2025-12-16T22:19:53.875337Z", - "shell.execute_reply": "2025-12-16T22:19:53.874787Z" + "iopub.execute_input": "2025-12-20T01:14:23.472884Z", + "iopub.status.busy": "2025-12-20T01:14:23.472759Z", + "iopub.status.idle": "2025-12-20T01:14:24.295864Z", + "shell.execute_reply": "2025-12-20T01:14:24.295401Z" } }, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 15, @@ -5788,59 +5795,209 @@ } ], "source": [ - "(attribution_alt - attribution_ref).plot_seqlogo(relative_loc=24045)" + "attribution_diff = attribution_alt - attribution_ref\n", + "attribution_diff.plot_seqlogo(relative_loc=24045)" ] }, { - "cell_type": "markdown", - "id": "4cb3192b", - "metadata": {}, + "cell_type": "code", + "execution_count": 16, + "id": "945a1fc4", + "metadata": { + "execution": { + "iopub.execute_input": "2025-12-20T01:14:24.296978Z", + "iopub.status.busy": "2025-12-20T01:14:24.296848Z", + "iopub.status.idle": "2025-12-20T01:14:28.354510Z", + "shell.execute_reply": "2025-12-20T01:14:28.354070Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
peakstartendattributionp-valuefrom_tss
0pos.chr9_133251979_C_T_ABO-ABO@2404074040740541.0621680.00024924040
1pos.chr9_133251979_C_T_ABO-ABO@2407174071740770.0848750.00030024071
2pos.chr9_133251979_C_T_ABO-ABO@2395073950739560.0771550.00030023950
3pos.chr9_133251979_C_T_ABO-ABO@78450784507900.0731980.000300784
4pos.chr9_133251979_C_T_ABO-ABO@75550755507600.0514040.000338755
.....................
39neg.chr9_133251979_C_T_ABO-ABO@4795047950483-0.0287380.000431479
40neg.chr9_133251979_C_T_ABO-ABO@196626966269671-0.1007730.00045119662
41neg.chr9_133251979_C_T_ABO-ABO@8575085750862-0.0390620.000451857
42neg.chr9_133251979_C_T_ABO-ABO@241847418474190-0.0383280.00048424184
43neg.chr9_133251979_C_T_ABO-ABO@236247362473628-0.0282240.00048423624
\n", + "

61 rows × 6 columns

\n", + "
" + ], + "text/plain": [ + " peak start end attribution p-value \\\n", + "0 pos.chr9_133251979_C_T_ABO-ABO@24040 74040 74054 1.062168 0.000249 \n", + "1 pos.chr9_133251979_C_T_ABO-ABO@24071 74071 74077 0.084875 0.000300 \n", + "2 pos.chr9_133251979_C_T_ABO-ABO@23950 73950 73956 0.077155 0.000300 \n", + "3 pos.chr9_133251979_C_T_ABO-ABO@784 50784 50790 0.073198 0.000300 \n", + "4 pos.chr9_133251979_C_T_ABO-ABO@755 50755 50760 0.051404 0.000338 \n", + ".. ... ... ... ... ... \n", + "39 neg.chr9_133251979_C_T_ABO-ABO@479 50479 50483 -0.028738 0.000431 \n", + "40 neg.chr9_133251979_C_T_ABO-ABO@19662 69662 69671 -0.100773 0.000451 \n", + "41 neg.chr9_133251979_C_T_ABO-ABO@857 50857 50862 -0.039062 0.000451 \n", + "42 neg.chr9_133251979_C_T_ABO-ABO@24184 74184 74190 -0.038328 0.000484 \n", + "43 neg.chr9_133251979_C_T_ABO-ABO@23624 73624 73628 -0.028224 0.000484 \n", + "\n", + " from_tss \n", + "0 24040 \n", + "1 24071 \n", + "2 23950 \n", + "3 784 \n", + "4 755 \n", + ".. ... \n", + "39 479 \n", + "40 19662 \n", + "41 857 \n", + "42 24184 \n", + "43 23624 \n", + "\n", + "[61 rows x 6 columns]" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "If you wish to identify regulatory motifs, you can obtain seqlets using recursive seqlet calling for the variant-gene pairs with the following command:" + "attribution_diff.peaks" ] }, { "cell_type": "code", - "execution_count": 16, - "id": "fcc21b89", + "execution_count": 17, + "id": "0b882954", "metadata": { "execution": { - "iopub.execute_input": "2025-12-16T22:19:53.876806Z", - "iopub.status.busy": "2025-12-16T22:19:53.876675Z", - "iopub.status.idle": "2025-12-16T22:20:11.703509Z", - "shell.execute_reply": "2025-12-16T22:20:11.702965Z" + "iopub.execute_input": "2025-12-20T01:14:28.355932Z", + "iopub.status.busy": "2025-12-20T01:14:28.355793Z", + "iopub.status.idle": "2025-12-20T01:15:04.649712Z", + "shell.execute_reply": "2025-12-20T01:15:04.649242Z" } }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[34m\u001b[1mwandb\u001b[0m: Downloading large artifact 'metadata:latest', 3122.32MB. 1 files...\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[34m\u001b[1mwandb\u001b[0m: 1 of 1 files downloaded. \n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Done. 00:00:01.8 (1764.0MB/s)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\r", - "Computing recursive seqlet calling...: 0%| | 0/1 [00:00\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
motifpeakstartendstrandscorep-valuematched_seqsite_attr_scoremotif_attr_scorefrom_tss
994ETV7.H13CORE.1.P.Cneg.chr9_133251979_C_T_ABO-ABO@240597404674053-10.4828470.000305CTTCCTC0.0264600.09535124046
859ETV7.H13CORE.1.P.Cneg.chr9_133251979_C_T_ABO-ABO@240527404674053-10.4828470.000305CTTCCTC0.0264600.09535124046
3198ETV7.H13CORE.1.P.Cpos.chr9_133251979_C_T_ABO-ABO@240407404674053-10.4828470.000305CTTCCTC0.0264600.09535124046
3193ETV4.H13CORE.0.P.Bpos.chr9_133251979_C_T_ABO-ABO@240407404474054-11.5787350.000074CACTTCCTCC0.0245990.08191224044
857ETV6.H13CORE.0.PS.Aneg.chr9_133251979_C_T_ABO-ABO@240527404474054-13.1253380.000025CACTTCCTCC0.0245990.08402224044
854ETV4.H13CORE.0.P.Bneg.chr9_133251979_C_T_ABO-ABO@240527404474054-11.5787350.000074CACTTCCTCC0.0245990.08191224044
3196ETV6.H13CORE.0.PS.Apos.chr9_133251979_C_T_ABO-ABO@240407404474054-13.1253380.000025CACTTCCTCC0.0245990.08402224044
3314ZNF683.H13CORE.0.PSG.Apos.chr9_133251979_C_T_ABO-ABO@240407404374054-7.4016500.000276CCACTTCCTCC0.0238180.06781524043
969ZNF683.H13CORE.0.PSG.Aneg.chr9_133251979_C_T_ABO-ABO@240527404374054-7.4016500.000276CCACTTCCTCC0.0238180.06781524043
871KLF17.H13CORE.1.P.Cneg.chr9_133251979_C_T_ABO-ABO@240527404374054-9.2489730.000253CCACTTCCTCC0.0238180.05239324043
3213KLF17.H13CORE.1.P.Cpos.chr9_133251979_C_T_ABO-ABO@240407404374054-9.2489730.000253CCACTTCCTCC0.0238180.05239324043
3185ERG.H13CORE.0.P.Bpos.chr9_133251979_C_T_ABO-ABO@240407404274054-11.8220330.000050CCCACTTCCTCC0.0227570.07349624042
915ZN124.H13CORE.0.P.Cneg.chr9_133251979_C_T_ABO-ABO@240527404274054+7.9469710.000269CCCACTTCCTCC0.0227570.05999324042
3199FLI1.H13CORE.0.PSM.Apos.chr9_133251979_C_T_ABO-ABO@240407404274054-11.9902910.000048CCCACTTCCTCC0.0227570.07341524042
3270ZN124.H13CORE.0.P.Cpos.chr9_133251979_C_T_ABO-ABO@240407404274054+7.9469710.000269CCCACTTCCTCC0.0227570.05999324042
846ERG.H13CORE.0.P.Bneg.chr9_133251979_C_T_ABO-ABO@240527404274054-11.8220330.000050CCCACTTCCTCC0.0227570.07349624042
860FLI1.H13CORE.0.PSM.Aneg.chr9_133251979_C_T_ABO-ABO@240527404274054-11.9902910.000048CCCACTTCCTCC0.0227570.07341524042
844ELK3.H13CORE.0.PSM.Aneg.chr9_133251979_C_T_ABO-ABO@240527404474055-5.0860730.000475CACTTCCTCCC0.0217330.07656324044
845ERF.H13CORE.0.PS.Aneg.chr9_133251979_C_T_ABO-ABO@240527404474055-12.3866360.000032CACTTCCTCCC0.0217330.07229824044
3184ERF.H13CORE.0.PS.Apos.chr9_133251979_C_T_ABO-ABO@240407404474055-12.3866360.000032CACTTCCTCCC0.0217330.07229824044
\n", + "" + ], + "text/plain": [ + " motif peak start \\\n", + "994 ETV7.H13CORE.1.P.C neg.chr9_133251979_C_T_ABO-ABO@24059 74046 \n", + "859 ETV7.H13CORE.1.P.C neg.chr9_133251979_C_T_ABO-ABO@24052 74046 \n", + "3198 ETV7.H13CORE.1.P.C pos.chr9_133251979_C_T_ABO-ABO@24040 74046 \n", + "3193 ETV4.H13CORE.0.P.B pos.chr9_133251979_C_T_ABO-ABO@24040 74044 \n", + "857 ETV6.H13CORE.0.PS.A neg.chr9_133251979_C_T_ABO-ABO@24052 74044 \n", + "854 ETV4.H13CORE.0.P.B neg.chr9_133251979_C_T_ABO-ABO@24052 74044 \n", + "3196 ETV6.H13CORE.0.PS.A pos.chr9_133251979_C_T_ABO-ABO@24040 74044 \n", + "3314 ZNF683.H13CORE.0.PSG.A pos.chr9_133251979_C_T_ABO-ABO@24040 74043 \n", + "969 ZNF683.H13CORE.0.PSG.A neg.chr9_133251979_C_T_ABO-ABO@24052 74043 \n", + "871 KLF17.H13CORE.1.P.C neg.chr9_133251979_C_T_ABO-ABO@24052 74043 \n", + "3213 KLF17.H13CORE.1.P.C pos.chr9_133251979_C_T_ABO-ABO@24040 74043 \n", + "3185 ERG.H13CORE.0.P.B pos.chr9_133251979_C_T_ABO-ABO@24040 74042 \n", + "915 ZN124.H13CORE.0.P.C neg.chr9_133251979_C_T_ABO-ABO@24052 74042 \n", + "3199 FLI1.H13CORE.0.PSM.A pos.chr9_133251979_C_T_ABO-ABO@24040 74042 \n", + "3270 ZN124.H13CORE.0.P.C pos.chr9_133251979_C_T_ABO-ABO@24040 74042 \n", + "846 ERG.H13CORE.0.P.B neg.chr9_133251979_C_T_ABO-ABO@24052 74042 \n", + "860 FLI1.H13CORE.0.PSM.A neg.chr9_133251979_C_T_ABO-ABO@24052 74042 \n", + "844 ELK3.H13CORE.0.PSM.A neg.chr9_133251979_C_T_ABO-ABO@24052 74044 \n", + "845 ERF.H13CORE.0.PS.A neg.chr9_133251979_C_T_ABO-ABO@24052 74044 \n", + "3184 ERF.H13CORE.0.PS.A pos.chr9_133251979_C_T_ABO-ABO@24040 74044 \n", + "\n", + " end strand score p-value matched_seq site_attr_score \\\n", + "994 74053 - 10.482847 0.000305 CTTCCTC 0.026460 \n", + "859 74053 - 10.482847 0.000305 CTTCCTC 0.026460 \n", + "3198 74053 - 10.482847 0.000305 CTTCCTC 0.026460 \n", + "3193 74054 - 11.578735 0.000074 CACTTCCTCC 0.024599 \n", + "857 74054 - 13.125338 0.000025 CACTTCCTCC 0.024599 \n", + "854 74054 - 11.578735 0.000074 CACTTCCTCC 0.024599 \n", + "3196 74054 - 13.125338 0.000025 CACTTCCTCC 0.024599 \n", + "3314 74054 - 7.401650 0.000276 CCACTTCCTCC 0.023818 \n", + "969 74054 - 7.401650 0.000276 CCACTTCCTCC 0.023818 \n", + "871 74054 - 9.248973 0.000253 CCACTTCCTCC 0.023818 \n", + "3213 74054 - 9.248973 0.000253 CCACTTCCTCC 0.023818 \n", + "3185 74054 - 11.822033 0.000050 CCCACTTCCTCC 0.022757 \n", + "915 74054 + 7.946971 0.000269 CCCACTTCCTCC 0.022757 \n", + "3199 74054 - 11.990291 0.000048 CCCACTTCCTCC 0.022757 \n", + "3270 74054 + 7.946971 0.000269 CCCACTTCCTCC 0.022757 \n", + "846 74054 - 11.822033 0.000050 CCCACTTCCTCC 0.022757 \n", + "860 74054 - 11.990291 0.000048 CCCACTTCCTCC 0.022757 \n", + "844 74055 - 5.086073 0.000475 CACTTCCTCCC 0.021733 \n", + "845 74055 - 12.386636 0.000032 CACTTCCTCCC 0.021733 \n", + "3184 74055 - 12.386636 0.000032 CACTTCCTCCC 0.021733 \n", + "\n", + " motif_attr_score from_tss \n", + "994 0.095351 24046 \n", + "859 0.095351 24046 \n", + "3198 0.095351 24046 \n", + "3193 0.081912 24044 \n", + "857 0.084022 24044 \n", + "854 0.081912 24044 \n", + "3196 0.084022 24044 \n", + "3314 0.067815 24043 \n", + "969 0.067815 24043 \n", + "871 0.052393 24043 \n", + "3213 0.052393 24043 \n", + "3185 0.073496 24042 \n", + "915 0.059993 24042 \n", + "3199 0.073415 24042 \n", + "3270 0.059993 24042 \n", + "846 0.073496 24042 \n", + "860 0.073415 24042 \n", + "844 0.076563 24044 \n", + "845 0.072298 24044 \n", + "3184 0.072298 24044 " + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "attribution_diff.scan_motifs().sort_values(\"site_attr_score\", ascending=False).head(20)" + ] + }, + { + "cell_type": "markdown", + "id": "4cb3192b", + "metadata": {}, + "source": [ + "If you wish to identify regulatory motifs, you can obtain seqlets using recursive seqlet calling for the variant-gene pairs with the following command:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "fcc21b89", + "metadata": { + "execution": { + "iopub.execute_input": "2025-12-20T01:15:04.651156Z", + "iopub.status.busy": "2025-12-20T01:15:04.651023Z", + "iopub.status.idle": "2025-12-20T01:15:27.476055Z", + "shell.execute_reply": "2025-12-20T01:15:27.475584Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[34m\u001b[1mwandb\u001b[0m: Downloading large artifact 'metadata:latest', 3122.32MB. 1 files...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[34m\u001b[1mwandb\u001b[0m: 1 of 1 files downloaded. \n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Done. 00:00:06.0 (522.3MB/s)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ "\r", - "Computing recursive seqlet calling...: 100%|██████████| 1/1 [00:06<00:00, 6.56s/it]" + "Computing recursive seqlet calling...: 0%| | 0/1 [00:00\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
chromstartendnamescorestrandattributionallele
0chr9133269430133269437neg.chr9_133251979_C_T_ABO@-65943.68934.-0.107681ref
1chr9133271380133271384neg.chr9_133251979_C_T_ABO@-46443.42949.-0.054561ref
2chr9133271429133271433neg.chr9_133251979_C_T_ABO@-45953.35865.-0.051218ref
3chr9133272960133272968neg.chr9_133251979_C_T_ABO@-30643.54188.-0.108948ref
4chr9133272984133272990neg.chr9_133251979_C_T_ABO@-30403.34346.-0.065654ref
...........................
129chr9133300139133300143neg.chr9_133251979_C_T_ABO@241153.30114.-0.057818alt
130chr9133300192133300198neg.chr9_133251979_C_T_ABO@241683.88320.-0.095046alt
131chr9133322208133322221neg.chr9_133251979_C_T_ABO@461843.52179.-0.247486alt
132chr9133322235133322241neg.chr9_133251979_C_T_ABO@462113.36768.-0.077182alt
133chr9133322241133322246neg.chr9_133251979_C_T_ABO@462173.72926.-0.078502alt
\n", - "

134 rows × 8 columns

\n", - "" - ], - "text/plain": [ - " chrom start end name score \\\n", - "0 chr9 133269430 133269437 neg.chr9_133251979_C_T_ABO@-6594 3.68934 \n", - "1 chr9 133271380 133271384 neg.chr9_133251979_C_T_ABO@-4644 3.42949 \n", - "2 chr9 133271429 133271433 neg.chr9_133251979_C_T_ABO@-4595 3.35865 \n", - "3 chr9 133272960 133272968 neg.chr9_133251979_C_T_ABO@-3064 3.54188 \n", - "4 chr9 133272984 133272990 neg.chr9_133251979_C_T_ABO@-3040 3.34346 \n", - ".. ... ... ... ... ... \n", - "129 chr9 133300139 133300143 neg.chr9_133251979_C_T_ABO@24115 3.30114 \n", - "130 chr9 133300192 133300198 neg.chr9_133251979_C_T_ABO@24168 3.88320 \n", - "131 chr9 133322208 133322221 neg.chr9_133251979_C_T_ABO@46184 3.52179 \n", - "132 chr9 133322235 133322241 neg.chr9_133251979_C_T_ABO@46211 3.36768 \n", - "133 chr9 133322241 133322246 neg.chr9_133251979_C_T_ABO@46217 3.72926 \n", - "\n", - " strand attribution allele \n", - "0 . -0.107681 ref \n", - "1 . -0.054561 ref \n", - "2 . -0.051218 ref \n", - "3 . -0.108948 ref \n", - "4 . -0.065654 ref \n", - ".. ... ... ... \n", - "129 . -0.057818 alt \n", - "130 . -0.095046 alt \n", - "131 . -0.247486 alt \n", - "132 . -0.077182 alt \n", - "133 . -0.078502 alt \n", - "\n", - "[134 rows x 8 columns]" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_peaks" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "d0060440", - "metadata": { - "execution": { - "iopub.execute_input": "2025-12-16T22:20:11.713216Z", - "iopub.status.busy": "2025-12-16T22:20:11.713088Z", - "iopub.status.idle": "2025-12-16T22:20:11.720849Z", - "shell.execute_reply": "2025-12-16T22:20:11.720413Z" + "iopub.execute_input": "2025-12-20T01:15:27.477513Z", + "iopub.status.busy": "2025-12-20T01:15:27.477378Z", + "iopub.status.idle": "2025-12-20T01:15:27.485446Z", + "shell.execute_reply": "2025-12-20T01:15:27.485103Z" } }, "outputs": [ @@ -6408,7 +6808,7 @@ "[9770 rows x 12 columns]" ] }, - "execution_count": 18, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -6416,6 +6816,198 @@ "source": [ "df_motifs" ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "0e65e660", + "metadata": { + "execution": { + "iopub.execute_input": "2025-12-20T01:15:27.486713Z", + "iopub.status.busy": "2025-12-20T01:15:27.486590Z", + "iopub.status.idle": "2025-12-20T01:15:27.505299Z", + "shell.execute_reply": "2025-12-20T01:15:27.504854Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
site_attr_scoresite_attr_score_altsite_attr_score_diff
motifpeak
TFDP1.H13CORE.0.P.Bneg.chr9_133251979_C_T_ABO@7890.0056040.0330310.027427
CTCFL.H13CORE.0.P.Bpos.chr9_133251979_C_T_ABO@755-0.0021480.0208810.023029
SP5.H13CORE.0.P.Bneg.chr9_133251979_C_T_ABO@751-0.0021730.0201720.022344
pos.chr9_133251979_C_T_ABO@755-0.0021730.0201720.022344
SLC2A4RG.H13CORE.0.PSG.Apos.chr9_133251979_C_T_ABO@7990.0135450.0344280.020883
...............
SP5.H13CORE.0.P.Bpos.chr9_133251979_C_T_ABO@7550.016457-0.001859-0.018316
neg.chr9_133251979_C_T_ABO@7510.016457-0.001859-0.018316
CTCFL.H13CORE.0.P.Bpos.chr9_133251979_C_T_ABO@7550.016934-0.001661-0.018594
SLC2A4RG.H13CORE.0.PSG.Apos.chr9_133251979_C_T_ABO@7990.0360240.012775-0.023249
neg.chr9_133251979_C_T_ABO@7890.0360240.012775-0.023249
\n", + "

4504 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " site_attr_score \\\n", + "motif peak \n", + "TFDP1.H13CORE.0.P.B neg.chr9_133251979_C_T_ABO@789 0.005604 \n", + "CTCFL.H13CORE.0.P.B pos.chr9_133251979_C_T_ABO@755 -0.002148 \n", + "SP5.H13CORE.0.P.B neg.chr9_133251979_C_T_ABO@751 -0.002173 \n", + " pos.chr9_133251979_C_T_ABO@755 -0.002173 \n", + "SLC2A4RG.H13CORE.0.PSG.A pos.chr9_133251979_C_T_ABO@799 0.013545 \n", + "... ... \n", + "SP5.H13CORE.0.P.B pos.chr9_133251979_C_T_ABO@755 0.016457 \n", + " neg.chr9_133251979_C_T_ABO@751 0.016457 \n", + "CTCFL.H13CORE.0.P.B pos.chr9_133251979_C_T_ABO@755 0.016934 \n", + "SLC2A4RG.H13CORE.0.PSG.A pos.chr9_133251979_C_T_ABO@799 0.036024 \n", + " neg.chr9_133251979_C_T_ABO@789 0.036024 \n", + "\n", + " site_attr_score_alt \\\n", + "motif peak \n", + "TFDP1.H13CORE.0.P.B neg.chr9_133251979_C_T_ABO@789 0.033031 \n", + "CTCFL.H13CORE.0.P.B pos.chr9_133251979_C_T_ABO@755 0.020881 \n", + "SP5.H13CORE.0.P.B neg.chr9_133251979_C_T_ABO@751 0.020172 \n", + " pos.chr9_133251979_C_T_ABO@755 0.020172 \n", + "SLC2A4RG.H13CORE.0.PSG.A pos.chr9_133251979_C_T_ABO@799 0.034428 \n", + "... ... \n", + "SP5.H13CORE.0.P.B pos.chr9_133251979_C_T_ABO@755 -0.001859 \n", + " neg.chr9_133251979_C_T_ABO@751 -0.001859 \n", + "CTCFL.H13CORE.0.P.B pos.chr9_133251979_C_T_ABO@755 -0.001661 \n", + "SLC2A4RG.H13CORE.0.PSG.A pos.chr9_133251979_C_T_ABO@799 0.012775 \n", + " neg.chr9_133251979_C_T_ABO@789 0.012775 \n", + "\n", + " site_attr_score_diff \n", + "motif peak \n", + "TFDP1.H13CORE.0.P.B neg.chr9_133251979_C_T_ABO@789 0.027427 \n", + "CTCFL.H13CORE.0.P.B pos.chr9_133251979_C_T_ABO@755 0.023029 \n", + "SP5.H13CORE.0.P.B neg.chr9_133251979_C_T_ABO@751 0.022344 \n", + " pos.chr9_133251979_C_T_ABO@755 0.022344 \n", + "SLC2A4RG.H13CORE.0.PSG.A pos.chr9_133251979_C_T_ABO@799 0.020883 \n", + "... ... \n", + "SP5.H13CORE.0.P.B pos.chr9_133251979_C_T_ABO@755 -0.018316 \n", + " neg.chr9_133251979_C_T_ABO@751 -0.018316 \n", + "CTCFL.H13CORE.0.P.B pos.chr9_133251979_C_T_ABO@755 -0.018594 \n", + "SLC2A4RG.H13CORE.0.PSG.A pos.chr9_133251979_C_T_ABO@799 -0.023249 \n", + " neg.chr9_133251979_C_T_ABO@789 -0.023249 \n", + "\n", + "[4504 rows x 3 columns]" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = (\n", + " df_motifs[df_motifs[\"allele\"] == \"ref\"]\n", + " .set_index([\"motif\", \"peak\"])[[\"site_attr_score\"]]\n", + " .join(\n", + " df_motifs[df_motifs[\"allele\"] == \"alt\"].set_index([\"motif\", \"peak\"])[[\"site_attr_score\"]],\n", + " rsuffix=\"_alt\",\n", + " how=\"inner\",\n", + " )\n", + ")\n", + "df[\"site_attr_score_diff\"] = df[\"site_attr_score_alt\"] - df[\"site_attr_score\"]\n", + "df.sort_values(\"site_attr_score_diff\", ascending=False)" + ] } ], "metadata": { diff --git a/src/decima/core/attribution.py b/src/decima/core/attribution.py index 4cfa413..4dee64e 100644 --- a/src/decima/core/attribution.py +++ b/src/decima/core/attribution.py @@ -555,7 +555,7 @@ def __sub__(self, other): return Attribution( inputs=self.inputs, attrs=self.attrs - other.attrs, - gene=f"{self.gene} - {other.gene}", + gene=f"{self.gene}-{other.gene}", chrom=self.chrom, start=self.start, end=self.end, @@ -944,11 +944,6 @@ def _recursive_seqlet_calling( pattern_type=pattern_type, **kwargs, ) - # if isinstance(attribution, list) or isinstance(attribution, tuple): - # df_peaks = pd.concat(df_peaks for df_peaks in attribution).reset_index(drop=True) - # df_motifs = pd.concat(df_motifs for df_motifs in attribution).reset_index(drop=True) - # return df_peaks, df_motifs - # else: df_peaks = attribution.peaks_to_bed() df_motifs = attribution.scan_motifs(motifs=meme_motif_db) return df_peaks, df_motifs @@ -993,7 +988,7 @@ def recursive_seqlet_calling( delayed(AttributionResult._recursive_seqlet_calling)( self.attribution_h5, self._idx[gene], - "_".join(gene), + gene if isinstance(gene, str) else "_".join(gene), self.tss_distance, chrom, start, diff --git a/tests/test_interpret_attribution.py b/tests/test_interpret_attribution.py index 3d7d84a..307a1d3 100644 --- a/tests/test_interpret_attribution.py +++ b/tests/test_interpret_attribution.py @@ -421,24 +421,10 @@ def test_Attribution_sub(attributions): diff = other - attributions assert (diff.attrs == attributions.attrs).all() - assert diff.gene == "OTHER_GENE - TEST2" + assert diff.gene == "OTHER_GENE-TEST2" assert diff.chrom == attributions.chrom assert diff.start == attributions.start - # Test failure: different inputs - other_diff_inputs = Attribution( - gene="OTHER_GENE", - inputs=torch.zeros_like(attributions.inputs), - attrs=other_attrs, - chrom=attributions.chrom, - start=attributions.start, - end=attributions.end, - strand=attributions.strand, - threshold=attributions.threshold, - ) - with pytest.raises(AssertionError, match="Sequences of attribution objects must be the same"): - _ = other_diff_inputs - attributions - # Test failure: different metadata other_diff_chrom = Attribution( gene="OTHER_GENE", @@ -453,23 +439,6 @@ def test_Attribution_sub(attributions): with pytest.raises(AssertionError, match="Chromosomes must be the same"): _ = other_diff_chrom - attributions - # Test warning: different parameters - other_diff_thresh = Attribution( - gene="OTHER_GENE", - inputs=attributions.inputs, - attrs=other_attrs, - chrom=attributions.chrom, - start=attributions.start, - end=attributions.end, - strand=attributions.strand, - threshold=attributions.threshold * 2, - ) - with pytest.warns(UserWarning, match="`threshold`.*are not same"): - res = other_diff_thresh - attributions - # Should use the left operand's threshold - assert res.threshold == other_diff_thresh.threshold - - def test_Attribution__sub__(attributions): other = Attribution( gene="OTHER", @@ -484,4 +453,4 @@ def test_Attribution__sub__(attributions): diff = other - attributions assert np.allclose(diff.attrs, 1.0) - assert diff.gene == "OTHER - TEST2" + assert diff.gene == "OTHER-TEST2" diff --git a/tests/test_vep.py b/tests/test_vep.py index d469637..9109fb1 100644 --- a/tests/test_vep.py +++ b/tests/test_vep.py @@ -261,7 +261,7 @@ def test_predict_variant_effect_save(df_variant, tmp_path): def test_predict_variant_effect_vcf(tmp_path): output_file = tmp_path / "test_predictions.parquet" - warnings_file = tmp_path / "test_predictions.parquet.warnings.log" + warnings_file = tmp_path / "test_predictions.warnings.log" predict_variant_effect( "tests/data/test.vcf", From bfe97f2f028fd82d47d60f0c32c20322d4543f39 Mon Sep 17 00:00:00 2001 From: Muhammed Hasan Celik Date: Thu, 25 Dec 2025 05:19:59 +0000 Subject: [PATCH 4/4] doc string fix --- src/decima/cli/vep.py | 2 +- src/decima/cli/vep_attribution.py | 2 +- src/decima/core/attribution.py | 3 +-- src/decima/vep/vep.py | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/decima/cli/vep.py b/src/decima/cli/vep.py index 7fc290b..fb00e7d 100644 --- a/src/decima/cli/vep.py +++ b/src/decima/cli/vep.py @@ -32,7 +32,7 @@ "-v", "--variants", type=click.Path(exists=True), - help="Path to the variant file .vcf file. VCF file need to be normalized. Try normalizing th vcf file incase of an error. `bcftools norm -f ref.fasta input.vcf.gz -o output.vcf.gz`", + help="Path to the variant .vcf file. VCF file needs to be normalized. Try normalizing th vcf file in case of an error. `bcftools norm -f ref.fasta input.vcf.gz -o output.vcf.gz`", ) @click.option("-o", "--output_pq", type=click.Path(), help="Path to the output parquet file.") @click.option("--tasks", type=str, default=None, help="Tasks to predict. If not provided, all tasks will be predicted.") diff --git a/src/decima/cli/vep_attribution.py b/src/decima/cli/vep_attribution.py index 0024b64..8dbe849 100644 --- a/src/decima/cli/vep_attribution.py +++ b/src/decima/cli/vep_attribution.py @@ -13,7 +13,7 @@ "-v", "--variants", type=click.Path(exists=True), - help="Path to the variant file .vcf file. VCF file need to be normalized. Try normalizing th vcf file incase of an error. `bcftools norm -f ref.fasta input.vcf.gz -o output.vcf.gz`", + help="Path to the variant .vcf file. VCF file needs to be normalized. Try normalizing the vcf file in case of an error. `bcftools norm -f ref.fasta input.vcf.gz -o output.vcf.gz`", ) @click.option("-o", "--output_prefix", type=click.Path(), help="Path to the output prefix.") @click.option("--tasks", type=str, default=None, help="Tasks to predict. If not provided, all tasks will be predicted.") diff --git a/src/decima/core/attribution.py b/src/decima/core/attribution.py index 4dee64e..54b168a 100644 --- a/src/decima/core/attribution.py +++ b/src/decima/core/attribution.py @@ -548,7 +548,7 @@ def __sub__(self, other): or (self.pattern_type != other.pattern_type) ): warnings.warn( - "`threshold`, `min_seqlet_len`, `max_seqlet_len`, `additional_flanks`, and `pattern_type` are not same overriding " + "`threshold`, `min_seqlet_len`, `max_seqlet_len`, `additional_flanks`, and `pattern_type` are not the same, overriding " "them with the values of the first attribution object." ) @@ -623,7 +623,6 @@ def open(self): self.model_name.append(h5.attrs["model_name"]) else: self.h5 = h5py.File(str(self.attribution_h5), "r") - self.genes = self.h5["genes"][:].astype("U100") self.model_name = self.h5.attrs["model_name"] self.genome = self.h5.attrs["genome"] self.genes = self.h5["genes"][:].astype("U100") diff --git a/src/decima/vep/vep.py b/src/decima/vep/vep.py index 91d82ca..fa6cddf 100644 --- a/src/decima/vep/vep.py +++ b/src/decima/vep/vep.py @@ -262,7 +262,7 @@ def _log_vep_warnings(warning_counter: Counter, num_variants: int, genome_path: if warning == WarningType.ALLELE_MISMATCH_WITH_REFERENCE_GENOME.value: logger.warning( f"{warning}: {count} alleles out of {num_variants} predictions mismatched with the genome file {genome_path}." - "If this is not expected, please check if you are using the correct genome version." + " If this is not expected, please check if you are using the correct genome version." ) elif warning == "no_overlap_found_for_chunk": logger.warning(f"{warning}: {count} chunks with no overlap found with genes.")