Performs a groupby of the unique Columns by index, as constructed from self.df.

        Args:
            index (str, or pd.Index): Index or column name of self.df.

        Returns:
            pd.DataFrame: A dataframe, aggregated by index, that contains the result
                of the various ColumnFunctions, and named accordingly.

def aggregate(self, index):
        """Performs a groupby of the unique Columns by index, as constructed from self.df.

        Args:
            index (str, or pd.Index): Index or column name of self.df.

        Returns:
            pd.DataFrame: A dataframe, aggregated by index, that contains the result
                of the various ColumnFunctions, and named accordingly.
        """

        # deal with index as a string vs index as a index/MultiIndex
        if isinstance(index, string_types):
            col_df_grouped = self.col_df.groupby(self.df[index])
        else:
            self.col_df.index = pd.MultiIndex.from_arrays([self.df[i] for i in index])
            col_df_grouped = self.col_df.groupby(level=index)
            self.col_df.index = self.df.index

        # perform the actual aggregation
        self.reduced_df = pd.DataFrame({
            colred: col_df_grouped[colred.column].agg(colred.agg_func)
            for colred in self.column_reductions
            })

        # then apply the functions to produce the final dataframe
        reduced_dfs = []
        for cf in self.column_functions:
            # each apply_and_name() calls get_reduced() with the column reductions it wants
            reduced_dfs.append(cf.apply_and_name(self))

        return pd.concat(reduced_dfs, axis=1)