熊猫数据帧的分位数归一化

Posted

技术标签:

【中文标题】熊猫数据帧的分位数归一化【英文标题】:quantile normalization on pandas dataframe 【发布时间】:2016-10-22 12:00:00 【问题描述】:

简单来说,如何在 Python 中对大型 Pandas 数据帧(可能有 2,000,000 行)应用分位数归一化?

PS。我知道有一个名为 rpy2 的包可以在子进程中运行 R,在 R 中使用分位数归一化。但事实是,当我使用如下数据集时,R 无法计算正确的结果:

5.690386092696389541e-05,2.051450375415418849e-05,1.963190184049079707e-05,1.258362869906251862e-04,1.503352476021528139e-04,6.881341586355676286e-06
8.535579139044583634e-05,5.128625938538547123e-06,1.635991820040899643e-05,6.291814349531259308e-05,3.006704952043056075e-05,6.881341586355676286e-06
5.690386092696389541e-05,2.051450375415418849e-05,1.963190184049079707e-05,1.258362869906251862e-04,1.503352476021528139e-04,6.881341586355676286e-06
2.845193046348194770e-05,1.538587781561563968e-05,2.944785276073619561e-05,4.194542899687506431e-05,6.013409904086112150e-05,1.032201237953351358e-05

编辑:

我想要什么:

鉴于上面显示的数据,如何按照https://en.wikipedia.org/wiki/Quantile_normalization 中的步骤应用分位数归一化。

我在 Python 中找到一段代码,声明它可以计算分位数归一化:

import rpy2.robjects as robjects
import numpy as np
from rpy2.robjects.packages import importr
preprocessCore = importr('preprocessCore')


matrix = [ [1,2,3,4,5], [1,3,5,7,9], [2,4,6,8,10] ]
v = robjects.FloatVector([ element for col in matrix for element in col ])
m = robjects.r['matrix'](v, ncol = len(matrix), byrow=False)
Rnormalized_matrix = preprocessCore.normalize_quantiles(m)
normalized_matrix = np.array( Rnormalized_matrix)

代码在代码中使用的示例数据可以正常工作,但是当我使用上面给出的数据对其进行测试时,结果出错了。

由于ryp2提供了在python子进程中运行R的接口,我直接在R中再次测试,结果还是错误。结果我认为原因是R中的方法是错误的。

【问题讨论】:

我删除了“R”标签,因为您 (1) 没有使用 R 并且 (2) 不想在答案中使用 R。但是,如果您说“R 无法计算正确的结果”,听起来您要么在贬低 R(为了什么目的?),要么希望有人更正您未发布的代码。无论哪种方式,也许我误解了你想要什么:分位数归一化需要一个源和目标分布,我不确定你在这里提供什么。你能澄清一下吗? @r2evans 感谢您的评论,我已经编辑了问题。仅供参考,我用谷歌搜索的代码将 R 作为 Python 的子进程运行。直接运行R后发现结果不对。此外,我不确定您所说的“目标分布”是什么意思。根据 Wiki,分位数归一化的计算不涉及该术语。希望我说清楚的问题是对我提供的数据应用分位数归一化。 你说得对,我的“目标”这个词不太好。 wiki 引用了“使两个发行版相同”,所以我想知道您的两个发行版是什么。现在您提供了额外的代码(和数据,定义为matrix),我很困惑您的实际数据是量化规范的。 (也许是一个愚蠢的问题,但是与您实际需要的相比,矩阵是否有可能被转置?) @r2evans 对于我造成的混乱,我深表歉意。仅供参考,实际数据是(2119055,124)矩阵。我上面给出的数据是它用于测试的一小部分。是的,我确实考虑了转置的问题。如您所见,在示例代码中,矩阵为 (3,5),但归一化结果为 (5,3),因此我总结要使用此代码,我需要先转置矩阵。更清楚地说,我的数据是 (4,6),使用代码我将转置数据,即 (6,4) 分配给变量matrix,然后继续。 【参考方案1】:

使用来自Wikipedia article的示例数据集:

df = pd.DataFrame('C1': 'A': 5, 'B': 2, 'C': 3, 'D': 4,
                   'C2': 'A': 4, 'B': 1, 'C': 4, 'D': 2,
                   'C3': 'A': 3, 'B': 4, 'C': 6, 'D': 8)

df
Out: 
   C1  C2  C3
A   5   4   3
B   2   1   4
C   3   4   6
D   4   2   8

对于每个等级,可以使用以下公式计算平均值:

