Quantile normalization, allowing for missing values (NaN).

    In case of nan values, this implementation will calculate evenly
    distributed quantiles and fill in the missing data with those values.
    Quantile normalization is then performed on the filled-in matrix,
    and the nan values are restored afterwards.

    Parameters
    ----------
    matrix: `ExpMatrix`
        The expression matrix (rows = genes, columns = samples).
    inplace: bool
        Whether or not to perform the operation in-place. [False]
    target: `numpy.ndarray`
        Target distribution to use. needs to be a vector whose first
        dimension matches that of the expression matrix. If ``None``,
        the target distribution is calculated based on the matrix
        itself. [None]

    Returns
    -------
    numpy.ndarray (ndim = 2)
        The normalized matrix.

def quantile_normalize(matrix, inplace=False, target=None):
    """Quantile normalization, allowing for missing values (NaN).

    In case of nan values, this implementation will calculate evenly
    distributed quantiles and fill in the missing data with those values.
    Quantile normalization is then performed on the filled-in matrix,
    and the nan values are restored afterwards.

    Parameters
    ----------
    matrix: `ExpMatrix`
        The expression matrix (rows = genes, columns = samples).
    inplace: bool
        Whether or not to perform the operation in-place. [False]
    target: `numpy.ndarray`
        Target distribution to use. needs to be a vector whose first
        dimension matches that of the expression matrix. If ``None``,
        the target distribution is calculated based on the matrix
        itself. [None]

    Returns
    -------
    numpy.ndarray (ndim = 2)
        The normalized matrix.
    """
    assert isinstance(matrix, ExpMatrix)
    assert isinstance(inplace, bool)
    if target is not None:
        assert isinstance(target, np.ndarray) and \
               np.issubdtype(target.dtype, np.float)

    if not inplace:
        # make a copy of the original data
        matrix = matrix.copy()

    X = matrix.X
    _, n = X.shape
    nan = []
     # fill in missing values with evenly spaced quantiles
    for j in range(n):
        nan.append(np.nonzero(np.isnan(X[:, j]))[0])
        if nan[j].size > 0:
            q = np.arange(1, nan[j].size + 1, dtype=np.float64) / \
                (nan[j].size + 1.0)
            fill = np.nanpercentile(X[:, j], 100 * q)
            X[nan[j], j] = fill

    # generate sorting indices
    A = np.argsort(X, axis=0, kind='mergesort')  # mergesort is stable

    # reorder matrix
    for j in range(n):
        matrix.iloc[:, j] = matrix.X[A[:, j], j]

    # determine target distribution
    if target is None:
        # No target distribution is specified, calculate one based on the
        # expression matrix.
        target = np.mean(matrix.X, axis=1)
    else:
        # Use specified target distribution (after sorting).
        target = np.sort(target)

    # generate indices to reverse sorting
    A = np.argsort(A, axis=0, kind='mergesort')  # mergesort is stable

    # quantile-normalize
    for j in range(n):
        matrix.iloc[:, j] = target[A[:, j]]

    # set missing values to NaN again
    for j in range(n):
        if nan[j].size > 0:
            matrix.iloc[nan[j], j] = np.nan

    return matrix