# Intervals

An `Interval` object is how `pybedtools` represents a line in a BED, GFF, GTF, or VCF file in a uniform fashion. This section will describe some useful features of `Interval` objects.

First, let’s get a `BedTool` to work with:

    >>> a = pybedtools.example_bedtool('a.bed')

We can access the `Intervals` of `a` several different ways. Probably the most convenient way is by indexing a `BedTool` object:

    >>> feature = a[0]

`BedTool` objects support slices, too:

    >>> features = a[1:3]

## Common `Interval` attributes

Printing a feature converts it into the original line from the file:

    >>> print(feature)
    chr1        1       100     feature1        0       +

The string representation of an `Interval` object is simply a valid line, including the newline, for the format from which that `Interval` was created (accessible via `Interval.file_type`).

All features, no matter what the file type, have `chrom`, `start`, `stop`, `name`, `score`, and `strand` attributes. Note that `start` and `stop` are integers, while everything else (including `score`) is a string.

`pybedtools` supports both Python 2 and 3. When using Python 3, all strings are the `str` type. When using Python 2, all strings are unicode.

Note

This documentation undergoes testing with Python 2 and Python 3. These versions handle strings differently. For example, under Python 2:

    >>> feature.chrom
    u'chr1'

But under Python 3:

    >>> feature.chrom
    'chr1'

Since all strings returned by Interval objects are unicode, we solve this by making a helper function `show_value` that converts unicode to native string – but only under Python 2.

    >>> import sys
    >>> def show_value(s):
    ...     """
    ...     Convert unicode to str under Python 2;
    ...     all other values pass through unchanged
    ...     """
    ...     if sys.version_info.major == 2:
    ...         if isinstance(s, unicode):
    ...             return str(s)
    ...     return s

    >>> show_value(feature.chrom)
    'chr1'

    >>> show_value(feature.start)
    1

    >>> show_value(feature.stop)
    100

    >>> show_value(feature.name)
    'feature1'

    >>> show_value(feature.score)
    '0'

    >>> show_value(feature.strand)
    '+'

Let’s make another feature that only has chrom, start, and stop to see how `pybedtools` deals with missing attributes:

    >>> feature2 = pybedtools.BedTool('chrX 500 1000', from_string=True)[0]

    >>> print(feature2)
    chrX        500     1000

    >>> show_value(feature2.chrom)
    'chrX'

    >>> show_value(feature2.start)
    500

    >>> show_value(feature2.stop)
    1000

    >>> show_value(feature2.name)
    '.'

    >>> show_value(feature2.score)
    '.'

    >>> show_value(feature2.strand)
    '.'

This illustrates that default values are the string “`.`”.

## Indexing into `Interval` objects

`Interval` objects can also be indexed by position into the original line (like a list) or indexed by name of attribute (like a dictionary).

    >>> print(feature)
    chr1        1       100     feature1        0       +

    >>> show_value(feature[0])
    'chr1'

    >>> show_value(feature['chrom'])
    'chr1'

    >>> show_value(feature[1])
    '1'

    >>> show_value(feature['start'])
    1

## Fields

`Interval` objects have a `Interval.fields` attribute that contains the original line split into a list of strings. When an integer index is used on the `Interval` (for example, `feature[3]`), it is the `fields` attribute that is actually being indexed into.

    >>> f = pybedtools.BedTool('chr1 1 100 asdf 0 + a b c d', from_string=True)[0]
    >>> [show_value(i) for i in f.fields]
    ['chr1', '1', '100', 'asdf', '0', '+', 'a', 'b', 'c', 'd']
    >>> len(f.fields)
    10

## BED is 0-based, others are 1-based

One troublesome part about working with multiple formats is that BED files have a different coordinate system than GFF/GTF/VCF/ files.

- BED files are 0-based (the first base of the chromosome is considered position 0) and the feature does not include the stop position.
- GFF, GTF, and VCF files are 1-based (the first base of the chromosome is considered position 1) and the feature includes the stop position.

`pybedtools` follows the following conventions:

- The value in `Interval.start` will always contain the 0-based start position, even if it came from a GFF or other 1-based feature.