rank_mean = df.stack().groupby(df.rank(method='first').stack().astype(int)).mean()

rank_mean
Out: 
1    2.000000
2    3.000000
3    4.666667
4    5.666667
dtype: float64

然后生成的系列,rank_mean,可以用作排名的映射以获得标准化结果:

df.rank(method='min').stack().astype(int).map(rank_mean).unstack()
Out: 
         C1        C2        C3
A  5.666667  4.666667  2.000000
B  2.000000  2.000000  3.000000
C  3.000000  4.666667  4.666667
D  4.666667  3.000000  5.666667

【讨论】:

优雅地使用groupbymapstacking/unstacking。你是pandas 开发者吗? 谢谢。不,我只是普通用户。 @ayhan 为什么你在第一和第二处理行做不同的排序方法,即first vs min 只是指出(和自我推销)这不会根据***产生“正确”的结果。我实现了一种快速方法,它确实产生了正确的结果,并且可以使用 conda 或 pip 安装:***.com/a/62792272/9544516 这看起来很棒!唯一需要修复具有相同等级的值是使用平均值,并在两者之间插入 rank_mean。我在帖子中添加了调整。 ***.com/a/67597273/1486196【参考方案2】:

好的我自己实现的方法效率比较高。

完成后,这个逻辑似乎有点简单,但无论如何,我决定在这里发布它,因为任何人都感到困惑,就像我无法搜索可用代码时一样。

代码在github:Quantile Normalize

【讨论】:

【参考方案3】:

值得注意的一点是,ayhan 和 shawn 的代码都对关系使用较小的秩平均值,但如果您使用 R 包 processcore 的 normalize.quantiles() ,它将使用秩平均值的平均值。

使用上面的例子:

> df

   C1  C2  C3
A   5   4   3
B   2   1   4
C   3   4   6
D   4   2   8

> normalize.quantiles(as.matrix(df))

         C1        C2        C3
A  5.666667  5.166667  2.000000
B  2.000000  2.000000  3.000000
C  3.000000  5.166667  4.666667
D  4.666667  3.000000  5.666667

【讨论】:

只是在这里说我为 Python 制作了一个名为 qnorm 的包/答案,它确实处理了关系:***.com/a/62792272/9544516【参考方案4】:

下面的代码给出了与preprocessCore::normalize.quantiles.use.target 相同的结果,我发现它比上面的解决方案更简单更清晰。对于巨大的数组长度,性能也应该很好。

import numpy as np

def quantile_normalize_using_target(x, target):
    """
    Both `x` and `target` are numpy arrays of equal lengths.
    """

    target_sorted = np.sort(target)

    return target_sorted[x.argsort().argsort()]

一旦你有一个pandas.DataFrame 很容易做到:

quantile_normalize_using_target(df[0].as_matrix(),
                                df[1].as_matrix())

(在上面的示例中将第一列标准化为第二列作为参考分布。)

【讨论】:

【参考方案5】:

这是一个小的调整,但我想很多人已经注意到 @ayhan 的 answer 中的细微“缺陷”。

我对其进行了小幅调整,得到了“正确”的答案,而不必求助于任何外部库来实现如此简单的功能。

唯一需要调整的是 [Add interpolated values] 部分。

import pandas as pd

df = pd.DataFrame('C1': 'A': 5, 'B': 2, 'C': 3, 'D': 4,
                   'C2': 'A': 4, 'B': 1, 'C': 4, 'D': 2,
                   'C3': 'A': 3, 'B': 4, 'C': 6, 'D': 8)

def quant_norm(df):
    ranks = (df.rank(method="first")
              .stack())
    rank_mean = (df.stack()
                   .groupby(ranks)
                   .mean())
    # Add interpolated values in between ranks
    finer_ranks = ((rank_mean.index+0.5).to_list() +
                    rank_mean.index.to_list())
    rank_mean = rank_mean.reindex(finer_ranks).sort_index().interpolate()
    return (df.rank(method='average')
              .stack()
              .map(rank_mean)
              .unstack())
quant_norm(df)

Out[122]: 
         C1        C2        C3
A  5.666667  5.166667  2.000000
B  2.000000  2.000000  3.000000
C  3.000000  5.166667  4.666667
D  4.666667  3.000000  5.666667

【讨论】:

【参考方案6】:

使用每行的中位数而不是平均值可能更稳健(基于来自 Shawn.L 的code):

