【问题标题】:pandas Series: compare values all vs. all熊猫系列:比较值全部与全部
【发布时间】:2018-03-12 12:41:55
【问题描述】:

我有一个DataFrame 有两列:sequenceid,例如:

import pandas as pd
data = {"id":["seq1", "seq2", "seq3"], "sequence":["ATCTGC", "AACTGC", "AACTCC"]}
df = pd.DataFrame(data)

(这是 +20k 序列的实际数据集的一个小例子)

我正在尝试以一种有效的方式获得一个序列距离矩阵。距离理解为每个序列之间不同字符的数量。
为此,我需要在sequence 列中应用所有值与所有值的函数

我当前的代码如下所示:

def count_differences( seq, df ):
  return df.apply(lambda x: sum(1 for i, j in zip(x["sequence"], seq) if i != j), axis=1)

df2 = df.apply(lambda x: count_differences( x["sequence"], df), axis=1)
df2 = df2.rename(df["id"], axis="columns").rename(df["id"], axis="rows")

#      seq1 seq2 seq3
# seq1    0    1    2
# seq2    1    0    1
# seq3    2    1    0

这是一个apply里面的apply。它工作正常,但是在我拥有的所有序列上运行它确实需要相当多的时间。

有没有更有效的方法?我一直在尝试查看是否可以使用Series.map 来加快速度,但到目前为止我还没有找到任何解决方案。

【问题讨论】:

    标签: python pandas


    【解决方案1】:

    这是一个想法,涉及到下降到numpy

    有几个步骤,但由于底层工作是通过numpy 数字数组完成的,这可能更有效。

    import pandas as pd, numpy as np
    
    data = {"id":["seq1", "seq2", "seq3"], "sequence":["ATCTGC", "AACTGC", "AACTCC"]}
    df = pd.DataFrame(data)
    
    a = np.array(list(map(list, df['sequence'])))
    values = np.unique(a, return_inverse=True)[1].reshape(a.shape)
    
    n = len(a)
    d = {(i, j): np.sum(a[i]!=a[j]) for i in range(n) for j in range(n) if j > i}
    
    res = np.zeros((n, n))
    keys = list(zip(*d.keys()))
    
    res[keys[0], keys[1]] = list(d.values())
    res += res.T
    
    df_res = pd.DataFrame(res, columns=data['id'], index=data['id'], dtype=int)
    
    #       seq1  seq2  seq3
    # seq1     0     1     2
    # seq2     1     0     1
    # seq3     2     1     0
    

    说明

    • 将您的序列转换为numpy 数组,其中每个元素都是一个字母。
    • 使用np.unique 分解您的数组(即将每个字母与一个数字相关联)。
    • 使用np.sum 查找因式分解数组中各行之间的字母差异,并将结果添加到字典中。由于您的结果是三角形的,因此只执行一半的计算。
    • 从字典中创建新的numpy 数组并添加逆以使三角数组充满。
    • 将您的字典转换为数据框。

    性能基准测试

    我看到了大约 7 倍的性能提升。

    %timeit original(df)  # 3.32s
    %timeit jp(df)        # 461ms
    
    import pandas as pd, numpy as np
    
    data = {"id":["seq1", "seq2", "seq3"], "sequence":["ATCTGC", "AACTGC", "AACTCC"]}
    df = pd.DataFrame(data)
    
    df = pd.concat([df]*100)
    
    def original(df):
        def count_differences( seq, df ):
            return df.apply(lambda x: sum(1 for i, j in zip(x["sequence"], seq) if i != j), axis=1)
    
        df2 = df.apply(lambda x: count_differences( x["sequence"], df), axis=1)
    
        return df2
    
    def jp(df):
    
        a = np.array(list(map(list, df['sequence'])))
        values = np.unique(a, return_inverse=True)[1].reshape(a.shape)
    
        n = len(a)
        d = {(i, j): np.sum(a[i]!=a[j]) for i in range(n) for j in range(n) if j > i}
    
        res = np.zeros((n, n))
        keys = list(zip(*d.keys()))
    
        res[keys[0], keys[1]] = list(d.values())
        res += res.T
    
        df_res = pd.DataFrame(res, columns=range(len(df['id'])), index=range(len(df['id'])), dtype=int)
    
        return df_res
    

    【讨论】:

    • 我也在尝试使用更大的集合,也得到了很好的改进,并检查了输出 DataFrames 是否相同。这是一个非常好的解决方案。不过,我不得不说,values 已分配但从未使用过。我已经在我的代码上评论了它,它的工作原理是一样的。我猜字符串比较也能正常工作?我喜欢你如何限制一半的计算。
    • 我希望字符串比较比比较数字更昂贵(可能更多的内存分配/更大的开销/但不确定)。
    • 这是一个old post 比较字符串与整数的比较,但不确定它是否仍然相关。算法似乎完全不同。
    • 我明白了。我想这里的问题是这是否会被认为是字符串比较或字符比较(因为我们已经在选择位置),我猜这可能是...... ¿更快?无论如何,您的解决方案就像一个魅力。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-17
    • 2014-03-31
    • 2017-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-11
    相关资源
    最近更新 更多