def weighted_average(df, data_col, weight_col, by):
    """Generate a weighted average.

    Args:
        df (pandas.DataFrame): A DataFrame containing, at minimum, the columns
            specified in the other parameters data_col and weight_col.
        data_col (string): column name of data column to average
        weight_col (string): column name to weight on
        by (list): A list of the columns to group by when calcuating
            the weighted average value.

    Returns:
        pandas.DataFrame: a table with ``by`` columns as the index and the
        weighted ``data_col``.
    """
    df["_data_times_weight"] = df[data_col] * df[weight_col]
    df["_weight_where_notnull"] = df.loc[df[data_col].notnull(), weight_col]
    g = df.groupby(by, observed=True)
    result = g["_data_times_weight"].sum(min_count=1) / g["_weight_where_notnull"].sum(
        min_count=1
    )
    del df["_data_times_weight"], df["_weight_where_notnull"]
    return result.to_frame(name=data_col)