Helper function for pairwise apply.
    Args:
        steps: an ordered collection of steps
        function: function to apply, first two positional arguments are steps
        symmetric: whether function is symmetric in the two steps
        diagonal: whether to apply on the diagonal
        block: apply only when the given columns match
        kwargs: keyword arguments to pass to the function

    Returns:
        DataFrame with index and columns equal to the steps argument

def apply_pairwise(self, function, symmetric=True, diagonal=False, block=None, **kwargs):
    """
    Helper function for pairwise apply.
    Args:
        steps: an ordered collection of steps
        function: function to apply, first two positional arguments are steps
        symmetric: whether function is symmetric in the two steps
        diagonal: whether to apply on the diagonal
        block: apply only when the given columns match
        kwargs: keyword arguments to pass to the function

    Returns:
        DataFrame with index and columns equal to the steps argument
    """
    steps = self.index
    r = pd.DataFrame(index=steps, columns=steps)
    for i, s1 in enumerate(steps):
        j = range(i+1 if symmetric else len(steps))
        if not diagonal:
            j.remove(i)
        other = set(steps[j])
        if block is not None:
            df = self.reset_index()
            df = df.merge(df, on=block)
            other &= set(df[df.index_x == s1].index_y)

        for s2 in other:
            r.ix[s1, s2] = function(s1, s2, **kwargs)
    return r