docs: def aggregate_groups(self, ct_agg, nr_groups, skip_key,
                           carray_factor, groupby_cols, agg_ops,
                           dtype_dict, bool_arr=None):
          '''Perform aggregation and place the result in the given ctable.
  
          Args:
              ct_agg (ctable): the table to hold the aggregation
              nr_groups (int): the number of groups (number of rows in output table)
              skip_key (int): index of the output row to remove from results (used for filtering)
              carray_factor: the carray for each row in the table a reference to the the unique group index
              groupby_cols: the list of 'dimension' columns that are used to perform the groupby over
              output_agg_ops (list): list of tuples of the form: (input_col, agg_op)
                      input_col (string): name of the column to act on
                      agg_op (int): aggregation operation to perform
              bool_arr: a boolean array containing the filter
  
          '''
  
          # this creates the groupby columns
          for col in groupby_cols:
  
              result_array = ctable_ext.groupby_value(self[col], carray_factor,
                                                      nr_groups, skip_key)
  
              if bool_arr is not None:
                  result_array = np.delete(result_array, skip_key)
  
              ct_agg.addcol(result_array, name=col)
              del result_array
  
          # this creates the aggregation columns
          for input_col_name, output_col_name, agg_op in agg_ops:
  
              input_col = self[input_col_name]
              output_col_dtype = dtype_dict[output_col_name]
  
              input_buffer = np.empty(input_col.chunklen, dtype=input_col.dtype)
              output_buffer = np.zeros(nr_groups, dtype=output_col_dtype)
  
              if agg_op == 'sum':
                  ctable_ext.aggregate_sum(input_col, carray_factor, nr_groups,
                                           skip_key, input_buffer, output_buffer)
              elif agg_op == 'mean':
                  ctable_ext.aggregate_mean(input_col, carray_factor, nr_groups,
                                            skip_key, input_buffer, output_buffer)
              elif agg_op == 'std':
                  ctable_ext.aggregate_std(input_col, carray_factor, nr_groups,
                                           skip_key, input_buffer, output_buffer)
              elif agg_op == 'count':
                  ctable_ext.aggregate_count(input_col, carray_factor, nr_groups,
                                             skip_key, input_buffer, output_buffer)
              elif agg_op == 'count_distinct':
                  ctable_ext.aggregate_count_distinct(input_col, carray_factor, nr_groups,
                                                      skip_key, input_buffer, output_buffer)
              elif agg_op == 'sorted_count_distinct':
                  ctable_ext.aggregate_sorted_count_distinct(input_col, carray_factor, nr_groups,
                                                             skip_key, input_buffer, output_buffer)
              else:
                  raise KeyError('Unknown aggregation operation ' + str(agg_op))
  
              if bool_arr is not None:
                  output_buffer = np.delete(output_buffer, skip_key)
  
              ct_agg.addcol(output_buffer, name=output_col_name)
              del output_buffer
  
          ct_agg.delcol('tmp_col_bquery__')