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.