Find nearest annotated upstream/downstream donor/acceptor for novel
    donor/acceptors.

    Parameters
    ----------
    df : pandas.DataFrame
        Dataframe with observed splice junctions (novel and known) with columns
        'chrom', 'first_bp_intron', 'last_bp_intron', 'strand', 'intron_motif',
        'annotated', 'ext_annotated', 'chrom:start', 'chrom:end', 'gene_id',
        'donor', 'acceptor', 'novel_donor', 'novel_acceptor'.

    d : dict
        If df contains novel donors, should be a dict whose keys are acceptors
        and whose values are the locations (integers) of all associated donors.
        If df contains novel acceptors, should be a dict whose keys are donors
        and whose values are the locations (integers) of all associated
        accepators.

    strand : str ('+' or '-')
        Strand that features are on.

    novel_feature : str ('donor' or 'acceptor')
        Whether the dataframe contains novel donors or novel acceptors.

    Returns
    -------
    up : list
        List of distances from novel feature to nearest feature (of same type)
        upstream. If upstream feature does not exist, the list will have a nan.

    down : list
        List of distances from novel feature to nearest feature (of same type)
        downstream. If upstream feature does not exist, the list will have a 
        nan.

def _dist_to_annot_donor_acceptor(df, d, strand, novel_feature):
    """Find nearest annotated upstream/downstream donor/acceptor for novel
    donor/acceptors.

    Parameters
    ----------
    df : pandas.DataFrame
        Dataframe with observed splice junctions (novel and known) with columns
        'chrom', 'first_bp_intron', 'last_bp_intron', 'strand', 'intron_motif',
        'annotated', 'ext_annotated', 'chrom:start', 'chrom:end', 'gene_id',
        'donor', 'acceptor', 'novel_donor', 'novel_acceptor'.

    d : dict
        If df contains novel donors, should be a dict whose keys are acceptors
        and whose values are the locations (integers) of all associated donors.
        If df contains novel acceptors, should be a dict whose keys are donors
        and whose values are the locations (integers) of all associated
        accepators.

    strand : str ('+' or '-')
        Strand that features are on.

    novel_feature : str ('donor' or 'acceptor')
        Whether the dataframe contains novel donors or novel acceptors.

    Returns
    -------
    up : list
        List of distances from novel feature to nearest feature (of same type)
        upstream. If upstream feature does not exist, the list will have a nan.

    down : list
        List of distances from novel feature to nearest feature (of same type)
        downstream. If upstream feature does not exist, the list will have a 
        nan.
    
    """
    if df.shape[0] > 0:
        assert len(set(df.strand)) == 1
    if novel_feature == 'donor':
        assert df.novel_donor.sum() == df.shape[0]
    if novel_feature == 'acceptor':
        assert df.novel_acceptor.sum() == df.shape[0]
    # For a novel donor, we want to return the distance to the nearest upstream
    # and downstream donors that use the same acceptor. For a novel acceptor, we
    # want to return the distance to the nearest upstream and downstream
    # acceptors that use the same donor. In some cases there may not be one of
    # the upstream or downstream donors/acceptors. In that case we will just
    # return nan.
    if strand == '+':
        if novel_feature == 'donor':
            annot_feature = 'acceptor'
            novel_location = 'start'
        if novel_feature == 'acceptor':
            annot_feature = 'donor'
            novel_location = 'end'
    if strand == '-':
        if novel_feature == 'donor':
            annot_feature = 'acceptor'
            novel_location = 'end' 
        if novel_feature == 'acceptor':
            annot_feature = 'donor'
            novel_location = 'start'

    upstream_dists = []
    downstream_dists = []
    
    for i in df.index:
        a = df.ix[i, annot_feature]
        diff = df.ix[i, novel_location] - d[a]
        pos = diff[diff > 0]
        neg = diff[diff < 0]
        if strand == '+':
            if pos.shape[0] == 0:
                upstream_dists.append(np.nan)
            else:
                upstream_dists.append(pos.min())
            if neg.shape[0] == 0:
                downstream_dists.append(np.nan)
            else:
                downstream_dists.append(np.abs(neg).min())
        if strand == '-':
            if pos.shape[0] == 0:
                downstream_dists.append(np.nan)
            else:
                downstream_dists.append(pos.min())
            if neg.shape[0] == 0:
                upstream_dists.append(np.nan)
            else:
                upstream_dists.append(np.abs(neg).min())
    return upstream_dists, downstream_dists