code: def group_by(keys, values=None, reduction=None, axis=0):
      
      g = GroupBy(keys, axis)
      if values is None:
          return g
      groups = g.split(values)
      if reduction is None:
          return g.unique, groups
      return [(key, reduction(group)) for key, group in zip(g.unique, groups)]
docs: construct a grouping object on the given keys, optionally performing the given reduction on the given values
  
      Parameters
      ----------
      keys : indexable object
          keys to group by
      values : array_like, optional
          sequence of values, of the same length as keys
          if a reduction function is provided, the given values are reduced by key
          if no reduction is provided, the given values are grouped and split by key
      reduction : lambda, optional
          reduction function to apply to the values in each group
      axis : int, optional
          axis to regard as the key-sequence, in case keys is multi-dimensional
  
      Returns
      -------
      iterable
          if values is None, a GroupBy object of the given keys object
          if reduction is None, an tuple of a sequence of unique keys and a sequence of grouped values
          else, a sequence of tuples of unique keys and reductions of values over that key-group
  
      See Also
      --------
      numpy_indexed.as_index : for information regarding the casting rules to a valid Index object