<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def group_by(keys, values=None, reduction=None, axis=0):
 """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 """

    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)]