# How to efficiently extract coordinates from a pybedtools.Interval?

I'm using pybedtools to process data, and at some point, I loop over a BedTool object and use unpacking to extract coordinates from the Interval:

for interval in my_bedtool:
(chrom, start, end, _, _, strand) = interval


Suppose I want to compute the interval length, as follows:

    length = end - start


This results in a TypeError: unsupported operand type(s) for -: 'str' and 'str'.

It happens that the unpacked end and start variables are strings. Converting them to ints is probably suboptimal given that Intervals can be created by providing ints for the coordinates:

from pybedtool import Interval
interval = Interval("X", 0, 100, strand="+")


The start and end attributes of an Interval are ints.

I could do:

    (chrom, start, end, strand) = (
interval.chrom, interval.start, interval.end, interval.strand)


But I think this looks a bit ugly.

Is there a better approach?

• i do not know pybedtools but list(Interval("X", 0, 100, strand="+")) >>> ['X', '0', '100', '.', '.', '+']- shouldn't the conversion of integers to strings be considered a bug? – Chris_Rands Jul 27 '18 at 9:15
• @Chris_Rands I was indeed surprised by this behaviour. As I say in my answer, I guess it might be related to how indexing works. It seems to rely on a string representation of the genomic interval: daler.github.io/pybedtools/… – bli Jul 27 '18 at 9:18
• I'm not sure, but the explanation of the behaviour could be found around these lines: github.com/daler/pybedtools/blob/master/pybedtools/… – bli Jul 27 '18 at 9:25
• Or rather, this one: github.com/daler/pybedtools/blob/master/pybedtools/… – bli Jul 27 '18 at 9:30
• Yes exactly, I was peaking at the same source code myself; so it's a design decision not a bug i guess, still seems odd to me (my guess is that keeping a homogenous list in terms of types might allow some performance benefits for cython) – Chris_Rands Jul 27 '18 at 9:31

The reason why unpacking an Interval results in strings instead of ints might be related to how indexing by position works.

Even without considering the string/int issue, it seems slightly more efficient to access the interval's attributes rather than unpacking them directly:

In [311]: %timeit [(chrom, start, end, strand) for (chrom, start, end, _, _, strand) in my_bedtool]
306 ms ± 26.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [312]: %timeit [(interval.chrom, interval.start, interval.end, interval.strand) for interval in my_bedtool]
238 ms ± 24.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)


To resolve the ugliness issue, attribute access can be made more compact through the use of operator.attrgetter:

In [313]: from operator import attrgetter

In [314]: get_interval_attrs = attrgetter("chrom", "start", "stop", "strand")

In [315]: %timeit [get_interval_attrs(interval) for interval in my_bedtool]
239 ms ± 26.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

• – Chris_Rands Jul 27 '18 at 12:30