访问 pandas 数据一百万次 - 需要提高效率

Posted

技术标签:

【中文标题】访问 pandas 数据一百万次 - 需要提高效率【英文标题】:accessing pandas data a million times -need to improve efficiency 【发布时间】:2018-08-07 18:02:39 【问题描述】:

我是一名试图验证实验的生物学家。在我的实验中,我在特定治疗后发现了 71 个突变。为了确定这些突变是否真的是由于我的治疗,我想将它们与一组随机生成的突变进行比较。有人建议我可能会尝试生成一百万组 71 个随机突变进行统计比较。

首先,我有一个数据框,其中包含感兴趣的基因组中的 7000 个基因。我知道他们的开始和结束位置。数据框的前五行如下所示:

    transcript_id   protein_id  start   end kogClass
0   g2.t1   695054  1   1999    Replication, recombination and repair 
1   g3.t1   630170  2000    3056    General function prediction only 
2   g5.t1   695056  3057    4087    Signal transduction mechanisms 
3   g6.t1   671982  4088    5183    N/A
4   g7.t1   671985  5184    8001    Chromatin structure and dynamics 

现在大约有 71 个随机突变的百万组:我编写了一个调用一百万次的函数,它似乎效率不高,因为 4 小时后它只完成了 1/10。这是我的代码。如果有人能提出一种加快速度的方法,我会欠你一杯啤酒!我的感激之情。

def get_71_random_genes(df, outfile):
    # how many nucleotides are there in all transcripts?
    end_pos_last_gene = df.iloc[-1,3]

    # this loop will go 71 times
    for i in range(71):
        # generate a number from 1 to the end of all transcripts
        random_number = randint(1, end_pos_last_gene)
        # this is the boolean condition - checks which gene a random number falls within 
        mask = (df['start'] <= random_number) & (df['end'] >= random_number)
        # collect the rows that match
        data = df.loc[mask]
        # write data to file.
        data.to_csv(outfile, sep='\t', index=False, header=False)

【问题讨论】:

