def jitter(x, factor=1, amount=None, random_state=None):
    """
    Add a small amount of noise to values in an array_like

    Parameters
    ----------
    x : array_like
        Values to apply a jitter
    factor : float
        Multiplicative value to used in automatically determining
        the `amount`. If the `amount` is given then the `factor`
        has no effect.
    amount : float
        This defines the range ([-amount, amount]) of the jitter to
        apply to the values. If `0` then ``amount = factor * z/50``.
        If `None` then ``amount = factor * d/5``, where d is about
        the smallest difference between `x` values and `z` is the
        range of the `x` values.
    random_state : int or ~numpy.random.RandomState, optional
        Seed or Random number generator to use. If ``None``, then
        numpy global generator :class:`numpy.random` is used.

    References:

        - Chambers, J. M., Cleveland, W. S., Kleiner, B. and Tukey,
          P.A. (1983) *Graphical Methods for Data Analysis*. Wadsworth;
          figures 2.8, 4.22, 5.4.
    """
    if len(x) == 0:
        return x

    if random_state is None:
        random_state = np.random
    elif isinstance(random_state, int):
        random_state = np.random.RandomState(random_state)

    x = np.asarray(x)

    try:
        z = np.ptp(x[np.isfinite(x)])
    except IndexError:
        z = 0

    if z == 0:
        z = np.abs(np.min(x))
    if z == 0:
        z = 1

    if amount is None:
        _x = np.round(x, 3-np.int(np.floor(np.log10(z)))).astype(np.int)
        xx = np.unique(np.sort(_x))
        d = np.diff(xx)
        if len(d):
            d = d.min()
        elif xx != 0:
            d = xx/10.
        else:
            d = z/10
        amount = factor/5. * abs(d)
    elif amount == 0:
        amount = factor * (z / 50.)

    return x + random_state.uniform(-amount, amount, len(x))