Skip to content

Commit

Permalink
Implement range based methods for GenomicRangesList (#110)
Browse files Browse the repository at this point in the history
- Expose the `generic_accessor` method used internally by `GenomicRangesList` to call functions from the underlying GenomicRanges for each element.
- Add class method to initialize `GenomicRangesList` from dictionary.
- Update tests
  • Loading branch information
jkanche authored Jul 14, 2024
1 parent dd21da1 commit 4e50ea0
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 18 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Version 0.4.26

- Expose the `generic_accessor` method used internally by `GenomicRangesList` to call functions from the underlying GenomicRanges for each element.
- Add class method to initialize `GenomicRangesList` from dictionary.
- Update tests

## Version 0.4.25

- Method to split `GenomicRanges` by a list of groups.
Expand Down
4 changes: 3 additions & 1 deletion src/genomicranges/GenomicRanges.py
Original file line number Diff line number Diff line change
Expand Up @@ -1568,9 +1568,11 @@ def narrow(
return output

def _group_indices_by_chrm(self, ignore_strand: bool = False) -> dict:
__strand = self._strand
__strand = self._strand.copy()
if ignore_strand:
__strand = np.zeros(len(self), dtype=np.int8)
# else:
# __strand[__strand == 0] = 1

_seqnames = [self._seqinfo._seqnames[i] for i in self._seqnames]
grp_keys = np.char.add(
Expand Down
59 changes: 43 additions & 16 deletions src/genomicranges/GenomicRangesList.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from warnings import warn

import biocutils as ut
from biocframe import BiocFrame
import numpy as np
from biocframe import BiocFrame

from .GenomicRanges import GenomicRanges

Expand Down Expand Up @@ -593,7 +593,9 @@ def groups(self, group: Union[str, int]) -> "GenomicRangesList":

return self[group]

def _generic_accessor(self, prop: str, func: bool = False) -> Dict[str, list]:
def generic_accessor(
self, prop: str, func: bool = False, cast: bool = False
) -> Union[Dict[str, list], "GenomicRangesList"]:
_all_prop = {}
_ranges = self.ranges
_groups = self.names
Expand All @@ -610,6 +612,10 @@ def _generic_accessor(self, prop: str, func: bool = False) -> Dict[str, list]:

_all_prop[_key] = _val

if cast is True:
current_class_const = type(self)
return current_class_const.from_dict(_all_prop)

return _all_prop

def element_nrows(self) -> Dict[str, List[str]]:
Expand All @@ -619,7 +625,7 @@ def element_nrows(self) -> Dict[str, List[str]]:
An integer vector where each value corresponds to the length of
the contained GenomicRanges object.
"""
return self._generic_accessor("__len__", func=True)
return self.generic_accessor("__len__", func=True)

def is_empty(self) -> bool:
"""Whether ``GRangesList`` has no elements or if all its elements are empty.
Expand Down Expand Up @@ -650,7 +656,7 @@ def seqnames(self) -> Dict[str, List[str]]:
A list with the same length as keys in the object,
each element in the list contains another list of sequence names.
"""
return self._generic_accessor("seqnames")
return self.generic_accessor("seqnames")

@property
def start(self) -> Dict[str, List[int]]:
Expand All @@ -660,7 +666,7 @@ def start(self) -> Dict[str, List[int]]:
A list with the same length as keys in the object,
each element in the list contains another list values.
"""
return self._generic_accessor("start")
return self.generic_accessor("start")

@property
def end(self) -> Dict[str, List[int]]:
Expand All @@ -670,7 +676,7 @@ def end(self) -> Dict[str, List[int]]:
A list with the same length as keys in the object,
each element in the list contains another list values.
"""
return self._generic_accessor("end")
return self.generic_accessor("end")

@property
def width(self) -> Dict[str, List[int]]:
Expand All @@ -680,7 +686,7 @@ def width(self) -> Dict[str, List[int]]:
A list with the same length as keys in the object,
each element in the list contains another list values.
"""
return self._generic_accessor("width")
return self.generic_accessor("width")

@property
def strand(self) -> Dict[str, List[int]]:
Expand All @@ -690,7 +696,7 @@ def strand(self) -> Dict[str, List[int]]:
A list with the same length as keys in the object,
each element in the list contains another list values.
"""
return self._generic_accessor("strand")
return self.generic_accessor("strand")

@property
def seq_info(self) -> Dict[str, List[int]]:
Expand All @@ -700,7 +706,7 @@ def seq_info(self) -> Dict[str, List[int]]:
A list with the same length as keys in the object,
each element in the list contains another list values.
"""
return self._generic_accessor("seq_info")
return self.generic_accessor("seq_info")

@property
def is_circular(self) -> Dict[str, List[int]]:
Expand All @@ -710,7 +716,7 @@ def is_circular(self) -> Dict[str, List[int]]:
A list with the same length as keys in the object,
each element in the list contains another list values.
"""
return self._generic_accessor("is_circular")
return self.generic_accessor("is_circular")

def get_range_lengths(self) -> dict:
"""
Expand Down Expand Up @@ -836,9 +842,9 @@ def __getitem__(

raise TypeError("Arguments to subset `GenomicRangesList` is not supported.")

##########################
######>> empty <<#########
##########################
#######################################
######>> class initializers <<#########
#######################################

@classmethod
def empty(cls, n: int):
Expand All @@ -851,11 +857,20 @@ def empty(cls, n: int):

return cls(ranges=GenomicRanges.empty(), range_lengths=_range_lengths)

@classmethod
def from_dict(cls, x: dict):
"""Create a `GenomicRangesList` object from :py:class:`~dict`.
Returns:
same type as caller, in this case a `GenomicRangesList`.
"""
return cls(ranges=list(x.values()), names=list(x.keys()))

###############################
######>> to granges <<#########
###############################

def to_genomic_ranges(self) -> GenomicRanges:
def as_genomic_ranges(self) -> GenomicRanges:
"""Coerce object to a :py:class:`~genomicranges.GenomicRanges.GenomicRanges`.
Returns:
Expand All @@ -873,9 +888,21 @@ def to_genomic_ranges(self) -> GenomicRanges:

return _combined_ranges

def to_granges(self) -> GenomicRanges:
def as_granges(self) -> GenomicRanges:
"""Alias to :py:meth:`~to_genomic_ranges`."""
return self.to_genomic_ranges()
return self.as_genomic_ranges()

####################################
######>> GRanges methods <<#########
####################################

def range(self) -> "GenomicRangesList":
"""Calculate range bounds for each genomic element.
Returns:
A new ``GenomicRangesList`` object with the range bounds.
"""
return self.generic_accessor("range", func=True, cast=True)


@ut.combine_sequences.register(GenomicRangesList)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_gr_to_grl.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def test_to_granges():
]
)

roundtrip = splits.to_genomic_ranges()
roundtrip = splits.as_genomic_ranges()

assert roundtrip is not None
assert isinstance(roundtrip, GenomicRanges)
Expand Down

0 comments on commit 4e50ea0

Please sign in to comment.