Python根据另一个文件的内容过滤多个文件

时间:2013-11-07 00:10:34

标签: python loops filtering

我是一个Python新手,遇到了一个我无法在任何地方找到答案的问题。

我正在尝试编写代码来根据另一个文件过滤一组文件。这些文件是具有多个行和列的数组。我想要的是从数据文件中删除与某些列的过滤器文件中的行匹配的行。

代码是:

paths = ('filepaths.txt')#file that has filepaths to open
filter_file = ('filter.txt')#file of items to filter
filtered = open('filtered.txt','w') #output file

filtering = open(filter_file, 'r').readlines()
for f in filtering:
    filt = f.rstrip().split('\t')

files = open(paths).read().splitlines()
for file in files:
    try:
        lines = open(file,'r').readlines()
        for l in lines:
            data = l.rstrip().split('\t')

        a = [data[0], data[5], data[6], data[10], data[11]] #data columns to match
        b= [filt[0], filt[1], filt[2], filt[3], filt[4]] #filter columns to match

        for i,j in zip(a,b): #loop through two lists to filter
            if i != j:
                matches = '\t'.join(data)
                print (matches)
                filtered.write(matches + '\n')
filtered.close()

代码执行,但不能按我的意愿运行。我得到的是每个文件的最后一行,重复5次。

显然,我错过了一些东西。我不确定zip是否是正确的功能,或者其他东西会更好。我很感激任何建议。

编辑:

过滤器的示例输入:

HSPG2   22161380    22161380    G   A
PPTC7   110974744   110974744   G   C
OR1S2   57971546    57971546    A   C

要过滤的文件的示例输入(剩下的额外列):

TKTL1   8277    broad.mit.edu   37  X   153558089   153558089   +   3'UTR   SNP G   C   C
MPP1    4354    broad.mit.edu   37  X   154014502   154014502   +   Silent  SNP G   A   A
BRCC3   79184   broad.mit.edu   37  X   154306908   154306908   +   Silent  SNP A   T   T

示例输出(剩下的额外列):

BRCC3   79184   broad.mit.edu   37  X   154306908   154306908   +   Silent  SNP A   T   T
BRCC3   79184   broad.mit.edu   37  X   154306908   154306908   +   Silent  SNP A   T   T
BRCC3   79184   broad.mit.edu   37  X   154306908   154306908   +   Silent  SNP A   T   T
BRCC3   79184   broad.mit.edu   37  X   154306908   154306908   +   Silent  SNP A   T   T
BRCC3   79184   broad.mit.edu   37  X   154306908   154306908   +   Silent  SNP A   T   T

2 个答案:

答案 0 :(得分:2)

