def create_upstream_index(
    df, downstream_col="downstream", upstream_col="upstream", exclude=None
):
    """Create an index of downstream ids to all their respective upstream ids.
    This is so that network traversal can start from a downstream-most segment,
    and then traverse upward for all segments that have that as a downstream segment.

    Parameters
    ----------
    df : DataFrame
        Data frame containing the pairs of upstream_col and downstream_col that
        represent the joins between segments.
    downstream_col : str, optional (default "downstream")
        Name of column containing downstream ids
    upstream_col : str, optional (default "upstream")
        Name of column containing upstream ids
    exclude : list-like, optional (default None)
        List-like containing segment ids to exclude from the list of upstreams.
        For example, barriers that break the network should be in this list.
        Otherwise, network traversal will operate from the downstream-most point
        to all upstream-most points, which can be very large for some networks.

    Returns
    -------
    dict
        dictionary of downstream_id to the corresponding upstream_id(s)
    """

    ix = (df[upstream_col] != 0) & (df[downstream_col] != 0)

    if exclude is not None:
        ix = ix & (~df[upstream_col].isin(exclude))

    # NOTE: this looks backward but is correct for the way that grouping works.
    return (
        df[ix, [downstream_col, upstream_col]]
        .set_index(upstream_col)
        .groupby(downstream_col)
        .groups
    )