This method gives subclasses the opportunity to define how
        join() fills missing values. Return value must be compatible with
        DataFrame.fillna() value argument. Examples:
            - return 0: replace missing values with zero
            - return df.mean(): replace missing values with column mean

        This default implimentation fills counts with zero.
        TODO: identify counts more robustly instead of relying on column name

        Typically fill other fields with mean but can't do that during the join
            because that would leak information across a train/test split

def fillna_value(self, df, left, **concat_args):
        """
        This method gives subclasses the opportunity to define how
        join() fills missing values. Return value must be compatible with
        DataFrame.fillna() value argument. Examples:
            - return 0: replace missing values with zero
            - return df.mean(): replace missing values with column mean

        This default implimentation fills counts with zero.
        TODO: identify counts more robustly instead of relying on column name

        Typically fill other fields with mean but can't do that during the join
            because that would leak information across a train/test split
        """
        value = pd.Series(
                0, index=[c for c in df.columns
                          if c.endswith('_count') and c.find('_per_') == -1])
        return value