我将从一些简单的更改开始,然后展示如何使用内置工具(如Python的csv库和any函数来简化代码。

这是一个版本,可以清理一些东西并使用正确的逻辑,但不会引入太多新的语言功能。它使用的主要新东西是with语句(退出时自动关闭文件)并直接在文件上迭代而不是使用readlines

paths = ('filepaths.txt')#file that has filepaths to open
filter_file = ('filter.txt')#file of items to filter
with open(filter_file, 'r') as filter_source:
    filters = []
    for line in filter_source:
        filters.append(line.rstrip().split('\t'))
with open(paths, 'r') as filename_source:
    filenames = []
    for line in filename_source:
        filenames.append(line.rstrip())
with open('filtered.txt','w') as filtered:
    for filename in filenames:
        with open(filename,'r') as datafile:
            for line in datafile:
                data = l.rstrip().split('\t')
                a = [data[0], data[5], data[6], data[10], data[11]] #data columns to match
                for filter in filters:
                    matched = True
                    for i,j in zip(a,filter):
                        if i != j:
                            matched = False
                            break
                    if matched:
                         # the data row matched a filter, stop checking for others
                         break
                if not matched:
                    filtered.write(line)

我们做过几次的事情是使用for循环来建立一个列表。有一个更简洁的表达式,它可以做同样的事情,称为列表理解。所以使用它,我们有:

with open(filter_file, 'r') as filter_source:
    filters = [line.rstrip().split('\t') for line in filter_source]
with open(paths, 'r') as filename_source:
    filenames = [line.rstrip() for line in filename_source]

但Python也有一个有用的csv库,可以处理以制表符分隔的格式:

import csv

with open(filter_file, 'rb') as filter_source:
    filter_reader = csv.reader(filter_source, delimiter='\t')
    filters = list(filter_reader)

迭代它时,它会返回由分隔符分隔的字段列表。请注意,我是以b模式打开它;这是否有所不同取决于您的平台,但如果确实如此,那么csv文档会指出它是必需的。

您可以类似地对数据文件使用它,甚至可以使用writer类编写过滤后的输出。

最后,anyall内置函数会迭代并返回True,如果迭代的任何或所有内容都计算为True。您可以使用这些来删除嵌套for循环,使用生成器表达式 - 这是一个类似于列表推导的构造,除了它被懒惰地评估,这很好,因为anyall将短路。所以这是写这个的方法:

def match(dataline, filter):
    return all(i==j for (i, j) in zip(dataline, filter))

在这种特殊情况下,我没有从短路中获得太多,因为我正在使用zip来构建实际的元组列表。但对于这样的短列表,它很好,并且zip在已经在内存中的列表上优于itertools.zip(惰性评估版本)。

然后您可以使用any简洁地将行与所有过滤器进行比较,只要匹配就会短路:

a = [data[0], data[5], data[6], data[10], data[11]]
if not any(match(a, filter) for filter in filters):
    filtered.write(line)

除了这仍然是矫枉过正。 match函数强制执行其两个输入中的所有元素必须相等,但是如果您测试两个列表是否相等,那么Python是自动执行的操作的一部分。我编写的match函数只要较长列表的起始元素与较短列表匹配,就会允许不等长的列表匹配,而Python列表相等则不会,但这不是问题。所以这也有效:

a = [data[0], data[5], data[6], data[10], data[11]]
if not any (a==filter for filter in filters):
    filtered.write(line)

或者,如果您想要容忍的时间长于正常过滤器:

if not any (a==filter[:5] for filter in filters):

非切片版本也可以使用直接列表成员资格测试编写:

if a not in filters:
    filtered.write(line)

另外,正如Blckknght所指出的,Python有一种更好的方法可以快速测试像线条这样的东西是否匹配任何一种模式 - set数据类型,它使用恒定时间查找。列表,如csv库或split返回的列表,不能是集合的成员 - 但元组可以,只要元组的成员本身可以清除。因此,如果将过滤器和数据行子集转换为元组,则可以维护集而不是列表,并更快地检查它。为此,您必须将每个过滤器转换为元组:

filters = set(tuple(filter) for filter in filter_reader)

然后,将a定义为元组:

a = (data[0], data[5], data[6], data[10], data[11])
if a not in filters:
    filtered.write(line)

如果您使用csv.writer实例来编写输出,您甚至可以使用writerows方法和生成器表达式进一步合并它:

filtered_writer.writerows(data for data in data_reader if (data[0], data[5], data[6], data[10], data[11]) not in filters)

所以把它全部包起来,我会这样做:

import csv

paths = ('filepaths.txt') #file that has filepaths to open
filter_file = ('filter.txt') #file of items to filter
with open(filter_file, 'rb') as filter_source:
    filters = set(tuple(filter) for filter in csv.reader(filter_source, delimiter='\t'))
with open(paths, 'r') as filename_source:
    filenames = [line.rstrip() for line in filename_source]
with open('filtered.txt','wb') as filtered:
    filtered_writer = csv.writer(filtered, delimiter='\t')
    for filename in filenames:
        with open(filename,'rb') as datafile:
            data_reader = csv.reader(datafile, delimiter='\t')
            filtered_writer.writerows(data for data in data_reader if (data[0], data[5], data[6], data[10], data[11]) not in filters)

答案 1 :(得分:0)

创建filt时,您将创建一个字符串变量并多次覆盖它。尝试替换

for f in filtering:
    filt = f.rstrip().split('\t')

filt = [f.rstrip().split('\t') for f in filtering]

现在filt是一个列表列表,每个元素代表一行。例如,filt[0]将为您提供第一行,filt[2][3]将为您提供第三行的第四列。您可能必须修改程序的其余部分才能正常使用它。