def quantileNormalize(df_input):
    df = df_input.copy()
    #compute rank
    dic = 
    for col in df:
        dic[col] = df[col].sort_values(na_position='first').values
    sorted_df = pd.DataFrame(dic)
    #rank = sorted_df.mean(axis = 1).tolist()
    rank = sorted_df.median(axis = 1).tolist()
    #sort
    for col in df:
        # compute percentile rank [0,1] for each score in column 
        t = df[col].rank( pct=True, method='max' ).values
        # replace percentile values in column with quantile normalized score
        # retrieve q_norm score using calling rank with percentile value
        df[col] = [ np.nanpercentile( rank, i*100 ) if ~np.isnan(i) else np.nan for i in t ]
    return df

【讨论】:

【参考方案7】:

我是熊猫的新手并且迟到了这个问题,但我认为答案也可能有用。它建立在 answer 和 @ayhan 的基础之上:

def quantile_normalize(dataframe, cols, pandas=pd):

    # copy dataframe and only use the columns with numerical values
    df = dataframe.copy().filter(items=cols)

    # columns from the original dataframe not specified in cols
    non_numeric = dataframe.filter(items=list(filter(lambda col: col not in cols, list(dataframe))))


    rank_mean = df.stack().groupby(df.rank(method='first').stack().astype(int)).mean()  

    norm = df.rank(method='min').stack().astype(int).map(rank_mean).unstack()


    result = pandas.concat([norm, non_numeric], axis=1)
    return result

这里的主要区别是更接近一些现实世界的应用程序。通常你只有数字数据矩阵,在这种情况下,原始答案就足够了。

有时您也有基于文本的数据。这使您可以指定数值数据的列cols,并将在这些列上运行分位数归一化。最后,它将合并原始数据框中的非数字(或不规范化)列。

例如如果您在 wiki 示例中添加了一些“元数据”(char):

df = pd.DataFrame(
    'rep1': [5, 2, 3, 4],
    'rep2': [4, 1, 4, 2],
    'rep3': [3, 4, 6, 8],
    'char': ['gene_a', 'gene_b', 'gene_c', 'gene_d']
, index = ['a', 'b', 'c', 'd'])

然后你就可以打电话了

quantile_normalize(t, ['rep1', 'rep2', 'rep3'])

得到

    rep1        rep2        rep3        char
a   5.666667    4.666667    2.000000    gene_a
b   2.000000    2.000000    3.000000    gene_b
c   3.000000    4.666667    4.666667    gene_c
d   4.666667    3.000000    5.666667    gene_d

【讨论】:

【参考方案8】:

正如@msg 所指出的,这里的解决方案都没有考虑到关系。我制作了一个名为qnorm 的python 包,它处理关系,并正确地重新创建Wikipedia quantile normalization example:

import pandas as pd
import qnorm

df = pd.DataFrame('C1': 'A': 5, 'B': 2, 'C': 3, 'D': 4,
                   'C2': 'A': 4, 'B': 1, 'C': 4, 'D': 2,
                   'C3': 'A': 3, 'B': 4, 'C': 6, 'D': 8)

print(qnorm.quantile_normalize(df))
         C1        C2        C3
A  5.666667  5.166667  2.000000
B  2.000000  2.000000  3.000000
C  3.000000  5.166667  4.666667
D  4.666667  3.000000  5.666667

可以使用 pip 或 conda 进行安装

pip install qnorm

conda config --add channels conda-forge
conda install qnorm

【讨论】:

这与在他的df.rank() 中指定method='average' 有何不同? @Sos 我无法清楚地将数据帧放入 cmets,但你为什么不试试呢?我使用method='average' 和 qnorm 得到不同的结果。它只是以不同的方式解决关系。 我尝试使用您的包,但它引发了非精确类型数组错误。我的输入也是一个数据框,知道如何解决它吗? @Xiaoxixi 感谢您告诉我,我从来没有遇到过这个问题。每列的类型是什么?您可以检查 df.dtypes。如果你有一个 github 帐户,你能在 github 页面上提出一个关于它的问题,用一小段代码来重现错误吗?然后我应该可以快速修复它:github.com/Maarten-vd-Sande/qnorm/issues/new @Xiaoxixi 我做了一些检查,当您使用“非标准”数据类型(例如 float16)时会发生这种情况。它仍然会崩溃,但现在会输出一条消息,告诉您转换为例如float32

以上是关于熊猫数据帧的分位数归一化的主要内容,如果未能解决你的问题,请参考以下文章

熊猫系列的分位数函数的倒数是啥?

人脸图像的几何归一化和灰度归一化

特征工程

python 熊猫逐列归一化

如何从频率数据中找到分位数?

数据标准化/归一化normalization