def apply_at(
        arr,
        func,
        mask=None,
        else_=None,
        in_place=False):
    """
    Apply a function to an array.

    Warning! Depending on the value of `in_place`, this function may alter
    the input array.

    Args:
        arr (np.ndarray): The input array.
        func (callable): The function to use.
            Must have the signature: func(np.ndarray) -> np.ndarray
        mask (np.ndarray[bool]): The mask where the function is applied.
            Must have the same shape as `arr`.
        else_ (callable|Any|None): The alternate behavior.
            If callable, this is a function applied to non-masked values.
            Must have the signature: func(np.ndarray) -> np.ndarray
            If Any, the value is assigned (through broadcasting) to the
            non-masked value.
            If None, the npn-masked value are left untouched.
        in_place (bool): Determine if the function is applied in-place.
            If True, the input gets modified.
            If False, the modification happen on a copy of the input.

    Returns:
        arr (np.ndarray): The output array.

    Examples:
        >>> arr = np.arange(10) - 5
        >>> print(arr)
        [-5 -4 -3 -2 -1  0  1  2  3  4]
        >>> print(apply_at(arr, np.abs, arr < 0))
        [5 4 3 2 1 0 1 2 3 4]
        >>> print(apply_at(arr, np.abs, arr < 2, 0))
        [5 4 3 2 1 0 1 0 0 0]
        >>> print(apply_at(arr, np.abs, arr < 0, lambda x: x ** 2, True))
        [ 5  4  3  2  1  0  1  4  9 16]
        >>> print(arr)
        [ 5  4  3  2  1  0  1  4  9 16]
    """
    if not in_place:
        arr = arr.copy()
    if mask is not None:
        arr[mask] = func(arr[mask])
        if else_ is not None:
            if callable(else_):
                arr[~mask] = else_(arr[~mask])
            else:
                arr[~mask] = else_
    else:
        arr[...] = func(arr)
    return arr