Biology & Informatics Krzysztof Kus

← Writing

Genomic arrays

A small NumPy-backed class for accumulating features along a chromosome-length array, and a reminder about right-open intervals.

A recurring task in genomics is mapping some quantity onto a coordinate system: coverage along a chromosome, the density of a feature, a score accumulated across overlapping annotations. The operation is always the same — take an interval, add a value to every position inside it, repeat a few million times.

The class below does that and nothing else. It allocates a zeroed NumPy array the length of the chromosome and exposes one method for adding a value across a range. NumPy handles the slice arithmetic, so the accumulation stays fast even when the array is a quarter of a billion positions long. You need NumPy installed to run it.

One warning worth internalising: this follows Python’s indexing rules. Positions are zero-based and the last coordinate is not included — the interval is open on the right. If your annotations came from a one-based, closed-interval format such as GFF, convert them before you get here rather than after you have plotted the result.

import numpy as np


class GenomicArray():
    def __init__(self, size):
        self.size = size
        self.store = np.zeros(self.size)

    def chunk_add(self, s, e, val):
        self.s = s
        self.e = e
        self.val = val
        self.store[self.s:self.e] += self.val
        return self.store


# length of array, e.g. for human chromosome 1
chr1_length = 247249719

# initialise chr1_array
chr1_array = GenomicArray(chr1_length)

chr1_array.chunk_add(3, 5, 10)
chr1_array.chunk_add(4, 10, 20)

# see that the values changed
print(chr1_array.store[0:20])

Running that gives 10 at position 3, 30 at position 4 where the two intervals overlap, and 20 from position 5 through 9. Position 10 stays at zero — the right-open rule doing its job, and the thing that will catch you out if you have spent the morning in a one-based format.

Why not a dictionary

The obvious alternative is a dictionary keyed on position, and for sparse data it is the better choice. But once features overlap across a significant fraction of a chromosome, the dictionary loses on both memory and speed, and you give up the ability to slice, window and summarise with NumPy directly.

A flat array of float64 across human chromosome 1 costs about two gigabytes. If that is more than you want to spend, cast the store to a smaller dtype when you construct it.

Download

python3 genomic_arrays.py intervals_example.bed