Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ repos:

- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.15.6
rev: v0.16.1
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
Expand Down
1 change: 0 additions & 1 deletion perf/genomicranges.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"source": [
"import biobear as bb\n",
"\n",
"\n",
"session = bb.new_session()\n",
"\n",
"bed = session.read_bed_file(\"consensus_peaks_bicnn.bed\", bb.BEDReadOptions(n_fields=4))"
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
if __name__ == "__main__":
try:
setup(use_scm_version={"version_scheme": "no-guess-dev"})
except: # noqa
except:
print(
"\n\nAn error occurred while building the project, "
"please ensure you have the most updated version of setuptools, "
Expand Down
101 changes: 50 additions & 51 deletions src/genomicranges/GenomicRanges.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from __future__ import annotations

from collections import defaultdict
from collections.abc import Sequence
from multiprocessing import Pool, cpu_count
from typing import Any, Dict, List, Literal, Optional, Sequence, Tuple, Union
from typing import Any, Literal
from warnings import warn

import biocutils as ut
Expand Down Expand Up @@ -129,11 +130,11 @@ def __init__(
self,
seqnames: Sequence[str],
ranges: IRanges,
strand: Optional[Union[Sequence[str], Sequence[int], np.ndarray]] = None,
names: Optional[Union[ut.Names, Sequence[str]]] = None,
mcols: Optional[BiocFrame] = None,
seqinfo: Optional[SeqInfo] = None,
metadata: Optional[Union[Dict[str, Any], ut.NamedList]] = None,
strand: Sequence[str] | Sequence[int] | np.ndarray | None = None,
names: ut.Names | Sequence[str] | None = None,
mcols: BiocFrame | None = None,
seqinfo: SeqInfo | None = None,
metadata: dict[str, Any] | ut.NamedList | None = None,
_validate: bool = True,
):
"""Initialize a ``GenomicRanges`` object.
Expand Down Expand Up @@ -437,7 +438,7 @@ def __str__(self) -> str:
######>> seqnames <<######
##########################

def get_seqnames(self, as_type: Literal["factor", "list"] = "list") -> Union[ut.Factor, List[str]]:
def get_seqnames(self, as_type: Literal["factor", "list"] = "list") -> ut.Factor | list[str]:
"""Access sequence names.

Args:
Expand All @@ -459,7 +460,7 @@ def get_seqnames(self, as_type: Literal["factor", "list"] = "list") -> Union[ut.
else:
raise ValueError("Argument 'as_type' must be 'factor' or 'list'.")

def set_seqnames(self, seqnames: Union[Sequence[str], np.ndarray], in_place: bool = False) -> GenomicRanges:
def set_seqnames(self, seqnames: Sequence[str] | np.ndarray, in_place: bool = False) -> GenomicRanges:
"""Set new sequence names.

Args:
Expand All @@ -486,12 +487,12 @@ def set_seqnames(self, seqnames: Union[Sequence[str], np.ndarray], in_place: boo
return output

@property
def seqnames(self) -> Union[Union[np.ndarray, List[str]], np.ndarray]:
def seqnames(self) -> np.ndarray | list[str]:
"""Alias for :py:meth:`~get_seqnames`."""
return self.get_seqnames()

@seqnames.setter
def seqnames(self, seqnames: Union[Sequence[str], np.ndarray]):
def seqnames(self, seqnames: Sequence[str] | np.ndarray):
"""Alias for :py:meth:`~set_seqnames` with ``in_place = True``.

As this mutates the original object, a warning is raised.
Expand Down Expand Up @@ -556,9 +557,7 @@ def ranges(self, ranges: IRanges):
######>> strand <<######
########################

def get_strand(
self, as_type: Literal["numpy", "factor", "list"] = "numpy"
) -> Union[Tuple[np.ndarray, dict], List[str]]:
def get_strand(self, as_type: Literal["numpy", "factor", "list"] = "numpy") -> tuple[np.ndarray, dict] | list[str]:
"""Access strand information.

Args:
Expand Down Expand Up @@ -598,7 +597,7 @@ def get_strand(
raise ValueError("Argument 'as_type' must be 'factor' or 'list'.")

def set_strand(
self, strand: Optional[Union[Sequence[str], Sequence[int], np.ndarray]], in_place: bool = False
self, strand: Sequence[str] | Sequence[int] | np.ndarray | None, in_place: bool = False
) -> GenomicRanges:
"""Set new strand information.

Expand Down Expand Up @@ -635,7 +634,7 @@ def strand(self) -> np.ndarray:
return self.get_strand()

@strand.setter
def strand(self, strand: Optional[Union[Sequence[str], Sequence[int], np.ndarray]]):
def strand(self, strand: Sequence[str] | Sequence[int] | np.ndarray | None):
"""Alias for :py:meth:`~set_strand` with ``in_place = True``.

As this mutates the original object, a warning is raised.
Expand All @@ -657,7 +656,7 @@ def get_names(self) -> ut.Names:
"""
return self._names

def set_names(self, names: Optional[Union[ut.Names, Sequence[str]]], in_place: bool = False) -> GenomicRanges:
def set_names(self, names: ut.Names | Sequence[str] | None, in_place: bool = False) -> GenomicRanges:
"""Set new names.

Args:
Expand Down Expand Up @@ -688,7 +687,7 @@ def names(self) -> ut.Names:
return self.get_names()

@names.setter
def names(self, names: Optional[Union[ut.Names, Sequence[str]]]):
def names(self, names: ut.Names | Sequence[str] | None):
"""Alias for :py:meth:`~set_names` with ``in_place = True``.

As this mutates the original object, a warning is raised.
Expand All @@ -710,7 +709,7 @@ def get_mcols(self) -> BiocFrame:
"""
return self._mcols

def set_mcols(self, mcols: Optional[BiocFrame], in_place: bool = False) -> GenomicRanges:
def set_mcols(self, mcols: BiocFrame | None, in_place: bool = False) -> GenomicRanges:
"""Set new range metadata.

Args:
Expand Down Expand Up @@ -743,7 +742,7 @@ def mcols(self) -> BiocFrame:
return self.get_mcols()

@mcols.setter
def mcols(self, mcols: Optional[BiocFrame]):
def mcols(self, mcols: BiocFrame | None):
"""Alias for :py:meth:`~set_mcols` with ``in_place = True``.

As this mutates the original object, a warning is raised.
Expand All @@ -765,7 +764,7 @@ def get_seqinfo(self) -> SeqInfo:
"""
return self._seqinfo

def set_seqinfo(self, seqinfo: Optional[SeqInfo], in_place: bool = False) -> GenomicRanges:
def set_seqinfo(self, seqinfo: SeqInfo | None, in_place: bool = False) -> GenomicRanges:
"""Set new sequence information.

Args:
Expand Down Expand Up @@ -806,7 +805,7 @@ def seqinfo(self) -> np.ndarray:
@seqinfo.setter
def seqinfo(
self,
seqinfo: Optional[SeqInfo],
seqinfo: SeqInfo | None,
):
"""Alias for :py:meth:`~set_seqinfo` with ``in_place = True``.

Expand Down Expand Up @@ -877,7 +876,7 @@ def get_seqlengths(self) -> np.ndarray:
######>> Slicers <<######
#########################

def get_subset(self, subset: Union[str, int, bool, Sequence]) -> GenomicRanges:
def get_subset(self, subset: str | int | bool | Sequence) -> GenomicRanges:
"""Subset ``GenomicRanges``, based on their indices or names.

Args:
Expand Down Expand Up @@ -910,13 +909,13 @@ def get_subset(self, subset: Union[str, int, bool, Sequence]) -> GenomicRanges:
metadata=self._metadata,
)

def __getitem__(self, subset: Union[str, int, bool, Sequence]) -> GenomicRanges:
def __getitem__(self, subset: str | int | bool | Sequence) -> GenomicRanges:
"""Alias to :py:attr:`~get_subset`."""
return self.get_subset(subset)

def set_subset(
self,
args: Union[Sequence, int, str, bool, slice, range],
args: Sequence | int | str | bool | slice | range,
value: GenomicRanges,
in_place: bool = False,
) -> GenomicRanges:
Expand Down Expand Up @@ -975,7 +974,7 @@ def set_subset(

def __setitem__(
self,
args: Union[Sequence, int, str, bool, slice, range],
args: Sequence | int | str | bool | slice | range,
value: GenomicRanges,
) -> GenomicRanges:
"""Alias to :py:attr:`~set_subset`.
Expand Down Expand Up @@ -1158,7 +1157,7 @@ def from_polars(cls, input) -> GenomicRanges:
def flank(
self,
width: int,
start: Union[bool, np.ndarray, List[bool]] = True,
start: bool | np.ndarray | list[bool] = True,
both: bool = False,
ignore_strand: bool = False,
in_place: bool = False,
Expand Down Expand Up @@ -1238,8 +1237,8 @@ def flank(

def resize(
self,
width: Union[int, List[int], np.ndarray],
fix: Union[Literal["start", "end", "center"], List[Literal["start", "end", "center"]]] = "start",
width: int | list[int] | np.ndarray,
fix: Literal["start", "end", "center"] | list[Literal["start", "end", "center"]] = "start",
ignore_strand: bool = False,
in_place: bool = False,
) -> GenomicRanges:
Expand Down Expand Up @@ -1299,7 +1298,7 @@ def resize(
output._ranges = self._ranges.resize(width=width, fix=fix_arr)
return output

def shift(self, shift: Union[int, List[int], np.ndarray] = 0, in_place: bool = False) -> GenomicRanges:
def shift(self, shift: int | list[int] | np.ndarray = 0, in_place: bool = False) -> GenomicRanges:
"""Shift all intervals.

Args:
Expand Down Expand Up @@ -1383,8 +1382,8 @@ def terminators(self, upstream: int = 2000, downstream: int = 200, in_place: boo

def restrict(
self,
start: Optional[Union[int, Dict[str, int], np.ndarray]] = None,
end: Optional[Union[int, Dict[str, int], np.ndarray]] = None,
start: int | dict[str, int] | np.ndarray | None = None,
end: int | dict[str, int] | np.ndarray | None = None,
keep_all_ranges: bool = False,
) -> GenomicRanges:
"""Restrict ranges to a given start and end positions.
Expand Down Expand Up @@ -1565,9 +1564,9 @@ def trim(self, in_place: bool = False) -> GenomicRanges:

def narrow(
self,
start: Optional[Union[int, List[int], np.ndarray]] = None,
width: Optional[Union[int, List[int], np.ndarray]] = None,
end: Optional[Union[int, List[int], np.ndarray]] = None,
start: int | list[int] | np.ndarray | None = None,
width: int | list[int] | np.ndarray | None = None,
end: int | list[int] | np.ndarray | None = None,
in_place: bool = False,
) -> GenomicRanges:
"""Narrow genomic positions by provided ``start``, ``width`` and ``end`` parameters.
Expand Down Expand Up @@ -1742,7 +1741,7 @@ def range(self, with_reverse_map: bool = False, ignore_strand: bool = False) ->
def gaps(
self,
start: int = 1,
end: Optional[Union[int, Dict[str, int]]] = None,
end: int | dict[str, int] | None = None,
ignore_strand: bool = False,
) -> GenomicRanges:
"""Identify complemented ranges for each distinct (seqname, strand) pair.
Expand Down Expand Up @@ -1899,8 +1898,8 @@ def disjoint_bins(self, ignore_strand: bool = False) -> np.ndarray:
return binned_results

def coverage(
self, shift: int = 0, width: Optional[int] = None, weight: int = 1, ignore_strand: bool = True
) -> Dict[str, np.ndarray]:
self, shift: int = 0, width: int | None = None, weight: int = 1, ignore_strand: bool = True
) -> dict[str, np.ndarray]:
"""
Calculate coverage for each chromosome. For each position, this method
counts the number of ranges that cover it.
Expand Down Expand Up @@ -2151,7 +2150,7 @@ def extract_groups_by_seqnames(self):
groups.append(idx)
return groups

def _get_query_common_groups(self, query: GenomicRanges) -> Tuple[np.ndarray, np.ndarray]:
def _get_query_common_groups(self, query: GenomicRanges) -> tuple[np.ndarray, np.ndarray]:
# smerged = merge_SeqInfo([self._seqinfo, query._seqinfo])
common_seqlevels = set(self._seqinfo._seqnames).intersection(query._seqinfo._seqnames)
q_group_idx = [self._seqinfo._seqnames.index(i) for i in common_seqlevels]
Expand Down Expand Up @@ -2415,7 +2414,7 @@ def nearest(
ignore_strand: bool = False,
num_threads: int = 1,
adjacent_equals_overlap: bool = True,
) -> Union[np.ndarray, BiocFrame]:
) -> np.ndarray | BiocFrame:
"""Search nearest positions both upstream and downstream that overlap with each range in ``query``.

Args:
Expand Down Expand Up @@ -2505,7 +2504,7 @@ def precede(
select: Literal["all", "first"] = "first",
ignore_strand: bool = False,
num_threads: int = 1,
) -> Union[np.ndarray, BiocFrame]:
) -> np.ndarray | BiocFrame:
"""Search nearest positions only downstream that overlap with each range in ``query``.

Args:
Expand Down Expand Up @@ -2585,7 +2584,7 @@ def follow(
select: Literal["all", "last"] = "last",
ignore_strand: bool = False,
num_threads: int = 1,
) -> Union[np.ndarray, BiocFrame]:
) -> np.ndarray | BiocFrame:
"""Search nearest positions only upstream that overlap with each range in ``query``.

Args:
Expand Down Expand Up @@ -2656,7 +2655,7 @@ def follow(
else:
return BiocFrame({"query_hits": final_qhits, "self_hits": final_shits})

def distance(self, query: Union[GenomicRanges, IRanges]) -> np.ndarray:
def distance(self, query: GenomicRanges | IRanges) -> np.ndarray:
"""Compute the pair-wise distance with intervals in query.

Args:
Expand Down Expand Up @@ -2723,7 +2722,7 @@ def match(self, query: GenomicRanges, ignore_strand: bool = False) -> np.ndarray

return result

def _get_ranges_as_list(self) -> List[Tuple[int, int, int]]:
def _get_ranges_as_list(self) -> list[tuple[int, int, int]]:
"""Internal method to get ranges as a list of tuples.

Returns:
Expand Down Expand Up @@ -2786,7 +2785,7 @@ def sort(self, decreasing: bool = False, in_place: bool = False) -> GenomicRange
output = self._define_output(in_place)
return output[list(order)]

def rank(self) -> List[int]:
def rank(self) -> list[int]:
"""Get rank of the ``GenomicRanges`` object.

For each range identifies its position is a sorted order.
Expand Down Expand Up @@ -2846,7 +2845,7 @@ def invert_strand(self, in_place: bool = False) -> GenomicRanges:
######>> window methods <<######
################################

def tile(self, n: Optional[int] = None, width: Optional[int] = None) -> List[GenomicRanges]:
def tile(self, n: int | None = None, width: int | None = None) -> list[GenomicRanges]:
"""Split each interval by ``n`` (number of sub intervals) or ``width`` (intervals with equal width).

Note: Either ``n`` or ``width`` must be provided but not both.
Expand Down Expand Up @@ -2891,7 +2890,7 @@ def tile(self, n: Optional[int] = None, width: Optional[int] = None) -> List[Gen

return result

def sliding_windows(self, width: int, step: int = 1) -> List[GenomicRanges]:
def sliding_windows(self, width: int, step: int = 1) -> list[GenomicRanges]:
"""Slide along each range by ``width`` (intervals with equal ``width``) and ``step``.

Also, checkout :py:func:`~genomicranges.io.tiling.tile_genome` for splitting
Expand Down Expand Up @@ -2932,9 +2931,9 @@ def sliding_windows(self, width: int, step: int = 1) -> List[GenomicRanges]:
@classmethod
def tile_genome(
cls,
seqlengths: Dict[str, int],
ntile: Optional[int] = None,
tilewidth: Optional[int] = None,
seqlengths: dict[str, int],
ntile: int | None = None,
tilewidth: int | None = None,
cut_last_tile_in_chrom: bool = False,
) -> GenomicRanges:
"""Tile genome into approximately equal-sized regions.
Expand Down Expand Up @@ -3123,7 +3122,7 @@ def binned_average(
######>> split <<######
#######################

def split(self, groups: list) -> "CompressedGenomicRangesList":
def split(self, groups: list) -> CompressedGenomicRangesList:
"""Split the `GenomicRanges` object into a :py:class:`~genomicranges.grangeslist.CompressedGenomicRangesList`.

Args:
Expand Down Expand Up @@ -3173,7 +3172,7 @@ def empty(cls):

def subtract(
self, other: GenomicRanges, min_overlap: int = 1, ignore_strand: bool = False
) -> "CompressedGenomicRangesList":
) -> CompressedGenomicRangesList:
"""Subtract searches for features in ``x`` that overlap ``self`` by at least the number of base pairs given by
``min_overlap``.

Expand Down
Loading
Loading