Source code for pytximport.utils._filter_by_biotype

from logging import log
from pathlib import Path
from typing import List, Optional, Union

import anndata as ad
import numpy as np
import pandas as pd
import xarray as xr


[docs] def filter_by_biotype( transcript_data: Union[xr.Dataset, ad.AnnData], transcript_gene_map: Optional[Union[pd.DataFrame, Path, str]] = None, biotype_filter: Optional[List[str]] = None, id_column: str = "transcript_id", recalculate_abundance: bool = False, ) -> Union[xr.Dataset, ad.AnnData]: """Filter the transcripts by biotype. This function filters the transcripts by biotype. The biotype is assumed to be present in the transcript_id separated by a bar. The biotype is checked against the biotype_filter and the transcripts that match the biotype are kept. This function is provided mainly for internal use if `biotype_filter` is provided to the main function. Args: transcript_data (Union[xr.Dataset, ad.AnnData]): The expression data. transcript_gene_map (Union[pd.DataFrame, Path, str], optional): The mapping from transcript to gene with the `gene_biotype` column. If None, the biotype is assumed to be present in the id_column. Defaults to None. biotype_filter (List[str]): The biotypes to keep. Defaults to None. id_column (str, optional): The column name for the transcript/gene ID. Defaults to "transcript_id". recalculate_abundance (bool, optional): Whether to recalculate the abundance after filtering. This converts the abundance to TPM of the remaining transcripts but has implications for how the abundance can be used statistically. Defaults to False. Returns: Union[xr.Dataset, ad.AnnData]: The expression data filtered by biotype. """ if isinstance(transcript_data, (str)): transcript_data = Path(transcript_data) if isinstance(transcript_data, ad.AnnData): transcript_ids = transcript_data.var_names.to_list() elif isinstance(transcript_data, xr.Dataset): transcript_ids = transcript_data.coords[id_column].values else: raise ValueError("The data type is not supported.") if biotype_filter is None: log(25, "No biotype filter provided. Skipping the biotype filtering.") return transcript_data if isinstance(transcript_gene_map, (str, Path)): transcript_gene_map = pd.read_csv(transcript_gene_map, header=0, index_col=None) if transcript_gene_map is None: # We can filter the transcripts by biotype if the biotype is present in the transcript_id since some # quantification tools include the biotype in the transcript_id # This only works if the transcript_id contains the biotype as one of the bar-separated fields and is not the # best way to filter the transcripts by biotype assert any("|" in transcript_id for transcript_id in transcript_ids), ( "The transcript_id column does not contain the biotype. Please use the `pytximport.utils.filter_by_biotype`" " function with the `transcript_gene_map` argument instead. This function can be called after the" " `tximport` function using the resulting AnnData object or xarray Dataset." ) transcript_id_fields = [transcript_id.split("|") for transcript_id in transcript_ids] transcript_keep_boolean = np.zeros_like(transcript_ids, dtype=bool) for biotype in biotype_filter: transcript_keep_boolean = np.logical_or( transcript_keep_boolean, [(biotype in transcript_id_field) for transcript_id_field in transcript_id_fields], ) else: assert id_column in transcript_gene_map.columns, f"The {id_column} column is not present in the mapping file." assert "gene_biotype" in transcript_gene_map.columns, ( "The gene_biotype column is not present in the mapping file." ) transcript_id_fields = [transcript_id.split("|") for transcript_id in transcript_gene_map[id_column].values] # Filter the transcript-to-gene mapping by the biotype transcripts_to_keep = transcript_gene_map[transcript_gene_map["gene_biotype"].isin(biotype_filter)][ [id_column] ].values # Filter the transcript data by the transcripts to keep transcript_keep_boolean = np.isin(transcript_ids, transcripts_to_keep) # Check that at least one transcript is protein-coding assert any(transcript_keep_boolean), "No transcript/gene with the desired biotype are present in the data." if isinstance(transcript_data, xr.Dataset): # Calculate the total abundance before filtering total_abundance = transcript_data["abundance"].sum(axis=0) transcript_data = transcript_data.isel( indexers={id_column: transcript_keep_boolean}, drop=True, ) elif isinstance(transcript_data, ad.AnnData): # Calculate the total abundance before filtering total_abundance = transcript_data.obsm["abundance"].sum(axis=1) transcript_data = transcript_data[:, transcript_keep_boolean] log( 25, f"Removed {len(transcript_keep_boolean) - sum(transcript_keep_boolean)} transcripts with other biotypes.", ) # Recalculate the abundance for each sample if recalculate_abundance: log(25, "Recalculating the abundance after filtering by biotype.") if isinstance(transcript_data, ad.AnnData): new_abundance = transcript_data.obsm["abundance"].sum(axis=1) ratio = total_abundance / new_abundance transcript_data.obsm["abundance"] = (transcript_data.obsm["abundance"].T * ratio).T elif isinstance(transcript_data, xr.Dataset): new_abundance = transcript_data["abundance"].sum(axis=0) ratio = total_abundance / new_abundance transcript_data["abundance"] = (transcript_data["abundance"].T * ratio).T return transcript_data