- Getting the `len()` of an `Interval` will always return `Interval.stop - Interval.start`, so no matter what format the original file was in, the length will be correct. This greatly simplifies underlying code, and it means you can treat all `Intervals` identically.

- The contents of `Interval.fields` will always be strings, which in turn always represent the original line in the file.

  > - This means that for a GFF feature, `Interval.fields[3]` or `Interval[3]`, which is 1-based according to the file format, will always be one bp larger than `Interval.start`, which always contains the 0-based start position. Their data types are different; `Interval[3]` will be a string and `Interval.start` will be a long.
  > - Printing an `Interval` object created from a GFF file will show the tab-delimited fields in GFF coords while printing an `Interval` object created from a BED file will show fields in BED coords.

### Worked example

To illustrate and confirm this functionality, let’s create a GFF feature and a BED feature from scratch and compare them.

First, let’s create a GFF `Interval` from scratch:

    >>> gff = ["chr1",
    ...        "fake",
    ...        "mRNA",
    ...        "51",   # <- start is 1 greater than start for the BED feature below
    ...        "300",
    ...        ".",
    ...        "+",
    ...        ".",
    ...        "ID=mRNA1;Parent=gene1;"]
    >>> gff = pybedtools.create_interval_from_list(gff)

Then let’s create a corresponding BED `Interval` that represents the same genomic coordinates of of the GFF feature, but since BED format is zero-based we need to subtract 1 from the start:

    >>> bed = ["chr1",
    ...        "50",
    ...        "300",
    ...        "mRNA1",
    ...        ".",
    ...        "+"]
    >>> bed = pybedtools.create_interval_from_list(bed)

Let’s confirm these new features were recognized as the right file type – the format is auto-detected based on the position of chrom/start/stop coords in the provided field list:

    >>> show_value(gff.file_type)
    'gff'

    >>> show_value(bed.file_type)
    'bed'

Printing the `Intervals` shows that the strings are in the appropriate coordinates:

    >>> # for testing, we make sure keys are sorted. Not needed in practice.
    >>> gff.attrs.sort_keys = True
    >>> print(gff)
    chr1        fake    mRNA    51      300     .       +       .       ID=mRNA1;Parent=gene1;

    >>> print(bed)
    chr1        50      300     mRNA1   .       +

Since `start` attributes are always zero-based, the GFF and BED `start` values should be identical:

    >>> bed.start == gff.start == 50
    True

For the BED feature, the second string field (representing the start position) and the `start` attribute should both be `50` (though one is an integer and the other is a string) …

    >>> show_value(bed.start)
    50
    >>> show_value(bed[1])
    '50'

… but for the GFF feature, they differ – the `start` attribute is zero-based while the string representation (the fourth field of a GFF file) remains in one-based GFF coords:

    >>> show_value(gff.start)
    50
    >>> show_value(gff[3])
    '51'

As long as we use the integer `start` attributes, we can treat the `Interval` objects identically, without having to check for their format every time:

    >>> len(bed) == len(gff) == 250
    True

## GFF features have access to attributes

GFF and GTF files have lots of useful information in their attributes field (the last field in each line). These attributes can be accessed with the `Interval.attrs` attribute, which acts like a dictionary. For speed, the attributes are lazy – they are only parsed when you ask for them. BED files, which do not have an attributes field, will return an empty dictionary.

    >>> # original feature
    >>> print(gff)
    chr1        fake    mRNA    51      300     .       +       .       ID=mRNA1;Parent=gene1;

    >>> # original attributes
    >>> sorted(gff.attrs.items())
    [('ID', 'mRNA1'), ('Parent', 'gene1')]

    >>> # add some new attributes
    >>> gff.attrs['Awesomeness'] = "99"
    >>> gff.attrs['ID'] = 'transcript1'

    >>> # Changes in attributes are propagated to the printable feature

    >>> # for testing, we make sure keys are sorted. Not needed in practice.
    >>> gff.attrs.sort_keys = True
    >>> assert gff.attrs.sort_keys
    >>> print(gff)
    chr1        fake    mRNA    51      300     .       +       .       Awesomeness=99;ID=transcript1;Parent=gene1;

Understanding `Interval` objects is important for using the powerful filtering and mapping facilities of `BedTool` objects, as described in the next section.