一个重要因素可能是data.to_csv():您打开和关闭文件 71,000,000 次,由于 I/O 是最慢的操作之一,我猜这是一个巨大的瓶颈。 这是有道理的。有什么更好的方法?构建一个包含 71,000,000 行的大型 df,然后将其写入文件?我认为我可怜的笔记本电脑会崩溃。也许是一本字典? 一个快速的解决方法就是在循环之后调用to_csv,而不是在循环内。 I/O 减少了 71 倍。绝对不想要 71e6 Pandas 专栏,那是灾难的根源。 :( 我会说,将你可以存储的内容存储在 Numpy 数组中,也许每 10,000 组然后写出数组并刷新它。 @ juanpa.arrivillaga - 在大多数情况下,每一行都是一个独特的基因。但是有些基因带有多个“kogClass”注释,布尔掩码返回多行。每个 random_number 旨在模拟单核苷酸多态性,或 DNA 序列的一个字符的变化。掩码旨在找到该序列所属的基因范围。也许最好删除重复的基因并选择 71 个随机行?你会如何建议这样做?我是初学者,请解释一下! @juanpa.arrivillaga 第一个版本大约一个小时,第二个版本大约 15 分钟。 【参考方案1】:

我很确定以下所有内容都可以:

for i in range(71):
    # generate a number from 1 to the end of all transcripts
    random_number = randint(1, end_pos_last_gene)
    # this is the boolean condition - checks which gene a random number falls within 
    mask = (df['start'] <= random_number) & (df['end'] >= random_number)
    # collect the rows that match
    data = df.loc[mask]
    # write data to file.
    data.to_csv(outfile, sep='\t', index=False, header=False)

是从数据框中随机选择 71 行而不进行替换。请注意,这是永远,因为每次你这样做

(df['start'] <= random_number) & (df['end'] >= random_number)

您遍历整个数据框三次,然后再进行一次:

data = df.loc[mask]

这是一种非常低效的对行进行采样的方法。您可以通过随机抽样 71 个索引来更有效地做到这一点,然后直接在数据帧上使用这些索引(这甚至不需要对数据帧进行一次完整的遍历)。但你不需要这样做,pd.DataFrame 对象已经实现了一个高效的示例方法,所以请注意:

In [12]: df = pd.DataFrame(np.random.randint(0, 20, (10, 10)), columns=["c%d"%d for d in range(10)])

In [13]: df
Out[13]:
   c0  c1  c2  c3  c4  c5  c6  c7  c8  c9
0  13   0  19   5   6  17   5  14   5  15
1   2   4   0  16  19  11  16   3  11   1
2  18   3   1  18  12   9  13   2  18  12
3   2   6  14  12   1   2  19  16   0  14
4  17   5   6  13   7  15  10  18  13   8
5   7  19  18   3   1  11  14   6  13  16
6  13   5  11   0   2  15   7  11   0   2
7   0  19  11   3  19   3   3   9   8  10
8   6   8   9   3  12  18  19   8  11   2
9   8  17  16   0   8   7  17  11  11   0

In [14]: df.sample(3, replace=True)
Out[14]:
   c0  c1  c2  c3  c4  c5  c6  c7  c8  c9
0  13   0  19   5   6  17   5  14   5  15
3   2   6  14  12   1   2  19  16   0  14
3   2   6  14  12   1   2  19  16   0  14

In [15]: df.sample(3, replace=True)
Out[15]:
   c0  c1  c2  c3  c4  c5  c6  c7  c8  c9
9   8  17  16   0   8   7  17  11  11   0
4  17   5   6  13   7  15  10  18  13   8
2  18   3   1  18  12   9  13   2  18  12

In [16]: df.sample(3, replace=True)
Out[16]:
   c0  c1  c2  c3  c4  c5  c6  c7  c8  c9
3   2   6  14  12   1   2  19  16   0  14
8   6   8   9   3  12  18  19   8  11   2
4  17   5   6  13   7  15  10  18  13   8

所以只需将那个循环替换为:

df.sample(71, replace=True).to_csv(outfile, sep='\t', index=False, header=False)

注意,这减少了 I/O 开销!

所以,只是做一个快速测试:

In [4]: import time
   ...: start = time.time()
   ...: with open('test.csv', 'w') as f:
   ...:     for _ in range(1000):
   ...:         df.sample(71, replace=True).to_csv(f, header=None, index=False)
   ...: stop = time.time()
   ...:

In [5]: stop - start
Out[5]: 0.789172887802124

所以,线性推断,我估计 1,000,000 次大约需要:

In [8]: (stop - start) * 1000
Out[8]: 789.172887802124

几秒钟,所以 10 多分钟

In [10]: !wc -l test.csv
   71000 test.csv

编辑以添加更有效的方法

因此,创建一个映射到数据框中的索引的数组:

size = df.end.max()

nucleotide_array = np.zeros(size, dtype=np.int) # this could get out of hand without being careful of our size

for row in df.itertuples(): # might be alittle slow, but its a one-time upfront cost
    i = row.start - 1
    j = row.end
    nucleotide_array[i:j] = row.Index

# sampling scheme:
with open('test.csv', 'w') as f:
    for _ in range(1000): # how ever many experiments
        snps = np.random.choice(nucleotide_array, 71, replace=True)
        df.loc[snps].to_csv(f, header=None, index=False)

请注意,上面是一个速写,还没有真正测试过。它做出假设,但我认为它们成立,无论如何,您可以轻松地调整您的 df 以便它可以工作。

【讨论】:

以上是关于访问 pandas 数据一百万次 - 需要提高效率的主要内容,如果未能解决你的问题,请参考以下文章

一个效率比较高红包算法

运行一百万次扫描的 hbase mapreduce 作业是不是有意义?

当有大量数据[超过一百万行] [重复]时,改进 R 中的循环以提高时间效率

每秒超一百万次请求,Netflix如何做负载均衡?

Matlab 之 数据元素访问

一百万个结构数组,根据其中一项值排序,用双链表还是数组排序效率更好