Skip to content

Commit

Permalink
Create empty GenomicRanges and GenomicRangesList (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkanche authored Sep 20, 2023
1 parent fa1a1b2 commit 5857acd
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 25 deletions.
9 changes: 9 additions & 0 deletions src/genomicranges/GenomicRanges.py
Original file line number Diff line number Diff line change
Expand Up @@ -2236,3 +2236,12 @@ def concat(self, *granges: "GenomicRanges") -> "GenomicRanges":
new_data[col].extend([None] * len(self))

return GenomicRanges(new_data, column_names=all_unique_columns)

@classmethod
def empty(cls):
"""Create an zero-length `GenomicRanges` object.
Returns:
same type as caller, in this case a `GenomicRanges`.
"""
return cls(number_of_rows=0)
59 changes: 35 additions & 24 deletions src/genomicranges/GenomicRangesList.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,48 +57,48 @@ class GenomicRangesList:

def __init__(
self,
ranges: List[GenomicRanges] = [],
number_of_ranges: Optional[int] = None,
ranges: Union[GenomicRanges, List[GenomicRanges]],
range_lengths: Optional[List[int]] = None,
names: Optional[List[str]] = None,
mcols: BiocOrPandasFrame = None,
metadata: Optional[dict] = None,
):
"""Initialize a `GenomicRangesList` object.
Args:
ranges (List[GenomicRanges], optional): List of genomic elements.
ranges (GenomicRangesList, optional): List of genomic elements.
All elements in this list must be :py:class:`genomicranges.GenomicRanges.GenomicRanges`
class. Defaults to [].
number_of_ranges (Optional[int], optional): Number of genomic elements. Defaults to None.
class.
range_lengths (Optional[List[int]], optional): Number of ranges within each genomic element.
Defaults to None.
names (Optional[List[str]], optional): Names of the genomic elements.
The length of this must match the number of genomic elements in ``ranges``.
Defaults to None.
mcols (BiocOrPandasFrame, optional): Metadata about each genomic element. Defaults to None.
metadata (Optional[Dict], optional): Additional metadata. Defaults to None.
"""
self._validate(ranges)
_data = {"ranges": ranges}
self._data = {"ranges": ranges}

if range_lengths is None:
range_lengths = [len(x) for x in ranges]

if number_of_ranges is None:
number_of_ranges = len(ranges)
self._range_lengths = range_lengths

if mcols is None:
mcols = BiocFrame(number_of_rows=number_of_ranges)
mcols = BiocFrame(number_of_rows=len(range_lengths))

_data["mcols"] = mcols

self._frame = BiocFrame(
_data,
number_of_rows=number_of_ranges,
row_names=names,
)
self._data["mcols"] = mcols

self._names = names
self._metadata = {} if metadata is None else metadata

def _validate(self, ranges: List[GenomicRanges]):
if not is_list_of_type(ranges, GenomicRanges):
def _validate(self, ranges: Union[GenomicRanges, List[GenomicRanges]]):
if not (
isinstance(ranges, GenomicRanges) or is_list_of_type(ranges, GenomicRanges)
):
raise TypeError(
"All genomic elements in `ranges` must be of type `GenomicRanges`."
"`ranges` must be either a `GenomicRanges` or a list of `GenomicRanges`."
)

@property
Expand Down Expand Up @@ -127,7 +127,7 @@ def ranges(self) -> Dict[str, List[str]]:
Dict[str, List[str]]: A list with the same length as keys in the object,
each element in the list contains another list of ranges names.
"""
return self._frame.column("ranges")
return self._data["ranges"]

@property
def groups(self) -> Optional[list]:
Expand All @@ -138,7 +138,7 @@ def groups(self) -> Optional[list]:
:py:attr:`genomicranges.GenomicRanges.GenomicRangesList.ranges`,
with each element specifying the name of the element. None if names are not provided.
"""
return self._frame.row_names
return self._names

@property
def names(self) -> Optional[list]:
Expand All @@ -158,8 +158,8 @@ def mcols(self) -> Optional[BiocOrPandasFrame]:
Returns:
(BiocOrPandasFrame, optional): Metadata frame or None if there is no element level metadata.
"""
if "mcols" in self._frame.column_names:
return self._frame.column("mcols")
if "mcols" in self._data:
return self._data["mcols"]

return None

Expand Down Expand Up @@ -361,4 +361,15 @@ def __len__(self) -> int:
Returns:
int: number of genomic elements.
"""
return len(self._frame)
return len(self._range_lengths)

@classmethod
def empty(cls, n: int):
"""Create an ``n``-length `GenomicRangesList` object.
Returns:
same type as caller, in this case a `GenomicRangesList`.
"""
_range_lengths = [0] * n

return cls(ranges=GenomicRanges.empty(), range_lengths=_range_lengths)
10 changes: 10 additions & 0 deletions tests/test_gr_initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,13 @@ def test_gr_empty():
gr = GenomicRanges(number_of_rows=100)

assert gr is not None
assert isinstance(gr, GenomicRanges)
assert len(gr) == 100
assert gr.shape == (100, 0)

gre = GenomicRanges.empty()

assert gre is not None
assert isinstance(gre, GenomicRanges)
assert len(gre) == 0
assert gre.shape == (0, 0)
3 changes: 2 additions & 1 deletion tests/test_grl_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ def test_is_empty_False():


def test_is_empty_True():
grl = GenomicRangesList()
grl = GenomicRangesList(GenomicRanges.empty(), range_lengths=[0] * 10)

assert grl.is_empty() == True
assert len(grl) == 10


def test_nrows():
Expand Down

0 comments on commit 5857acd

Please sign in to comment.