If return_bounds is False then returns precision on the
        labeled examples in the top k.
    If return_bounds is True the returns a tuple containing:
        - precision on the labeled examples in the top k
        - number of labeled examples in the top k
        - lower bound of precision in the top k, assuming all
            unlabaled examples are False
        - upper bound of precision in the top k, assuming all
            unlabaled examples are True

def precision(y_true, y_score, k=None, return_bounds=False):
    """
    If return_bounds is False then returns precision on the
        labeled examples in the top k.
    If return_bounds is True the returns a tuple containing:
        - precision on the labeled examples in the top k
        - number of labeled examples in the top k
        - lower bound of precision in the top k, assuming all
            unlabaled examples are False
        - upper bound of precision in the top k, assuming all
            unlabaled examples are True
    """
    y_true, y_score = to_float(y_true, y_score)
    top = _argtop(y_score, k)

    n = np.nan_to_num(y_true[top]).sum()    # fill missing labels with 0
    d = (~np.isnan(y_true[top])).sum()      # count number of labels
    p = n/d

    if return_bounds:
        k = len(y_true) if k is None else k
        bounds = (n/k, (n+k-d)/k) if k != 0 else (np.nan, np.nan)
        return p, d, bounds[0], bounds[1]
    else:
        return p