所以,这个给了我一个艰难的时刻! 我正在使用 HUGE 文本文件,而且我的意思是100Gb +。具体来说,它们位于fastq format。这种格式用于DNA测序数据,由四行记录组成,如下所示:
@REC1
GATTTGGGGTTCAAAGCAGTATCGATCAAATAGTAAATCCATTTGTTCAACTCACAGTTT
+
!''*((((***+))%%%++)(%%%%).1***-+*''))*55CCF>>>>>>CCCCCCC65
@REC2
GATTTGGGGTTCAAAGCAGTATCGATCAAATAGTAAATCCATTTGTTCAACTCACAGTTT
+
!''*((((***+))%%%++)(%%%%).1***-+*''))**55CCF>>>>>>CCCCCCC65
.
.
.
为了这个问题,只需关注标题行,以'@'开头。
因此,出于QA目的,我需要比较两个这样的文件。这些文件应该有匹配的标题,因此另一个文件中的第一个记录也应该有标题'@ REC1',下一个应该是'@ REC2',依此类推。在进行大量下游分析之前,我想确保情况确实如此
由于文件太大,字符串比较的天真迭代需要很长时间,但是这个QA步骤将运行多次,我不能等待那么久。所以我认为更好的方法是从文件中的几个点采样记录,例如每10%的记录。如果记录的顺序搞砸了,我很可能会发现它
到目前为止,我已经能够通过估计文件大小来处理这些文件,而不是使用python的file.seek()
来访问文件中间的记录。例如,要访问大约中间的一行,我会这样做:
file_size = os.stat(fastq_file).st_size
start_point = int(file_size/2)
with open(fastq_file) as f:
f.seek(start_point)
# look for the next beginning of record, never mind how
但现在问题更复杂,因为我不知道如何在两个文件之间进行协调,因为字节位置不是文件中行索引的指示符。换句话说,如何访问两个文件中的10,567,311行,以确保它们是相同的,而不会遍历整个文件?
会欣赏任何想法\提示。也许并行迭代?但究竟如何呢? 谢谢!
答案 0 :(得分:3)
抽样是一种方法,但你依靠运气。此外,Python是这项工作的错误工具。您可以使用标准的Unix命令行工具以不同的方式做事并以合理有效的方式计算确切的答案:
diff
。如果存在差异,diff
会报告。要进行线性化,您可以通过awk
:
$ awk '\
BEGIN { \
n = 0; \
} \
{ \
a[n % 4] = $0; \
if ((n+1) % 4 == 0) { \
print a[0]"\t"a[1]"\t"a[2]"\t"a[3]; \
} \
n++; \
}' example.fq > example.fq.linear
比较一对文件:
$ diff example_1.fq.linear example_2.fq.linear
如果存在任何差异,diff
会找到它并告诉您哪个FASTQ记录有所不同。
您可以直接在这两个文件上运行diff
,而无需进行线性化的额外工作,但如果您首先进行线性化,则更容易看出哪个读取有问题。
所以这些都是大文件。编写新文件的时间和磁盘空间都很昂贵。有一种方法可以使用streams改善这一点。
如果您将awk
脚本放入文件(例如,linearize_fq.awk
),您可以这样运行:
$ awk -f linearize_fq.awk example.fq > example.fq.linear
这可能对您的100多个Gb文件有用,因为您现在可以通过bash
process substitutions设置两个Unix文件流,并直接在这些流上运行diff
:< / p>
$ diff <(awk -f linearize_fq.awk example_1.fq) <(awk -f linearize_fq.awk example_2.fq)
或者您可以使用named pipes:
$ mkfifo example_1.fq.linear
$ mkfifo example_2.fq.linear
$ awk -f linearize_fq.awk example_1.fq > example_1.fq.linear &
$ awk -f linearize_fq.awk example_2.fq > example_2.fq.linear &
$ diff example_1.fq.linear example_2.fq.linear
$ rm example_1.fq.linear example_2.fq.linear
命名管道和进程替换都避免了创建额外(常规)文件的步骤,这可能是您的输入问题。将100+ Gb文件的线性化副本写入磁盘可能需要一段时间才能完成,而这些副本也可能使用您可能没有太多的磁盘空间。
使用流可以解决这两个问题,这使它们对于以有效的方式处理生物信息学数据集非常有用。
你可以用Python重现这些方法,但它几乎肯定会运行得慢得多,因为Python在这类I / O繁重的任务上非常慢。
答案 1 :(得分:2)
并行迭代可能是在Python中执行此操作的最佳方法。我不知道它的运行速度有多快(快速的SSD可能是提高速度的最好方法),但是既然你不得不计算两个文件中的换行符,我就不会看到一种方法围绕这个:
with open(file1) as f1, open(file2) as f2:
for l1, l2 in zip(f1,f2):
if l1.startswith("@REC"):
if l1 != l2:
print("Difference at record", l1)
break
else:
print("No differences")
这是为Python 3编写的,其中zip
返回迭代器;在Python 2中,您需要使用itertools.izip()
代替。
答案 2 :(得分:1)
您是否考虑过使用rdiff
命令
rdiff的好处是:
rdiff的缺点是:
答案 3 :(得分:0)
import sys
import re
""" To find of the difference record in two HUGE files. This is expected to
use of minimal memory. """
def get_rec_num(fd):
""" Look for the record number. If not found return -1"""
while True:
line = fd.readline()
if len(line) == 0: break
match = re.search('^@REC(\d+)', line)
if match:
num = int(match.group(1))
return(num)
return(-1)
f1 = open('hugefile1', 'r')
f2 = open('hugefile2', 'r')
hf1 = dict()
hf2 = dict()
while f1 or f2:
if f1:
r = get_rec_num(f1)
if r < 0:
f1.close()
f1 = None
else:
# if r is found in f2 hash, no need to store in f1 hash
if not r in hf2:
hf1[r] = 1
else:
del(hf2[r])
pass
pass
if f2:
r = get_rec_num(f2)
if r < 0:
f2.close()
f2 = None
else:
# if r is found in f1 hash, no need to store in f2 hash
if not r in hf1:
hf2[r] = 1
else:
del(hf1[r])
pass
pass
print('Records found only in f1:')
for r in hf1:
print('{}, '.format(r));
print('Records found only in f2:')
for r in hf2:
print('{}, '.format(r));
答案 4 :(得分:0)
从我的角度来看,来自@AlexReynolds和@TimPietzcker的答案都非常出色,但我想把我的两分钱放进去。你也可能想要加速你的硬件:
n
SSD并创建RAID 0.在完美的世界中,您的磁盘IO速度将提高n
倍。最后一个选项与NOR SSD特别相关。不要追求最低的RAM利用率,但要尽可能多地阅读以保持磁盘读取速度。例如,从两个文件并行读取单行可能会降低读取速度 - 想象一下两个文件中的两行始终位于同一磁盘的同一侧的HDD。