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?

  • $\begingroup$ 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? $\endgroup$ Jul 27 '18 at 9:15
  • $\begingroup$ @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/… $\endgroup$
    – bli
    Jul 27 '18 at 9:18
  • 1
    $\begingroup$ I'm not sure, but the explanation of the behaviour could be found around these lines: github.com/daler/pybedtools/blob/master/pybedtools/… $\endgroup$
    – bli
    Jul 27 '18 at 9:25
  • 1
    $\begingroup$ Or rather, this one: github.com/daler/pybedtools/blob/master/pybedtools/… $\endgroup$
    – bli
    Jul 27 '18 at 9:30
  • 1
    $\begingroup$ 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) $\endgroup$ 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)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.