def mask_cmp_op(x, y, op, allowed_types):
    """
    Apply the function `op` to only non-null points in x and y.

    Parameters
    ----------
    x : array-like
    y : array-like
    op : binary operation
    allowed_types : class or tuple of classes

    Returns
    -------
    result : ndarray[bool]
    """
    # TODO: Can we make the allowed_types arg unnecessary?
    xrav = x.ravel()
    result = np.empty(x.size, dtype=bool)
    if isinstance(y, allowed_types):
        yrav = y.ravel()
        mask = notna(xrav) & notna(yrav)
        result[mask] = op(np.array(list(xrav[mask])),
                          np.array(list(yrav[mask])))
    else:
        mask = notna(xrav)
        result[mask] = op(np.array(list(xrav[mask])), y)

    if op == operator.ne:  # pragma: no cover
        np.putmask(result, ~mask, True)
    else:
        np.putmask(result, ~mask, False)
    result = result.reshape(x.shape)
    return result
Apply the function `op` to only non-null points in x and y.

    Parameters
    ----------
    x : array-like
    y : array-like
    op : binary operation
    allowed_types : class or tuple of classes

    Returns
    -------
    result : ndarray[bool]