Source code for gunz_cm.matrix

"""
Defines the ContactMatrix data structure.
"""

# =============================================================================
# METADATA
# =============================================================================
__author__ = "Yeremia Gunawan Adhisantoso"
__email__ = "adhisant@tnt.uni-hannover.de"
__license__ = "Clear BSD"

from dataclasses import dataclass, field
import typing as t
import warnings
import pandas as pd
from scipy import sparse as sp


[docs]class ContactMatrix: """ A data container for a contact matrix and its associated metadata. This class acts as a simple, data-oriented container to group a contact matrix (as a pandas DataFrame or a SciPy sparse matrix) with important metadata like its genomic coordinates and resolution. It supports lazy loading of data via a loader function. Attributes ---------- chromosome1 : str The name of the first chromosome. bin_size_bp : int The bin size (resolution) of the contact matrix in base pairs. loader_func : callable A function or callable that returns the raw data when called. loader_kwargs : dict Keyword arguments to pass to the loader function. chromosome2 : str, optional The name of the second chromosome, if different from the first (for inter-chromosomal matrices). Defaults to chromosome1. metadata : dict A dictionary to hold any other relevant metadata. n_pairs : int Total number of valid (filtered) read pairs used to build the matrix. Default is 0. coverage_ratio : float Ratio of observed coverage versus canonical depth expectation. Default is 1.0. effective_resolution_bp : int | None The effective (usable) resolution computed from the data. Computed lazily via :meth:`compute_effective_resolution`. Default is None. fragment_resolution_bp : int | None Physical limit imposed by fragment size (e.g., from restriction enzyme). Optional; None if unknown or not applicable. protocol_type : str | None Experimental protocol type (e.g., "Hi-C", "Micro-C", "HiChIP"). Optional; None if unknown. Examples -------- >>> from gunz_cm.matrix import ContactMatrix >>> import numpy as np >>> def dummy_loader(n): return np.eye(n) >>> cm = ContactMatrix("chr1", 10000, loader_func=dummy_loader, loader_kwargs={"n": 5}) >>> print(cm.data.shape) (5, 5) """ chromosome1: str bin_size_bp: int loader_func: t.Callable[..., t.Any] = field(repr=False) loader_kwargs: dict[str, t.Any] = field(default_factory=dict, repr=False) chromosome2: str | None = None metadata: dict[str, t.Any] = field(default_factory=dict) n_pairs: int = 0 coverage_ratio: float = 1.0 effective_resolution_bp: int | None = None fragment_resolution_bp: int | None = None protocol_type: str | None = None _data: t.Any = field(init=False, repr=False, default=None) # ----------------------------------------------------------------------- # __init__: manual to support deprecated 'resolution' kwarg in v2.10.0 # ----------------------------------------------------------------------- def __init__( self, chromosome1: str, bin_size_bp: int, loader_func: t.Callable[..., t.Any], *, loader_kwargs: dict[str, t.Any] | None = None, chromosome2: str | None = None, metadata: dict[str, t.Any] | None = None, n_pairs: int = 0, coverage_ratio: float = 1.0, effective_resolution_bp: int | None = None, fragment_resolution_bp: int | None = None, protocol_type: str | None = None, resolution: int | None = None, # deprecated kwarg, v2.10.0 only ) -> None: # Handle deprecated 'resolution' kwarg if resolution is not None: if bin_size_bp is None: warnings.warn( "The 'resolution' parameter is deprecated and will be removed in v2.11.0. " "Use 'bin_size_bp' instead.", DeprecationWarning, stacklevel=2, ) bin_size_bp = resolution elif resolution != bin_size_bp: raise TypeError( f"Conflicting values: 'resolution'={resolution} and " f"'bin_size_bp'={bin_size_bp}. Use 'bin_size_bp' only; " "'resolution' is deprecated in v2.10.0 and will be removed in v2.11.0." ) # If both passed the same value, bin_size_bp wins (no warning, no error) # Assign fields self.chromosome1 = chromosome1 self.bin_size_bp = bin_size_bp self.loader_func = loader_func self.loader_kwargs = loader_kwargs if loader_kwargs is not None else {} self.chromosome2 = chromosome2 self.metadata = metadata if metadata is not None else {} self.n_pairs = n_pairs self.coverage_ratio = coverage_ratio self.effective_resolution_bp = effective_resolution_bp self.fragment_resolution_bp = fragment_resolution_bp self.protocol_type = protocol_type # Lazy-load cache; class-level `_data = field(...)` only exists to # document the attribute for readers, so we must set the actual cache # value on the instance here (otherwise `cm.data` returns the Field # descriptor instead of triggering lazy load). self._data = None # Post-initialisation: derive chromosome2 default from chromosome1 self._post_init() def _post_init(self) -> None: """Set defaults that depend on other field values.""" if self.chromosome2 is None: self.chromosome2 = self.chromosome1 # ----------------------------------------------------------------------- # Properties # ----------------------------------------------------------------------- @property def data(self) -> t.Any: """ The raw contact matrix data, loaded lazily. Returns ------- object The raw data returned by the loader function (usually a DataFrame or Sparse Matrix). """ if self._data is None: self._data = self.loader_func(**self.loader_kwargs) return self._data
# ----------------------------------------------------------------------- # Conversion methods (DOP Phase 2, v2.19.0 → v2.25.0) # ----------------------------------------------------------------------- # v2.19.0: introduced 5 method shims (as_coo, as_csr, as_csc, # as_dataframe, compute_effective_resolution) as DeprecationWarning # wrappers delegating to free functions in gunz_cm.structs.cm_views. # v2.25.0 (Phase 8): REMOVED the shim methods. Callers must use the # free functions directly: # # from gunz_cm.structs.cm_views import ( # cm_to_coo, cm_to_csr, cm_to_csc, cm_to_dataframe, # cm_compute_effective_resolution, # ) # # See tests/structs/test_cm_views.py for the canonical API.