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

2024-06-07 15:57:45 发布

您现在位置:Python中文网/ 问答频道 /正文

我是一个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是否是正确的函数,或者其他更好的功能。如果有什么建议,我将不胜感激。在

编辑:

过滤器的样本输入:

^{pr2}$

要筛选的文件的示例输入(多余的列保留):

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

Tags: 文件tofordatamitopenfilterfiltered
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循环来建立一个列表。有一个更简洁的表达方式,它做同样的事情叫做列表理解。因此,使用它,我们可以:

^{pr2}$

{tab>但也可以注意Python的tab}格式

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类编写过滤后的输出。在

最后,any和{}内置函数接受iterables,如果iterables的任何或所有内容的计算结果为True,则返回{}。您可以使用这些来删除嵌套的for循环,使用一个生成器表达式-这是一个类似于列表理解的结构,只是它是惰性求值的,这很好,因为any和{}将短路。下面是一个写这个的方法:

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 list equality则不匹配,但这不是问题所在。所以这也会起作用:

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)

当您创建filt时,您正在创建一个字符串变量并多次重写它。尝试更换

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

^{pr2}$

现在,filt是一个列表列表,每个元素代表一行。例如,filt[0]将给出第一行,filt[2][3]将给出第三行的第四列。您可能需要修改程序的其余部分才能正常工作。在

相关问题 更多 >

    热门问题